diff options
| author | Adam Soltys <[email protected]> | 2024-09-12 15:23:45 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2024-09-17 09:19:25 -0700 |
| commit | 2114156be3f89a2ef4c5bd13fd6d7a51d8fbc135 (patch) | |
| tree | ae764021c0f9017493366862d42ea6cac904a76e /src | |
| parent | 1f63848ca9fb01336f04b41434de1cc06a5b3d93 (diff) | |
| download | coinos-ui-2114156be3f89a2ef4c5bd13fd6d7a51d8fbc135.tar.gz coinos-ui-2114156be3f89a2ef4c5bd13fd6d7a51d8fbc135.zip | |
self custodial send
Diffstat (limited to 'src')
30 files changed, 322 insertions, 139 deletions
diff --git a/src/components/Account.svelte b/src/components/Account.svelte index eb1b0bac..f83068b5 100644 --- a/src/components/Account.svelte +++ b/src/components/Account.svelte @@ -1,4 +1,5 @@ <script> + import { account as acc } from "$lib/store"; import Icon from "$comp/Icon.svelte"; import Balance from "$comp/Balance.svelte"; import { t } from "$lib/translations"; @@ -6,19 +7,17 @@ export let user, rate, account; let { seed, balance, id } = account; $: type = seed ? "Savings" : "Cash"; + + let setAccount = () => + document.cookie = `account=${id}; path=/; max-age=86400`; </script> -<a href={`/payments`} class="block"> +<a href={`/payments`} class="block" on:click={setAccount}> <div class="border rounded-xl shadow p-4 space-y-5"> - <div class="flex justify-center lg:justify-start"> - <Balance {balance} {user} {rate} /> - <div class="ml-auto text-right"> - <div>{type} account</div> - </div> - </div> + <Balance {balance} {user} {rate} /> <div class="flex flex-wrap gap-3 justify-center w-full text-xl"> - <a href={`/send`} class="contents grow"> + <a href={`/send`} class="contents grow" on:click={setAccount}> <button type="button" class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-primary grow" @@ -30,7 +29,7 @@ </button> </a> - <a href={`/invoice`} class="contents"> + <a href={`/invoice?account=${id}`} class="contents" on:click={setAccount}> <button class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-primary grow" > diff --git a/src/components/LandingHeader.svelte b/src/components/LandingHeader.svelte index 92de7bcd..72913999 100644 --- a/src/components/LandingHeader.svelte +++ b/src/components/LandingHeader.svelte @@ -19,22 +19,22 @@ </script> <header - class="w-full md:w-5/6 py-5 mx-auto fixed md:sticky z-10 top-0 bg-white/90" + class="w-full lg:w-5/6 py-5 mx-auto fixed sticky z-10 top-0 bg-white/90" bind:this={header} > - <nav class="block md:flex justify-between items-center"> + <nav class="block lg:flex justify-between items-center"> <div - class="flex justify-start md:justify-center items-center md:space-x-10" + class="flex justify-start lg:justify-center items-center lg:space-x-10" > <a href="/" on:click={() => scroll(header)}> - <Icon icon="logo" style="hidden md:block mb-5 xl:mb-0" /> - <Icon icon="logo" style="block md:hidden ml-5 w-48" /> + <Icon icon="logo" style="hidden lg:block mb-5 xl:mb-0" /> + <Icon icon="logo" style="block lg:hidden ml-5 w-48" /> </a> </div> <!-- desktop nav --> <div - class="hidden space-x-10 md:flex justify-center items-center font-bold" + class="hidden space-x-10 lg:flex justify-center items-center font-bold" > {#if $page.url.pathname === "/"} <button class="hover:opacity-80" on:click={() => scroll(howItWorks)} @@ -76,13 +76,13 @@ <!-- mobile nav --> <button - class="block md:hidden absolute top-[34.5px] right-10 z" + class="block lg:hidden absolute top-[34.5px] right-10 z" on:click={() => (showMobileMenu = !showMobileMenu)} ><Icon icon={!showMobileMenu ? "menu" : "close"} /> </button> <div - class="container w-full px-10 md:hidden absolute top-0 {showMobileMenu + class="container w-full px-10 lg:hidden absolute top-0 {showMobileMenu ? 'right-0' : 'right-[-100%]'} transition-all ease-in-out duration-300 h-[100vh] w-full bg-white" > diff --git a/src/components/Request.svelte b/src/components/Request.svelte deleted file mode 100644 index 297b7438..00000000 --- a/src/components/Request.svelte +++ /dev/null @@ -1,39 +0,0 @@ -<script> - import { t } from "$lib/translations"; - import { fly } from "svelte/transition"; - - import Avatar from "$comp/Avatar.svelte"; - import Icon from "$comp/Icon.svelte"; - - import { request, requestRedirect } from "$lib/store"; - import { page } from "$app/stores"; - - export let user; - - $requestRedirect = $page.url.pathname; -</script> - -{#if $request} - <div class="fixed bottom-0 border-t w-full p-4 bg-white z-50" in:fly> - <div class="flex flex-wrap md:flex-nowrap max-w-xl mx-auto gap-2"> - <div class="my-auto mx-auto"> - <div class="flex"> - <Avatar user={$request.requester} size={12} /> - <h1 class="my-auto"> - <b>{$request.requester.username}</b> - {$t("payments.readyToPay")} - </h1> - </div> - </div> - <div class="mx-auto my-auto flex gap-2"> - <a href={`/receive/${$request.id}`}> - <button - class="rounded-full border py-2 px-4 font-bold hover:opacity-80 w-32" - > - {$t("payments.invoice")} - </button> - </a> - </div> - </div> - </div> -{/if} diff --git a/src/lib/invoice.ts b/src/lib/invoice.ts index 3c885346..0ab0a9af 100644 --- a/src/lib/invoice.ts +++ b/src/lib/invoice.ts @@ -1,13 +1,14 @@ -import { auth, get, post } from "$lib/utils"; +import { auth, get, post, setAccount } from "$lib/utils"; import { redirect } from "@sveltejs/kit"; -export default async ({ cookies, request }) => { +export default async ({ cookies, request, url }) => { let form = await request.formData(); let rates = await get("/rates"); let amount = parseInt(form.get("amount")); let invoice = { + account, amount, items: JSON.parse(form.get("items")), memo: form.get("memo"), @@ -16,7 +17,7 @@ export default async ({ cookies, request }) => { type: form.get("type"), prompt: form.get("prompt") === "true", rate: parseFloat(form.get("rate")) || rates[form.get("currency")], - id: undefined + id: undefined, }; let user = { username: form.get("username"), currency: form.get("currency") }; diff --git a/src/lib/store.ts b/src/lib/store.ts index 3c1ff321..42b41127 100644 --- a/src/lib/store.ts +++ b/src/lib/store.ts @@ -38,6 +38,7 @@ let persistLocal = (key, defaultValue: any = undefined) => { return s; }; +export let account = writable(); export let amountPrompt = persistLocal("amountPrompt"); export let avatar = writable(); export let banner = writable(); diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 69e0f949..c20a7d29 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,7 +1,7 @@ import { browser } from "$app/environment"; import { toast } from "@zerodevx/svelte-toast"; import { goto } from "$app/navigation"; -import { PUBLIC_COINOS_URL } from "$env/static/public"; +import { PUBLIC_COINOS_NETWORK, PUBLIC_COINOS_URL } from "$env/static/public"; import { page } from "$app/stores"; import { get as getStore } from "svelte/store"; @@ -41,7 +41,11 @@ export let get = (url: string, headers = {}) => { }); }; -export let post = async (url: string, body: object, headers: HeadersInit | undefined = undefined) => { +export let post = async ( + url: string, + body: object, + headers: HeadersInit | undefined = undefined +) => { headers = { "content-type": "application/json", accept: "application/json", @@ -242,7 +246,11 @@ export let select = (el: HTMLInputElement): any => export let sleep = (n: number): Promise<void> => new Promise((r) => setTimeout(r, n)); -export let wait = async (f: () => boolean | Promise<boolean>, n: number = 100, s: number = 300): Promise<boolean> => { +export let wait = async ( + f: () => boolean | Promise<boolean>, + n: number = 100, + s: number = 300 +): Promise<boolean> => { let i = 0; while (!(await f()) && i < s) { await sleep(n); @@ -327,3 +335,30 @@ export function setCookie(name: string, value: string, seconds: number): void { const expires = "expires=" + now.toUTCString(); document.cookie = name + "=" + value + ";" + expires + ";path=/"; } + +export let network = { + bitcoin: { + bech32: "bc", + pubKeyHash: 0x00, + scriptHash: 0x05, + wif: 0x80, + }, + regtest: { + bech32: "bcrt", + pubKeyHash: 0x6f, + scriptHash: 0xc4, + wif: 0xef, + }, +}[PUBLIC_COINOS_NETWORK]; + +export let versions = { + bitcoin: { + private: 0x0488ade4, + public: 0x0488b21e, + }, + regtest: { + private: 0x04358394, + public: 0x043587cf, + }, +}[PUBLIC_COINOS_NETWORK]; + diff --git a/src/locales/en.json b/src/locales/en.json index b1bbe1c4..fd2807b4 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -299,6 +299,7 @@ } }, "payments": { + "enterWalletPass": "Please enter your wallet password", "legacyVersion": "Legacy version", "newVersion": "New version", "income": "Income", diff --git a/src/routes/(app)/+error.svelte b/src/routes/(app)/+error.svelte new file mode 100644 index 00000000..6ce7979c --- /dev/null +++ b/src/routes/(app)/+error.svelte @@ -0,0 +1,38 @@ +<script> + import { back } from "$lib/utils"; + import Icon from "$comp/Icon.svelte"; + import { page } from "$app/stores"; + + export let data; + let { user } = data; + + let link = user ? `/${user.username}` : "/"; +</script> + +<div class="container px-4 max-w-xl mx-auto mt-10 space-y-5"> + <div class="w-full flex"> + <a href="/" class="mx-auto"> + <Icon icon="logo" /> + </a> + </div> + + <div class="flex justify-center items-center"> + <div class="shadow-xl rounded-3xl px-10 pt-5 pb-10 space-y-5 w-full mx-5"> + <div class="relative"> + <h1 class="text-2xl md:text-3xl font-semibold text-center">Error</h1> + </div> + <p class="text-secondary text-center">Something went wrong</p> + <p class="text-secondary text-center">{$page.error.message}</p> + + <div class="flex justify-center py-24"> + <a href={link}> + <button + class="bg-black text-white border rounded-full px-8 py-4 font-bold hover:opacity-80 text-xl" + > + Back + </button> + </a> + </div> + </div> + </div> +</div> diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte index 0ff4816d..05ca0d0c 100644 --- a/src/routes/(app)/+layout.svelte +++ b/src/routes/(app)/+layout.svelte @@ -17,7 +17,6 @@ import AppHeader from "$comp/AppHeader.svelte"; import Invoice from "$comp/Invoice.svelte"; import Password from "$comp/Password.svelte"; - import Request from "$comp/Request.svelte"; import { getCookie, warning, protectedRoutes } from "$lib/utils"; import { t, locale, loading } from "$lib/translations"; import { goto, onNavigate } from "$app/navigation"; @@ -36,7 +35,6 @@ }); let { generate, rate, user, subject, token, rates } = data; - console.log("SUB", subject) $: update(data); let update = (data) => { @@ -127,10 +125,6 @@ <Invoice {user} /> {/if} -{#if $request} - <Request {user} /> -{/if} - <style> @keyframes fade-in { from { diff --git a/src/routes/(app)/[username]/(profile)/+page.svelte b/src/routes/(app)/[username]/(profile)/+page.svelte index 9e5babd2..98b77fc9 100644 --- a/src/routes/(app)/[username]/(profile)/+page.svelte +++ b/src/routes/(app)/[username]/(profile)/+page.svelte @@ -40,30 +40,32 @@ <Account {user} {rate} {account} /> {/each} - <button - class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-black text-white" - > - <a href={`/account/setup`}> - <div class="mx-auto flex gap-2"> - <Icon icon="plus" style="w-8 mx-auto invert" /> - <div class="my-auto text-xl whitespace-nowrap">Add account</div> - </div> - </a> - </button> - - {#if $installPrompt} + <div class="flex justify-center gap-2"> <button - class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-black text-white grow basis-full fixed bottom-16 xl:hidden" - on:click={install} + class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-black text-white w-full justify-center" > - <div class="mx-auto flex gap-2"> - <Icon icon="save" style="w-8 mx-auto invert" /> - <div class="my-auto text-xl whitespace-nowrap"> - {$t("user.install")} + <a href={`/account/setup`}> + <div class="mx-auto flex gap-2"> + <Icon icon="plus" style="w-8 mx-auto invert" /> + <div class="my-auto text-xl whitespace-nowrap">Add account</div> </div> - </div> + </a> </button> - {/if} + + {#if $installPrompt} + <button + class="rounded-2xl border py-5 px-6 font-bold hover:opacity-80 flex bg-black text-white xl:hidden w-full justify-center" + on:click={install} + > + <div class="mx-auto flex gap-2"> + <Icon icon="save" style="w-8 mx-auto invert" /> + <div class="my-auto text-xl whitespace-nowrap"> + {$t("user.install")} + </div> + </div> + </button> + {/if} + </div> {/if} <Items {items} {subject} {user} {total} {currency} {rate} /> diff --git a/src/routes/(app)/[username]/+layout.server.ts b/src/routes/(app)/[username]/+layout.server.ts index c340544d..9a520036 100644 --- a/src/routes/(app)/[username]/+layout.server.ts +++ b/src/routes/(app)/[username]/+layout.server.ts @@ -13,7 +13,6 @@ export let load = async ({ let subject = await get(`/users/${username}`); let { user } = await parent(); if (!user) user = subject; - console.log("SUBJECT", subject) let expires = new Date(); expires.setSeconds(expires.getSeconds() + 380 * 24 * 60 * 60); diff --git a/src/routes/(app)/account/setup/step2/+page.svelte b/src/routes/(app)/account/setup/step2/+page.svelte index 8169ce06..221e545e 100644 --- a/src/routes/(app)/account/setup/step2/+page.svelte +++ b/src/routes/(app)/account/setup/step2/+page.svelte @@ -1,28 +1,44 @@ <script lang="ts"> - import { focus } from "$lib/utils"; import { t } from "$lib/translations"; import { mnemonic } from "$lib/store"; import Icon from "$comp/Icon.svelte"; - import { encrypt, decrypt } from "nostr-tools/nip49"; - import { mnemonicToEntropy, entropyToMnemonic } from "@scure/bip39"; + import { encrypt } from "nostr-tools/nip49"; + import { + mnemonicToSeed, + mnemonicToEntropy, + entropyToMnemonic, + } from "@scure/bip39"; import { wordlist } from "@scure/bip39/wordlists/english"; - import { fail, post } from "$lib/utils"; + import { focus, versions, fail, post } from "$lib/utils"; import { goto } from "$app/navigation"; + import { HDKey } from "@scure/bip32"; export let data; + let { user } = data; let password; let submit = async () => { let seed = await encrypt(mnemonicToEntropy($mnemonic, wordlist), password); + let master = HDKey.fromMasterSeed( + await mnemonicToSeed($mnemonic), + versions + ); + let child = master.derive("m/84'/0'/0'"); + let pubkey = child.publicExtendedKey; + let fingerprint = child.fingerprint.toString(16).padStart(8, "0"); + + let receiving = child.deriveChild(0); try { - await post("/api/accounts", { seed }); + await post("/api/accounts", { fingerprint, pubkey, seed }); goto(`/${user.username}`); } catch (e) { console.log(e); fail("Something went wrong"); } + + $mnemonic = ""; }; </script> @@ -41,7 +57,7 @@ than your login password. We can't reset it if you lose it. </p> - <input use:focus type="text" placeholder="Password" bind:value={password} /> + <input use:focus type="text" placeholder="Password" bind:value={password} /> <button class="bg-black text-white w-full px-5 py-6 text-xl rounded-2xl font-bold" diff --git a/src/routes/(app)/invoice/+page.server.ts b/src/routes/(app)/invoice/+page.server.ts index 83127457..05851430 100644 --- a/src/routes/(app)/invoice/+page.server.ts +++ b/src/routes/(app)/invoice/+page.server.ts @@ -2,8 +2,8 @@ import { auth, post } from "$lib/utils"; import { error, redirect } from "@sveltejs/kit"; import type { Invoice } from "$lib/types"; -export let load = async ({ cookies, params, parent }) => { - let { account } = params; +export let load = async ({ cookies, parent }) => { + let account = cookies.get("account"); let { subject, user, rates } = await parent(); let invoice: Invoice = { diff --git a/src/routes/(app)/invoice/[id]/+layout.server.ts b/src/routes/(app)/invoice/[id]/+layout.server.ts index e7144b15..71f53e09 100644 --- a/src/routes/(app)/invoice/[id]/+layout.server.ts +++ b/src/routes/(app)/invoice/[id]/+layout.server.ts @@ -1,4 +1,4 @@ -import Qr from "qrcode-base64"; +import * as Qr from "qrcode-base64"; import { get } from "$lib/utils"; import { redirect } from "@sveltejs/kit"; @@ -26,11 +26,10 @@ export async function load({ depends, params, url, parent }) { (!amount && (pending || received)) || (amount > 0 && (pending >= amount || received >= amount)); if (paid && !url.pathname.endsWith("paid")) { - redirect( 307, `/invoice/${id}/paid` + (options ? "?options=true" : "") - ); + redirect(307, `/invoice/${id}/paid` + (options ? "?options=true" : "")); } - let src = Qr.drawImg(invoice.text || "", { size: 340 }); + let src = Qr.drawImg(invoice.text || "", { size: 500 }); return { id, invoice, subject, src }; } diff --git a/src/routes/(app)/invoice/[id]/+page.svelte b/src/routes/(app)/invoice/[id]/+page.svelte index c4810889..36f5d261 100644 --- a/src/routes/(app)/invoice/[id]/+page.svelte +++ b/src/routes/(app)/invoice/[id]/+page.svelte @@ -136,14 +136,14 @@ > <img {src} - class="mx-auto z-10 shadow-inner rounded-2xl" + class="mx-auto z-10 rounded-2xl w-[260px] rounded-2xl" bind:this={qr} alt={txt} /> </a> </div> {:else} - <div class="break-all pb-5 text-lg text-center"> + <div class="break-all pb-5 text-center text-secondary"> {txt} </div> {/if} diff --git a/src/routes/(app)/logout/+page.server.ts b/src/routes/(app)/logout/+page.server.ts index ab5df7b9..9c2a029c 100644 --- a/src/routes/(app)/logout/+page.server.ts +++ b/src/routes/(app)/logout/+page.server.ts @@ -7,6 +7,7 @@ const opts = { export async function load({ cookies }) { let lang = cookies.get("lang"); + cookies.set("account", "", opts); cookies.set("lang", "", opts); cookies.set("username", "", opts); cookies.set("token", "", opts); diff --git a/src/routes/(app)/payments/[...page]/+page.server.ts b/src/routes/(app)/payments/[...page]/+page.server.ts index c2cd5683..e781022e 100644 --- a/src/routes/(app)/payments/[...page]/+page.server.ts +++ b/src/routes/(app)/payments/[...page]/+page.server.ts @@ -5,6 +5,7 @@ let limit = 25; export async function load({ cookies, depends, params }) { depends("app:payments"); + let account = cookies.get("account"); let parts = params.page.split("/"); let start, end, page; @@ -16,7 +17,7 @@ export async function load({ cookies, depends, params }) { let offset = (page - 1) * limit; if (!start) start = Date.now() / 1000 - 24 * 60 * 60; - let url = `/payments?account=${params.account}&start=${ + let url = `/payments?account=${account}&start=${ start * 1000 }&limit=${limit}&offset=${offset}`; if (end) url += `&end=${end * 1000}`; diff --git a/src/routes/(app)/qr/[text]/+page.svelte b/src/routes/(app)/qr/[text]/+page.svelte index c9562582..8b6d48ab 100644 --- a/src/routes/(app)/qr/[text]/+page.svelte +++ b/src/routes/(app)/qr/[text]/+page.svelte @@ -6,10 +6,6 @@ let { src, text } = data; </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-4xl mx-auto space-y-5"> <div class="flex justify-center items-center"> <div 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..3e56e15a --- /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 account = cookies.get("account"); + let { balance } = await get(`/account/${account}`, 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..b656dc06 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; 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..33e96595 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,17 +1,31 @@ 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 account = cookies.get("account"); + try { - let { fee, fees, ourfee, hex } = await post( + let { fee, fees, inputs, ourfee, hex } = await post( "/bitcoin/fee", - { address, amount, feeRate }, + { address, amount, feeRate, account }, auth(cookies) ); - return { amount, address, fee, fees, feeRate, ourfee, hex }; + account = await get(`/account/${account}`, auth(cookies)); + + return { + account, + amount, + address, + fee, + fees, + feeRate, + ourfee, + hex, + inputs, + }; } catch (e: any) { - return { amount, address, feeRate, message: e.message }; + return { account, amount, address, feeRate, message: e.message }; } } @@ -22,7 +36,8 @@ export const actions = { request, }) => { try { - await post("/bitcoin/send", await fd(request), auth(cookies)); + let body = await fd(request); + 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 }); 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..1267a992 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,84 @@ <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 { 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 { 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 revealPassword = false; + 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); + } + }; + }; + + let signTx = async () => { + let entropy = await decrypt(account.seed, password); + let mnemonic = entropyToMnemonic(entropy, wordlist); + let seed = await mnemonicToSeed(mnemonic); + 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 { nonWitnessUtxo, path } = inputs[i]; + tx.updateInput(i, { nonWitnessUtxo }); + let key = child.derive(path); + let { privateKey, publicKey } = key; + let address = btc.getAddress("wpkh", privateKey, network); + tx.signIdx(privateKey, i); + } + tx.finalize(); + hex = tx.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 = () => { @@ -115,10 +179,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="account" value={account.id} type="hidden" /> + <input name="signed" value={signed} type="hidden" /> <div class="flex justify-center gap-2"> <button @@ -139,6 +205,68 @@ {/if} </div> +{#if passwordPrompt} + <div + class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-20" + > + <div + class="relative top-1/3 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white space-y-5" + > + <h1 class="text-center text-2xl font-semibold"> + {$t("payments.enterWalletPass")} + </h1> + <form on:submit|preventDefault={signTx}> + <div class="relative mb-5"> + {#if revealPassword} + <input + name="password" + type="text" + required + class="bg-primary" + bind:value={password} + autocapitalize="none" + use:focus + /> + {:else} + <input + name="password" + type="password" + required + class="bg-primary" + bind:value={password} + autocapitalize="none" + use:focus + /> + {/if} + <button + type="button" + on:click={() => (revealPassword = !revealPassword)} + class="absolute right-5 top-6" + > + <Icon icon={revealPassword ? "eye" : "eye-off"} /> + </button> + </div> + <div class="w-full flex"> + <button + type="button" + class="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80 mx-auto" + on:click={cancel} + on:keydown={cancel} + > + <div class="my-auto">Cancel</div> + </button> + <button + type="submit" + class="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80 mx-auto" + > + <div class="my-auto">Submit</div> + </button> + </div> + </form> + </div> + </div> +{/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..615c068a 100644 --- a/src/routes/(app)/send/ecash/+page.svelte +++ b/src/routes/(app)/send/ecash/+page.svelte @@ -29,12 +29,6 @@ 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")} diff --git a/src/routes/(app)/sent/+page.server.ts b/src/routes/(app)/sent/+page.server.ts index 62a4d9b4..4cf580ee 100644 --- a/src/routes/(app)/sent/+page.server.ts +++ b/src/routes/(app)/sent/+page.server.ts @@ -1,6 +1,7 @@ import { get, auth } from "$lib/utils"; export let load = async ({ cookies }) => { - let { payments } = await get("/payments", auth(cookies)); + let account = cookies.get("account"); + let { payments } = await get(`/payments?account=${account}`, auth(cookies)); return { payments }; }; diff --git a/src/routes/(app)/settings/[...tab]/+page.svelte b/src/routes/(app)/settings/[...tab]/+page.svelte index c495a5cc..7f4dc87e 100644 --- a/src/routes/(app)/settings/[...tab]/+page.svelte +++ b/src/routes/(app)/settings/[...tab]/+page.svelte @@ -206,7 +206,7 @@ <input type="hidden" name="pin" value={$pin} /> <input type="hidden" name="tab" value={tab} /> - <div class="mt-24 mb-20 px-3 md:px-0 w-full md:w-[400px] mx-auto space-y-8"> + <div class="mt-24 mb-20 px-3 md:px-0 w-full md:max-w-lg mx-auto space-y-8"> <div class="header"> <h1 class="text-center text-3xl md:text-4xl font-semibold mb-10"> {$t("user.settings.header")} diff --git a/src/routes/(static)/+layout.svelte b/src/routes/(static)/+layout.svelte index 4b350bd0..8b14fd38 100644 --- a/src/routes/(static)/+layout.svelte +++ b/src/routes/(static)/+layout.svelte @@ -9,7 +9,7 @@ <SvelteToast options={{ reversed: true, intro: { y: 192 } }} /> <LandingHeader {user} /> -<main class="container flex px-4 md:px-40 mx-auto min-h-[600px] py-28"> +<main class="container flex px-4 md:px-40 mx-auto min-h-[600px]"> <slot /> </main> <Footer /> diff --git a/src/routes/(static)/login/+page.svelte b/src/routes/(static)/login/+page.svelte index 84e1ea89..df7db5e6 100644 --- a/src/routes/(static)/login/+page.svelte +++ b/src/routes/(static)/login/+page.svelte @@ -76,7 +76,7 @@ <Pin bind:value={token} title="Enter 2FA Code" {cancel} notify={false} /> {/if} -<div class="mx-auto md:shadow-xl rounded-3xl max-w-xl w-full md:w-[480px] md:p-8"> +<div class="mx-auto md:shadow-xl rounded-3xl max-w-xl w-full md:w-[480px] md:p-8 mb-20"> <h1 class="text-2xl font-bold text-center">{$t("login.signIn")}</h1> {#if form?.error} diff --git a/src/routes/(static)/register/+page.svelte b/src/routes/(static)/register/+page.svelte index b6a4c44e..3491bd5d 100644 --- a/src/routes/(static)/register/+page.svelte +++ b/src/routes/(static)/register/+page.svelte @@ -155,7 +155,7 @@ {/if} <div - class="mx-auto md:shadow-xl rounded-3xl max-w-xl w-full md:w-[480px] md:p-8 space-y-5" + class="mx-auto md:shadow-xl rounded-3xl max-w-xl w-full md:w-[480px] md:p-8 space-y-5 mb-20" > <h1 class="text-2xl font-bold text-center">{$t("login.createAccount")}</h1> <input diff --git a/src/routes/(static)/support/+page.svelte b/src/routes/(static)/support/+page.svelte index 20e41280..6b32124e 100644 --- a/src/routes/(static)/support/+page.svelte +++ b/src/routes/(static)/support/+page.svelte @@ -53,11 +53,9 @@ ></script></svelte:head > -<div - class="container max-w-lg mx-auto mt-20 space-y-8 shadow-xl rounded-3xl p-10" -> +<div class="container max-w-lg mx-auto space-y-8 p-4 pb-20"> {#if sent} - <h1 class="text-center text-3xl md:text-4xl font-semibold">Thank you!</h1> + <h1 class="text-center text-3xl lg:text-4xl font-semibold">Thank you!</h1> <div class="text-center text-lg text-secondary"> Someone will be in touch shortly. </div> @@ -88,7 +86,6 @@ >{$t("user.support.accountName")}</label > <input - class="bg-primary" type="text" name="account" required @@ -101,7 +98,6 @@ >{$t("user.support.email")}</label > <input - class="bg-primary" type="email" name="email" bind:value={email} @@ -115,7 +111,6 @@ > <textarea rows={5} - class="bg-primary" type="text" name="message" bind:value={message} diff --git a/src/routes/+error.svelte b/src/routes/+error.svelte index 6ce7979c..faa7d738 100644 --- a/src/routes/+error.svelte +++ b/src/routes/+error.svelte @@ -1,5 +1,4 @@ <script> - import { back } from "$lib/utils"; import Icon from "$comp/Icon.svelte"; import { page } from "$app/stores"; |
