aboutsummaryrefslogtreecommitdiffstats
path: root/src/routes
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-07-30 08:51:45 -0700
committerAdam Soltys <[email protected]>2026-07-30 08:51:45 -0700
commitf884ffabc9afab2d6051379062434fc9e7f61176 (patch)
tree5bcfd4ecc7c6cec85e3cd269aecfb4227f406af0 /src/routes
parent2f0e6d052efbca7f695dda942ab7da6221da3aa9 (diff)
downloadcoinos-ui-f884ffabc9afab2d6051379062434fc9e7f61176.tar.gz
coinos-ui-f884ffabc9afab2d6051379062434fc9e7f61176.zip
bolt12 zaps: accept offers in send flow, show/publish user offer
- parse lno= from BIP-321 bitcoin: URIs; send page parses offers for preview now that /parse handles them - settings/nostr shows the user's standing lno1 offer with copy/QR and a button to publish it as a kind 10058 offer list for NIP-177 zaps - new locale strings translated Co-Authored-By: Claude Fable 5 <[email protected]>
Diffstat (limited to 'src/routes')
-rw-r--r--src/routes/(app)/send/lightning/[payreq]/[...memo]/+page.server.ts4
-rw-r--r--src/routes/(app)/settings/nostr/+page.server.ts8
-rw-r--r--src/routes/(app)/settings/nostr/+page.svelte78
3 files changed, 84 insertions, 6 deletions
diff --git a/src/routes/(app)/send/lightning/[payreq]/[...memo]/+page.server.ts b/src/routes/(app)/send/lightning/[payreq]/[...memo]/+page.server.ts
index eb324775..2a30defa 100644
--- a/src/routes/(app)/send/lightning/[payreq]/[...memo]/+page.server.ts
+++ b/src/routes/(app)/send/lightning/[payreq]/[...memo]/+page.server.ts
@@ -6,9 +6,7 @@ export async function load({ cookies, params, parent }) {
const { user } = await parent();
let data = { ...params };
const rates = await getRates();
- if (!params.payreq.startsWith("lno")) {
- data = { ...data, ...(await post("/parse", params, auth(cookies))) };
- }
+ data = { ...data, ...(await post("/parse", params, auth(cookies))) };
data.rate = rates[user.currency];
return data;
diff --git a/src/routes/(app)/settings/nostr/+page.server.ts b/src/routes/(app)/settings/nostr/+page.server.ts
index 1767ebaf..84b3fa27 100644
--- a/src/routes/(app)/settings/nostr/+page.server.ts
+++ b/src/routes/(app)/settings/nostr/+page.server.ts
@@ -4,7 +4,13 @@ import { auth, get } from "$lib/utils";
export const load = async ({ cookies }) => {
const { challenge } = await get("/challenge");
const apps = await get("/apps", auth(cookies));
- return { apps, challenge };
+
+ let offer;
+ try {
+ ({ hash: offer } = await get("/offer", auth(cookies)));
+ } catch (e) {}
+
+ return { apps, challenge, offer };
};
export const actions = {
diff --git a/src/routes/(app)/settings/nostr/+page.svelte b/src/routes/(app)/settings/nostr/+page.svelte
index 7168f4f7..5ba5f636 100644
--- a/src/routes/(app)/settings/nostr/+page.svelte
+++ b/src/routes/(app)/settings/nostr/+page.svelte
@@ -1,7 +1,7 @@
<script>
import { untrack } from "svelte";
import { browser } from "$app/environment";
- import { getNsec } from "$lib/nostr";
+ import { getNsec, send, sign } from "$lib/nostr";
import { page } from "$app/stores";
import { tick } from "svelte";
import { t } from "$lib/translations";
@@ -13,7 +13,7 @@
import { signer, save } from "$lib/store";
let { data } = $props();
- let { apps, challenge, user } = $derived(data);
+ let { apps, challenge, offer, user } = $derived(data);
let npub = $state(untrack(() => user.npub));
let extensionAvailable = $derived(browser && window.nostr);
let { locale } = $derived(user);
@@ -41,6 +41,33 @@
await tick();
$save.click();
};
+
+ let publishing = $state(),
+ published = $state();
+
+ // Kind 10058 is the NIP-177 BOLT12 offer list — publishing it lets nostr
+ // clients (Amethyst v1.13+) zap this user over BOLT12. Replaceable event,
+ // so republishing just updates it.
+ let publishOffer = async () => {
+ try {
+ publishing = true;
+ let event = {
+ kind: 10058,
+ content: "",
+ created_at: Math.round(Date.now() / 1000),
+ tags: [["offer", offer]],
+ };
+
+ event = await sign(event, user);
+ await send(event);
+ published = true;
+ } catch (e) {
+ console.log(e);
+ fail($t("user.settings.offerPublishFailed"));
+ } finally {
+ publishing = false;
+ }
+ };
</script>
<input type="hidden" name="challenge" value={challenge} />
@@ -145,6 +172,53 @@
</div>
</div>
+{#if offer}
+ <div class="space-y-2">
+ <h2 class="text-2xl font-bold mb-2">
+ {$t("user.settings.bolt12Offer")}
+ </h2>
+ <p class="text-secondary mb-4">
+ {$t("user.settings.bolt12OfferDescription")}
+ </p>
+
+ <div class="break-all text-secondary">{offer}</div>
+
+ <div class="flex my-auto gap-1">
+ <button
+ type="button"
+ class="my-auto btn btn-circle !w-auto grow"
+ onclick={() => copy(offer)}
+ aria-label="Copy"
+ ><iconify-icon noobserver icon="ph:copy-bold" width="32"
+ ></iconify-icon> {$t("accounts.copy")}</button
+ >
+
+ <a
+ href={`/qr/${encodeURIComponent(offer)}`}
+ class="my-auto btn btn-circle !w-auto grow"
+ aria-label="QR"
+ >
+ <iconify-icon noobserver icon="ph:qr-code-bold" width="32"
+ ></iconify-icon>
+ {$t("accounts.qr")}
+ </a>
+
+ <button
+ type="button"
+ class="my-auto btn btn-circle !w-auto grow"
+ onclick={publishOffer}
+ disabled={publishing}
+ aria-label="Publish"
+ ><iconify-icon noobserver icon="ph:megaphone-bold" width="32"
+ ></iconify-icon>
+ {published
+ ? $t("user.settings.offerPublished")
+ : $t("user.settings.publishOffer")}</button
+ >
+ </div>
+ </div>
+{/if}
+
{#if revealNwc}
<div class="break-all grow">
{nwc}