diff options
| author | Adam Soltys <[email protected]> | 2025-11-26 10:56:00 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-02-11 15:33:08 -0800 |
| commit | 5f1c31b6618f0b895fb98c8f2a841989625eaf16 (patch) | |
| tree | 668a3cddaacbb03a1f3b5fdcf8700e49674dada0 /src | |
| parent | 21023052918c0b787916ef78937e0be41f46142a (diff) | |
| download | coinos-ui-5f1c31b6618f0b895fb98c8f2a841989625eaf16.tar.gz coinos-ui-5f1c31b6618f0b895fb98c8f2a841989625eaf16.zip | |
ark
Diffstat (limited to 'src')
| -rw-r--r-- | src/components/InvoiceActions.svelte | 79 | ||||
| -rw-r--r-- | src/components/InvoiceData.svelte | 2 | ||||
| -rw-r--r-- | src/lib/ark.ts | 15 | ||||
| -rw-r--r-- | src/lib/parse.ts | 1 | ||||
| -rw-r--r-- | src/lib/utils.ts | 1 | ||||
| -rw-r--r-- | src/routes/(app)/+layout.svelte | 32 | ||||
| -rw-r--r-- | src/routes/(app)/ark/+page.svelte | 13 | ||||
| -rw-r--r-- | src/routes/(app)/invoice/[id]/+layout.server.ts | 1 | ||||
| -rw-r--r-- | src/routes/(app)/invoice/[id]/+page.svelte | 8 | ||||
| -rw-r--r-- | src/routes/(app)/send/ark/[address]/+layout.server.ts | 25 | ||||
| -rw-r--r-- | src/routes/(app)/send/ark/[address]/+page.server.ts | 10 | ||||
| -rw-r--r-- | src/routes/(app)/send/ark/[address]/+page.svelte | 80 | ||||
| -rw-r--r-- | src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.server.ts | 40 | ||||
| -rw-r--r-- | src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.svelte | 107 |
14 files changed, 373 insertions, 41 deletions
diff --git a/src/components/InvoiceActions.svelte b/src/components/InvoiceActions.svelte index e2a31ab8..08f9920d 100644 --- a/src/components/InvoiceActions.svelte +++ b/src/components/InvoiceActions.svelte @@ -91,7 +91,7 @@ </button> </div> - {#if txt.length > 120} + {#if txt?.length > 120} <div class="w-full flex justify-center gap-2 flex-wrap"> <button class="btn" onclick={() => (showQr = !showQr)}> {#if showQr} @@ -109,29 +109,51 @@ {/if} <div class="flex gap-2"> - <button - class="btn flex-nowrap !w-auto grow" - class:bg-base-300={type === types.lightning} - class:text-secondary={type === types.lightning} - onclick={() => { - $amountPrompt = false; - newAmount = undefined; - setType(types.lightning); - }} - > - <div - class="bg-black rounded-full w-8 h-8 items-center justify-center flex" + {#if aid === user.id} + <button + class="btn flex-nowrap !w-auto grow" + class:bg-base-300={type === types.lightning} + class:text-secondary={type === types.lightning} + onclick={() => { + $amountPrompt = false; + newAmount = undefined; + setType(types.lightning); + }} > - <div class="m-auto"> - <iconify-icon - noobserver - icon="ph:lightning-fill" - class="text-yellow-300 text-2xl" - ></iconify-icon> + <div + class="bg-black rounded-full w-8 h-8 items-center justify-center flex" + > + <div class="m-auto"> + <iconify-icon + noobserver + icon="ph:lightning-fill" + class="text-yellow-300 text-2xl" + ></iconify-icon> + </div> </div> - </div> - <div class="my-auto text-lg">Lightning</div> - </button> + <div class="my-auto text-lg">Lightning</div> + </button> + {:else} + <button + class="btn flex-nowrap !w-auto grow" + class:bg-base-300={type === types.ark} + class:text-secondary={type === types.ark} + onclick={() => { + $amountPrompt = false; + newAmount = undefined; + setType(types.ark); + }} + > + <div + class="bg-black rounded-full w-8 h-8 items-center justify-center flex" + > + <div class="m-auto"> + <img src="/images/ark.png" class="w-8 h-8 rounded-full object-cover" alt="Bitcoin" /> + </div> + </div> + <div class="my-auto text-lg">Ark</div> + </button> + {/if} <button class="btn flex-nowrap !w-auto grow" @@ -144,10 +166,11 @@ </button> </div> - - <button type="button" class="btn" onclick={toggleType}> - <img src="/images/liquid.svg" class="w-8" alt="Liquid" /> - <img src="/images/cash.png" class="w-8 my-auto" /> - <div class="my-auto">{t("payments.moreOptions")}</div> - </button> + {#if aid === user.id} + <button type="button" class="btn" onclick={toggleType}> + <img src="/images/liquid.svg" class="w-8" alt="Liquid" /> + <img src="/images/cash.png" class="w-8 my-auto" /> + <div class="my-auto">{t("payments.moreOptions")}</div> + </button> + {/if} </div> diff --git a/src/components/InvoiceData.svelte b/src/components/InvoiceData.svelte index 8edc9257..e08916fe 100644 --- a/src/components/InvoiceData.svelte +++ b/src/components/InvoiceData.svelte @@ -53,7 +53,7 @@ </div> {/if} -{#if !showQr || txt.length <= 120} +{#if !showQr || txt?.length <= 120} <div class="break-all text-center text-secondary text-xl flex gap-1 items-center justify-center" > diff --git a/src/lib/ark.ts b/src/lib/ark.ts index 0986983a..2dec57d9 100644 --- a/src/lib/ark.ts +++ b/src/lib/ark.ts @@ -1,5 +1,20 @@ import { persistLocal } from "$lib/store"; +import { SingleKey, Wallet } from "@arkade-os/sdk"; const prefix = "ark"; export const mnemonic = persistLocal(`${prefix}mnemonic`, ""); export const password = persistLocal(`${prefix}password`, ""); +export const arkkey = persistLocal(`${prefix}key`, ""); + +export const getWallet = async () => { + // const identity = SingleKey.fromHex(get(arkkey)); + + const identity = SingleKey.fromHex( + "ce66c68f8875c0c98a502c666303dc183a21600130013c06f9d1edf60207abf2", + ); + + return Wallet.create({ + identity, + arkServerUrl: "https://arkade.computer", + }); +}; diff --git a/src/lib/parse.ts b/src/lib/parse.ts index 6ff4556c..c69032d1 100644 --- a/src/lib/parse.ts +++ b/src/lib/parse.ts @@ -11,6 +11,7 @@ export default async (s, host, cookies) => { let invoice; let user; + if (t.startsWith("ark")) redirect(307, `/send/ark/${t}`); if (t.startsWith("http")) redirect(307, t); if (t.startsWith(host)) redirect(307, `http://${t}`); diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 14a1370e..d3333280 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -398,6 +398,7 @@ export const wait = async ( }; export const types = { + ark: "ark", bitcoin: "bitcoin", liquid: "liquid", lightning: "lightning", diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index 830e5291..cf29ad7c 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -1,4 +1,5 @@ <script> + import { getWallet } from "$lib/ark"; import { SvelteToast } from "@zerodevx/svelte-toast"; import { onDestroy, onMount } from "svelte"; import { close, connect, send, socket } from "$lib/socket"; @@ -19,7 +20,7 @@ import AppHeader from "$comp/AppHeader.svelte"; import Nostr from "$comp/Nostr.svelte"; import Password from "$comp/Password.svelte"; - import { getCookie, warning } from "$lib/utils"; + import { s, success, post, getCookie, warning } from "$lib/utils"; import { t, locale, loading } from "$lib/translations"; import { goto, afterNavigate, preloadData } from "$app/navigation"; @@ -41,11 +42,40 @@ } }); + let handleArkPayment = async (notification) => { + console.log("NOTIF", notification); + let amount = notification.newVtxos.reduce((a, b) => a + b.value, 0); + console.log("AMOUNT", amount); + success(`Received ⚡️${s(amount)}!`); + const wallet = await getWallet(); + const address = await wallet.getAddress(); + + let invoice = await post(`/invoice`, { + invoice: { aid, type: "ark", amount }, + user, + }); + + let p = await post(`/post/ark/receive`, { + amount: vtxo.value, + iid: invoice.id, + }); + + goto(`/invoice/${p.iid}`, { + invalidateAll: true, + noScroll: true, + }); + }; + onMount(async () => { if (browser) { checkSocket(); $pin = getCookie("pin"); + if (user) { + const wallet = await getWallet(); + wallet.notifyIncomingFunds(handleArkPayment); + } + // if (window.NDEFReader) { // try { // $ndef = new NDEFReader(); diff --git a/src/routes/(app)/ark/+page.svelte b/src/routes/(app)/ark/+page.svelte index 81ef9354..09d4d9a3 100644 --- a/src/routes/(app)/ark/+page.svelte +++ b/src/routes/(app)/ark/+page.svelte @@ -1,11 +1,7 @@ <script> - import { SingleKey, Wallet } from "@arkade-os/sdk"; import { onMount, tick } from "svelte"; import { HDKey } from "@scure/bip32"; - - const identity = SingleKey.fromHex( - "ce66c68f8875c0c98a502c666303dc183a21600130013c06f9d1edf60207abf2", - ); + import { wallet } from "$lib/ark"; let loading = $state(); let balance = $state(); @@ -18,12 +14,7 @@ e?.preventDefault(); loading = true; - const wallet = await Wallet.create({ - identity, - arkServerUrl: "https://arkade.computer", - }); - - balance = await wallet.getBalance(); + balance = await wallet().getBalance(); loading = false; }; diff --git a/src/routes/(app)/invoice/[id]/+layout.server.ts b/src/routes/(app)/invoice/[id]/+layout.server.ts index ab41dddc..a18dad4e 100644 --- a/src/routes/(app)/invoice/[id]/+layout.server.ts +++ b/src/routes/(app)/invoice/[id]/+layout.server.ts @@ -11,6 +11,7 @@ export async function load({ cookies, depends, params, url, parent }) { if (id) { invoice = await get(`/invoice/${id}`); + console.log("INV", invoice); const options = !!url.searchParams.get("options"); let { amount, pending, received } = invoice; amount = parseInt(amount); diff --git a/src/routes/(app)/invoice/[id]/+page.svelte b/src/routes/(app)/invoice/[id]/+page.svelte index 29dd815d..30fa3dce 100644 --- a/src/routes/(app)/invoice/[id]/+page.svelte +++ b/src/routes/(app)/invoice/[id]/+page.svelte @@ -1,4 +1,5 @@ <script> + import { getWallet } from "$lib/ark"; import { enhance } from "$app/forms"; import { send } from "$lib/socket"; import { @@ -8,6 +9,7 @@ copy, f, fail, + success, get, types, sat, @@ -97,6 +99,12 @@ if (typeof newAmount !== "undefined") invoice.amount = newAmount; invoice.address_type = address_type; invoice.type = type; + + if (type === "ark") { + const wallet = await getWallet(); + invoice.hash = await wallet.getAddress(); + } + await update(); } settingType = false; diff --git a/src/routes/(app)/send/ark/[address]/+layout.server.ts b/src/routes/(app)/send/ark/[address]/+layout.server.ts new file mode 100644 index 00000000..e7a216d6 --- /dev/null +++ b/src/routes/(app)/send/ark/[address]/+layout.server.ts @@ -0,0 +1,25 @@ +import { auth, get } from "$lib/utils"; +import { redirect } from "@sveltejs/kit"; + +export async function load({ cookies, params, parent }) { + const { user } = await parent(); + const aid = cookies.get("aid") || user.id; + const { address, amount } = params; + const { balance } = await get(`/account/${aid}`, auth(cookies)); + + let invoice; + try { + invoice = await get(`/invoice/${address}`); + } catch (e) { + // invoice not found + } + + if (invoice) { + const recipient = await get(`/users/${invoice.uid}`); + let r = `/pay/${recipient.username}`; + if (amount) r += `/${amount}`; + if (recipient?.id !== user.id) redirect(307, r); + } + + return { balance }; +} diff --git a/src/routes/(app)/send/ark/[address]/+page.server.ts b/src/routes/(app)/send/ark/[address]/+page.server.ts new file mode 100644 index 00000000..b7d3c5cf --- /dev/null +++ b/src/routes/(app)/send/ark/[address]/+page.server.ts @@ -0,0 +1,10 @@ +import getRates from "$lib/rates"; +import { auth, get } from "$lib/utils"; + +export async function load({ cookies, parent }) { + const { user } = await parent(); + const rates = await getRates(); + const aid = cookies.get("aid") || user.id; + const { balance } = await get(`/account/${aid}`, auth(cookies)); + return { balance, rate: rates[user.currency] }; +} diff --git a/src/routes/(app)/send/ark/[address]/+page.svelte b/src/routes/(app)/send/ark/[address]/+page.svelte new file mode 100644 index 00000000..6ff6ed58 --- /dev/null +++ b/src/routes/(app)/send/ark/[address]/+page.svelte @@ -0,0 +1,80 @@ +<script> + import { tick } from "svelte"; + import { t } from "$lib/translations"; + import Icon from "$comp/Icon.svelte"; + import Numpad from "$comp/Numpad.svelte"; + import { page } from "$app/stores"; + import { rate } from "$lib/store"; + import { loc, fail, s } from "$lib/utils"; + + let { data } = $props(); + + let { balance, user } = data; + let { address } = $page.params; + let { currency, username } = user; + let locale = loc(user); + + let amount = $state(0); + let a = $state(0); + let submit = $state(), + fiat = $state(); + + $effect(() => ($rate = data.rate)); + $effect(() => (amount = a)); + + let setMax = async (e) => { + e.preventDefault(); + fiat = false; + amount = balance; + await tick(); + submit.click(); + }; + + let send = async () => { + const txid = await wallet.sendBitcoin({ + address, + amount, + }); + + console.log(txid); + }; +</script> + +<div class="container px-4 max-w-xl mx-auto space-y-5 text-center"> + <h1 class="text-3xl md:text-4xl font-semibold mb-2">{$t("payments.send")}</h1> + + <div class="text-xl text-secondary break-all">{address}</div> + + <Numpad + bind:amount={a} + bind:fiat + {currency} + {submit} + bind:rate={$rate} + {locale} + /> + + <div class="flex justify-center gap-2"> + <button + type="button" + class="btn !w-auto grow" + onclick={setMax} + onkeydown={setMax}>Max ⚡️{s(balance)}</button + > + + <form + action={`/send/ark/${address}/${amount}`} + class="contents" + onsubmit={send} + > + <button + use:focus + bind:this={submit} + type="submit" + class="btn !w-auto grow btn-accent" + > + {$t("payments.next")} + </button> + </form> + </div> +</div> diff --git a/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.server.ts b/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.server.ts new file mode 100644 index 00000000..926a141d --- /dev/null +++ b/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.server.ts @@ -0,0 +1,40 @@ +import getRates from "$lib/rates"; +import { auth, fd, get, post } from "$lib/utils"; +import { fail, redirect } from "@sveltejs/kit"; + +export async function load({ params: { address, amount }, cookies, parent }) { + const rates = await getRates(); + const { user } = await parent(); + const aid = cookies.get("aid") || user.id; + + const account = await get(`/account/${aid}`, auth(cookies)); + const rate = rates[user.currency]; + console.log("rate", rate); + + return { + account, + amount, + address, + rate, + }; +} + +export const actions = { + default: async ({ + cookies, + params: { address, amount, feeRate }, + request, + }) => { + let p; + try { + const body = await fd(request); + p = await post("/ark/send", body, auth(cookies)); + } catch (e) { + console.log("problem sending bitcoin", e); + const { message } = e as Error; + return fail(400, { address, amount, feeRate, message }); + } + + redirect(307, `/sent/${p.id}`); + }, +}; diff --git a/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.svelte b/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.svelte new file mode 100644 index 00000000..e77abdb5 --- /dev/null +++ b/src/routes/(app)/send/ark/[address]/[amount]/[...feeRate]/+page.svelte @@ -0,0 +1,107 @@ +<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 { 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 Amount from "$comp/Amount.svelte"; + + let { data, form } = $props(); + + let passwordPrompt = $state(); + let password = $state(); + let cancel = $state(() => (passwordPrompt = false)); + + let togglePassword = () => (passwordPrompt = !passwordPrompt); + let handler = async ({ cancel }) => { + let wallet = await getWallet(); + const txid = await wallet.sendBitcoin({ + address, + amount, + }); + + console.log(txid); + + return async ({ result }) => { + if (result.type === "redirect") { + goto(result.location); + } else { + await applyAction(result); + invalidate("app:payments"); + } + }; + }; + + let toggle = () => (submitting = !submitting); + + let { account, amount, address, message } = $derived(data); + + let { feeRate } = $state(data); + + $effect(() => { + if (!$rate) $rate = data.rate; + }); + + let { balance, currency } = data.user; + let submitting = $state(), + submit = $state(), + showSettings; + + let toggleSettings = () => (showSettings = !showSettings); +</script> + +<div + class="container px-4 max-w-xl mx-auto space-y-5 text-center no-transition" +> + <h1 class="text-3xl md:text-4xl font-semibold mb-2">{$t("payments.send")}</h1> + + {#if form?.message || message} + <div class="mb-5"> + <div class="text-red-600">{form?.message || message}</div> + </div> + {:else} + <div class="text-xl text-secondary break-all">{address}</div> + + <Amount {amount} rate={$rate} {currency} /> + + <form method="POST" use:enhance={handler}> + <input name="pin" value={$pin} type="hidden" /> + <input name="rate" value={$rate} type="hidden" /> + <input name="aid" value={account.id} type="hidden" /> + + <div class="flex justify-center gap-2"> + <button + use:focus + bind:this={submit} + type="submit" + class="btn btn-accent" + disabled={submitting} + > + {#if submitting} + <Spinner /> + {:else} + {$t("payments.send")} + {/if} + </button> + </div> + </form> + {/if} +</div> + +{#if passwordPrompt} + <WalletPass bind:password bind:cancel submit={signTx} /> +{/if} + +<style> + .no-transition { + view-transition-name: fee; + } +</style> |
