aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2024-11-21 16:31:25 -0800
committerAdam Soltys <[email protected]>2024-11-21 16:31:31 -0800
commit38660e024c055952180bfa3f9067274cf2e0d6ed (patch)
treeded80bc71beab042a26b811cf5e7681af3379338 /src
parentd946ec9dbcba7c8e49d2e5b4221cb11a7f726528 (diff)
downloadcoinos-ui-38660e024c055952180bfa3f9067274cf2e0d6ed.tar.gz
coinos-ui-38660e024c055952180bfa3f9067274cf2e0d6ed.zip
ecash requests
Diffstat (limited to 'src')
-rw-r--r--src/lib/nostr.ts3
-rw-r--r--src/lib/parse.ts29
-rw-r--r--src/routes/(app)/ecash/mint/+server.ts7
-rw-r--r--src/routes/(app)/send/ecash/[request]/+page.server.ts16
-rw-r--r--src/routes/(app)/send/ecash/[request]/+page.svelte107
5 files changed, 146 insertions, 16 deletions
diff --git a/src/lib/nostr.ts b/src/lib/nostr.ts
index 2e3ea142..bba46ef4 100644
--- a/src/lib/nostr.ts
+++ b/src/lib/nostr.ts
@@ -140,7 +140,8 @@ type SignParams = {
};
export const sign = async ({ event, user }: SignParams) => {
- event = finalizeEvent(event, await getPrivateKey(user));
+ const sk = await getPrivateKey(user);
+ event = finalizeEvent(event, sk);
};
export const send = (event: EventTemplate) => {
diff --git a/src/lib/parse.ts b/src/lib/parse.ts
index e4bd3497..03d21943 100644
--- a/src/lib/parse.ts
+++ b/src/lib/parse.ts
@@ -1,26 +1,17 @@
import { PUBLIC_DOMAIN } from "$env/static/public";
-import { get, sats, isLiquid, post } from "$lib/utils";
+import { get, isLiquid, auth, post, sats } from "$lib/utils";
import { redirect } from "@sveltejs/kit";
import bip21 from "bip21";
import { validate } from "bitcoin-address-validation";
export default async (s, host) => {
if (!s) return;
- let t = s;
-
- if (t.includes("lightning=")) {
- const url = new URL(t);
- const params = new URLSearchParams(url.search);
- t = params.get("lightning");
- }
-
- if (t.startsWith("http")) redirect(307, t);
- if (t.startsWith(host)) redirect(307, `http://${t}`);
-
+ let t = s.trim();
let amount;
let user;
- t = t.trim();
+ if (t.startsWith("http")) redirect(307, t);
+ if (t.startsWith(host)) redirect(307, `http://${t}`);
if (t.startsWith("lightning:")) t = t.replace("lightning:", "");
if (t.endsWith(`@${PUBLIC_DOMAIN}`)) t = t.split("@")[0];
@@ -30,8 +21,6 @@ export default async (s, host) => {
} catch (e) {}
}
- if (t.includes("/fund")) redirect(307, t.substring(t.indexOf("/fund")));
-
// lightning
if (t.toLowerCase().startsWith("lightning:"))
t = t.toLowerCase().replace("lightning:", "");
@@ -44,6 +33,12 @@ export default async (s, host) => {
redirect(307, `/send/lightning/${t}`);
}
+ if (t.includes("lightning=")) {
+ const url = new URL(t);
+ const params = new URLSearchParams(url.search);
+ t = params.get("lightning");
+ }
+
if (t.toLowerCase().startsWith("ln")) {
try {
await get(`/invoice/${t}`);
@@ -70,6 +65,8 @@ export default async (s, host) => {
redirect(307, `/ecash/${id}`);
}
+ if (t.startsWith("creq")) redirect(307, `/send/ecash/${t}`);
+
// user
try {
user = await get(`/users/${t.split("/")[0]}`);
@@ -79,6 +76,8 @@ export default async (s, host) => {
if (user) redirect(307, `/pay/${t}`);
let fund;
+ if (t.includes("/fund")) redirect(307, t.substring(t.indexOf("/fund")));
+
try {
fund = await get(`/fund/${t}`);
} catch (e) {}
diff --git a/src/routes/(app)/ecash/mint/+server.ts b/src/routes/(app)/ecash/mint/+server.ts
new file mode 100644
index 00000000..02170650
--- /dev/null
+++ b/src/routes/(app)/ecash/mint/+server.ts
@@ -0,0 +1,7 @@
+import { auth, post } from "$lib/utils";
+import { json } from "@sveltejs/kit";
+
+export async function POST({ cookies, request }) {
+ const result = await post("/mint", await request.json(), auth(cookies));
+ return json(result);
+}
diff --git a/src/routes/(app)/send/ecash/[request]/+page.server.ts b/src/routes/(app)/send/ecash/[request]/+page.server.ts
new file mode 100644
index 00000000..53a5e013
--- /dev/null
+++ b/src/routes/(app)/send/ecash/[request]/+page.server.ts
@@ -0,0 +1,16 @@
+import { auth, fd, get, post } from "$lib/utils";
+import { decodePaymentRequest } from "@cashu/cashu-ts";
+import { fail, redirect } from "@sveltejs/kit";
+
+export async function load({ params }) {
+ const req = { ...decodePaymentRequest(params.request) };
+ const { target } = req.transport[0];
+ const recipient = await get(`/profile/${target}`);
+ return { req, recipient };
+}
+
+export const actions = {
+ setAmount: async ({ request }) => {
+ return await fd(request);
+ },
+};
diff --git a/src/routes/(app)/send/ecash/[request]/+page.svelte b/src/routes/(app)/send/ecash/[request]/+page.svelte
new file mode 100644
index 00000000..7f2ac7ba
--- /dev/null
+++ b/src/routes/(app)/send/ecash/[request]/+page.svelte
@@ -0,0 +1,107 @@
+<script lang="ts">
+ import { post } from "$lib/utils";
+ import { applyAction, deserialize } from "$app/forms";
+ import { t } from "$lib/translations";
+ import { enhance } from "$app/forms";
+ import Amount from "$comp/Amount.svelte";
+ import Numpad from "$comp/Numpad.svelte";
+ import Spinner from "$comp/Spinner.svelte";
+ import { rate, pin } from "$lib/store";
+ import { wrapEvent } from "nostr-tools/nip17";
+ import { getPrivateKey, sign } from "$lib/nostr";
+ import { getDecodedToken } from "@cashu/cashu-ts";
+ import type { PaymentRequestPayload } from "@cashu/cashu-ts";
+
+ const { data, form } = $props();
+
+ let formElement = $state();
+ let submitting = $state();
+ let submit = async (e) => {
+ e.preventDefault();
+ submitting = true;
+
+ let { token } = await post("/ecash/mint", { amount });
+ const dec = (token) => getDecodedToken(token).token[0];
+ let { proofs } = dec(token);
+ let { id, mints, unit = "" } = req;
+ // let mint = mints[0];
+ let mint = "http://mint:3338";
+ const paymentPayload: PaymentRequestPayload = {
+ id,
+ mint,
+ unit,
+ proofs,
+ };
+
+ const paymentPayloadString = JSON.stringify(paymentPayload);
+ let event = wrapEvent(
+ await getPrivateKey(user),
+ { publicKey: recipient.pubkey },
+ JSON.stringify(paymentPayload),
+ );
+
+ await sign({ event, user });
+ submitting = false;
+ };
+
+ let { rates, recipient, req, user } = $derived({ ...data, ...form });
+ let { currency } = $derived(user);
+ let a = $state();
+ let next = $state();
+ let toggle = () => (show = !show);
+ let amount = $derived(form?.amount || data.req.amount);
+
+ $effect(() => ($rate ||= rates[currency]));
+ $effect(() => form && (submitting = false));
+</script>
+
+<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">
+ {$t(form.message)}
+ </div>
+ {/if}
+
+ <div>
+ <h1 class="text-lg text-secondary">
+ {$t("payments.send")}
+ </h1>
+
+ <Amount {amount} rate={$rate} {currency} />
+ </div>
+
+ <div class="text-xl">
+ <span class="text-secondary">{$t("payments.to")}</span>
+ <span class="break-words font-semibold">{recipient.name}</span>
+ </div>
+
+ <form onsubmit={submit} class="space-y-2" bind:this={formElement}>
+ <input name="amount" value={amount} type="hidden" />
+ <input name="pin" value={$pin} type="hidden" />
+
+ <button
+ type="submit"
+ class="btn btn-primary"
+ disabled={submitting}
+ use:focus
+ >
+ {#if submitting}
+ <Spinner />
+ {:else}
+ {$t("payments.send")}
+ {/if}
+ </button>
+ </form>
+ {:else}
+ <form method="POST" action="?/setAmount" class="space-y-2" use:enhance>
+ <input type="hidden" value={a} name="amount" />
+ <input name="rate" value={$rate} type="hidden" />
+
+ <Numpad bind:amount={a} {currency} bind:rate={$rate} submit={next} />
+ <button type="submit" class="btn" bind:this={next}
+ >{$t("payments.next")}</button
+ >
+ </form>
+ {/if}
+</div>