aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-02-15 22:26:55 -0800
committerAdam Soltys <[email protected]>2026-02-15 22:26:55 -0800
commit8905ad48f91e24cbb2427256a88128462a51a7a0 (patch)
tree5caa0acb92988511b01cba5aa19297cf96aa241a /src
parentc7e8f628aa215caaa999f06d7fccb910fd5cc099 (diff)
downloadcoinos-ui-8905ad48f91e24cbb2427256a88128462a51a7a0.tar.gz
coinos-ui-8905ad48f91e24cbb2427256a88128462a51a7a0.zip
e2e tests
Diffstat (limited to 'src')
-rw-r--r--src/lib/ark.ts64
-rw-r--r--src/routes/(app)/+layout.svelte11
-rw-r--r--src/routes/(app)/send/bitcoin/[address]/+layout.server.ts7
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 };
}