diff options
| author | Adam Soltys <[email protected]> | 2026-02-15 22:26:55 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-02-15 22:26:55 -0800 |
| commit | 8905ad48f91e24cbb2427256a88128462a51a7a0 (patch) | |
| tree | 5caa0acb92988511b01cba5aa19297cf96aa241a /src/lib | |
| parent | c7e8f628aa215caaa999f06d7fccb910fd5cc099 (diff) | |
| download | coinos-ui-8905ad48f91e24cbb2427256a88128462a51a7a0.tar.gz coinos-ui-8905ad48f91e24cbb2427256a88128462a51a7a0.zip | |
e2e tests
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/ark.ts | 64 |
1 files changed, 62 insertions, 2 deletions
diff --git a/src/lib/ark.ts b/src/lib/ark.ts index dcc42868..a90236da 100644 --- a/src/lib/ark.ts +++ b/src/lib/ark.ts @@ -116,10 +116,30 @@ export const addProcessedVtxos = (keys: string[]) => { // Flag to suppress notifyIncomingFunds during sends export let arkSending = false; +const withTimeout = <T>(promise: Promise<T>, ms: number, label: string) => + Promise.race([ + promise, + new Promise<T>((_, reject) => + setTimeout(() => reject(new Error(`${label} timed out`)), ms), + ), + ]); + export const sendArk = async (address: string, amount: number) => { const wallet = await getWallet(); if (!wallet) throw new Error("Ark wallet not available"); + // Refresh expired VTXOs before attempting to send + const balance = await wallet.getBalance(); + if (balance.available < amount && balance.recoverable > 0) { + console.log("ark send: refreshing expired VTXOs before send"); + await refresh(); + } + + const freshBalance = await wallet.getBalance(); + if (freshBalance.available < amount) { + throw new Error(`Insufficient funds: ${freshBalance.available} available, need ${amount}`); + } + // Suppress incoming notifications during send to prevent // change VTXOs from being credited as incoming payments arkSending = true; @@ -127,13 +147,21 @@ export const sendArk = async (address: string, amount: number) => { try { let txid; try { - txid = await wallet.sendBitcoin({ address, amount }); + txid = await withTimeout( + wallet.sendBitcoin({ address, amount }), + 45_000, + "Ark send", + ); } catch (e: any) { if (e?.message?.includes("VTXO_ALREADY_REGISTERED")) { await wallet.settle(); const vtxos = await wallet.getVtxos({ spendableOnly: false }); if (vtxos?.length) addProcessedVtxos(vtxos.map(vtxoKey)); - txid = await wallet.sendBitcoin({ address, amount }); + txid = await withTimeout( + wallet.sendBitcoin({ address, amount }), + 45_000, + "Ark send retry", + ); } else { throw e; } @@ -175,6 +203,38 @@ export const sendArkViaForward = async ({ return post("/post/ark/receive", { amount, hash: txid, iid: inv.id }); }; +export const refresh = async () => { + const wallet = await getWallet(); + if (!wallet) return; + + const { VtxoManager } = await loadArkSdk(); + const balance = await wallet.getBalance(); + console.log("ark refresh: balance", balance); + const manager = new VtxoManager(wallet); + + let acted = false; + + if (balance.recoverable > 0) { + console.log("ark refresh: recovering", balance.recoverable, "swept sats"); + await manager.recoverVtxos(); + acted = true; + } + + const expiring = await manager.getExpiringVtxos(); + if (expiring.length > 0) { + console.log("ark refresh: renewing", expiring.length, "expiring vtxos"); + await manager.renewVtxos(); + acted = true; + } + + // Mark post-recovery/renewal VTXOs as processed so notifyIncomingFunds + // doesn't treat them as new incoming payments + if (acted) { + const vtxos = await wallet.getVtxos({ spendableOnly: false }); + if (vtxos?.length) addProcessedVtxos(vtxos.map(vtxoKey)); + } +}; + export const settle = async () => { const wallet = await getWallet(); if (!wallet) return; |
