diff options
| author | Adam Soltys <[email protected]> | 2026-08-01 19:02:25 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-08-01 19:02:25 -0700 |
| commit | b73bb9ee51b9be33bc1859fd56d3e11905c3d456 (patch) | |
| tree | 8a008d57e450961d1ab203a663e9d8fa233a2686 /src/nwc-pouch.js | |
| parent | 2a7a825ce5850a2a836effae820adbae0b66d093 (diff) | |
| download | coinosv3-b73bb9ee51b9be33bc1859fd56d3e11905c3d456.tar.gz coinosv3-b73bb9ee51b9be33bc1859fd56d3e11905c3d456.zip | |
Background NWC: the worker now answers — and pays — from the pouch
The no-tap half of background wallet-connect. The push payload carries
the (encrypted, client-signed) request event; a bundled service-worker
responder (src/sw-nwc.js -> nwc-respond.js) finds the matching pouch in
IndexedDB, decrypts with the connection's service key, and answers
get_info/get_balance/list_transactions from pouch state. pay_invoice
pays through the swap bridge with an ArkManager running over the
pouch's chain-6 key window (one HTLC lock, one HTTP swap), enforcing
per-payment and daily budgets from the pouch's own spend log, checking
the notifier's /answered endpoint so it never double-pays or shouts
over an open device, and persisting the swap before handing it off so
the app can refund a crashed flight. Anything it can't handle falls
back to the wake-the-user notification.
The notifier grows /publish (verified 23195s only, rate-limited) as the
worker's reply pipe and /answered for race avoidance. The app gains
pouch funding (mailbox-less arkoor self-send whose recipient bytes we
keep), emptying, spend/budget reconciliation on open, and refund
recovery with the pouch's own keys.
Co-Authored-By: Claude Fable 5 <[email protected]>
Diffstat (limited to 'src/nwc-pouch.js')
| -rw-r--r-- | src/nwc-pouch.js | 38 |
1 files changed, 31 insertions, 7 deletions
diff --git a/src/nwc-pouch.js b/src/nwc-pouch.js index 0d4fcbc..187ab7a 100644 --- a/src/nwc-pouch.js +++ b/src/nwc-pouch.js @@ -73,6 +73,23 @@ const get = (s, k) => new Promise((res, rej) => { const r = s.get(k); r.onsucces export async function loadPouch(walletKey) { try { return (await tx('readonly', (s) => get(s, walletKey))) || null; } catch { return null; } } +// The service worker doesn't know wallet cache keys — it finds the right +// pouch by the connection's service pubkey across all of them. +export async function allPouches() { + try { + return await tx('readonly', (s) => new Promise((res, rej) => { + const out = []; + const cur = s.openCursor(); + cur.onsuccess = () => { + const c = cur.result; + if (!c) return res(out); + out.push({ walletKey: c.key, pouch: c.value }); + c.continue(); + }; + cur.onerror = () => rej(cur.error); + })); + } catch { return []; } +} export async function savePouch(walletKey, pouch) { return tx('readwrite', (s) => { s.put(pouch, walletKey); }); } @@ -80,25 +97,32 @@ export async function clearPouch(walletKey) { return tx('readwrite', (s) => { s.delete(walletKey); }); } +// The worker can't derive keys (no seed), so the pouch carries a fixed window +// of pre-derived chain-6 keys: funding uses low indices, the worker's change +// and HTLC-refund outputs allocate upward from POUCH_CHANGE_BASE. +export const POUCH_KEY_WINDOW = 50; +export const POUCH_CHANGE_BASE = 20; + // Everything the service worker needs to spend, and nothing more: the ASP -// endpoints, the pouch vtxos with their bytes, and the private keys for -// exactly those vtxos. -export function buildPouch({ arkUrl, esploraUrl, network, serverPubkey, vtxos, account, connections }) { +// endpoints, the bridge, the pouch vtxos with their bytes, and the private +// keys for exactly the pouch's key window. +export function buildPouch({ arkUrl, esploraUrl, network, serverPubkey, bridge, vtxos, account, connections }) { const keys = {}; - for (const v of vtxos) { - const k = pouchKey(account, v.keyIndex); - keys[String(v.keyIndex)] = hex.encode(k.privkey); + for (let i = 0; i < POUCH_KEY_WINDOW; i++) { + keys[String(i)] = hex.encode(pouchKey(account, i).privkey); } return { - v: 1, + v: 2, updated: Date.now(), ark: { arkUrl, esploraUrl, network, serverPubkey }, + bridge: bridge || null, // { url, token } — the worker pays through this // [{ id, bytes, keyIndex, amountSat, expiryHeight }] vtxos: vtxos.map((v) => ({ id: v.id, bytes: v.bytes, keyIndex: v.keyIndex, amountSat: v.amountSat, expiryHeight: v.expiryHeight, })), keys, + nextKeyIndex: POUCH_CHANGE_BASE, // service keys for the connections the SW may answer for, so it can // decrypt requests and sign replies without the wallet connections: (connections || []).map((c) => ({ |
