From 63fad61716c013b6663df789ed96f6e294630642 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Tue, 3 Mar 2026 23:02:57 -0800 Subject: vault wallet fixes --- lib/ark.ts | 70 +++++++++++++++++++++++++++++++++----------------------------- 1 file changed, 37 insertions(+), 33 deletions(-) (limited to 'lib/ark.ts') diff --git a/lib/ark.ts b/lib/ark.ts index 9736b419..dd35ca21 100644 --- a/lib/ark.ts +++ b/lib/ark.ts @@ -22,6 +22,13 @@ let lastRefresh = 0; const REFRESH_COOLDOWN = 30_000; const failedBoardingOutpoints = new Set(); +let recoveryFailures = 0; +let renewalFailures = 0; +const MAX_FAILURES_BEFORE_BACKOFF = 3; +const BACKOFF_CYCLES = 30; // skip ~30 cycles (30 min at 60s interval) +let recoverySkipUntil = 0; +let renewalSkipUntil = 0; + const withTimeout = (promise: Promise, ms: number, label: string) => Promise.race([ promise, @@ -49,26 +56,42 @@ export const refreshArkWallet = async (force = false) => { const manager = new VtxoManager(w); - // Recover swept/expired VTXOs - if (balance.recoverable > 0) { + // Recover swept/expired VTXOs (with backoff on repeated failures) + if (balance.recoverable > 0 && now >= recoverySkipUntil) { try { const txid = await withTimeout(manager.recoverVtxos(), 60_000, "ark recovery"); l("ark recovered swept vtxos, txid:", txid); + recoveryFailures = 0; } catch (e: any) { - warn("ark vtxo recovery failed:", e.message); + recoveryFailures++; + if (recoveryFailures >= MAX_FAILURES_BEFORE_BACKOFF) { + recoverySkipUntil = Date.now() + BACKOFF_CYCLES * 60_000; + warn("ark vtxo recovery failed", recoveryFailures, "times, backing off 30m"); + } else { + warn("ark vtxo recovery failed:", e.message); + } } } - // Renew VTXOs approaching expiry (SDK default: 3 days) - try { - const expiring = await manager.getExpiringVtxos(); - if (expiring.length > 0) { - l("ark renewing", expiring.length, "expiring vtxos"); - const txid = await withTimeout(manager.renewVtxos(), 60_000, "ark renewal"); - l("ark renewed vtxos, txid:", txid); + // Renew VTXOs approaching expiry (with backoff on repeated failures) + if (now >= renewalSkipUntil) { + try { + const expiring = await manager.getExpiringVtxos(); + if (expiring.length > 0) { + l("ark renewing", expiring.length, "expiring vtxos"); + const txid = await withTimeout(manager.renewVtxos(), 60_000, "ark renewal"); + l("ark renewed vtxos, txid:", txid); + renewalFailures = 0; + } + } catch (e: any) { + renewalFailures++; + if (renewalFailures >= MAX_FAILURES_BEFORE_BACKOFF) { + renewalSkipUntil = Date.now() + BACKOFF_CYCLES * 60_000; + warn("ark vtxo renewal failed", renewalFailures, "times, backing off 30m"); + } else { + warn("ark vtxo renewal failed:", e.message); + } } - } catch (e: any) { - warn("ark vtxo renewal failed:", e.message); } // Onboard confirmed boarding UTXOs @@ -97,8 +120,8 @@ export const refreshArkWallet = async (force = false) => { failedBoardingOutpoints.clear(); break; } catch (e: any) { - if (!/not enough intent confirmations/i.test(e.message)) - failedBoardingOutpoints.add(outpointKey(utxo)); + const retriable = /not enough intent confirmations/i.test(e.message); + if (!retriable) failedBoardingOutpoints.add(outpointKey(utxo)); warn("ark onboard failed:", utxo.value, "sats:", e.message); } } @@ -114,25 +137,6 @@ export const refreshArkWallet = async (force = false) => { setTimeout(refreshArkWallet, 60_000); setInterval(() => refreshArkWallet(), 60_000); -// Log ark wallet balances every 60s -setInterval(async () => { - try { - const w = await getWallet(); - const balance = await w.getBalance(); - l( - "ark balance — available:", - balance.available, - "recoverable:", - balance.recoverable, - "boarding:", - balance.boarding.confirmed, - "pending:", - balance.boarding.unconfirmed, - ); - } catch (e: any) { - warn("ark balance check failed:", e.message); - } -}, 60_000); export const sendArk = async (address: string, amount: number) => { const timeout = new Promise((_, reject) => -- cgit v1.2.3