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 | |
| parent | c7e8f628aa215caaa999f06d7fccb910fd5cc099 (diff) | |
| download | coinos-ui-8905ad48f91e24cbb2427256a88128462a51a7a0.tar.gz coinos-ui-8905ad48f91e24cbb2427256a88128462a51a7a0.zip | |
e2e tests
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib/ark.ts | 64 | ||||
| -rw-r--r-- | src/routes/(app)/+layout.svelte | 11 | ||||
| -rw-r--r-- | src/routes/(app)/send/bitcoin/[address]/+layout.server.ts | 7 |
3 files changed, 76 insertions, 6 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; diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index 2d252385..2eda8072 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -1,5 +1,5 @@ <script lang="ts"> - import { getWallet, syncTransactions, settle, arkkey, arkaid, sendArk } from "$lib/ark"; + import { getWallet, syncTransactions, settle, refresh, arkkey, arkaid, sendArk } from "$lib/ark"; import { SvelteToast } from "@zerodevx/svelte-toast"; import { onDestroy, onMount } from "svelte"; import { close, connect, send, socket } from "$lib/socket"; @@ -91,6 +91,7 @@ }; let arkInitializedKey: string | undefined; + let arkRefreshTimer: ReturnType<typeof setInterval> | undefined; const initArk = async () => { const key = $arkkey; @@ -102,7 +103,14 @@ localStorage.setItem("arkkey:uid", user.id); wallet.notifyIncomingFunds(arkSync); arkSync(); + refresh().catch((e) => console.error("Ark refresh failed:", e)); settle().catch((e) => console.error("Ark settle failed:", e)); + + // Periodically recover expired VTXOs + if (arkRefreshTimer) clearInterval(arkRefreshTimer); + arkRefreshTimer = setInterval(() => { + refresh().catch((e) => console.error("Ark periodic refresh failed:", e)); + }, 60_000); } }; @@ -175,6 +183,7 @@ if (browser) { close(); clearTimeout(checkTimer); + if (arkRefreshTimer) clearInterval(arkRefreshTimer); } }); </script> diff --git a/src/routes/(app)/send/bitcoin/[address]/+layout.server.ts b/src/routes/(app)/send/bitcoin/[address]/+layout.server.ts index 6acb36b6..acf6a370 100644 --- a/src/routes/(app)/send/bitcoin/[address]/+layout.server.ts +++ b/src/routes/(app)/send/bitcoin/[address]/+layout.server.ts @@ -5,7 +5,8 @@ export async function load({ cookies, params, parent }) { const { user } = await parent(); const aid = cookies.get("aid") || user.id; const { address, amount } = params; - const { balance } = await get(`/account/${aid}`, auth(cookies)); + const account = await get(`/account/${aid}`, auth(cookies)); + const senderIsVault = account.pubkey || account.seed; let invoice; try { @@ -14,7 +15,7 @@ export async function load({ cookies, params, parent }) { // invoice not found } - if (invoice) { + if (invoice && !senderIsVault) { const isVault = invoice.account?.pubkey || invoice.account?.seed; if (isVault) { // Vault account — stay on /send/bitcoin for on-chain send @@ -24,5 +25,5 @@ export async function load({ cookies, params, parent }) { } } - return { balance }; + return { balance: account.balance }; } |
