diff options
| author | Adam Soltys <[email protected]> | 2026-08-30 20:44:09 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-08-30 20:44:09 -0700 |
| commit | d8b0e275ad76d2950e3d3ffcafd3c6c3d0ea0e7a (patch) | |
| tree | 4c9ccec107ad76e27714a8af0a3327b210f590b3 | |
| parent | 735ee60d386e9fcf5e8e7c62ab53ba0534e43850 (diff) | |
| download | coinosv3-d8b0e275ad76d2950e3d3ffcafd3c6c3d0ea0e7a.tar.gz coinosv3-d8b0e275ad76d2950e3d3ffcafd3c6c3d0ea0e7a.zip | |
A board that never funded stops masquerading as work in progress: the tx build failing now marks the action failed on the spot, and connect sweeps any unfunded board stub older than an hour — a lingering 'created' stub silently blocked auto-renewal on that wallet for good (cooney had one from the Max shortfall, another wallet a 50k one)
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
| -rw-r--r-- | src/features/ark.js | 23 |
1 files changed, 23 insertions, 0 deletions
diff --git a/src/features/ark.js b/src/features/ark.js index dc4c0f1..bacf49e 100644 --- a/src/features/ark.js +++ b/src/features/ark.js @@ -386,6 +386,19 @@ export function arkFeature(ctx) { arkConnectPromise = mgr.init().then(() => { if (gen !== arkInitGen) throw new Error('superseded'); // wallet switched mid-connect ark = mgr; + // Boards that died before funding (a failed tx build, a closed tab) + // used to linger at 'created' forever — and an in-flight-looking + // action blocks auto-renewal. Anything unfunded after an hour is a + // stub, not a plan; the id carries its birth time. + { + let swept = false; + for (const a of mgr.state.actions || []) { + if (a.type !== 'board' || a.fundingTxid || ['done', 'failed'].includes(a.step)) continue; + const ts = Number((a.id || '').split('-')[1]) || 0; + if (ts && Date.now() - ts > 3600_000) { a.step = 'failed'; a.error = a.error || 'never funded'; swept = true; } + } + if (swept) mgr._save(); + } announceArkAddress(mgr); // ark zaps: tell nostr where our mailbox lives startNwcFunding(mgr); // NWC bridge: honor funding requests within the allowance // Publish this device's ark state to its per-device sync slot on connect. @@ -1076,12 +1089,15 @@ export function arkFeature(ctx) { const sats = parseInt((ui.arkBoardAmt || '').trim(), 10); if (!sats) return; ui.arkBusy = 'board'; ui.arkError = ''; render(); + let unfunded = null; // the action id, until the funding tx is on the wire try { const { actionId, fundingAddress, feeSat } = await ark.startBoard(sats); + unfunded = actionId; const feeRate = (wallet.feeRates && wallet.feeRates.halfHourFee) || 5; const draft = wallet.buildTx({ recipients: [{ address: fundingAddress, amount: sats }], feeRate, noSort: true }); const hexTx = wallet.sign(draft.tx); const txid = await wallet.broadcast(hexTx); + unfunded = null; // money moved — this action must live on whatever happens next // Like the main send flow: reflect the spend locally and let the // poll/watcher reconcile. A scan here would race the explorer's indexing // and could resurrect the just-spent coin. @@ -1090,6 +1106,13 @@ export function arkFeature(ctx) { ui.arkBoardAmt = ''; ui.arkBoarded = { txid, netSat: sats - feeSat }; } catch (e) { + // A board that never funded is not in flight. Leaving the stub at + // 'created' looked like an in-flight action forever — which silently + // blocked auto-renewal (it refuses to start a round beside one). + if (unfunded && ark && ark.state) { + const a = (ark.state.actions || []).find((x) => x.id === unfunded); + if (a && !a.fundingTxid) { a.step = 'failed'; a.error = e.message; try { ark._save(); } catch {} } + } ui.arkError = e.message; } ui.arkBusy = null; render(); |
