Add zaps: NWC wallet connect + NIP-57 zap flow

- NWC client (nwc.ts): parse URI, encrypt/send kind 23194, await kind 23195 response
- Lightning store: persist NWC URI to localStorage, zap() via NDKZapper + lnPay callback
- ZapModal: amount presets (21/100/500/1000/5000 sats), custom amount, optional comment,
  paying/success/error states, prompts to Settings if no wallet connected
-  zap button on NoteCard (action row) and ProfileView (header, next to follow)
- Settings > Lightning Wallet section: paste NWC URI, connect/disconnect

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jure
2026-03-09 17:45:39 +01:00
parent b324db6bac
commit 8fcabac450
6 changed files with 458 additions and 11 deletions

82
src/stores/lightning.ts Normal file
View File

@@ -0,0 +1,82 @@
import { create } from "zustand";
import { NDKEvent, NDKUser, NDKZapper, LnPayCb } from "@nostr-dev-kit/ndk";
import { getNDK } from "../lib/nostr";
import { payInvoiceViaNWC, isValidNwcUri } from "../lib/lightning/nwc";
const NWC_STORAGE_KEY = "wrystr_nwc_uri";
interface ZapTarget {
type: "note";
event: NDKEvent;
recipientPubkey: string;
recipientLud16?: string;
}
interface ZapProfileTarget {
type: "profile";
pubkey: string;
lud16?: string;
}
export type ZapTargetSpec = ZapTarget | ZapProfileTarget;
interface LightningState {
nwcUri: string | null;
setNwcUri: (uri: string) => void;
clearNwcUri: () => void;
zap: (target: ZapTargetSpec, amountSats: number, comment?: string) => Promise<void>;
}
export const useLightningStore = create<LightningState>(() => ({
nwcUri: localStorage.getItem(NWC_STORAGE_KEY),
setNwcUri: (uri: string) => {
if (!isValidNwcUri(uri)) throw new Error("Invalid NWC URI");
localStorage.setItem(NWC_STORAGE_KEY, uri);
useLightningStore.setState({ nwcUri: uri });
},
clearNwcUri: () => {
localStorage.removeItem(NWC_STORAGE_KEY);
useLightningStore.setState({ nwcUri: null });
},
zap: async (targetSpec: ZapTargetSpec, amountSats: number, comment?: string) => {
const { nwcUri } = useLightningStore.getState();
if (!nwcUri) throw new Error("No wallet connected. Add an NWC connection in Settings.");
const ndk = getNDK();
if (!ndk.signer) throw new Error("Not logged in");
let target: NDKEvent | NDKUser;
if (targetSpec.type === "note") {
target = targetSpec.event;
} else {
target = ndk.getUser({ pubkey: targetSpec.pubkey });
}
const lnPay: LnPayCb = async ({ pr }) => {
const preimage = await payInvoiceViaNWC(nwcUri, pr);
return { preimage };
};
const zapper = new NDKZapper(target, amountSats * 1000, "msat", {
comment,
lnPay,
ndk,
});
return new Promise((resolve, reject) => {
zapper.on("complete", (results) => {
const errors = Array.from(results.values()).filter((r) => r instanceof Error);
if (errors.length > 0) {
reject(errors[0]);
} else {
resolve();
}
});
zapper.zap().catch(reject);
});
},
}));