aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2024-09-12 15:23:45 -0700
committerAdam Soltys <[email protected]>2024-09-17 09:19:25 -0700
commit2114156be3f89a2ef4c5bd13fd6d7a51d8fbc135 (patch)
treeae764021c0f9017493366862d42ea6cac904a76e
parent1f63848ca9fb01336f04b41434de1cc06a5b3d93 (diff)
downloadcoinos-ui-2114156be3f89a2ef4c5bd13fd6d7a51d8fbc135.tar.gz
coinos-ui-2114156be3f89a2ef4c5bd13fd6d7a51d8fbc135.zip
self custodial send
-rw-r--r--.env.production1
-rwxr-xr-xbun.lockbbin158882 -> 161519 bytes
-rw-r--r--package.json3
-rw-r--r--src/components/Account.svelte17
-rw-r--r--src/components/LandingHeader.svelte16
-rw-r--r--src/components/Request.svelte39
-rw-r--r--src/lib/invoice.ts7
-rw-r--r--src/lib/store.ts1
-rw-r--r--src/lib/utils.ts41
-rw-r--r--src/locales/en.json1
-rw-r--r--src/routes/(app)/+error.svelte38
-rw-r--r--src/routes/(app)/+layout.svelte6
-rw-r--r--src/routes/(app)/[username]/(profile)/+page.svelte42
-rw-r--r--src/routes/(app)/[username]/+layout.server.ts1
-rw-r--r--src/routes/(app)/account/setup/step2/+page.svelte28
-rw-r--r--src/routes/(app)/invoice/+page.server.ts4
-rw-r--r--src/routes/(app)/invoice/[id]/+layout.server.ts7
-rw-r--r--src/routes/(app)/invoice/[id]/+page.svelte4
-rw-r--r--src/routes/(app)/logout/+page.server.ts1
-rw-r--r--src/routes/(app)/payments/[...page]/+page.server.ts3
-rw-r--r--src/routes/(app)/qr/[text]/+page.svelte4
-rw-r--r--src/routes/(app)/send/bitcoin/[address]/+page.server.ts7
-rw-r--r--src/routes/(app)/send/bitcoin/[address]/+page.svelte4
-rw-r--r--src/routes/(app)/send/bitcoin/[address]/[amount]/[...feeRate]/+page.server.ts27
-rw-r--r--src/routes/(app)/send/bitcoin/[address]/[amount]/[...feeRate]/+page.svelte136
-rw-r--r--src/routes/(app)/send/ecash/+page.svelte6
-rw-r--r--src/routes/(app)/sent/+page.server.ts3
-rw-r--r--src/routes/(app)/settings/[...tab]/+page.svelte2
-rw-r--r--src/routes/(static)/+layout.svelte2
-rw-r--r--src/routes/(static)/login/+page.svelte2
-rw-r--r--src/routes/(static)/register/+page.svelte2
-rw-r--r--src/routes/(static)/support/+page.svelte9
-rw-r--r--src/routes/+error.svelte1
33 files changed, 325 insertions, 140 deletions
diff --git a/.env.production b/.env.production
index 1ae57c17..8b72abf4 100644
--- a/.env.production
+++ b/.env.production
@@ -6,3 +6,4 @@ PUBLIC_LIQUID_EXPLORER=https://liquid.network
PUBLIC_DOMAIN=coinos.io
PUBLIC_COINOS_PUBKEY=ba80990666ef0b6f4ba5059347beb13242921e54669e680064ca755256a1e3a6
PUBLIC_COINOS_RELAY=wss://relay.coinos.io
+PUBLIC_COINOS_NETWORK=regtest
diff --git a/bun.lockb b/bun.lockb
index df7d1899..97e0841d 100755
--- a/bun.lockb
+++ b/bun.lockb
Binary files differ
diff --git a/package.json b/package.json
index 848db710..2ed4c699 100644
--- a/package.json
+++ b/package.json
@@ -7,8 +7,9 @@
"@capacitor/cli": "^6.0.0",
"@capacitor/core": "^6.0.0",
"@capacitor/ios": "^6.1.2",
- "@scure/base": "^1.1.1",
+ "@scure/base": "^1.1.8",
"@scure/bip39": "^1.2.1",
+ "@scure/btc-signer": "^1.3.2",
"@sveltejs/adapter-node": "^2.0.0",
"@sveltejs/kit": "^2.5.22",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
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";