deviant/shells/mobile/nodejs-assets/nodejs-project/main.js
walerikus 518a3ce174 chore: initial project baseline — full Deviant codebase under version control
Brings the whole project into git (previously only the on-ramp feature and
a few parallel files were tracked). Includes: wallet core (src/), CashScript
contracts/, the Capacitor shell (shells/), scripts/, tests, and the docs
corpus (wiki/, snapshots/, Parameters/, Archive/, KnowledgeBase text).

Excluded via .gitignore: node_modules/, dist/, .env (secrets), testnet WIF
key files (.chipnet-*.json, devWallets.ts), KnowledgeBase/RAW media (~9.6 GB),
transcripts/ (session logs), and machine-local config (.claude/*.local,
*.lock). Secret-scanned before commit — no credentials in the tree.

Note: src/swapMath.ts, src/swapBuilder.ts, contracts/OpenMintToken.cash are
legacy (OP_RETURN era) but still imported; committed as-is, flagged for a
separate removal once de-referenced.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 20:38:07 +02:00

160 lines
5.7 KiB
JavaScript

/**
* Deviant mobile — embedded Node core bridge (ADR-0022).
*
* Runs INSIDE the on-device `nodejs-mobile` process (a real Node/V8 runtime), so
* libauth's WASM crypto and mainnet-js networking work here unmodified — the
* thing Hermes/JSC cannot do. This file is the Node end of the RN<->Node bridge:
* it receives `EnvelopedRequest`s, dispatches to the SAME core handlers the web
* shell uses (`src/web/api.ts`), and posts back one correlated `BridgeResponse`.
*
* It rebuilds the `RunContext` exactly as `src/web/server.ts` does — an
* `InMemoryTokenRegistry` plus a `connectWallet(...)` wallet — but per session,
* because the mnemonic only arrives when the user unlocks.
*
* STATUS — UNVERIFIED / UNBUILT (see shells/mobile/README.md):
* - The `./core/*` imports below are the COMPILED core (`dist/`), copied/bundled
* into this `nodejs-project` by the device build step (not yet wired).
* - On iOS this process runs V8 `--jitless`; whether libauth's WASM instantiates
* there is the ADR-0022 OPEN RISK and must be device-verified first.
* - Nothing here can run from the current Windows / no-RN-toolchain environment.
*/
'use strict';
// nodejs-mobile injects this bridge module at runtime.
const rn_bridge = require('rn-bridge');
// --- core imports (compiled `dist/`, supplied by the device build step) -----
// Mirror of src/web/api.ts + the entrypoint wiring in scripts/deviant-web.mjs.
const { handleBalance, handleTokens, handleCommand, handleRegister, handleFavorite, handleForget } =
require('./core/web/api.js');
const { InMemoryTokenRegistry } = require('./core/token/registry.js');
const { connectWallet } = require('./core/wallet/runtime.js');
const { Wallet, TestNetWallet, RegTestWallet } = require('mainnet-js');
/** Map our network name to the mainnet-js wallet class used for derivation. */
function walletClassFor(network) {
if (network === 'mainnet') return Wallet;
// chipnet + testnet4 both derive via the testnet class; the chain endpoint is
// selected by connectWallet's ElectrumNetworkProvider(network).
return network === 'regtest' ? RegTestWallet : TestNetWallet;
}
let seq = 0;
/** sessionId -> { ctx: RunContext } */
const sessions = new Map();
function bridgeIdentity(w, network, derivationPath) {
const owned = [w.cashaddr, w.tokenaddr].filter(Boolean);
return {
cashaddr: w.cashaddr,
tokenAddress: w.tokenaddr,
ownedAddresses: owned,
publicKeyHex: Buffer.from(w.publicKeyCompressed).toString('hex'),
network,
derivationPath,
};
}
/** Build a fresh RunContext from a derived mainnet-js wallet. */
function openContext(w, network) {
const registry = new InMemoryTokenRegistry();
const wallet = connectWallet({
registry,
network,
privateKey: w.privateKey,
cashaddr: w.cashaddr,
tokenAddress: w.tokenaddr,
mainnetWallet: w, // enables genesis (Artemision) on this session
});
return { wallet, registry, ownTokenAddress: w.tokenaddr };
}
function requireSession(req) {
const session = sessions.get(req.sessionId);
if (!session) {
const err = new Error(`no such session: ${req.sessionId}`);
err.code = 'no-session';
throw err;
}
return session.ctx;
}
async function dispatch(req) {
switch (req.kind) {
case 'createWallet': {
const WClass = walletClassFor(req.network);
const w = await WClass.newRandom();
const ctx = openContext(w, req.network);
const sessionId = `s${(seq++).toString(36)}-${Date.now().toString(36)}`;
sessions.set(sessionId, { ctx });
return {
sessionId,
identity: bridgeIdentity(w, req.network, w.derivationPath ?? req.derivationPath),
privateKeyHex: Buffer.from(w.privateKey).toString('hex'),
mnemonic: w.mnemonic,
};
}
case 'restoreWallet':
case 'openSession': {
const WClass = walletClassFor(req.network);
let w;
try {
w = await WClass.fromSeed(req.mnemonic, req.derivationPath);
} catch (e) {
const err = new Error(`invalid mnemonic: ${e && e.message ? e.message : e}`);
err.code = 'invalid-mnemonic';
throw err;
}
const ctx = openContext(w, req.network);
const sessionId = `s${(seq++).toString(36)}-${Date.now().toString(36)}`;
sessions.set(sessionId, { ctx });
return {
sessionId,
identity: bridgeIdentity(w, req.network, req.derivationPath),
privateKeyHex: Buffer.from(w.privateKey).toString('hex'),
};
}
case 'closeSession': {
sessions.delete(req.sessionId);
return {};
}
case 'balance':
return handleBalance(requireSession(req));
case 'tokens':
return handleTokens(requireSession(req));
case 'command':
return handleCommand(requireSession(req), {
command: req.command,
args: req.args,
flags: req.flags,
});
case 'register':
return handleRegister(requireSession(req), { definition: req.definition });
case 'favorite':
return handleFavorite(requireSession(req), { id: req.id, pinned: req.pinned });
case 'forget':
return handleForget(requireSession(req), { id: req.id });
default: {
const err = new Error(`unknown request kind: ${req && req.kind}`);
err.code = 'core-error';
throw err;
}
}
}
rn_bridge.channel.on('message', async (req) => {
if (!req || typeof req.id !== 'string') return; // not one of ours
try {
const result = await dispatch(req);
rn_bridge.channel.send({ id: req.id, ok: true, result });
} catch (err) {
rn_bridge.channel.send({
id: req.id,
ok: false,
error: { code: (err && err.code) || 'core-error', message: (err && err.message) || String(err) },
});
}
});
rn_bridge.channel.send({ id: '__ready__', ok: true, result: { ready: true } });