aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-02-28 22:06:27 -0800
committerAdam Soltys <[email protected]>2026-02-28 22:06:27 -0800
commit6ca9170a25b253483e7ce15767b7daae67c1334f (patch)
tree38be88205312e504630dc84c5ab0223a4f7cff6a /src
parent36ae7b0cfc66aa614287f96ac2be85ff183130a2 (diff)
downloadcoinos-ui-6ca9170a25b253483e7ce15767b7daae67c1334f.tar.gz
coinos-ui-6ca9170a25b253483e7ce15767b7daae67c1334f.zip
ark wait for send
Diffstat (limited to 'src')
-rw-r--r--src/components/Account.svelte30
-rw-r--r--src/lib/ark.ts62
-rw-r--r--src/lib/socket.ts3
-rw-r--r--src/routes/(app)/+layout.svelte6
-rw-r--r--src/routes/(app)/[username]/(profile)/+page.svelte2
-rw-r--r--src/routes/(static)/login/+page.server.ts2
-rw-r--r--src/routes/(static)/login/+page.svelte26
7 files changed, 86 insertions, 45 deletions
diff --git a/src/components/Account.svelte b/src/components/Account.svelte
index ff9218fe..66b1cf12 100644
--- a/src/components/Account.svelte
+++ b/src/components/Account.svelte
@@ -129,14 +129,14 @@
{/if}
</div>
<div class="flex items-center gap-1 shrink-0 text-base-content">
- <a
- href="/payments"
- class="hover:text-gray-800 w-10 h-10 flex items-center justify-center"
- aria-label="Payment history"
- onclick={(e) => setAccount(e, "/payments")}
- >
- <iconify-icon noobserver icon="ph:clock-counter-clockwise-bold" width="38"></iconify-icon>
- </a>
+ <!-- <a -->
+ <!-- href="/payments" -->
+ <!-- class="hover:text-gray-800 w-10 h-10 flex items-center justify-center" -->
+ <!-- aria-label="Payment history" -->
+ <!-- onclick={(e) => setAccount(e, "/payments")} -->
+ <!-- > -->
+ <!-- <iconify-icon noobserver icon="ph:clock-counter-clockwise-bold" width="38"></iconify-icon> -->
+ <!-- </a> -->
<a
href={`/account/${id}`}
class="hover:opacity-80 relative w-10 h-10 flex items-center justify-center"
@@ -152,12 +152,12 @@
{:else}
<img src="/images/icon.png" class="w-8 h-8" alt="Coinos" />
{/if}
- <iconify-icon
- noobserver
- icon="ph:gear-bold"
- width="20"
- class="absolute -bottom-1 -right-1 text-base-content bg-base-100 rounded-full"
- ></iconify-icon>
+ <!-- <iconify-icon -->
+ <!-- noobserver -->
+ <!-- icon="ph:faders-bold" -->
+ <!-- width="20" -->
+ <!-- class="absolute -bottom-1 -right-1 text-base-content bg-base-100 rounded-full" -->
+ <!-- ></iconify-icon> -->
</a>
</div>
</div>
@@ -172,7 +172,7 @@
</a>
<a href={`/send`} class="contents" onclick={(e) => setAccount(e, "/send")}>
- <button type="button" class="btn btn-accent !w-auto flex-1">
+ <button type="button" class="btn !w-auto flex-1">
<iconify-icon noobserver icon="ph:paper-plane-right-bold" width="32"></iconify-icon>
<div class="my-auto">{$t("user.dashboard.send")}</div>
</button>
diff --git a/src/lib/ark.ts b/src/lib/ark.ts
index 75609381..b59baa04 100644
--- a/src/lib/ark.ts
+++ b/src/lib/ark.ts
@@ -338,6 +338,8 @@ export const addProcessedVtxos = (keys: string[]) => {
// Flag to suppress sync during sends
export let arkSending = false;
+// Flag to suppress "Received" toasts during vault forward sends
+export let vaultForwarding = false;
let syncPromise: Promise<any> | null = null;
const withTimeout = <T>(promise: Promise<T>, ms: number, label: string) =>
@@ -392,27 +394,36 @@ export const sendArk = async (address: string, amount: number) => {
}
}
- // Verify the send actually changed the balance
- const postBalance = await sendWallet.getBalance();
- const postSendTotal = (postBalance.available || 0) + (postBalance.preconfirmed || 0);
- console.log(
- "ark send: pre-balance",
- preSendTotal,
- "post-balance",
- postSendTotal,
- "amount",
- amount,
- "txid",
- txid,
- );
- if (postSendTotal >= preSendTotal) {
- throw new Error("Send failed: balance unchanged after sendBitcoin");
+ // Verify the send and mark VTXOs as processed, but don't let
+ // stalled SDK calls block the flow — the send already succeeded
+ try {
+ const postBalance: any = await withTimeout(sendWallet.getBalance(), 10_000, "post-send getBalance");
+ const postSendTotal = (postBalance.available || 0) + (postBalance.preconfirmed || 0);
+ console.log(
+ "ark send: pre-balance",
+ preSendTotal,
+ "post-balance",
+ postSendTotal,
+ "amount",
+ amount,
+ "txid",
+ txid,
+ );
+ if (postSendTotal >= preSendTotal) {
+ console.warn("ark send: balance unchanged, but txid was returned — proceeding");
+ }
+ } catch (e: any) {
+ console.warn("ark send: post-send balance check failed:", e.message);
}
// Mark post-send VTXOs as processed so notifyIncomingFunds
// ignores change VTXOs from this send
- const vtxos = await sendWallet.getVtxos({ spendableOnly: false });
- if (vtxos?.length) addProcessedVtxos(vtxos.map(vtxoKey));
+ try {
+ const vtxos: any[] = await withTimeout(sendWallet.getVtxos({ spendableOnly: false }), 10_000, "post-send getVtxos");
+ if (vtxos?.length) addProcessedVtxos(vtxos.map(vtxoKey));
+ } catch (e: any) {
+ console.warn("ark send: post-send getVtxos failed:", e.message);
+ }
return txid;
} finally {
@@ -433,16 +444,21 @@ export const sendArkViaForward = async ({
forward?: string;
user: any;
}) => {
- const txid = await sendArk(serverArkAddress, amount);
+ vaultForwarding = true;
+ try {
+ const txid = await sendArk(serverArkAddress, amount);
- await post("/post/ark/vault-send", { hash: txid, amount, aid });
+ await post("/post/ark/vault-send", { hash: txid, amount, aid });
- const invoice: any = { type: "ark", amount, aid };
- if (forward) invoice.forward = forward;
+ const invoice: any = { type: "ark", amount, aid };
+ if (forward) invoice.forward = forward;
- const inv = await post("/post/invoice", { invoice, user });
+ const inv = await post("/post/invoice", { invoice, user });
- return post("/post/ark/receive", { amount, hash: txid, iid: inv.id });
+ return await post("/post/ark/receive", { amount, hash: txid, iid: inv.id });
+ } finally {
+ vaultForwarding = false;
+ }
};
export const refresh = async () => {
diff --git a/src/lib/socket.ts b/src/lib/socket.ts
index 3e71e1ce..46784503 100644
--- a/src/lib/socket.ts
+++ b/src/lib/socket.ts
@@ -1,6 +1,7 @@
import { goto, invalidate } from "$app/navigation";
import { navigating, page } from "$app/stores";
import { PUBLIC_SOCKET } from "$env/static/public";
+import { vaultForwarding } from "$lib/ark";
import { event, importing, invoice, last, lastPayment, paymentSignal, request } from "$lib/store";
import { s, sleep, success, wait } from "$lib/utils";
import cookies from "js-cookie";
@@ -62,7 +63,7 @@ export const messages = (data) => ({
const username = cookies.get("username");
- if (amount > 0) {
+ if (amount > 0 && !vaultForwarding) {
if (
pathname.includes(`/${username}`) ||
pathname.includes("/receive") ||
diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte
index a7224a15..c04dc6f9 100644
--- a/src/routes/(app)/+layout.svelte
+++ b/src/routes/(app)/+layout.svelte
@@ -1,5 +1,5 @@
<script lang="ts">
- import { getWallet, subscribeToAsp, syncTransactions, settle, refresh, arkkey, arkaid, sendArk, arkSending, autoUnlockArk, getServiceWorkerWallet, cleanupServiceWorkerWallet } from "$lib/ark";
+ import { getWallet, subscribeToAsp, syncTransactions, settle, refresh, arkkey, arkaid, sendArk, arkSending, vaultForwarding, autoUnlockArk, getServiceWorkerWallet, cleanupServiceWorkerWallet } from "$lib/ark";
import { SvelteToast } from "@zerodevx/svelte-toast";
import { onDestroy, onMount } from "svelte";
import { close, connect, send, socket } from "$lib/socket";
@@ -77,7 +77,7 @@
} finally {
arkForwarding = false;
}
- } else if (result?.received > 0) {
+ } else if (result?.received > 0 && !vaultForwarding) {
const paidPayment = result.payments?.find((p: any) => p.iid && p.amount > 0);
if (paidPayment) {
goto(`/invoice/${paidPayment.iid}`);
@@ -117,7 +117,7 @@
await new Promise((r) => setTimeout(r, delay));
const aid = $arkaid || getCookie("aid") || user.id;
const result = await syncTransactions(aid);
- if (result?.received > 0) {
+ if (result?.received > 0 && !vaultForwarding) {
const paidPayment = result.payments?.find((p: any) => p.iid && p.amount > 0);
if (paidPayment) {
goto(`/invoice/${paidPayment.iid}`);
diff --git a/src/routes/(app)/[username]/(profile)/+page.svelte b/src/routes/(app)/[username]/(profile)/+page.svelte
index f2f1fd46..a234854d 100644
--- a/src/routes/(app)/[username]/(profile)/+page.svelte
+++ b/src/routes/(app)/[username]/(profile)/+page.svelte
@@ -120,7 +120,7 @@
{#each accounts as account}
<Account {user} {rates} {account} />
{/each}
- <button class="btn btn-accent w-full" onclick={addAccount}>
+ <button class="btn w-full" onclick={addAccount}>
{#if adding}
<Spinner />
{:else}
diff --git a/src/routes/(static)/login/+page.server.ts b/src/routes/(static)/login/+page.server.ts
index 68143d83..0c493422 100644
--- a/src/routes/(static)/login/+page.server.ts
+++ b/src/routes/(static)/login/+page.server.ts
@@ -66,7 +66,7 @@ export const actions = {
if (res.status === 401) {
if (text.startsWith("2fa")) throw new Error("2fa");
- throw new Error(text);
+ return fail(400, { error: "Login failed" });
}
if (!res.ok) {
diff --git a/src/routes/(static)/login/+page.svelte b/src/routes/(static)/login/+page.svelte
index c23acc8b..20b56134 100644
--- a/src/routes/(static)/login/+page.svelte
+++ b/src/routes/(static)/login/+page.svelte
@@ -161,7 +161,31 @@
await invalidateAll();
goto(result.data?.redirectUrl || `/${uname}`);
} else {
- applyAction(result);
+ // Challenge-response failed — fall back to password-based login
+ console.log("Auth key login failed, falling back to password login");
+ formData.set("authPubkey", pubkey);
+ const fallbackRes = await fetch("/login?/login", {
+ method: "POST",
+ body: formData,
+ });
+ const fallbackResult: any = deserialize(await fallbackRes.text());
+ if (fallbackResult.type === "success") {
+ try {
+ const oldPrfKey = await deriveWalletEntropy(sk);
+ await resolveCanonicalKey(
+ passwordMethodId(),
+ sk.buffer as ArrayBuffer,
+ fallbackResult.data?.encryptedKeys,
+ oldPrfKey,
+ );
+ } catch (e) {
+ console.log("Canonical key resolution failed", e);
+ }
+ await invalidateAll();
+ goto(fallbackResult.data?.redirectUrl || `/${uname}`);
+ } else {
+ applyAction(fallbackResult);
+ }
}
return;
}