diff options
| author | Adam Soltys <[email protected]> | 2026-03-12 12:13:05 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-03-12 12:13:05 -0700 |
| commit | 4f2bf9387fe509243501938f3d1fb8a9aed14e93 (patch) | |
| tree | 42cf34bf86832690d100e59cdba3cd6a8756ea52 /lib | |
| parent | 5347d890c4dca651500d6bdc31d4ae5d4ea20440 (diff) | |
| download | coinos-server-4f2bf9387fe509243501938f3d1fb8a9aed14e93.tar.gz coinos-server-4f2bf9387fe509243501938f3d1fb8a9aed14e93.zip | |
ark wallet monitoring
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/ark.ts | 26 |
1 files changed, 22 insertions, 4 deletions
@@ -3,8 +3,16 @@ if (!globalThis.EventSource) globalThis.EventSource = EventSource as any; import config from "$config"; import { SingleKey, Wallet, Ramps, RestArkProvider, VtxoManager } from "@arkade-os/sdk"; +import { db } from "$lib/db"; import { l, warn } from "$lib/logging"; +// Log ark wallet operations to Redis for auditing/reconciliation +const logArkOp = async (op: string, details: Record<string, any>) => { + const entry = { op, ts: Date.now(), ...details }; + await db.lPush("ark:ops", JSON.stringify(entry)); + await db.lTrim("ark:ops", 0, 9999); +}; + let wallet: any; const getWallet = async () => { @@ -61,6 +69,7 @@ export const refreshArkWallet = async (force = false) => { try { const txid = await withTimeout(manager.recoverVtxos(), 60_000, "ark recovery"); l("ark recovered swept vtxos, txid:", txid); + await logArkOp("recovery", { txid, amount: balance.recoverable }); recoveryFailures = 0; } catch (e: any) { recoveryFailures++; @@ -74,13 +83,17 @@ export const refreshArkWallet = async (force = false) => { } // Renew VTXOs approaching expiry (with backoff on repeated failures) + // 1h threshold — default SDK is 24h which causes excessive renewals/fees + const RENEWAL_THRESHOLD_MS = 60 * 60 * 1000; if (now >= renewalSkipUntil) { try { - const expiring = await manager.getExpiringVtxos(); + const expiring = await manager.getExpiringVtxos(RENEWAL_THRESHOLD_MS); if (expiring.length > 0) { - l("ark renewing", expiring.length, "expiring vtxos"); + const expiringTotal = expiring.reduce((s: number, v: any) => s + v.value, 0); + l("ark renewing", expiring.length, "expiring vtxos, total:", expiringTotal, "sats"); const txid = await withTimeout(manager.renewVtxos(), 60_000, "ark renewal"); l("ark renewed vtxos, txid:", txid); + await logArkOp("renewal", { txid, vtxoCount: expiring.length, amount: expiringTotal }); renewalFailures = 0; } } catch (e: any) { @@ -117,6 +130,7 @@ export const refreshArkWallet = async (force = false) => { "ark onboard", ); l("ark onboarded boarding utxo:", utxo.value, "sats, txid:", txid); + await logArkOp("onboard", { txid, amount: utxo.value, boardingTxid: utxo.txid, vout: utxo.vout }); failedBoardingOutpoints.clear(); break; } catch (e: any) { @@ -145,14 +159,18 @@ export const sendArk = async (address: string, amount: number) => { const send = async () => { let w = await getWallet(); try { - return await w.sendBitcoin({ address, amount }); + const txid = await w.sendBitcoin({ address, amount }); + await logArkOp("send", { txid, address, amount }); + return txid; } catch (e: any) { if (/insufficient funds/i.test(e.message) || /VTXO_RECOVERABLE/i.test(e.message) || /VTXO_ALREADY_SPENT/i.test(e.message) || /VTXO_ALREADY_REGISTERED/i.test(e.message)) { l("ark send failed:", e.message, "— recreating wallet and retrying"); wallet = null; await refreshArkWallet(true); w = await getWallet(); - return w.sendBitcoin({ address, amount }); + const txid = await w.sendBitcoin({ address, amount }); + await logArkOp("send", { txid, address, amount }); + return txid; } throw e; } |
