diff options
| author | Adam Soltys <[email protected]> | 2026-02-09 01:19:24 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-02-11 15:34:34 -0800 |
| commit | 8aad862255f18f80de9547eb14d2514aad16b783 (patch) | |
| tree | 93f6ef16483735a20be1941e818465d3a77cebf6 /src | |
| parent | f85f5bebc49f566eaeb7bd81a285b6516dbefa9f (diff) | |
| download | coinos-ui-8aad862255f18f80de9547eb14d2514aad16b783.tar.gz coinos-ui-8aad862255f18f80de9547eb14d2514aad16b783.zip | |
ark fixes
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib/ark.ts | 48 | ||||
| -rw-r--r-- | src/routes/(app)/+layout.svelte | 89 | ||||
| -rw-r--r-- | src/routes/(app)/send/ark/[address]/+page.svelte | 14 | ||||
| -rw-r--r-- | src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.svelte | 56 |
4 files changed, 93 insertions, 114 deletions
diff --git a/src/lib/ark.ts b/src/lib/ark.ts index 25122a8c..ff82393b 100644 --- a/src/lib/ark.ts +++ b/src/lib/ark.ts @@ -8,16 +8,27 @@ export const arkkey = persistLocal("arkkey", ""); let arkSdkPromise: Promise<typeof import("@arkade-os/sdk")> | undefined; const loadArkSdk = () => (arkSdkPromise ||= import("@arkade-os/sdk")); +let walletInstance: any; +let walletKey: string | undefined; + export const getWallet = async () => { const key = get(arkkey); - if (!key) return; + if (!key) { + walletInstance = undefined; + walletKey = undefined; + return; + } + if (walletInstance && walletKey === key) return walletInstance; + const { SingleKey, Wallet } = await loadArkSdk(); const identity = SingleKey.fromHex(key); - return Wallet.create({ + walletInstance = await Wallet.create({ identity, arkServerUrl, }); + walletKey = key; + return walletInstance; }; export const getAddress = async (): Promise<string> => { @@ -44,7 +55,8 @@ export const syncTransactions = async (aid: string) => { if (!history.length) return; const transactions = history.map((tx) => ({ - hash: tx.key.arkTxid || tx.key.commitmentTxid, + arkTxid: tx.key.arkTxid || null, + commitmentTxid: tx.key.commitmentTxid || null, amount: tx.type === "RECEIVED" ? tx.amount : -tx.amount, settled: tx.settled, createdAt: tx.createdAt, @@ -73,18 +85,29 @@ export const addProcessedVtxos = (keys: string[]) => { localStorage.setItem("processedArkVtxos", JSON.stringify(arr)); }; +// Flag to suppress notifyIncomingFunds during sends +export let arkSending = false; + export const sendArk = async (address: string, amount: number) => { const wallet = await getWallet(); if (!wallet) throw new Error("Ark wallet not available"); - const txid = await wallet.sendBitcoin({ address, amount }); + // Suppress incoming notifications during send to prevent + // change VTXOs from being credited as incoming payments + arkSending = true; - // Mark post-send VTXOs as processed so notifyIncomingFunds - // ignores change VTXOs from this send - const vtxos = await wallet.getVtxos({ spendableOnly: false }); - if (vtxos?.length) addProcessedVtxos(vtxos.map(vtxoKey)); + try { + const txid = await wallet.sendBitcoin({ address, amount }); + + // Mark post-send VTXOs as processed so notifyIncomingFunds + // ignores change VTXOs from this send + const vtxos = await wallet.getVtxos({ spendableOnly: false }); + if (vtxos?.length) addProcessedVtxos(vtxos.map(vtxoKey)); - return txid; + return txid; + } finally { + arkSending = false; + } }; export const settle = async () => { @@ -94,5 +117,10 @@ export const settle = async () => { const balance = await wallet.getBalance(); if (!balance.preconfirmed) return; - return wallet.settle(); + await wallet.settle(); + + // Mark post-settle VTXOs as processed so notifyIncomingFunds + // ignores renewed VTXOs (they get new txids after an Ark round) + const vtxos = await wallet.getVtxos({ spendableOnly: false }); + if (vtxos?.length) addProcessedVtxos(vtxos.map(vtxoKey)); }; diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index 587a4b98..b67194b7 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -1,5 +1,5 @@ <script> - import { getWallet, getAddress, getVtxos, syncTransactions, settle, vtxoKey, getProcessedVtxos, addProcessedVtxos } from "$lib/ark"; + import { getWallet, syncTransactions, settle } from "$lib/ark"; import { SvelteToast } from "@zerodevx/svelte-toast"; import { onDestroy, onMount } from "svelte"; import { close, connect, send, socket } from "$lib/socket"; @@ -22,7 +22,7 @@ import AppHeader from "$comp/AppHeader.svelte"; import Nostr from "$comp/Nostr.svelte"; import Password from "$comp/Password.svelte"; - import { s, f, toFiat, success, post, getCookie, warning } from "$lib/utils"; + import { s, f, toFiat, success, getCookie, warning } from "$lib/utils"; import { t, locale, loading } from "$lib/translations"; import { goto, invalidate, afterNavigate, preloadData } from "$app/navigation"; @@ -46,68 +46,29 @@ } }); - // Prevent concurrent processing of Ark notifications - let arkProcessing = false; + let arkSyncing = false; - let handleArkPayment = async (notification) => { - // Skip if already processing (prevents race conditions from rapid SDK callbacks) - if (arkProcessing) return; - arkProcessing = true; - - console.log("ARK payment notification:", notification); - - // Filter out already-processed VTXOs using txid:vout composite key - let processed = getProcessedVtxos(); - let newVtxos = notification.newVtxos.filter((v) => !processed.has(vtxoKey(v))); - - if (newVtxos.length === 0) { - arkProcessing = false; - return; - } - - // Sum only truly new VTXOs - let amount = newVtxos.reduce((a, b) => a + b.value, 0); - - // Mark these VTXOs as processed immediately - addProcessedVtxos(newVtxos.map(vtxoKey)); - - // Only process positive incoming amounts - if (amount <= 0) { - arkProcessing = false; - return; - } - - if ($fiat && $rateStore && user?.currency) { - success(`Received ${f(toFiat(amount, $rateStore), user.currency)}!`); - } else { - success(`Received ⚡️${s(amount)}!`); - } + let arkSync = async () => { + if (arkSyncing) return; + arkSyncing = true; try { - let aid = getCookie("aid") || user.id; - - let inv = await post(`/post/invoice`, { - invoice: { type: "ark", amount, aid }, - user, - }); - - let hash = newVtxos[0]?.txid; + const aid = getCookie("aid") || user.id; + const result = await syncTransactions(aid); - await post(`/post/ark/receive`, { - amount, - hash, - iid: inv.id, - }); - - goto(`/invoice/${inv.id}`, { - invalidateAll: true, - noScroll: true, - }); + if (result?.received > 0) { + if ($fiat && $rateStore && user?.currency) { + success(`Received ${f(toFiat(result.received, $rateStore), user.currency)}!`); + } else { + success(`Received ⚡️${s(result.received)}!`); + } + } + if (result?.synced) invalidate("app:payments"); } catch (e) { - console.error("Failed to record ARK payment:", e); + console.error("Ark sync failed:", e); } finally { - arkProcessing = false; + arkSyncing = false; } }; @@ -119,19 +80,9 @@ if (user) { const wallet = await getWallet(); if (wallet) { - // Pre-populate processed VTXOs with existing ones before listening - const existingVtxos = await getVtxos(); - if (existingVtxos?.length) { - addProcessedVtxos(existingVtxos.map(vtxoKey)); - console.log("Pre-populated", existingVtxos.length, "existing VTXOs"); - } - - wallet.notifyIncomingFunds(handleArkPayment); + wallet.notifyIncomingFunds(arkSync); - const aid = getCookie("aid") || user.id; - syncTransactions(aid).then((result) => { - if (result?.synced) invalidate("app:payments"); - }).catch((e) => console.error("Ark sync failed:", e)); + arkSync(); settle().catch((e) => console.error("Ark settle failed:", e)); } diff --git a/src/routes/(app)/send/ark/[address]/+page.svelte b/src/routes/(app)/send/ark/[address]/+page.svelte index 140a2393..5361f292 100644 --- a/src/routes/(app)/send/ark/[address]/+page.svelte +++ b/src/routes/(app)/send/ark/[address]/+page.svelte @@ -5,8 +5,6 @@ import { page } from "$app/stores"; import { fiat as fiatStore, rate } from "$lib/store"; import { loc, fail, s, f, sats } from "$lib/utils"; - import { getWallet } from "$lib/ark"; - let { data } = $props(); let { balance, user } = data; @@ -30,16 +28,6 @@ submit.click(); }; - let send = async () => { - console.log("SENDING", a); - let wallet = await getWallet(); - const txid = await wallet.sendBitcoin({ - address, - amount, - }); - - console.log(txid); - }; </script> <div class="container px-4 max-w-xl mx-auto space-y-5 text-center"> @@ -72,10 +60,8 @@ <form action={`/send/ark/${address}/${amount}`} class="contents" - onsubmit={send} > <button - use:focus bind:this={submit} type="submit" class="btn !w-auto grow btn-accent" diff --git a/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.svelte b/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.svelte index 92bddd15..95812266 100644 --- a/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.svelte +++ b/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.svelte @@ -2,7 +2,7 @@ import { t } from "$lib/translations"; import { enhance } from "$app/forms"; import Spinner from "$comp/Spinner.svelte"; - import { focus, s } from "$lib/utils"; + import { focus, s, post } from "$lib/utils"; import { pin } from "$lib/store"; import { goto, invalidate } from "$app/navigation"; import { rate } from "$lib/store"; @@ -15,30 +15,44 @@ let error = $state(""); - let handler = async ({ cancel }) => { - submitting = true; - error = ""; + let handler = ({ cancel }) => { + if (account.type === "ark") { + cancel(); - try { - if (account.type === "ark") { - const txid = await sendArk(address, amount); - console.log("ARK tx:", txid); - } + (async () => { + submitting = true; + error = ""; + try { + const txid = await sendArk(address, parseInt(amount)); - return async ({ result }) => { - submitting = false; - if (result.type === "redirect") { - goto(result.location); - } else { - await applyAction(result); - invalidate("app:payments"); + const p = await post("/post/ark/vault-send", { + hash: txid, + amount: parseInt(amount), + aid: account.id, + }); + + goto(`/sent/${p.id}`, { invalidateAll: true }); + } catch (e) { + submitting = false; + error = e.message || "Failed to send"; } - }; - } catch (e) { - submitting = false; - error = e.message || "Failed to send"; - cancel(); + })(); + + return; } + + submitting = true; + error = ""; + + return async ({ result }) => { + submitting = false; + if (result.type === "redirect") { + goto(result.location); + } else { + await applyAction(result); + invalidate("app:payments"); + } + }; }; let { account, amount, address, message } = $derived(data); |
