diff options
| author | Adam Soltys <[email protected]> | 2025-11-26 14:53:20 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-02-11 15:33:08 -0800 |
| commit | 68e7fe9655f94efc573f3aab7acf60d03c4162a0 (patch) | |
| tree | 48f35ea51823ac1cd0d74afc453628f5e128bf71 /src/routes | |
| parent | 5f1c31b6618f0b895fb98c8f2a841989625eaf16 (diff) | |
| download | coinos-ui-68e7fe9655f94efc573f3aab7acf60d03c4162a0.tar.gz coinos-ui-68e7fe9655f94efc573f3aab7acf60d03c4162a0.zip | |
ark balance
Diffstat (limited to 'src/routes')
| -rw-r--r-- | src/routes/(app)/+layout.svelte | 49 | ||||
| -rw-r--r-- | src/routes/(app)/ark/+page.server.ts | 11 | ||||
| -rw-r--r-- | src/routes/(app)/ark/+page.svelte | 115 | ||||
| -rw-r--r-- | src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.svelte | 66 |
4 files changed, 164 insertions, 77 deletions
diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index cf29ad7c..ed76e103 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -1,5 +1,5 @@ <script> - import { getWallet } from "$lib/ark"; + import { getWallet, getAddress } from "$lib/ark"; import { SvelteToast } from "@zerodevx/svelte-toast"; import { onDestroy, onMount } from "svelte"; import { close, connect, send, socket } from "$lib/socket"; @@ -43,27 +43,30 @@ }); let handleArkPayment = async (notification) => { - console.log("NOTIF", notification); + console.log("ARK payment notification:", notification); let amount = notification.newVtxos.reduce((a, b) => a + b.value, 0); - console.log("AMOUNT", amount); + if (!amount) return; + success(`Received ⚡️${s(amount)}!`); - const wallet = await getWallet(); - const address = await wallet.getAddress(); - let invoice = await post(`/invoice`, { - invoice: { aid, type: "ark", amount }, - user, - }); + try { + let inv = await post(`/invoice`, { + invoice: { type: "ark", amount }, + user, + }); - let p = await post(`/post/ark/receive`, { - amount: vtxo.value, - iid: invoice.id, - }); + await post(`/ark/receive`, { + amount, + iid: inv.id, + }); - goto(`/invoice/${p.iid}`, { - invalidateAll: true, - noScroll: true, - }); + goto(`/invoice/${inv.id}`, { + invalidateAll: true, + noScroll: true, + }); + } catch (e) { + console.error("Failed to record ARK payment:", e); + } }; onMount(async () => { @@ -74,6 +77,18 @@ if (user) { const wallet = await getWallet(); wallet.notifyIncomingFunds(handleArkPayment); + + // Sync ARK address with backend if changed + const arkAddress = await getAddress(); + if (arkAddress !== user.arkAddress) { + try { + console.log("SYNCING", arkAddress); + await post("/post/user", { arkAddress }); + user.arkAddress = arkAddress; + } catch (e) { + console.error("Failed to sync ARK address:", e); + } + } } // if (window.NDEFReader) { diff --git a/src/routes/(app)/ark/+page.server.ts b/src/routes/(app)/ark/+page.server.ts new file mode 100644 index 00000000..bac0a02a --- /dev/null +++ b/src/routes/(app)/ark/+page.server.ts @@ -0,0 +1,11 @@ +import getRates from "$lib/rates"; + +export async function load({ depends, parent }) { + depends("app:payments"); + depends("app:items"); + + const { user } = await parent(); + + const rates = await getRates(); + return { rate: rates[user.currency] }; +} diff --git a/src/routes/(app)/ark/+page.svelte b/src/routes/(app)/ark/+page.svelte index 09d4d9a3..3089d887 100644 --- a/src/routes/(app)/ark/+page.svelte +++ b/src/routes/(app)/ark/+page.svelte @@ -1,44 +1,113 @@ <script> - import { onMount, tick } from "svelte"; - import { HDKey } from "@scure/bip32"; - import { wallet } from "$lib/ark"; + import { onMount } from "svelte"; + import { getBalance, getAddress, keyToNsec } from "$lib/ark"; + import Amount from "$comp/Amount.svelte"; + import { s, copy } from "$lib/utils"; + import { t } from "$lib/translations"; - let loading = $state(); - let balance = $state(); + let { data } = $props(); + let { user, rate } = $derived(data); + + let loading = $state(true); + let balance = $state({ available: 0, pending: 0 }); + let address = $state(""); + let showNsec = $state(false); + let nsec = $state(""); onMount(async () => { - setTimeout(checkBalance, 1000); + await refresh(); }); - let checkBalance = async (e) => { - e?.preventDefault(); + let refresh = async () => { loading = true; + try { + [balance, address] = await Promise.all([getBalance(), getAddress()]); + } catch (e) { + console.error("Failed to load ARK wallet:", e); + } + loading = false; + }; - balance = await wallet().getBalance(); + let revealNsec = () => { + nsec = keyToNsec(); + showNsec = true; + }; - loading = false; + let copyNsec = () => { + copy(nsec); }; </script> <div class="space-y-5"> - <h1 class="text-center text-3xl font-semibold">Ark</h1> + <div class="flex items-center justify-center gap-2"> + <iconify-icon noobserver icon="ph:vault-bold" width="32"></iconify-icon> + <h1 class="text-3xl font-semibold">Ark Wallet</h1> + </div> - <div class="container w-full mx-auto text-lg px-4 max-w-xl space-y-5"> + <div class="container w-full mx-auto px-4 max-w-xl space-y-5"> {#if loading} - <div> - <div class="loading loading-spinner"></div> + <div class="flex justify-center py-8"> + <div class="loading loading-spinner loading-lg"></div> </div> - {:else if typeof balance !== "undefined"} - <div class="flex items-center gap-2"> - <div> - {balance.available} sats + {:else} + <div class="text-center space-y-2"> + <Amount + amount={balance.available} + {rate} + currency={user?.currency || "USD"} + /> + {#if balance.pending > 0} + <div class="text-sm text-warning"> + +{s(balance.pending)} pending + </div> + {/if} + </div> + + <div class="flex gap-2 justify-center w-full text-xl"> + <a href="/invoice" class="btn flex-grow"> + <iconify-icon noobserver icon="ph:hand-coins-bold" width="24" flip="horizontal"></iconify-icon> + Receive + </a> + <a href="/send" class="btn flex-grow"> + <iconify-icon noobserver icon="ph:paper-plane-right-bold" width="24"></iconify-icon> + Send + </a> + </div> + + <button class="btn btn-ghost btn-sm w-full" onclick={refresh}> + <iconify-icon noobserver icon="ph:arrows-clockwise-bold" width="20"></iconify-icon> + Refresh + </button> + + <div class="divider"></div> + + <div class="space-y-2"> + <div class="text-sm text-secondary">Your ARK Address</div> + <div + class="text-xs break-all bg-base-200 p-3 rounded-lg cursor-pointer hover:bg-base-300" + onclick={() => copy(address)} + > + {address} </div> </div> - {/if} - <div class="flex gap-2 justify-center w-full"> - <a href="/receive" class="btn grow">Receive</a> - <a href="/send" class="btn grow">Send</a> - </div> + <div class="space-y-2"> + <div class="text-sm text-secondary">Backup Key</div> + {#if showNsec} + <div + class="text-xs break-all bg-warning/20 p-3 rounded-lg cursor-pointer border border-warning" + onclick={copyNsec} + > + {nsec} + </div> + <div class="text-xs text-warning">⚠️ Keep this secret! Anyone with this key can spend your funds.</div> + {:else} + <button class="btn btn-warning btn-sm" onclick={revealNsec}> + <iconify-icon noobserver icon="ph:eye-bold" width="20"></iconify-icon> + Reveal nsec + </button> + {/if} + </div> + {/if} </div> </div> 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 e77abdb5..37b8dad9 100644 --- a/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.svelte +++ b/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.svelte @@ -1,47 +1,44 @@ <script> - import { tick } from "svelte"; import { t } from "$lib/translations"; import { enhance } from "$app/forms"; - import Toggle from "$comp/Toggle.svelte"; import Spinner from "$comp/Spinner.svelte"; - import { page } from "$app/stores"; - import { toFiat, f, focus, s, sat, closest, network } from "$lib/utils"; + import { focus, s } from "$lib/utils"; import { pin } from "$lib/store"; import { goto, invalidate } from "$app/navigation"; import { rate } from "$lib/store"; import { applyAction } from "$app/forms"; - import { getWallet } from "$lib/ark"; + import { sendArk } from "$lib/ark"; import Amount from "$comp/Amount.svelte"; let { data, form } = $props(); - let passwordPrompt = $state(); - let password = $state(); - let cancel = $state(() => (passwordPrompt = false)); + let error = $state(""); - let togglePassword = () => (passwordPrompt = !passwordPrompt); let handler = async ({ cancel }) => { - let wallet = await getWallet(); - const txid = await wallet.sendBitcoin({ - address, - amount, - }); + submitting = true; + error = ""; - console.log(txid); + try { + const txid = await sendArk(address, amount); + console.log("ARK tx:", txid); - return async ({ result }) => { - if (result.type === "redirect") { - goto(result.location); - } else { - await applyAction(result); - invalidate("app:payments"); - } - }; + return async ({ result }) => { + submitting = false; + if (result.type === "redirect") { + goto(result.location); + } else { + await applyAction(result); + invalidate("app:payments"); + } + }; + } catch (e) { + submitting = false; + error = e.message || "Failed to send"; + cancel(); + } }; - let toggle = () => (submitting = !submitting); - let { account, amount, address, message } = $derived(data); let { feeRate } = $state(data); @@ -51,11 +48,8 @@ }); let { balance, currency } = data.user; - let submitting = $state(), - submit = $state(), - showSettings; - - let toggleSettings = () => (showSettings = !showSettings); + let submitting = $state(false), + submit = $state(); </script> <div @@ -63,11 +57,13 @@ > <h1 class="text-3xl md:text-4xl font-semibold mb-2">{$t("payments.send")}</h1> - {#if form?.message || message} + {#if form?.message || message || error} <div class="mb-5"> - <div class="text-red-600">{form?.message || message}</div> + <div class="text-red-600">{form?.message || message || error}</div> </div> - {:else} + {/if} + + {#if !error} <div class="text-xl text-secondary break-all">{address}</div> <Amount {amount} rate={$rate} {currency} /> @@ -96,10 +92,6 @@ {/if} </div> -{#if passwordPrompt} - <WalletPass bind:password bind:cancel submit={signTx} /> -{/if} - <style> .no-transition { view-transition-name: fee; |
