feat: LNC wallet

This commit is contained in:
2023-02-24 10:25:14 +00:00
parent 1b363ec15f
commit cff605b188
8 changed files with 125 additions and 46 deletions

View File

@ -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",

View File

@ -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) {
<button className="wallet-action" type="button" onClick={() => window.open(`lightning:${invoice}`)}>
<FormattedMessage {...messages.OpenWallet} />
</button>
<button className="wallet-action" type="button" onClick={() => payWithWallet(pr)}>
<FormattedMessage defaultMessage="Pay with Snort" />
</button>
{wallet && (
<button className="wallet-action" type="button" onClick={() => payWithWallet(invoice)}>
<FormattedMessage defaultMessage="Pay with Wallet" />
</button>
)}
</>
)}
</div>
@ -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 (

View File

@ -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/"];

View File

@ -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,

View File

@ -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<WalletError | Login> {
throw new Error("Not implemented");
}
async getInfo(): Promise<WalletInfo | WalletError> {
const nodeInfo = await this.#lnc.lnd.lightning.getInfo();
return {
nodePubKey: nodeInfo.identityPubkey,
alias: nodeInfo.alias,
} as WalletInfo;
}
close(): Promise<boolean | WalletError> {
if (this.#lnc.isConnected) {
this.#lnc.disconnect();
}
return Promise.resolve(true);
}
async login(): Promise<boolean | WalletError> {
await this.#lnc.connect();
while (!this.#lnc.isConnected) {
await new Promise(resolve => {
setTimeout(resolve, 100);
});
}
return true;
}
async getBalance(): Promise<number | WalletError> {
const rsp = await this.#lnc.lnd.lightning.channelBalance();
console.debug(rsp);
return parseInt(rsp.localBalance?.sat ?? "0");
}
createInvoice(req: InvoiceRequest): Promise<WalletInvoice | WalletError> {
throw new Error("Not implemented");
}
payInvoice(pr: string): Promise<WalletInvoice | WalletError> {
throw new Error("Not implemented");
}
async getInvoices(): Promise<WalletInvoice[] | WalletError> {
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;
});
}
}

View File

@ -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<boolean | WalletError> {
throw new Error("Not implemented");
}
async createAccount() {
return Promise.resolve(UnknownWalletError);
}
@ -136,7 +139,7 @@ export default class LNDHubWallet implements LNWallet {
private async getJson<T>(method: "GET" | "POST", path: string, body?: any): Promise<T | WalletError> {
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}`, {

View File

@ -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<Login | WalletError>;
getInfo: () => Promise<WalletInfo | WalletError>;
login: () => Promise<boolean | WalletError>;
close: () => Promise<boolean | WalletError>;
getBalance: () => Promise<Sats | WalletError>;
createInvoice: (req: InvoiceRequest) => Promise<WalletInvoice | WalletError>;
payInvoice: (pr: string) => Promise<WalletInvoice | WalletError>;
getInvoices: () => Promise<WalletInvoice[] | WalletError>;
}
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<LNWallet>();
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;
}