diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/ark/manager.js | 20 | ||||
| -rw-r--r-- | src/features/ark.js | 20 |
2 files changed, 36 insertions, 4 deletions
diff --git a/src/ark/manager.js b/src/ark/manager.js index df199af..33bac55 100644 --- a/src/ark/manager.js +++ b/src/ark/manager.js @@ -208,8 +208,12 @@ export function renewPolicy(tier, network) { } export class ArkManager { - constructor({ account, storage, arkUrl, esploraUrl, network = 'regtest', onUpdate, lnQuoteUrl, statusHint, honorsExpired = false }) { + constructor({ account, storage, arkUrl, esploraUrl, network = 'regtest', onUpdate, lnQuoteUrl, statusHint, honorsExpired = false, infoHint = null }) { this.account = account; // HDKey node; ark keys derived beneath it + // The server's last published info, kept by the app: lets the wallet open + // when the server can't be reached (see init) + this.infoHint = infoHint || null; + this.offline = false; // the server cosigns spends of a coin past its timelock (coinos): such a // coin stays money here — spendable, receivable — just not exitable this.honorsExpired = !!honorsExpired; @@ -275,7 +279,19 @@ export class ArkManager { seen.add(m.vtxoId); return true; }); - this.info = await getArkInfo(this.arkUrl); + try { + this.info = await getArkInfo(this.arkUrl); + this.offline = false; + } catch (e) { + // The server is unreachable. With its last published info in hand the + // wallet still opens on its own records: coins read, a unilateral exit + // can start (that needs the chain, not the server), and anything that + // does need the server fails when it asks. Without that info there is + // no server key to check anything against — then it really is closed. + if (!this.infoHint) throw e; + this.info = this.infoHint; + this.offline = true; + } const hs = await handshake(this.arkUrl).catch(() => null); this.psa = hs?.psa; this.serverPub = hex.decode(this.info.serverPubkey); diff --git a/src/features/ark.js b/src/features/ark.js index 6952fc0..94dfc09 100644 --- a/src/features/ark.js +++ b/src/features/ark.js @@ -494,6 +494,7 @@ export function arkFeature(ctx) { } let arkTimer = null; let arkConnectPromise = null; + let arkInitPromise = null; // the manager's own init, apart from the snapshot gate let arkInitGen = 0; // guards against a stale init() resolving after a wallet switch function stopArk() { @@ -514,6 +515,7 @@ export function arkFeature(ctx) { ark = null; arkInited = null; arkConnectPromise = null; + arkInitPromise = null; // The renewal warning describes the coins of the wallet we're leaving. // Switching accounts used to carry it onto the next one — with the old // wallet's amount and deadline, over the new wallet's balance — and the @@ -663,6 +665,9 @@ export function arkFeature(ctx) { esploraUrl: cfg.esplora, network: getNetwork(), honorsExpired: !!cfg.honorsExpired, + // the server's last published info: the wallet opens on it when the + // server is gone, so coins can still be read and exited + infoHint: (cachedArkInfo(cfg.ark) || {}).info || null, // the wallet's own tx table as a second witness for confirmations — // its data source is user-configurable and often healthier than the // ark preset's esplora from this device @@ -679,9 +684,10 @@ export function arkFeature(ctx) { const inited = mgr.init().then(() => { if (gen !== arkInitGen) return; arkInited = mgr; // readable now; `ark` (and the mailbox) wait for the snapshot - rememberArkInfo(cfg.ark, mgr.info, mgr._tipH); + if (!mgr.offline) rememberArkInfo(cfg.ark, mgr.info, mgr._tipH); render(); }); + arkInitPromise = inited; // The tip: expiries and exit costs are judged against it, and init alone // never asks for it (the first sync does, after the snapshot gate). Ask // now, off the connect path, and remember it for the next refresh. @@ -2938,7 +2944,17 @@ export function arkFeature(ctx) { async function doArkExit(ids = null) { ui.arkBusy = 'exit'; ui.arkError = ''; render(); try { - const mgr = await connectArk(); + // The manager's own init is enough for an exit — the server may be + // gone (that is what a unilateral exit is for), and the snapshot gate + // the full connect also waits on may never clear without the relays. + let mgr = arkView(); + if (!mgr) { + connectArk().catch(() => {}); + for (let i = 0; i < 50 && !arkInitPromise; i++) await new Promise((r) => setTimeout(r, 100)); + try { await arkInitPromise; } catch {} + mgr = arkView(); + } + if (!mgr) throw new Error(t('arkNotConnected')); const spendables = exitableCoins(mgr.state, mgr._tipH || 0).filter((v) => !ids || ids.includes(v.id)); if (!spendables.length) throw new Error(freeCoins(mgr.state).length ? t('arkExitNoneExitable') : t('arkNotConnected')); // Fees are paid hop by hop from Savings as the exit unrolls — an exit |
