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/ark | |
| 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/ark')
| -rw-r--r-- | src/ark/manager.js | 34 |
1 files changed, 32 insertions, 2 deletions
diff --git a/src/ark/manager.js b/src/ark/manager.js index e70acfb..2edcd8a 100644 --- a/src/ark/manager.js +++ b/src/ark/manager.js @@ -500,6 +500,32 @@ export class ArkManager { return action.id; } + // A send whose recipient is one of the USER's own foreign-branch keys (the + // NWC pouch): no address, no mailbox delivery — the caller receives the + // signed recipient vtxo bytes directly and stores them wherever the branch + // lives. Same cosign machinery as send(), so all its failure handling holds. + async sendToPubkey(destPubkeyHex, amountSat) { + const input = this._selectInput(amountSat); + const changeSat = input.amountSat - amountSat; + const action = { + id: `send-${Date.now()}`, type: 'send', step: 'created', + inputId: input.id, amountSat, destAddress: null, + destPubkey: destPubkeyHex, destBlindedId: null, + changeIndex: changeSat > 0 ? this.state.nextKeyIndex++ : null, changeSat, + }; + input.state = 'pending'; + this.state.actions.push(action); + this._save(); + await this._driveSend(action); + if (action.step !== 'done') throw new Error(action.error || 'send did not complete'); + return { + vtxos: (action.destBytesList || []).map((b) => { + const v = decodeVtxo(hex.decode(b)); + return { id: v.id, bytes: b, amountSat: v.amountSat, expiryHeight: v.expiryHeight }; + }), + }; + } + _sendOutputs(action) { const outputs = [{ amountSat: action.amountSat, userPubkey: hex.decode(action.destPubkey) }]; if (action.changeSat > 0) { @@ -558,8 +584,12 @@ export class ArkManager { this._save(); } if (action.step === 'registered') { - await postArkoorMessage(this.arkUrl, hex.decode(action.destBlindedId), - destList().map((b) => hex.decode(b))); + // a pouch-bound send has no mailbox to deliver to — the caller takes + // the bytes from the returned action instead + if (action.destBlindedId) { + await postArkoorMessage(this.arkUrl, hex.decode(action.destBlindedId), + destList().map((b) => hex.decode(b))); + } for (const b of changeList()) { const change = this._vtxo(decodeVtxo(hex.decode(b)).id); if (change && change.state === 'pending') change.state = 'spendable'; |
