aboutsummaryrefslogtreecommitdiffstats
path: root/src/routes/(app)/send
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 /src/routes/(app)/send
parent1f63848ca9fb01336f04b41434de1cc06a5b3d93 (diff)
downloadcoinos-ui-2114156be3f89a2ef4c5bd13fd6d7a51d8fbc135.tar.gz
coinos-ui-2114156be3f89a2ef4c5bd13fd6d7a51d8fbc135.zip
self custodial send
Diffstat (limited to 'src/routes/(app)/send')
-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
5 files changed, 162 insertions, 18 deletions
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")}