diff options
| author | Adam Soltys <[email protected]> | 2026-02-05 16:44:55 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-02-11 15:34:32 -0800 |
| commit | cf1d8029efc1f9497bcef71b5c959c42794f3566 (patch) | |
| tree | b3a9658e7e426a82a545477df0414bc6b61509f2 /src | |
| parent | ed4d86167d32503ad8813c81dda9591d1b1b8190 (diff) | |
| download | coinos-ui-cf1d8029efc1f9497bcef71b5c959c42794f3566.tar.gz coinos-ui-cf1d8029efc1f9497bcef71b5c959c42794f3566.zip | |
payment forwarding
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib/ark.ts | 29 | ||||
| -rw-r--r-- | src/lib/parse.ts | 2 | ||||
| -rw-r--r-- | src/routes/(app)/+layout.svelte | 22 | ||||
| -rw-r--r-- | src/routes/(app)/invoice/[id]/+layout.server.ts | 3 | ||||
| -rw-r--r-- | src/routes/(app)/send/invoice/[id]/+page.server.ts | 1 | ||||
| -rw-r--r-- | src/routes/(app)/send/lightning/[payreq]/[...memo]/+page.server.ts | 13 | ||||
| -rw-r--r-- | src/routes/(app)/send/lightning/[payreq]/[...memo]/+page.svelte | 52 |
7 files changed, 89 insertions, 33 deletions
diff --git a/src/lib/ark.ts b/src/lib/ark.ts index 26c16a93..25122a8c 100644 --- a/src/lib/ark.ts +++ b/src/lib/ark.ts @@ -53,11 +53,38 @@ export const syncTransactions = async (aid: string) => { return post("/post/ark/sync", { transactions, aid }); }; +export const vtxoKey = (v: { txid: string; vout: number }) => + `${v.txid}:${v.vout}`; + +export const getProcessedVtxos = (): Set<string> => { + try { + return new Set( + JSON.parse(localStorage.getItem("processedArkVtxos") || "[]"), + ); + } catch { + return new Set(); + } +}; + +export const addProcessedVtxos = (keys: string[]) => { + const processed = getProcessedVtxos(); + for (const k of keys) processed.add(k); + const arr = [...processed].slice(-200); + localStorage.setItem("processedArkVtxos", JSON.stringify(arr)); +}; + export const sendArk = async (address: string, amount: number) => { const wallet = await getWallet(); if (!wallet) throw new Error("Ark wallet not available"); - return wallet.sendBitcoin({ address, amount }); + 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; }; export const settle = async () => { diff --git a/src/lib/parse.ts b/src/lib/parse.ts index 8520c27a..3249b9db 100644 --- a/src/lib/parse.ts +++ b/src/lib/parse.ts @@ -72,8 +72,10 @@ export default async (s, host, cookies) => { invoice = { id: iid }; } else if (t.toLowerCase().startsWith("ln")) { try { + const aid = cookies.get("aid"); invoice = await get(`/invoice/${t}`); if (invoice.user.username === "mint") throw new Error("mint payment"); + if (aid && aid !== invoice.aid) throw new Error("cross-account"); } catch (e) { if (t.toLowerCase().startsWith("lno")) { const { offer_amount_msat: a } = await get(`/decode/${t}`); diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index d9f6a0d0..587a4b98 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -1,5 +1,5 @@ <script> - import { getWallet, getAddress, getVtxos, syncTransactions, settle } from "$lib/ark"; + import { getWallet, getAddress, getVtxos, syncTransactions, settle, vtxoKey, getProcessedVtxos, addProcessedVtxos } from "$lib/ark"; import { SvelteToast } from "@zerodevx/svelte-toast"; import { onDestroy, onMount } from "svelte"; import { close, connect, send, socket } from "$lib/socket"; @@ -46,26 +46,6 @@ } }); - // 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 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; diff --git a/src/routes/(app)/invoice/[id]/+layout.server.ts b/src/routes/(app)/invoice/[id]/+layout.server.ts index ab41dddc..ce76d150 100644 --- a/src/routes/(app)/invoice/[id]/+layout.server.ts +++ b/src/routes/(app)/invoice/[id]/+layout.server.ts @@ -24,9 +24,10 @@ export async function load({ cookies, depends, params, url, parent }) { subject = invoice.user; + const aid = cookies.get("aid") || user?.id; if ( user && - invoice.uid !== user?.id && + (invoice.uid !== user?.id || aid !== invoice.aid) && !(url.pathname.includes("tip") || url.pathname.includes("memo")) && !options ) { diff --git a/src/routes/(app)/send/invoice/[id]/+page.server.ts b/src/routes/(app)/send/invoice/[id]/+page.server.ts index 70b007a8..c9c7dbd9 100644 --- a/src/routes/(app)/send/invoice/[id]/+page.server.ts +++ b/src/routes/(app)/send/invoice/[id]/+page.server.ts @@ -64,6 +64,7 @@ export const actions = { try { const body = await fd(request); body.hash = id; + body.aid = cookies.get("aid"); p = await post("/payments", body, auth(cookies)); } catch (e) { diff --git a/src/routes/(app)/send/lightning/[payreq]/[...memo]/+page.server.ts b/src/routes/(app)/send/lightning/[payreq]/[...memo]/+page.server.ts index 5c3e297c..e6cd0e76 100644 --- a/src/routes/(app)/send/lightning/[payreq]/[...memo]/+page.server.ts +++ b/src/routes/(app)/send/lightning/[payreq]/[...memo]/+page.server.ts @@ -1,5 +1,5 @@ import getRates from "$lib/rates"; -import { auth, fd, post } from "$lib/utils"; +import { auth, fd, get, post } from "$lib/utils"; import { fail, redirect } from "@sveltejs/kit"; export async function load({ cookies, params, parent }) { @@ -11,6 +11,16 @@ export async function load({ cookies, params, parent }) { } data.rate = rates[user.currency]; + + const aid = cookies.get("aid") || user.id; + if (aid !== user.id) { + const account = await get(`/account/${aid}`, auth(cookies)); + if (account.type === "ark") { + data.account = account; + data.serverArkAddress = await get("/ark/address"); + } + } + return data; } @@ -36,6 +46,7 @@ export const actions = { let p; try { const body = await fd(request); + body.aid = cookies.get("aid"); console.log("BODY", body); diff --git a/src/routes/(app)/send/lightning/[payreq]/[...memo]/+page.svelte b/src/routes/(app)/send/lightning/[payreq]/[...memo]/+page.svelte index 4dfaabc2..02ff06ae 100644 --- a/src/routes/(app)/send/lightning/[payreq]/[...memo]/+page.svelte +++ b/src/routes/(app)/send/lightning/[payreq]/[...memo]/+page.svelte @@ -2,12 +2,14 @@ import { untrack } from "svelte"; import { t } from "$lib/translations"; import { enhance } from "$app/forms"; + import { goto } from "$app/navigation"; import Amount from "$comp/Amount.svelte"; import Numpad from "$comp/Numpad.svelte"; import Spinner from "$comp/Spinner.svelte"; import { page } from "$app/stores"; - import { loc, back, toFiat, f, s, focus } from "$lib/utils"; + import { loc, back, toFiat, f, s, post, focus } from "$lib/utils"; import { fiat, rate, pin } from "$lib/store"; + import { sendArk } from "$lib/ark"; let { data, form } = $props(); @@ -20,9 +22,9 @@ $effect(() => form && (loading = false)); let showMax = $state(); + let error = $state(""); let loading = $state(); - let submit = () => (loading = true); let next = $state(); let toggle = () => (show = !show); @@ -31,16 +33,49 @@ Math.max(5, Math.round(untrack(() => amount) * 0.02 || 0)), ); $effect(() => (maxfee = Math.max(5, Math.round(amount * 0.02) || 0))); + + let handler = ({ cancel }) => { + loading = true; + error = ""; + + if (data.account?.type === "ark") { + cancel(); + + (async () => { + try { + const txid = await sendArk(data.serverArkAddress, parseInt(amount)); + + const inv = await post("/post/invoice", { + invoice: { type: "ark", amount: parseInt(amount), forward: payreq, aid: data.account.id }, + user, + }); + + const p = await post("/post/ark/receive", { + amount: parseInt(amount), + hash: txid, + iid: inv.id, + }); + + goto(`/sent/${p.id}`, { invalidateAll: true }); + } catch (e) { + loading = false; + error = e.message || "Failed to send"; + } + })(); + + return; + } + }; </script> <div class="container px-4 max-w-xl mx-auto text-center space-y-2"> - {#if form?.message} + {#if form?.message || error} <div class="text-red-600 text-center"> - {#if form.message.includes($t("payments.insufficientFunds"))} - <div>{form.message}</div> - <div>{$t("payments.lockedBalance")}: {locked}</div> + {#if (form?.message || error).includes($t("payments.insufficientFunds"))} + <div>{form?.message || error}</div> + <div>{$t("payments.lockedBalance")}: {locked}</div> {:else} - {$t("payments.failedToRoute")} + {form?.message || error} {/if} </div> {/if} @@ -78,8 +113,7 @@ <form method="POST" - use:enhance - onsubmit={submit} + use:enhance={handler} action="?/send" class="space-y-2" > |
