diff options
| author | Adam Soltys <[email protected]> | 2026-02-05 09:34:30 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-02-11 15:34:32 -0800 |
| commit | ec304f8bd1ffef69a85b75da033fbccbe6bfc72f (patch) | |
| tree | 664e765f3d7e9c4f26463760ed155dabca76486b /src/routes | |
| parent | 59fbd17aad12acfa19eb730c4ede67d50962ba9b (diff) | |
| download | coinos-ui-ec304f8bd1ffef69a85b75da033fbccbe6bfc72f.tar.gz coinos-ui-ec304f8bd1ffef69a85b75da033fbccbe6bfc72f.zip | |
fix ark payment notifications
Diffstat (limited to 'src/routes')
| -rw-r--r-- | src/routes/(app)/+layout.svelte | 28 | ||||
| -rw-r--r-- | src/routes/(app)/account/[id]/+page.svelte | 38 | ||||
| -rw-r--r-- | src/routes/(app)/account/ark/+page.svelte | 28 | ||||
| -rw-r--r-- | src/routes/(app)/account/pass/+page.svelte | 28 | ||||
| -rw-r--r-- | src/routes/(app)/invoice/[id]/tip/+page.svelte | 8 | ||||
| -rw-r--r-- | src/routes/(app)/send/bitcoin/[address]/[amount]/[...feeRate]/+page.svelte | 28 |
6 files changed, 141 insertions, 17 deletions
diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index 3a399c3a..c56a487a 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -46,10 +46,30 @@ } }); + // Track last known Ark balance to compute incoming amount + let getLastArkBalance = () => { + return parseInt(localStorage.getItem("lastArkBalance") || "0"); + }; + + let setLastArkBalance = (bal) => { + localStorage.setItem("lastArkBalance", bal.toString()); + }; + let handleArkPayment = async (notification) => { console.log("ARK payment notification:", notification); - let amount = notification.newVtxos.reduce((a, b) => a + b.value, 0); - if (!amount) return; + + // Calculate current total from newVtxos + let currentTotal = notification.newVtxos.reduce((a, b) => a + b.value, 0); + let lastBalance = getLastArkBalance(); + + // Compute delta (incoming amount) + let amount = currentTotal - lastBalance; + + // Update tracked balance + setLastArkBalance(currentTotal); + + // Only process positive incoming amounts + if (amount <= 0) return; if ($fiat && $rateStore && user?.currency) { success(`Received ${f(toFiat(amount, $rateStore), user.currency)}!`); @@ -58,12 +78,12 @@ } try { - let inv = await post(`/invoice`, { + let inv = await post(`/post/invoice`, { invoice: { type: "ark", amount }, user, }); - await post(`/ark/receive`, { + await post(`/post/ark/receive`, { amount, iid: inv.id, }); diff --git a/src/routes/(app)/account/[id]/+page.svelte b/src/routes/(app)/account/[id]/+page.svelte index a8970ca7..db93a04d 100644 --- a/src/routes/(app)/account/[id]/+page.svelte +++ b/src/routes/(app)/account/[id]/+page.svelte @@ -8,6 +8,10 @@ import { t } from "$lib/translations"; import { entropyToMnemonic, mnemonicToSeed } from "@scure/bip39"; import { wordlist } from "@scure/bip39/wordlists/english.js"; + import { + getRememberedWalletPassword, + forgetWalletPassword, + } from "$lib/passwordCache"; let { data } = $props(); let { account, user } = data; @@ -26,12 +30,36 @@ let cancel = $state(() => (passwordPrompt = false)); let toggle = () => (passwordPrompt = !passwordPrompt); + let revealWithCachedPassword = async () => { + const cached = getRememberedWalletPassword(); + if (!cached) return false; + try { + const { decrypt } = await import("nostr-tools/nip49"); + let entropy = await decrypt(seed, cached); + mnemonic = entropyToMnemonic(entropy, wordlist); + password = cached; + return true; + } catch (e) { + forgetWalletPassword(); + return false; + } + }; - let submit = $state(async () => { + let requestMnemonic = async () => { + if (await revealWithCachedPassword()) return; toggle(); - const { decrypt } = await import("nostr-tools/nip49"); - let entropy = await decrypt(seed, password); - mnemonic = entropyToMnemonic(entropy, wordlist); + }; + + let submit = $state(async () => { + passwordPrompt = false; + try { + const { decrypt } = await import("nostr-tools/nip49"); + let entropy = await decrypt(seed, password); + mnemonic = entropyToMnemonic(entropy, wordlist); + } catch (e) { + fail("Invalid password, try again"); + passwordPrompt = true; + } }); let del = async () => { @@ -71,7 +99,7 @@ <div class="my-auto">{$t("accounts.copy")}</div></button > {:else if seed} - <button onclick={toggle} type="button" class="btn"> + <button onclick={requestMnemonic} type="button" class="btn"> <iconify-icon noobserver icon="ph:eye-bold" width="32" ></iconify-icon> <div class="my-auto">{$t("accounts.revealMnemonic")}</div></button diff --git a/src/routes/(app)/account/ark/+page.svelte b/src/routes/(app)/account/ark/+page.svelte index 99ab750b..aee80837 100644 --- a/src/routes/(app)/account/ark/+page.svelte +++ b/src/routes/(app)/account/ark/+page.svelte @@ -8,6 +8,12 @@ import { focus, fail, post, copy } from "$lib/utils"; import { goto } from "$app/navigation"; import { arkServerUrl } from "$lib/ark"; + import { + rememberForOptions, + defaultRememberForMs, + rememberWalletPassword, + forgetWalletPassword, + } from "$lib/passwordCache"; let { data } = $props(); let { user } = data; @@ -23,6 +29,7 @@ let confirm = $state(""); let revealPassword = $state(false); let revealConfirm = $state(false); + let rememberForMs = $state(defaultRememberForMs); let name = "Ark Vault"; let type = "ark"; @@ -73,6 +80,11 @@ let seed = await encrypt(hex.decode(privateKey), password); await post("/account", { name, seed, type, arkAddress }); + if (rememberForMs) { + rememberWalletPassword(password, rememberForMs); + } else { + forgetWalletPassword(); + } goto(`/${user.username}`); } catch (e) { console.log(e); @@ -205,6 +217,22 @@ </button> </label> + <div class="space-y-2"> + <label for="rememberFor" class="text-sm text-secondary"> + Remember for + </label> + <select + id="rememberFor" + class="w-full" + value={rememberForMs} + onchange={(e) => (rememberForMs = Number(e.target.value))} + > + {#each rememberForOptions as option} + <option value={option.ms}>{option.label}</option> + {/each} + </select> + </div> + <div class="flex gap-2"> <button type="button" diff --git a/src/routes/(app)/account/pass/+page.svelte b/src/routes/(app)/account/pass/+page.svelte index 7c57679a..74e45f17 100644 --- a/src/routes/(app)/account/pass/+page.svelte +++ b/src/routes/(app)/account/pass/+page.svelte @@ -14,6 +14,12 @@ import { focus, versions, fail, post } from "$lib/utils"; import { goto } from "$app/navigation"; import { HDKey } from "@scure/bip32"; + import { + rememberForOptions, + defaultRememberForMs, + rememberWalletPassword, + forgetWalletPassword, + } from "$lib/passwordCache"; let { data } = $props(); @@ -24,6 +30,7 @@ password = $state(), revealPassword = $state(), revealConfirm = $state(); + let rememberForMs = $state(defaultRememberForMs); let type = "bitcoin"; let name = $t("accounts.savings"); @@ -55,6 +62,11 @@ let pubkey = child.publicExtendedKey; let fingerprint = child.fingerprint.toString(16).padStart(8, "0"); await post("/account", { fingerprint, pubkey, name, seed, type }); + if (rememberForMs) { + rememberWalletPassword(password, rememberForMs); + } else { + forgetWalletPassword(); + } $mnemonic = ""; goto(`/${user.username}`); } catch (e) { @@ -159,6 +171,22 @@ </button> </label> + <div class="space-y-2"> + <label for="rememberFor" class="text-sm text-secondary"> + Remember for + </label> + <select + id="rememberFor" + class="w-full" + value={rememberForMs} + onchange={(e) => (rememberForMs = Number(e.target.value))} + > + {#each rememberForOptions as option} + <option value={option.ms}>{option.label}</option> + {/each} + </select> + </div> + <div class="flex gap-2"> <a href={`/account/seed`} class="contents"> <button type="button" class="btn !w-auto grow"> diff --git a/src/routes/(app)/invoice/[id]/tip/+page.svelte b/src/routes/(app)/invoice/[id]/tip/+page.svelte index 41cc4636..4be2f536 100644 --- a/src/routes/(app)/invoice/[id]/tip/+page.svelte +++ b/src/routes/(app)/invoice/[id]/tip/+page.svelte @@ -1,7 +1,4 @@ <script> - import { untrack } from "svelte"; - - import { browser } from "$app/environment"; import { fiat, invoice as inv, request } from "$lib/store"; import { loc, copy, focus, f, sat, get, s, sats } from "$lib/utils"; import Slider from "$comp/Slider.svelte"; @@ -78,7 +75,7 @@ let locale = $derived(loc(user)); let qr; - let tipPercent = $state(); + let tipPercent = $state(amount > 0 && tip > 0 ? (tip / amount) * 100 : 0); let fullscreen; @@ -86,8 +83,7 @@ let rate = invoice.rate * (data.rate / data.invoiceRate); $effect(() => { - if (typeof tipPercent === "undefined") tipPercent = (tip / amount) * 100; - tip = Math.round((amount / 100) * untrack(() => tipPercent)); + tip = Math.round((amount / 100) * tipPercent); }); let amountFiat = $derived(parseFloat(((amount * rate) / sats).toFixed(2))); diff --git a/src/routes/(app)/send/bitcoin/[address]/[amount]/[...feeRate]/+page.svelte b/src/routes/(app)/send/bitcoin/[address]/[amount]/[...feeRate]/+page.svelte index 609e795b..c31ce60d 100644 --- a/src/routes/(app)/send/bitcoin/[address]/[amount]/[...feeRate]/+page.svelte +++ b/src/routes/(app)/send/bitcoin/[address]/[amount]/[...feeRate]/+page.svelte @@ -17,6 +17,10 @@ import { HDKey } from "@scure/bip32"; import { entropyToMnemonic, mnemonicToSeed } from "@scure/bip39"; import { wordlist } from "@scure/bip39/wordlists/english.js"; + import { + getRememberedWalletPassword, + forgetWalletPassword, + } from "$lib/passwordCache"; import Amount from "$comp/Amount.svelte"; @@ -28,9 +32,29 @@ let cancel = $state(() => (passwordPrompt = false)); let signed = $state(); - let togglePassword = () => (passwordPrompt = !passwordPrompt); + let trySignWithCachedPassword = async () => { + const cached = getRememberedWalletPassword(); + if (!cached) return false; + try { + password = cached; + await signTx(); + return true; + } catch (e) { + forgetWalletPassword(); + return false; + } + }; + + let togglePassword = async () => { + if (passwordPrompt) { + passwordPrompt = false; + return; + } + if (await trySignWithCachedPassword()) return; + passwordPrompt = true; + }; let handler = ({ cancel }) => { - if (account.seed && !signed) cancel(), togglePassword(); + if (account.seed && !signed) cancel(), void togglePassword(); return async ({ result }) => { if (result.type === "redirect") { goto(result.location); |
