diff --git a/packages/app/package.json b/packages/app/package.json index 4f9cc05f..35deb4c6 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -7,6 +7,7 @@ "@fortawesome/free-solid-svg-icons": "^6.2.1", "@fortawesome/react-fontawesome": "^0.2.0", "@jukben/emoji-search": "^2.0.1", + "@lightninglabs/lnc-web": "^0.2.3-alpha", "@noble/hashes": "^1.2.0", "@noble/secp256k1": "^1.7.0", "@protobufjs/base64": "^1.1.2", diff --git a/packages/app/src/Element/SendSats.tsx b/packages/app/src/Element/SendSats.tsx index e3f3492d..ccb5961c 100644 --- a/packages/app/src/Element/SendSats.tsx +++ b/packages/app/src/Element/SendSats.tsx @@ -19,7 +19,7 @@ import { LNURL, LNURLError, LNURLErrorCode, LNURLInvoice, LNURLSuccessAction } f import { debounce } from "Util"; import messages from "./messages"; -import { openWallet } from "Wallet"; +import { useWallet } from "Wallet"; enum ZapType { PublicZap = 1, @@ -81,6 +81,7 @@ export default function SendSats(props: SendSatsProps) { const { formatMessage } = useIntl(); const publisher = useEventPublisher(); const canComment = handler ? (handler.canZap && zapType !== ZapType.NonZap) || handler.maxCommentLength > 0 : false; + const wallet = useWallet(); useEffect(() => { if (props.show) { @@ -298,11 +299,9 @@ 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); + if (wallet) { const rsp = await wallet.payInvoice(pr); console.debug(rsp); setSuccess(rsp as LNURLSuccessAction); @@ -332,9 +331,11 @@ export default function SendSats(props: SendSatsProps) { - + {wallet && ( + + )} )} @@ -365,9 +366,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/Pages/Layout.tsx b/packages/app/src/Pages/Layout.tsx index dfa95165..c1758daf 100644 --- a/packages/app/src/Pages/Layout.tsx +++ b/packages/app/src/Pages/Layout.tsx @@ -45,9 +45,6 @@ export default function Layout() { const [pageClass, setPageClass] = useState("page"); const pub = useEventPublisher(); useLoginFeed(); - useEffect(() => { - System.nip42Auth = pub.nip42Auth; - }, [pub]); const shouldHideNoteCreator = useMemo(() => { const hideOn = ["/settings", "/messages", "/new", "/login", "/donate", "/p/"]; diff --git a/packages/app/src/Pages/WalletPage.tsx b/packages/app/src/Pages/WalletPage.tsx index 1d66dda6..e336aafe 100644 --- a/packages/app/src/Pages/WalletPage.tsx +++ b/packages/app/src/Pages/WalletPage.tsx @@ -6,7 +6,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faCheck, faClock, faXmark } from "@fortawesome/free-solid-svg-icons"; import NoteTime from "Element/NoteTime"; -import { openWallet, WalletInvoice, Sats, WalletInfo, WalletInvoiceState, useWallet, LNWallet } from "Wallet"; +import { WalletInvoice, Sats, WalletInfo, WalletInvoiceState, useWallet, LNWallet } from "Wallet"; export const WalletRoutes: RouteObject[] = [ { @@ -51,9 +51,7 @@ export default function WalletPage() { } async function createInvoice() { - const cfg = window.localStorage.getItem("wallet-lndhub"); - if (cfg) { - const wallet = await openWallet(cfg); + if (wallet) { const rsp = await wallet.createInvoice({ memo: "test", amount: 100, diff --git a/packages/app/src/Wallet/LNCWallet.ts b/packages/app/src/Wallet/LNCWallet.ts new file mode 100644 index 00000000..23eded20 --- /dev/null +++ b/packages/app/src/Wallet/LNCWallet.ts @@ -0,0 +1,84 @@ +import { InvoiceRequest, LNWallet, Login, WalletError, WalletInfo, WalletInvoice, WalletInvoiceState } from "Wallet"; + +import LNC from "@lightninglabs/lnc-web"; +export class LNCWallet implements LNWallet { + #lnc: LNC; + + private constructor(pairingPhrase?: string, password?: string) { + this.#lnc = new LNC({ + pairingPhrase, + password, + }); + } + + static async Initialize(pairingPhrase: string, password: string) { + const lnc = new LNCWallet(pairingPhrase, password); + await lnc.login(); + return lnc; + } + + static async Connect(password: string) { + const lnc = new LNCWallet(undefined, password); + await lnc.login(); + return lnc; + } + + createAccount(): Promise { + throw new Error("Not implemented"); + } + + async getInfo(): Promise { + const nodeInfo = await this.#lnc.lnd.lightning.getInfo(); + return { + nodePubKey: nodeInfo.identityPubkey, + alias: nodeInfo.alias, + } as WalletInfo; + } + + close(): Promise { + if (this.#lnc.isConnected) { + this.#lnc.disconnect(); + } + return Promise.resolve(true); + } + + async login(): Promise { + await this.#lnc.connect(); + while (!this.#lnc.isConnected) { + await new Promise(resolve => { + setTimeout(resolve, 100); + }); + } + return true; + } + + async getBalance(): Promise { + const rsp = await this.#lnc.lnd.lightning.channelBalance(); + console.debug(rsp); + return parseInt(rsp.localBalance?.sat ?? "0"); + } + + createInvoice(req: InvoiceRequest): Promise { + throw new Error("Not implemented"); + } + + payInvoice(pr: string): Promise { + throw new Error("Not implemented"); + } + + async getInvoices(): Promise { + const invoices = await this.#lnc.lnd.lightning.listPayments({ + includeIncomplete: true, + maxPayments: "10", + reversed: true, + }); + + return invoices.payments.map(a => { + return { + amount: parseInt(a.valueSat), + state: a.status === "SUCCEEDED" ? WalletInvoiceState.Paid : WalletInvoiceState.Pending, + timestamp: parseInt(a.creationTimeNs) / 1e9, + } as WalletInvoice; + }); + } +} diff --git a/packages/app/src/Wallet/LNDHub.ts b/packages/app/src/Wallet/LNDHub.ts index 76465b40..52d2993d 100644 --- a/packages/app/src/Wallet/LNDHub.ts +++ b/packages/app/src/Wallet/LNDHub.ts @@ -1,5 +1,4 @@ import { EventPublisher } from "Feed/EventPublisher"; -import EventKind from "Nostr/EventKind"; import { InvoiceRequest, LNWallet, @@ -48,6 +47,10 @@ export default class LNDHubWallet implements LNWallet { } } + close(): Promise { + throw new Error("Not implemented"); + } + async createAccount() { return Promise.resolve(UnknownWalletError); } @@ -136,7 +139,7 @@ export default class LNDHubWallet implements LNWallet { 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); + const ev = await this.publisher?.generic(`${new URL(this.url).pathname}${path}`, 30_000); auth = JSON.stringify(ev?.ToObject()); } const rsp = await fetch(`${this.url}${path}`, { diff --git a/packages/app/src/Wallet/index.ts b/packages/app/src/Wallet/index.ts index 84f87a91..cc94f8b3 100644 --- a/packages/app/src/Wallet/index.ts +++ b/packages/app/src/Wallet/index.ts @@ -1,7 +1,3 @@ -import useEventPublisher, { EventPublisher } from "Feed/EventPublisher"; -import { useEffect, useState } from "react"; -import LNDHubWallet from "./LNDHub"; - export enum WalletErrorCode { BadAuth = 1, NotEnoughBalance = 2, @@ -70,32 +66,13 @@ export interface LNWallet { createAccount: () => Promise; getInfo: () => Promise; login: () => Promise; + close: () => Promise; getBalance: () => Promise; createInvoice: (req: InvoiceRequest) => Promise; payInvoice: (pr: string) => Promise; getInvoices: () => Promise; } -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; +export function useWallet(): LNWallet | undefined { + return undefined; } diff --git a/yarn.lock b/yarn.lock index 044d8049..3ff2f76e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1681,6 +1681,19 @@ resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== +"@lightninglabs/lnc-core@0.2.3-alpha": + version "0.2.3-alpha" + resolved "https://registry.yarnpkg.com/@lightninglabs/lnc-core/-/lnc-core-0.2.3-alpha.tgz#e1b92a9071d1dfb92e2d565710b56f28f57cbbd4" + integrity sha512-93D/uU64ayAaJv5kv4Pqwvkt+uT7yCtmHD08aUzvql+lbWm6U7m5loZLxz7tACFLXVPOQ8OHJL25W+3QMEYthg== + +"@lightninglabs/lnc-web@^0.2.3-alpha": + version "0.2.3-alpha" + resolved "https://registry.yarnpkg.com/@lightninglabs/lnc-web/-/lnc-web-0.2.3-alpha.tgz#d41184d815034c15fc9966da390222babc4eb19c" + integrity sha512-Pr02Ti9a0YzEIP2FTJT+IuoE02xgXqhMKoo8lK+Y6kSf3xk8/wJXJssFMA96iWV5Phf1Ra9ynmLVIQjD176BxA== + dependencies: + "@lightninglabs/lnc-core" "0.2.3-alpha" + crypto-js "4.1.1" + "@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": version "5.1.1-v1" resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129" @@ -3716,6 +3729,11 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" +crypto-js@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf" + integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw== + crypto-random-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"