diff options
| author | Adam Soltys <[email protected]> | 2024-11-21 16:31:25 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2024-11-21 16:31:31 -0800 |
| commit | 38660e024c055952180bfa3f9067274cf2e0d6ed (patch) | |
| tree | ded80bc71beab042a26b811cf5e7681af3379338 | |
| parent | d946ec9dbcba7c8e49d2e5b4221cb11a7f726528 (diff) | |
| download | coinos-ui-38660e024c055952180bfa3f9067274cf2e0d6ed.tar.gz coinos-ui-38660e024c055952180bfa3f9067274cf2e0d6ed.zip | |
ecash requests
| -rwxr-xr-x | bun.lockb | bin | 184530 -> 186623 bytes | |||
| -rw-r--r-- | package.json | 1 | ||||
| -rw-r--r-- | src/lib/nostr.ts | 3 | ||||
| -rw-r--r-- | src/lib/parse.ts | 29 | ||||
| -rw-r--r-- | src/routes/(app)/ecash/mint/+server.ts | 7 | ||||
| -rw-r--r-- | src/routes/(app)/send/ecash/[request]/+page.server.ts | 16 | ||||
| -rw-r--r-- | src/routes/(app)/send/ecash/[request]/+page.svelte | 107 |
7 files changed, 147 insertions, 16 deletions
| Binary files differ diff --git a/package.json b/package.json index 20ae4021..f152b00f 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "@capacitor/cli": "^6.0.0", "@capacitor/core": "^6.0.0", "@capacitor/ios": "^6.1.2", + "@cashu/cashu-ts": "^1.2.1", "@iconify/iconify": "^3.1.1", "@scure/base": "^1.1.8", "@scure/bip39": "^1.2.1", 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> |
