From 4d3377ba1d37147b194c1fb0504edee3c73211a5 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Sun, 15 Feb 2026 10:32:40 -0800 Subject: ark refresh --- lib/ark.ts | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 106 insertions(+), 1 deletion(-) (limited to 'lib/ark.ts') diff --git a/lib/ark.ts b/lib/ark.ts index 0d2d180f..52f441d7 100644 --- a/lib/ark.ts +++ b/lib/ark.ts @@ -2,7 +2,14 @@ import { EventSource } from "eventsource"; if (!globalThis.EventSource) globalThis.EventSource = EventSource; import config from "$config"; -import { SingleKey, Wallet } from "@arkade-os/sdk"; +import { + SingleKey, + Wallet, + Ramps, + RestArkProvider, + VtxoManager, +} from "@arkade-os/sdk"; +import { l, warn } from "$lib/logging"; let wallet: any; @@ -16,6 +23,104 @@ const getWallet = async () => { return wallet; }; +let refreshing = false; + +const withTimeout = (promise: Promise, ms: number, label: string) => + Promise.race([ + promise, + new Promise((_, reject) => + setTimeout(() => reject(new Error(`${label} timed out`)), ms), + ), + ]); + +export const refreshArkWallet = async () => { + if (refreshing) return; + try { + refreshing = true; + const w = await getWallet(); + const balance = await w.getBalance(); + l( + "ark wallet check — available:", + balance.available, + "recoverable:", + balance.recoverable, + "boarding:", + balance.boarding.confirmed, + ); + + const manager = new VtxoManager(w); + + // Recover swept/expired VTXOs + if (balance.recoverable > 0) { + try { + const txid = await withTimeout( + manager.recoverVtxos(), + 60_000, + "ark recovery", + ); + l("ark recovered swept vtxos, txid:", txid); + } catch (e: any) { + 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); + } + } catch (e: any) { + warn("ark vtxo renewal failed:", e.message); + } + + // Onboard confirmed boarding UTXOs + const boardingUtxos = await w.getBoardingUtxos(); + const confirmed = boardingUtxos.filter((u: any) => u.status?.confirmed); + + if (confirmed.length > 0) { + const provider = new RestArkProvider(config.ark.arkServerUrl); + const info = await provider.getInfo(); + const ramps = new Ramps(w); + + // Try each boarding UTXO, freshest first + const sorted = [...confirmed].sort( + (a: any, b: any) => + (b.status?.block_height || 0) - (a.status?.block_height || 0), + ); + + for (const utxo of sorted) { + try { + const txid = await withTimeout( + ramps.onboard(info.fees, [utxo], undefined, () => {}), + 60_000, + "ark onboard", + ); + l("ark onboarded boarding utxo:", utxo.value, "sats, txid:", txid); + break; + } catch (e: any) { + warn("ark onboard failed:", utxo.value, "sats:", e.message); + } + } + } + } catch (e: any) { + warn("ark wallet refresh failed:", e.message); + } finally { + refreshing = false; + } +}; + +// Check every 2 minutes +setInterval(refreshArkWallet, 2 * 60 * 1000); +// Initial check after 30s startup delay +setTimeout(refreshArkWallet, 30_000); + export const sendArk = async (address: string, amount: number) => { const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error("Ark send timed out")), 45000), -- cgit v1.2.3