diff options
Diffstat (limited to 'src/features')
| -rw-r--r-- | src/features/ark.js | 46 | ||||
| -rw-r--r-- | src/features/sync.js | 6 |
2 files changed, 52 insertions, 0 deletions
diff --git a/src/features/ark.js b/src/features/ark.js index a6959f2..fe1aab3 100644 --- a/src/features/ark.js +++ b/src/features/ark.js @@ -53,6 +53,7 @@ export function arkFeature(ctx) { if (arkTimer) clearInterval(arkTimer); arkTimer = null; if (ark) ark.stopMailboxStream(); + stopNwcFunding(); ark = null; arkConnectPromise = null; } @@ -92,6 +93,7 @@ export function arkFeature(ctx) { if (gen !== arkInitGen) throw new Error('superseded'); // wallet switched mid-connect ark = mgr; announceArkAddress(mgr); // ark zaps: tell nostr where our mailbox lives + startNwcFunding(mgr); // NWC bridge: honor funding requests within the allowance const tick = () => mgr.sync().catch(() => {}).then(() => driveExits(mgr)).catch(() => {}); tick(); // Receives arrive in real time over the mailbox stream; the poll is the @@ -457,6 +459,50 @@ export function arkFeature(ctx) { : null)); } + // ---- NWC bridge funding (PoC) -------------------------------------------- + // An external NWC wallet service (e.g. coinos) can bridge ark -> lightning: + // a nostr client asks IT to pay_invoice, it asks US (kind 23196, funding + // request) to cover the amount over ark, we auto-pay within a user-set + // allowance and ack (kind 23197). Custody window = seconds in flight. + // PoC config in localStorage 'btc-wallet-arknwc': { bridgePk, budgetSat }. + const NWC_FUND_KIND = 23196; + const NWC_FUND_ACK_KIND = 23197; + let nwcUnsub = null; + let nwcSpent = 0; + + function stopNwcFunding() { + if (nwcUnsub) { try { nwcUnsub(); } catch {} nwcUnsub = null; } + } + + function startNwcFunding(mgr) { + if (nwcUnsub || !wallet.nostrSubscribe || !wallet.nostrPubkey || !wallet.nostrPubkey()) return; + let cfg = null; + try { cfg = JSON.parse(localStorage.getItem('btc-wallet-arknwc') || 'null'); } catch {} + if (!cfg || !cfg.bridgePk || !(cfg.budgetSat > 0)) return; + const seen = new Set(); + nwcUnsub = wallet.nostrSubscribe( + { kinds: [NWC_FUND_KIND], authors: [cfg.bridgePk], '#p': [wallet.nostrPubkey()] }, + async (ev) => { + if (seen.has(ev.id)) return; + seen.add(ev.id); + let req; + try { req = JSON.parse(ev.content); } catch { return; } + const sats = Math.round(req.amountSat || 0); + if (!sats || !req.address || nwcSpent + sats > cfg.budgetSat) return; // over allowance: bridge times out + try { + const actionId = await mgr.send(req.address, sats); + const action = mgr.state.actions.find((a) => a.id === actionId); + if (!action || action.step === 'failed') return; + nwcSpent += sats; + await wallet.nostrPublish({ + kind: NWC_FUND_ACK_KIND, + tags: [['p', cfg.bridgePk], ['e', ev.id]], + content: JSON.stringify({ id: req.id }), + }); + } catch {} + }); + } + // ---- unilateral exit (trustless: no server cooperation) ------------------ const addrScript = (address) => btc.OutScript.encode(btc.Address(wallet.netCfg.net).decode(address)); diff --git a/src/features/sync.js b/src/features/sync.js index 8349b5d..9e77664 100644 --- a/src/features/sync.js +++ b/src/features/sync.js @@ -37,6 +37,12 @@ export function installSyncWallet(wallet) { this.nostr.setRelays(sync.relays); return this.nostr.fetchEvents(filter, maxWait); }, + nostrSubscribe(filter, onEvent) { + const sync = getSyncConfig(); + if (this.offline || !sync.enabled) return () => {}; + this.nostr.setRelays(sync.relays); + return this.nostr.subscribeEvents(filter, onEvent); + }, // Pull the latest state from Nostr; apply it if it's newer than what we have. // Returns true if state was applied (so the caller can skip a full scan). |
