From 1b363ec15f4d7c2e7bf93123550199ef96043523 Mon Sep 17 00:00:00 2001 From: Kieran Date: Mon, 13 Feb 2023 15:29:25 +0000 Subject: [PATCH] progress --- packages/app/src/Element/SendSats.tsx | 20 ++++- packages/app/src/Feed/EventPublisher.ts | 15 +++- packages/app/src/Icons/Bitcoin.tsx | 8 +- packages/app/src/Pages/WalletPage.tsx | 62 ++++++------- packages/app/src/Pages/messages.js | 8 -- packages/app/src/Wallet/LNDHub.ts | 113 +++++++++++++++++------- packages/app/src/Wallet/index.ts | 24 ++++- packages/app/src/index.tsx | 5 +- packages/nostr/src/legacy/EventKind.ts | 1 + 9 files changed, 171 insertions(+), 85 deletions(-) delete mode 100644 packages/app/src/Pages/messages.js diff --git a/packages/app/src/Element/SendSats.tsx b/packages/app/src/Element/SendSats.tsx index f4c76ba..e3f3492 100644 --- a/packages/app/src/Element/SendSats.tsx +++ b/packages/app/src/Element/SendSats.tsx @@ -19,6 +19,7 @@ import { LNURL, LNURLError, LNURLErrorCode, LNURLInvoice, LNURLSuccessAction } f import { debounce } from "Util"; import messages from "./messages"; +import { openWallet } from "Wallet"; enum ZapType { PublicZap = 1, @@ -297,6 +298,16 @@ export default function SendSats(props: SendSatsProps) { ); } + + async function payWithWallet(pr: string) { + const cfg = window.localStorage.getItem("wallet-lndhub"); + if (cfg) { + const wallet = await openWallet(cfg); + const rsp = await wallet.payInvoice(pr); + console.debug(rsp); + setSuccess(rsp as LNURLSuccessAction); + } + } function payInvoice() { if (success || !invoice) return null; @@ -321,6 +332,9 @@ export default function SendSats(props: SendSatsProps) { + )} @@ -351,9 +365,9 @@ export default function SendSats(props: SendSatsProps) { const defaultTitle = handler?.canZap ? formatMessage(messages.SendZap) : formatMessage(messages.SendSats); const title = target ? formatMessage(messages.ToTarget, { - action: defaultTitle, - target, - }) + action: defaultTitle, + target, + }) : defaultTitle; if (!(props.show ?? false)) return null; return ( diff --git a/packages/app/src/Feed/EventPublisher.ts b/packages/app/src/Feed/EventPublisher.ts index 7234e33..ce8c94f 100644 --- a/packages/app/src/Feed/EventPublisher.ts +++ b/packages/app/src/Feed/EventPublisher.ts @@ -8,6 +8,7 @@ import { HexKey, RawEvent, u256, UserMetadata, Lists } from "@snort/nostr"; import { bech32ToHex, delay, unwrap } from "Util"; import { DefaultRelays, HashtagRegex } from "Const"; import { System } from "System"; +import { useMemo } from "react"; declare global { interface Window { @@ -23,6 +24,8 @@ declare global { } } +export type EventPublisher = ReturnType; + export default function useEventPublisher() { const pubKey = useSelector(s => s.login.publicKey); const privKey = useSelector(s => s.login.privateKey); @@ -78,7 +81,7 @@ export default function useEventPublisher() { ev.Content = content; } - return { + const ret = { nip42Auth: async (challenge: string, relay: string) => { if (pubKey) { const ev = NEvent.ForPubKey(pubKey); @@ -393,7 +396,17 @@ export default function useEventPublisher() { publicKey: pubKey, }; }, + generic: async (content: string, kind: EventKind) => { + if (pubKey) { + const ev = NEvent.ForPubKey(pubKey); + ev.Kind = kind; + ev.Content = content; + return await signEvent(ev); + } + }, }; + + return useMemo(() => ret, [pubKey, relays, follows]); } let isNip07Busy = false; diff --git a/packages/app/src/Icons/Bitcoin.tsx b/packages/app/src/Icons/Bitcoin.tsx index 807496f..f05ccf3 100644 --- a/packages/app/src/Icons/Bitcoin.tsx +++ b/packages/app/src/Icons/Bitcoin.tsx @@ -1,12 +1,6 @@ const Bitcoin = () => { return ( - + (); const [balance, setBalance] = useState(); const [history, setHistory] = useState(); + const wallet = useWallet(); - async function loadWallet() { - let cfg = window.localStorage.getItem("wallet-lndhub"); - if (cfg) { - let wallet = await openWallet(cfg); - let i = await wallet.getInfo(); - if ("error" in i) { - return; - } - setInfo(i as WalletInfo); - let b = await wallet.getBalance(); - setBalance(b as Sats); - let h = await wallet.getInvoices(); - setHistory( - (h as WalletInvoice[]).sort((a, b) => b.timestamp - a.timestamp) - ); + async function loadWallet(wallet: LNWallet) { + const i = await wallet.getInfo(); + if ("error" in i) { + return; } + setInfo(i as WalletInfo); + const b = await wallet.getBalance(); + setBalance(b as Sats); + const h = await wallet.getInvoices(); + setHistory((h as WalletInvoice[]).sort((a, b) => b.timestamp - a.timestamp)); } useEffect(() => { - loadWallet().catch(console.warn); - }, []); + if (wallet) { + loadWallet(wallet).catch(console.warn); + } + }, [wallet]); function stateIcon(s: WalletInvoiceState) { switch (s) { @@ -58,26 +49,35 @@ export default function WalletPage() { return ; } } + + async function createInvoice() { + const cfg = window.localStorage.getItem("wallet-lndhub"); + if (cfg) { + const wallet = await openWallet(cfg); + const rsp = await wallet.createInvoice({ + memo: "test", + amount: 100, + }); + console.debug(rsp); + } + } + return ( <>

{info?.alias}

Balance: {(balance ?? 0).toLocaleString()} sats
- +

History

- {history?.map((a) => ( + {history?.map(a => (
{(a.memo ?? "").length === 0 ? <>  : a.memo}
-
+
{stateIcon(a.state)} {a.amount.toLocaleString()} sats
diff --git a/packages/app/src/Pages/messages.js b/packages/app/src/Pages/messages.js deleted file mode 100644 index 87a38ad..0000000 --- a/packages/app/src/Pages/messages.js +++ /dev/null @@ -1,8 +0,0 @@ -import { defineMessages } from "react-intl"; -import { addIdAndDefaultMessageToMessages } from "Util"; - -const messages = defineMessages({ - Login: "Login", -}); - -export default addIdAndDefaultMessageToMessages(messages, "Pages"); diff --git a/packages/app/src/Wallet/LNDHub.ts b/packages/app/src/Wallet/LNDHub.ts index 0a35dd5..76465b4 100644 --- a/packages/app/src/Wallet/LNDHub.ts +++ b/packages/app/src/Wallet/LNDHub.ts @@ -1,3 +1,5 @@ +import { EventPublisher } from "Feed/EventPublisher"; +import EventKind from "Nostr/EventKind"; import { InvoiceRequest, LNWallet, @@ -15,20 +17,35 @@ const defaultHeaders = { }; export default class LNDHubWallet implements LNWallet { + type: "lndhub" | "snort"; url: string; user: string; password: string; auth?: AuthResponse; + publisher?: EventPublisher; - constructor(url: string) { - const regex = /^lndhub:\/\/([\S-]+):([\S-]+)@(.*)$/i; - const parsedUrl = url.match(regex); - if (!parsedUrl || parsedUrl.length !== 4) { - throw new Error("Invalid LNDHUB config"); + constructor(url: string, publisher?: EventPublisher) { + if (url.startsWith("lndhub://")) { + const regex = /^lndhub:\/\/([\S-]+):([\S-]+)@(.*)$/i; + const parsedUrl = url.match(regex); + console.debug(parsedUrl); + if (!parsedUrl || parsedUrl.length !== 4) { + throw new Error("Invalid LNDHUB config"); + } + this.url = new URL(parsedUrl[3]).toString(); + this.user = parsedUrl[1]; + this.password = parsedUrl[2]; + this.type = "lndhub"; + } else if (url.startsWith("snort://")) { + const u = new URL(url); + this.url = `https://${u.host}${u.pathname}`; + this.user = ""; + this.password = ""; + this.type = "snort"; + this.publisher = publisher; + } else { + throw new Error("Invalid config"); } - this.url = new URL(parsedUrl[3]).toString(); - this.user = parsedUrl[1]; - this.password = parsedUrl[2]; } async createAccount() { @@ -40,6 +57,8 @@ export default class LNDHubWallet implements LNWallet { } async login() { + if (this.type === "snort") return true; + const rsp = await this.getJson("POST", "/auth?type=auth", { login: this.user, password: this.password, @@ -53,31 +72,56 @@ export default class LNDHubWallet implements LNWallet { } async getBalance(): Promise { - let rsp = await this.getJson("GET", "/balance"); + const rsp = await this.getJson("GET", "/balance"); if ("error" in rsp) { return rsp as WalletError; } - let bal = Math.floor((rsp as GetBalanceResponse).BTC.AvailableBalance); + const bal = Math.floor((rsp as GetBalanceResponse).BTC.AvailableBalance); return bal as Sats; } async createInvoice(req: InvoiceRequest) { - return Promise.resolve(UnknownWalletError); - } - - async payInvoice(pr: string) { - return Promise.resolve(UnknownWalletError); - } - - async getInvoices(): Promise { - let rsp = await this.getJson( - "GET", - "/getuserinvoices" - ); + const rsp = await this.getJson("POST", "/addinvoice", { + amt: req.amount, + memo: req.memo, + }); if ("error" in rsp) { return rsp as WalletError; } - return (rsp as GetUserInvoicesResponse[]).map((a) => { + + const pRsp = rsp as UserInvoicesResponse; + return { + pr: pRsp.payment_request, + memo: req.memo, + amount: req.amount, + paymentHash: pRsp.payment_hash, + timestamp: pRsp.timestamp, + } as WalletInvoice; + } + + async payInvoice(pr: string) { + const rsp = await this.getJson("POST", "/payinvoice", { + invoice: pr, + }); + + if ("error" in rsp) { + return rsp as WalletError; + } + + const pRsp = rsp as PayInvoiceResponse; + return { + pr: pr, + paymentHash: pRsp.payment_hash, + state: pRsp.payment_error === undefined ? WalletInvoiceState.Paid : WalletInvoiceState.Pending, + } as WalletInvoice; + } + + async getInvoices(): Promise { + const rsp = await this.getJson("GET", "/getuserinvoices"); + if ("error" in rsp) { + return rsp as WalletError; + } + return (rsp as UserInvoicesResponse[]).map(a => { return { memo: a.description, amount: Math.floor(a.amt), @@ -89,17 +133,18 @@ export default class LNDHubWallet implements LNWallet { }); } - private async getJson( - method: "GET" | "POST", - path: string, - body?: any - ): Promise { + private async getJson(method: "GET" | "POST", path: string, body?: any): Promise { + let auth = `Bearer ${this.auth?.access_token}`; + if (this.type === "snort") { + const ev = await this.publisher?.generic(`${new URL(this.url).pathname}${path}`, EventKind.Ephemeral); + auth = JSON.stringify(ev?.ToObject()); + } const rsp = await fetch(`${this.url}${path}`, { method: method, body: body ? JSON.stringify(body) : undefined, headers: { ...defaultHeaders, - Authorization: `Bearer ${this.auth?.access_token}`, + Authorization: auth, }, }); const json = await rsp.json(); @@ -122,7 +167,7 @@ interface GetBalanceResponse { }; } -interface GetUserInvoicesResponse { +interface UserInvoicesResponse { amt: number; description: string; ispaid: boolean; @@ -131,4 +176,12 @@ interface GetUserInvoicesResponse { pay_req: string; payment_hash: string; payment_request: string; + r_hash: string; +} + +interface PayInvoiceResponse { + payment_error?: string; + payment_hash: string; + payment_preimage: string; + payment_route?: { total_amt: number; total_fees: number }; } diff --git a/packages/app/src/Wallet/index.ts b/packages/app/src/Wallet/index.ts index 101d309..84f87a9 100644 --- a/packages/app/src/Wallet/index.ts +++ b/packages/app/src/Wallet/index.ts @@ -1,3 +1,5 @@ +import useEventPublisher, { EventPublisher } from "Feed/EventPublisher"; +import { useEffect, useState } from "react"; import LNDHubWallet from "./LNDHub"; export enum WalletErrorCode { @@ -74,8 +76,26 @@ export interface LNWallet { getInvoices: () => Promise; } -export async function openWallet(config: string) { - let wallet = new LNDHubWallet(config); +export async function openWallet(config: string, publisher?: EventPublisher) { + const wallet = new LNDHubWallet(config, publisher); await wallet.login(); return wallet; } + +export function useWallet() { + const [wallet, setWallet] = useState(); + const publisher = useEventPublisher(); + + useEffect(() => { + if (publisher) { + const cfg = window.localStorage.getItem("wallet-lndhub"); + if (cfg) { + openWallet(cfg, publisher) + .then(a => setWallet(a)) + .catch(console.error); + } + } + }, [publisher]); + + return wallet; +} diff --git a/packages/app/src/index.tsx b/packages/app/src/index.tsx index f15f79c..f5bb266 100644 --- a/packages/app/src/index.tsx +++ b/packages/app/src/index.tsx @@ -26,10 +26,9 @@ import HashTagsPage from "Pages/HashTagsPage"; import SearchPage from "Pages/SearchPage"; import HelpPage from "Pages/HelpPage"; import { NewUserRoutes } from "Pages/new"; -import NostrLinkHandler from "Pages/NostrLinkHandler"; -import { IntlProvider } from "./IntlProvider"; -import { unwrap } from "Util"; import { WalletRoutes } from "Pages/WalletPage"; +import NostrLinkHandler from "Pages/NostrLinkHandler"; +import { unwrap } from "Util"; /** * HTTP query provider diff --git a/packages/nostr/src/legacy/EventKind.ts b/packages/nostr/src/legacy/EventKind.ts index 2ab6297..5b4bb0d 100644 --- a/packages/nostr/src/legacy/EventKind.ts +++ b/packages/nostr/src/legacy/EventKind.ts @@ -9,6 +9,7 @@ enum EventKind { Repost = 6, // NIP-18 Reaction = 7, // NIP-25 Relays = 10002, // NIP-65 + Ephemeral = 20_000, Auth = 22242, // NIP-42 PubkeyLists = 30000, // NIP-51a NoteLists = 30001, // NIP-51b