import LNC from "@lightninglabs/lnc-web"; import debug from "debug"; import { InvoiceRequest, LNWallet, Login, prToWalletInvoice, WalletError, WalletErrorCode, WalletEvents, WalletInfo, WalletInvoice, WalletInvoiceState, } from "."; import { unwrap } from "@snort/shared"; import EventEmitter from "eventemitter3"; enum Payment_PaymentStatus { UNKNOWN = "UNKNOWN", IN_FLIGHT = "IN_FLIGHT", SUCCEEDED = "SUCCEEDED", FAILED = "FAILED", UNRECOGNIZED = "UNRECOGNIZED", } export class LNCWallet extends EventEmitter implements LNWallet { #lnc: LNC; readonly #log = debug("LNC"); private constructor(pairingPhrase?: string, password?: string) { super(); this.#lnc = new LNC({ pairingPhrase, password, }); } canAutoLogin() { return false; } canGetInvoices() { return true; } canGetBalance() { return true; } canCreateInvoice() { return true; } canPayInvoice() { return true; } isReady(): boolean { return this.#lnc.isReady; } static async Initialize(pairingPhrase: string) { const lnc = new LNCWallet(pairingPhrase); await lnc.login(); return lnc; } static Empty() { return new LNCWallet(); } setPassword(pw: string) { if (this.#lnc.credentials.password && pw !== this.#lnc.credentials.password) { throw new WalletError(WalletErrorCode.GeneralError, "Password is already set, cannot update password"); } this.#lnc.credentials.password = pw; } 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(password?: string): Promise { if (password) { this.setPassword(password); this.#lnc.run(); } 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(); this.#log(rsp); return parseInt(rsp.localBalance?.sat ?? "0"); } async createInvoice(req: InvoiceRequest): Promise { const rsp = await this.#lnc.lnd.lightning.addInvoice({ memo: req.memo, value: req.amount.toString(), expiry: req.expiry?.toString(), }); return unwrap(prToWalletInvoice(rsp.paymentRequest)); } async payInvoice(pr: string): Promise { return new Promise((resolve, reject) => { this.#lnc.lnd.router.sendPaymentV2( { paymentRequest: pr, timeoutSeconds: 60, feeLimitSat: "100", }, msg => { this.#log(msg); if (msg.status === Payment_PaymentStatus.SUCCEEDED) { resolve({ preimage: msg.paymentPreimage, state: WalletInvoiceState.Paid, timestamp: parseInt(msg.creationTimeNs) / 1e9, } as WalletInvoice); } }, err => { this.#log(err); reject(err); }, ); }); } async getInvoices(): Promise { const invoices = await this.#lnc.lnd.lightning.listPayments({ maxPayments: "10", reversed: true, }); this.#log(invoices); return invoices.payments.map(a => { const parsedInvoice = prToWalletInvoice(a.paymentRequest); if (!parsedInvoice) { throw new WalletError(WalletErrorCode.InvalidInvoice, `Could not parse ${a.paymentRequest}`); } return { ...parsedInvoice, state: (() => { switch (a.status) { case Payment_PaymentStatus.SUCCEEDED: return WalletInvoiceState.Paid; case Payment_PaymentStatus.FAILED: return WalletInvoiceState.Failed; default: return WalletInvoiceState.Pending; } })(), preimage: a.paymentPreimage, } as WalletInvoice; }); } }