diff options
| author | Adam Soltys <[email protected]> | 2024-09-24 20:12:43 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2024-09-24 20:12:43 -0700 |
| commit | d49d4119811dee9270f0858f3f03e5d8cd0d95ba (patch) | |
| tree | 42101a3ccf47f7b7290637700b90a843476e56f7 /src/routes/(app)/send | |
| parent | 20c0c52f8b850bfbab490735b0b5cd5d08851488 (diff) | |
| parent | 9531ccbdf01b07c392b1b5cb6d8ad4680ee29d2c (diff) | |
| download | coinos-ui-d49d4119811dee9270f0858f3f03e5d8cd0d95ba.tar.gz coinos-ui-d49d4119811dee9270f0858f3f03e5d8cd0d95ba.zip | |
Merge branch 'self-custody'
Diffstat (limited to 'src/routes/(app)/send')
11 files changed, 167 insertions, 74 deletions
diff --git a/src/routes/(app)/send/[...text]/+page.svelte b/src/routes/(app)/send/[...text]/+page.svelte index 780f70c1..faa4a766 100644 --- a/src/routes/(app)/send/[...text]/+page.svelte +++ b/src/routes/(app)/send/[...text]/+page.svelte @@ -5,7 +5,6 @@ import { tick } from "svelte"; import { browser } from "$app/environment"; import { t } from "$lib/translations"; - import AppHeader from "$comp/AppHeader.svelte"; import Avatar from "$comp/Avatar.svelte"; import Icon from "$comp/Icon.svelte"; import Spinner from "$comp/Spinner.svelte"; @@ -19,7 +18,7 @@ let el, text, pasted, w; - let keypress = (e) => e.key === "Enter" && (e.preventDefault() || el.click()); + let keypress = (e) => e.key === "Enter" ? (e.preventDefault() || el.click()) : (pasted = false); let paste = async () => { text = await navigator.clipboard.readText(); @@ -31,7 +30,6 @@ <svelte:window bind:innerWidth={w} /> -<AppHeader {data} /> <div class="container px-4 max-w-lg mx-auto space-y-5 mt-20"> <h1 class="px-3 md:px-0 text-center text-3xl md:text-4xl font-semibold"> {$t("payments.send")} @@ -54,7 +52,7 @@ name="text" placeholder={$t("user.send.placeholder")} on:keypress={keypress} - class="w-full p-4 border rounded-xl h-48 text-xl" + class="w-full p-4 border rounded-xl h-32 text-xl" bind:value={text} on:paste={() => (pasted = true)} autocapitalize="none" @@ -65,7 +63,7 @@ <a href="/scan" class="w-full"> <button type="button" - class="flex border rounded-2xl px-6 py-5 font-bold hover:opacity-80 w-full bg-primary justify-center gap-2" + class="flex border rounded-2xl px-5 py-4 hover:opacity-80 w-full justify-center gap-2" > <Icon icon="camera" style="w-8 my-auto" /> <div class="my-auto">{$t("user.send.scan")}</div> @@ -74,7 +72,7 @@ <button type="button" - class="flex border rounded-2xl px-6 py-5 font-bold hover:opacity-80 w-full bg-primary justify-center gap-2" + class="flex border rounded-2xl px-5 py-4 hover:opacity-80 w-full justify-center gap-2" on:click={paste} > <Icon icon="paste" style="w-8 my-auto" /> @@ -97,7 +95,7 @@ type="button" class="flex border rounded-2xl px-6 py-5 w-full font-bold gap-2 justify-center text-xl bg-primary" > - <Icon icon="voucher" style="w-8 my-auto" /> + <img src="/images/cash.png" class="w-8" /> <div class="my-auto">{$t("payments.createEcash")}</div> </button> </a> diff --git a/src/routes/(app)/send/bitcoin/[address]/+layout.server.ts b/src/routes/(app)/send/bitcoin/[address]/+layout.server.ts new file mode 100644 index 00000000..a6edf8b0 --- /dev/null +++ b/src/routes/(app)/send/bitcoin/[address]/+layout.server.ts @@ -0,0 +1,23 @@ +import { auth, get } from "$lib/utils"; +import { redirect } from "@sveltejs/kit"; + +export async function load({ cookies, params, parent }) { + let { user } = await parent(); + let aid = cookies.get("aid"); + let { address } = params; + let { balance } = await get(`/account/${aid}`, auth(cookies)); + + try { + let invoice = await get(`/invoice/${address}`); + + if (invoice) { + let recipient = await get(`/users/${invoice.uid}`); + if (recipient?.id !== user.id) + redirect(307, `/pay/${recipient.username}`); + } + } catch (e) { + // invoice not found + } + + return { balance }; +} diff --git a/src/routes/(app)/send/bitcoin/[address]/+page.server.ts b/src/routes/(app)/send/bitcoin/[address]/+page.server.ts new file mode 100644 index 00000000..a9b72787 --- /dev/null +++ b/src/routes/(app)/send/bitcoin/[address]/+page.server.ts @@ -0,0 +1,7 @@ +import { auth, get } from "$lib/utils"; + +export async function load({ cookies }) { + let aid = cookies.get("aid"); + let { balance } = await get(`/account/${aid}`, auth(cookies)); + return { balance }; +} diff --git a/src/routes/(app)/send/bitcoin/[address]/+page.svelte b/src/routes/(app)/send/bitcoin/[address]/+page.svelte index 838c5c04..6b938c38 100644 --- a/src/routes/(app)/send/bitcoin/[address]/+page.svelte +++ b/src/routes/(app)/send/bitcoin/[address]/+page.svelte @@ -8,9 +8,9 @@ export let data; - let { rates, user } = data; + let { balance, rates, user } = data; let { address } = $page.params; - let { balance, currency, username } = user; + let { currency, username } = user; let amount = 0; let submit, fiat; @@ -24,12 +24,6 @@ }; </script> -<a href="/send"> - <button type="button" class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80"> - <Icon icon="arrow-left" style="w-10" /> - </button> -</a> - <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> diff --git a/src/routes/(app)/send/bitcoin/[address]/[amount]/[...feeRate]/+page.server.ts b/src/routes/(app)/send/bitcoin/[address]/[amount]/[...feeRate]/+page.server.ts index 7efe6e49..e46a9f56 100644 --- a/src/routes/(app)/send/bitcoin/[address]/[amount]/[...feeRate]/+page.server.ts +++ b/src/routes/(app)/send/bitcoin/[address]/[amount]/[...feeRate]/+page.server.ts @@ -1,15 +1,29 @@ import { fail, redirect } from "@sveltejs/kit"; -import { auth, post, fd } from "$lib/utils"; +import { auth, get, post, fd } from "$lib/utils"; export async function load({ params: { address, amount, feeRate }, cookies }) { + let aid = cookies.get("aid"); + try { - let { fee, fees, ourfee, hex } = await post( + let { fee, fees, inputs, ourfee, hex } = await post( "/bitcoin/fee", - { address, amount, feeRate }, + { address, amount, feeRate, aid }, auth(cookies) ); - return { amount, address, fee, fees, feeRate, ourfee, hex }; + let account = await get(`/account/${aid}`, auth(cookies)); + + return { + account, + amount, + address, + fee, + fees, + feeRate, + ourfee, + hex, + inputs, + }; } catch (e: any) { return { amount, address, feeRate, message: e.message }; } @@ -21,13 +35,15 @@ export const actions = { params: { address, amount, feeRate }, request, }) => { + let p; try { - await post("/bitcoin/send", await fd(request), auth(cookies)); + let body = await fd(request); + p = await post("/bitcoin/send", body, auth(cookies)); } catch (e: any) { console.log("problem sending bitcoin", e); return fail(400, { address, amount, feeRate, message: e.message }); } - redirect(307, "/sent"); + redirect(307, `/sent/${p.id}`); }, }; 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 84506e6b..ba87436f 100644 --- a/src/routes/(app)/send/bitcoin/[address]/[amount]/[...feeRate]/+page.svelte +++ b/src/routes/(app)/send/bitcoin/[address]/[amount]/[...feeRate]/+page.svelte @@ -1,20 +1,96 @@ <script> + import { tick } from "svelte"; import { t } from "$lib/translations"; import { enhance } from "$app/forms"; import Toggle from "$comp/Toggle.svelte"; import Icon from "$comp/Icon.svelte"; import Spinner from "$comp/Spinner.svelte"; + import WalletPass from "$comp/WalletPass.svelte"; import { page } from "$app/stores"; - import { fiat as toFiat, f, focus, s, sat, closest } from "$lib/utils"; + import { + fiat as 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 { hex as hexUtil } from "@scure/base"; + import * as btc from "@scure/btc-signer"; + import { hash160 } from "@scure/btc-signer/utils"; + import { decrypt } from "nostr-tools/nip49"; + import { HDKey } from "@scure/bip32"; + import { entropyToMnemonic, mnemonicToSeed } from "@scure/bip39"; + import { wordlist } from "@scure/bip39/wordlists/english"; export let data; export let form; - let { amount, address, message, fee, fees, feeRate, ourfee, rates, hex } = - data; + let { encode, decode } = hexUtil; + let passwordPrompt; + let password; + let cancel = () => (passwordPrompt = false); + let signed; + + let togglePassword = () => (passwordPrompt = !passwordPrompt); + let handler = ({ cancel }) => { + if (account.seed && !signed) cancel(), togglePassword(); + return async ({ result }) => { + if (result.type === "redirect") { + goto(result.location); + } else { + await applyAction(result); + invalidate("app:payments"); + } + }; + }; + + let signTx = async () => { + let entropy = await decrypt(account.seed, password); + let mnemonic = entropyToMnemonic(entropy, wordlist); + let seed = await mnemonicToSeed(mnemonic, password); + let master = HDKey.fromMasterSeed(seed, network); + let child = master.derive("m/84'/0'/0'"); + + let tx = btc.Transaction.fromRaw(decode(hex)); + + for (let [i, input] of tx.inputs.entries()) { + let { witnessUtxo, path } = inputs[i]; + witnessUtxo.amount = BigInt(witnessUtxo.amount); + witnessUtxo.script = decode(witnessUtxo.script); + let key = child.derive(path); + tx.updateInput(i, { witnessUtxo }); + tx.signIdx(key.privateKey, i); + } + + tx.finalize(); + hex = tx.hex; + console.log("HEX", hex); + signed = true; + await tick(); + submit.click(); + }; + + let toggle = () => (submitting = !submitting); + + let { + account, + amount, + address, + message, + fee, + fees, + feeRate, + ourfee, + rates, + hex, + inputs, + } = data; $: reload(data); let reload = () => { @@ -48,15 +124,6 @@ let goBack = () => goto(`/send/bitcoin/${address}`); </script> -<button - type="button" - class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80" - data-sveltekit-preload-data="false" - on:click={goBack} -> - <Icon icon="arrow-left" style="w-10" /> -</button> - <div class="container px-4 max-w-xl mx-auto space-y-5 text-center no-transition" > @@ -115,10 +182,12 @@ </div> {/if} - <form method="POST" use:enhance on:submit={() => (submitting = true)}> + <form method="POST" use:enhance={handler}> <input name="pin" value={$pin} type="hidden" /> <input name="hex" value={hex} type="hidden" /> <input name="rate" value={$rate} type="hidden" /> + <input name="aid" value={account.id} type="hidden" /> + <input name="signed" value={signed} type="hidden" /> <div class="flex justify-center gap-2"> <button @@ -139,6 +208,10 @@ {/if} </div> +{#if passwordPrompt} + <WalletPass bind:password bind:cancel submit={signTx} /> +{/if} + <style> .no-transition { view-transition-name: fee; diff --git a/src/routes/(app)/send/ecash/+page.svelte b/src/routes/(app)/send/ecash/+page.svelte index 15c7c5d3..ef8e368c 100644 --- a/src/routes/(app)/send/ecash/+page.svelte +++ b/src/routes/(app)/send/ecash/+page.svelte @@ -29,25 +29,15 @@ let toggle = () => (submitting = !submitting); </script> -<a href="/send"> - <button type="button" class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80"> - <Icon icon="arrow-left" style="w-10" /> - </button> -</a> - <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.createEcash")} - </h1> - <form use:enhance={handler(toggle)} method="POST"> <Numpad bind:amount bind:fiat {currency} {submit} bind:rate={$rate} /> <input type="hidden" name="amount" bind:value={amount} /> - <div class="flex justify-center gap-2"> + <div> <button type="button" - class="hover:opacity-80 bg-black text-white rounded-2xl py-3 px-4 mt-2 border font-bold" + class="hover:opacity-80 rounded-2xl py-3 px-4 mt-2 border font-bold w-full max-w-[340px] mx-auto" on:click|preventDefault={setMax} disabled={submitting} on:keydown={setMax}>Max ⚡️{s(balance)}</button @@ -57,12 +47,13 @@ use:focus bind:this={submit} type="submit" - class="opacity-100 hover:opacity-80 rounded-2xl border py-3 font-bold mt-2 bg-black text-white px-4 w-24" + class="opacity-100 hover:opacity-80 rounded-2xl border py-3 font-bold mt-2 bg-black text-white px-4 w-full flex justify-center gap-2 max-w-[340px] mx-auto" > {#if submitting} <Spinner /> {:else} - {$t("payments.mint")} + <img src="/images/cash.png" class="w-8" /> + <div class="my-auto">{$t("payments.mint")}</div> {/if} </button> </div> diff --git a/src/routes/(app)/send/invoice/[id]/+page.server.ts b/src/routes/(app)/send/invoice/[id]/+page.server.ts index ed0198cf..6c89912d 100644 --- a/src/routes/(app)/send/invoice/[id]/+page.server.ts +++ b/src/routes/(app)/send/invoice/[id]/+page.server.ts @@ -7,31 +7,32 @@ export async function load({ params: { id }, parent }) { let invoice = await get(`/invoice/${id}`); if (invoice.prompt && invoice.tip === null) - redirect(307, `/${invoice.user.username}/invoice/${id}/tip`); + redirect(307, `/invoice/${id}/tip`); if (invoice.memoPrompt && invoice.memo === null) - redirect(307, `/${invoice.user.username}/invoice/${id}/memo`); + redirect(307, `/invoice/${id}/memo`); - if (invoice.user.username === user?.username) + if (invoice.account === user?.id) error(500, { message: "Cannot send to self" }); - if (!user) redirect(307, `/${invoice.user.username}/invoice/${id}`); + if (!user) redirect(307, `/invoice/${id}`); return { invoice, rates, user }; } export const actions = { default: async ({ cookies, params: { id }, request }) => { + let p; try { let body = await fd(request); body.hash = id; - await post("/payments", body, auth(cookies)); + p = await post("/payments", body, auth(cookies)); } catch (e: any) { console.log("payment failed", id, e); error(500, e.message); } - redirect(307, "/sent"); + redirect(307, `/sent/${p.id}`); }, }; diff --git a/src/routes/(app)/send/invoice/[id]/+page.svelte b/src/routes/(app)/send/invoice/[id]/+page.svelte index 81a2c521..4348e473 100644 --- a/src/routes/(app)/send/invoice/[id]/+page.svelte +++ b/src/routes/(app)/send/invoice/[id]/+page.svelte @@ -52,7 +52,7 @@ let toggle = () => (submitting = !submitting); let external = async () => { - let { id } = await post(`/${recipient.username}/invoice`, { + let { id } = await post(`/invoice`, { invoice: { ...invoice, type: "lightning", @@ -62,7 +62,7 @@ user: { username: recipient.username }, }); - goto(`/${recipient.username}/invoice/${id}?options=true`, { + goto(`/invoice/${id}?options=true`, { invalidateAll: true, }); }; @@ -93,10 +93,6 @@ // }); </script> -<button class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80" on:click={back}> - <Icon icon="arrow-left" style="w-10" /> -</button> - {#if form?.message} <div class="text-red-600 text-center"> {form.message} diff --git a/src/routes/(app)/send/lightning/[payreq]/+page.server.ts b/src/routes/(app)/send/lightning/[payreq]/+page.server.ts index ddec2968..3e87fa53 100644 --- a/src/routes/(app)/send/lightning/[payreq]/+page.server.ts +++ b/src/routes/(app)/send/lightning/[payreq]/+page.server.ts @@ -9,16 +9,17 @@ export const actions = { setAmount: async ({ request }) => fd(request), send: async ({ cookies, request }) => { + let p; try { let body = await fd(request); - await post("/payments", body, auth(cookies)); + p = await post("/payments", body, auth(cookies)); } catch (e: any) { if (e.message.includes("unusable")) e.message = "Failed to route payment, try sending a lower amount"; return fail(400, { message: e.message }); } - redirect(307, "/sent"); + redirect(307, `/sent/${p.id}`); }, }; diff --git a/src/routes/(app)/send/lightning/[payreq]/+page.svelte b/src/routes/(app)/send/lightning/[payreq]/+page.svelte index 57a17e01..74c100f8 100644 --- a/src/routes/(app)/send/lightning/[payreq]/+page.svelte +++ b/src/routes/(app)/send/lightning/[payreq]/+page.svelte @@ -58,11 +58,7 @@ let toggle = () => (show = !show); </script> -<button class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80" on:click={back}> - <Icon icon="arrow-left" style="w-10" /> -</button> - -<div class="container px-4 max-w-xl mx-auto text-center space-y-5"> +<div class="container px-4 max-w-xl mx-auto text-center space-y-2"> {#if amount} {#if form?.message} <div class="text-red-600 text-center"> @@ -81,12 +77,9 @@ <h3 class="text-secondary text-xl">⚡️{s(amount)}</h3> </div> - <div> - <div class="text-lg text-secondary"> - {$t("payments.to")} - </div> - - <p class="text-2xl break-words font-semibold mb-8">{alias}</p> + <div class="text-xl"> + <span class="text-secondary">{$t("payments.to")}</span> + <span class="break-words font-semibold">{alias}</span> </div> {#if ourfee} @@ -109,7 +102,7 @@ use:enhance on:submit={submit} action="?/send" - class="space-y-5" + class="space-y-2" > <input name="payreq" value={payreq} type="hidden" /> <input name="amount" value={amount} type="hidden" /> |
