diff options
| author | Adam Soltys <[email protected]> | 2026-09-22 11:24:34 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-09-22 11:24:34 -0700 |
| commit | 229746922fbd566ef9014845c2111c5044ac91fa (patch) | |
| tree | 074d800cef61ce3c75bdfd7731828d8ad2f0280d /src | |
| parent | ff0038baed91f79722c3a3ac71651cdd1fc145e8 (diff) | |
| download | coinosv3-229746922fbd566ef9014845c2111c5044ac91fa.tar.gz coinosv3-229746922fbd566ef9014845c2111c5044ac91fa.zip | |
Ark: receives waiting with another server are claimed; notifications belong to the identity.
Once per session the wallet visits every other Ark server it holds state
with — its own manager, a receive rescan, a sync to claim — and says what
arrived and where; the coins wait there until a switch back. The stored
notification list remembers which identity fetched it, so another identity
on the same wallet starts empty instead of reading the previous one's.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tmfg8k5f3TbNJS34qiqHAp
Diffstat (limited to 'src')
| -rw-r--r-- | src/features/ark.js | 39 | ||||
| -rw-r--r-- | src/features/messages.js | 9 | ||||
| -rw-r--r-- | src/i18n.js | 1 |
3 files changed, 46 insertions, 3 deletions
diff --git a/src/features/ark.js b/src/features/ark.js index 2b81a1e..ab7f28d 100644 --- a/src/features/ark.js +++ b/src/features/ark.js @@ -417,6 +417,7 @@ export function arkFeature(ctx) { // the next connect's first tick re-decides for the wallet now open. arkRenewWarn = null; arkAutoRefreshAt = 0; + rescuedServers = false; } function arkAvailable() { @@ -443,7 +444,7 @@ export function arkFeature(ctx) { store.open().then(() => { ctx.forgetAmountAnim && ctx.forgetAmountAnim(); render(); initArk(); }).catch(() => {}); return; } - if (arkAvailable() && arkWanted()) { lastAutoInit = Date.now(); connectArk().catch(() => {}); } + if (arkAvailable() && arkWanted()) { lastAutoInit = Date.now(); connectArk().then(() => rescueOtherServers()).catch(() => {}); } } // Automatic re-inits (a synced snapshot arriving) must not restart a @@ -452,6 +453,42 @@ export function arkFeature(ctx) { // card's "connecting" indicator flickers on every snapshot. User-driven // calls to initArk() stay immediate. let lastAutoInit = 0; + // A payment can land with a server this wallet is NOT connected to: an + // invoice minted for its coinos name by the background answerer before a + // switch, or by another device still on the old server. Once per session, + // every other server this wallet holds state with gets a short visit — its + // own manager, a receive rescan, a sync to claim — and a toast says what + // arrived and where. The coins stay with that server until a switch back. + let rescuedServers = false; + async function rescueOtherServers() { + if (rescuedServers || wallet.watchOnly) return; + rescuedServers = true; + const cur = getArkConfig(); + if (!cur) return; + for (const p of arkPresets(getNetwork())) { + if (!p.ark || p.ark === cur.ark || p.id === 'custom' || p.id === 'off') continue; + if (!wallet.loadArkState(p.ark)) continue; // never used that server + try { + const mgr = new ArkManager({ + account: wallet.account(), + storage: { + load: () => wallet.loadArkState(p.ark), + adopt: (serverPubkey) => wallet.adoptArkState(serverPubkey, p.ark), + save: (s) => { wallet.saveArkState(mergeArkStates(s, wallet.loadArkState(p.ark)), p.ark); }, + }, + arkUrl: p.ark, esploraUrl: p.esplora, network: getNetwork(), + }); + await mgr.init(); + const done = () => (mgr.state.movements || []).filter((m) => m.type === 'ln-receive' && m.status === 'complete'); + const before = done().length; + await mgr.sync(); // rescans receives and drives the claims + await new Promise((r) => setTimeout(r, 2500)); + await mgr.sync(); // a claim takes more than one step + const got = done().slice(before).reduce((n, m) => n + (m.amountSat || 0), 0); + if (got > 0) toast(t('arkOtherServerReceipt', { amount: fmtSats(got), server: p.label.replace(/ \(.*\)$/, '') }), 9000); + } catch (e) { console.warn('ark: rescue on ' + p.ark + ' failed', e); } + } + } function maybeInitArk() { if (ark || arkConnectPromise) return; if (Date.now() - lastAutoInit < 20000) return; diff --git a/src/features/messages.js b/src/features/messages.js index b97ff8d..e5bc300 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -7063,9 +7063,14 @@ export function messagesFeature(ctx) { let notif = null; // { status, items } let notifAt = 0, notifUnsub = null; const notifSeen = () => st().notifSeen || 0; + // Notifications are about the IDENTITY, not the wallet: the stored list + // remembers whose it is, and another identity on the same wallet starts + // empty instead of reading the previous one's. + const identityPk = () => (hook('nostrLoginIdentity') || {}).pubkey || (wallet.nostr && wallet.nostr.pk) || null; function notifNow() { if (!notif) { - const stored = st().notifs || []; + const s = st(); + const stored = (!s.notifsPk || s.notifsPk === identityPk()) ? (s.notifs || []) : []; notif = { status: stored.length ? 'ready' : 'loading', items: stored }; refreshNotifs(); } @@ -7116,7 +7121,7 @@ export function messagesFeature(ctx) { c.items = kept.slice(0, NOTIF_KEEP); // the rows already say who; a reply row also wants its reader-facing text, // which it carries — the events themselves are not kept - const s2 = st(); s2.notifs = c.items; save(s2); + const s2 = st(); s2.notifs = c.items; s2.notifsPk = identityPk(); save(s2); // a reply is a note we can open straight away; keep it in hand for (const ev of evs) if (ev.kind === 1) notifNotes.set(ev.id, ev); return true; diff --git a/src/i18n.js b/src/i18n.js index 74219ac..d1a6962 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -860,6 +860,7 @@ const STR = { arkCmpMin: 'min {n} sats', arkCmpNote: 'Fees graded by expiry cost more the earlier a coin is renewed or exited. Figures come from each server’s own published info, read just now. Either server pays Lightning invoices and takes Lightning payments to your coinos name.', arkServerSwitched: 'Ark server switched', + arkOtherServerReceipt: '{amount} sats arrived with {server}. Switch to that server in Settings → Advanced to spend them.', arkForeignBip21: 'Their Ark address is on a different Ark server — paying the on-chain address instead.', arkForeignServer: 'That Ark address belongs to a different Ark server, so it can’t be paid from here directly. Ask for a Lightning invoice, or pay their coinos name or npub — those fall back to Lightning by themselves.', arkLnFromSavingsTitle: 'Lightning pays from Spending', |
