aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-02-05 11:13:16 -0800
committerAdam Soltys <[email protected]>2026-02-11 15:34:32 -0800
commited4d86167d32503ad8813c81dda9591d1b1b8190 (patch)
tree90121a8315e349a259289bafd3a1eea8859ad194 /src
parentec304f8bd1ffef69a85b75da033fbccbe6bfc72f (diff)
downloadcoinos-ui-ed4d86167d32503ad8813c81dda9591d1b1b8190.tar.gz
coinos-ui-ed4d86167d32503ad8813c81dda9591d1b1b8190.zip
fix ark handling
Diffstat (limited to 'src')
-rw-r--r--src/lib/ark.ts6
-rw-r--r--src/lib/store.ts29
-rw-r--r--src/routes/(app)/+layout.svelte71
3 files changed, 89 insertions, 17 deletions
diff --git a/src/lib/ark.ts b/src/lib/ark.ts
index 7b289d8a..26c16a93 100644
--- a/src/lib/ark.ts
+++ b/src/lib/ark.ts
@@ -30,6 +30,12 @@ export const getBalance = async (): Promise<{ available: number }> => {
return wallet.getBalance();
};
+export const getVtxos = async () => {
+ const wallet = await getWallet();
+ if (!wallet) return [];
+ return wallet.getVtxos({ spendableOnly: false });
+};
+
export const syncTransactions = async (aid: string) => {
const wallet = await getWallet();
if (!wallet) return;
diff --git a/src/lib/store.ts b/src/lib/store.ts
index 0148f480..7cd17aec 100644
--- a/src/lib/store.ts
+++ b/src/lib/store.ts
@@ -44,13 +44,40 @@ export const persistLocal = (key, defaultValue: any = undefined) => {
return s;
};
+export const persistLocalValue = (key, defaultValue: any = undefined) => {
+ const s = writable(
+ browser &&
+ localStorage.getItem(key) &&
+ localStorage.getItem(key) !== "undefined"
+ ? JSON.parse(localStorage.getItem(key) || "")
+ : defaultValue,
+ );
+
+ s.subscribe((v) => {
+ try {
+ if (browser) {
+ if (v === undefined) {
+ localStorage.removeItem(key);
+ } else {
+ localStorage.setItem(key, JSON.stringify(v));
+ }
+ }
+ } catch (e) {
+ console.log("problem setting key", v);
+ console.log(e);
+ }
+ });
+
+ return s;
+};
+
export const account = writable();
export const amountPrompt = persistLocal("amountPrompt");
export const avatar = writable();
export const banner = writable();
export const event = writable();
export const events = writable({});
-export const fiat = persistLocal("fiat", true);
+export const fiat = persistLocalValue("fiat", true);
export const installPrompt = writable();
export const invoice = writable();
export const last = writable();
diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte
index c56a487a..d9f6a0d0 100644
--- a/src/routes/(app)/+layout.svelte
+++ b/src/routes/(app)/+layout.svelte
@@ -1,5 +1,5 @@
<script>
- import { getWallet, getAddress, syncTransactions, settle } from "$lib/ark";
+ import { getWallet, getAddress, getVtxos, syncTransactions, settle } from "$lib/ark";
import { SvelteToast } from "@zerodevx/svelte-toast";
import { onDestroy, onMount } from "svelte";
import { close, connect, send, socket } from "$lib/socket";
@@ -46,30 +46,56 @@
}
});
- // Track last known Ark balance to compute incoming amount
- let getLastArkBalance = () => {
- return parseInt(localStorage.getItem("lastArkBalance") || "0");
+ // Get unique VTXO key (txid:vout like arkade-wallet uses)
+ let vtxoKey = (v) => `${v.txid}:${v.vout}`;
+
+ // Track processed VTXO keys to avoid duplicates
+ let getProcessedVtxos = () => {
+ try {
+ return new Set(JSON.parse(localStorage.getItem("processedArkVtxos") || "[]"));
+ } catch {
+ return new Set();
+ }
};
- let setLastArkBalance = (bal) => {
- localStorage.setItem("lastArkBalance", bal.toString());
+ let addProcessedVtxos = (keys) => {
+ let processed = getProcessedVtxos();
+ keys.forEach((k) => processed.add(k));
+ // Keep only last 200 to avoid unbounded growth
+ let arr = [...processed].slice(-200);
+ localStorage.setItem("processedArkVtxos", JSON.stringify(arr));
};
+ // Prevent concurrent processing of Ark notifications
+ let arkProcessing = 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);
- // Calculate current total from newVtxos
- let currentTotal = notification.newVtxos.reduce((a, b) => a + b.value, 0);
- let lastBalance = getLastArkBalance();
+ // Filter out already-processed VTXOs using txid:vout composite key
+ let processed = getProcessedVtxos();
+ let newVtxos = notification.newVtxos.filter((v) => !processed.has(vtxoKey(v)));
- // Compute delta (incoming amount)
- let amount = currentTotal - lastBalance;
+ if (newVtxos.length === 0) {
+ arkProcessing = false;
+ return;
+ }
- // Update tracked balance
- setLastArkBalance(currentTotal);
+ // 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) return;
+ if (amount <= 0) {
+ arkProcessing = false;
+ return;
+ }
if ($fiat && $rateStore && user?.currency) {
success(`Received ${f(toFiat(amount, $rateStore), user.currency)}!`);
@@ -78,13 +104,18 @@
}
try {
+ let aid = getCookie("aid") || user.id;
+
let inv = await post(`/post/invoice`, {
- invoice: { type: "ark", amount },
+ invoice: { type: "ark", amount, aid },
user,
});
+ let hash = newVtxos[0]?.txid;
+
await post(`/post/ark/receive`, {
amount,
+ hash,
iid: inv.id,
});
@@ -93,9 +124,10 @@
noScroll: true,
});
- settle().catch((e) => console.error("Ark settle failed:", e));
} catch (e) {
console.error("Failed to record ARK payment:", e);
+ } finally {
+ arkProcessing = false;
}
};
@@ -107,6 +139,13 @@
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);
const aid = getCookie("aid") || user.id;