feat: LNC wallet
This commit is contained in:
parent
1b363ec15f
commit
cff605b188
@ -7,6 +7,7 @@
|
|||||||
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@jukben/emoji-search": "^2.0.1",
|
"@jukben/emoji-search": "^2.0.1",
|
||||||
|
"@lightninglabs/lnc-web": "^0.2.3-alpha",
|
||||||
"@noble/hashes": "^1.2.0",
|
"@noble/hashes": "^1.2.0",
|
||||||
"@noble/secp256k1": "^1.7.0",
|
"@noble/secp256k1": "^1.7.0",
|
||||||
"@protobufjs/base64": "^1.1.2",
|
"@protobufjs/base64": "^1.1.2",
|
||||||
|
@ -19,7 +19,7 @@ import { LNURL, LNURLError, LNURLErrorCode, LNURLInvoice, LNURLSuccessAction } f
|
|||||||
import { debounce } from "Util";
|
import { debounce } from "Util";
|
||||||
|
|
||||||
import messages from "./messages";
|
import messages from "./messages";
|
||||||
import { openWallet } from "Wallet";
|
import { useWallet } from "Wallet";
|
||||||
|
|
||||||
enum ZapType {
|
enum ZapType {
|
||||||
PublicZap = 1,
|
PublicZap = 1,
|
||||||
@ -81,6 +81,7 @@ export default function SendSats(props: SendSatsProps) {
|
|||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const publisher = useEventPublisher();
|
const publisher = useEventPublisher();
|
||||||
const canComment = handler ? (handler.canZap && zapType !== ZapType.NonZap) || handler.maxCommentLength > 0 : false;
|
const canComment = handler ? (handler.canZap && zapType !== ZapType.NonZap) || handler.maxCommentLength > 0 : false;
|
||||||
|
const wallet = useWallet();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (props.show) {
|
if (props.show) {
|
||||||
@ -298,11 +299,9 @@ export default function SendSats(props: SendSatsProps) {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function payWithWallet(pr: string) {
|
async function payWithWallet(pr: string) {
|
||||||
const cfg = window.localStorage.getItem("wallet-lndhub");
|
if (wallet) {
|
||||||
if (cfg) {
|
|
||||||
const wallet = await openWallet(cfg);
|
|
||||||
const rsp = await wallet.payInvoice(pr);
|
const rsp = await wallet.payInvoice(pr);
|
||||||
console.debug(rsp);
|
console.debug(rsp);
|
||||||
setSuccess(rsp as LNURLSuccessAction);
|
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}`)}>
|
<button className="wallet-action" type="button" onClick={() => window.open(`lightning:${invoice}`)}>
|
||||||
<FormattedMessage {...messages.OpenWallet} />
|
<FormattedMessage {...messages.OpenWallet} />
|
||||||
</button>
|
</button>
|
||||||
<button className="wallet-action" type="button" onClick={() => payWithWallet(pr)}>
|
{wallet && (
|
||||||
<FormattedMessage defaultMessage="Pay with Snort" />
|
<button className="wallet-action" type="button" onClick={() => payWithWallet(invoice)}>
|
||||||
</button>
|
<FormattedMessage defaultMessage="Pay with Wallet" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -365,9 +366,9 @@ export default function SendSats(props: SendSatsProps) {
|
|||||||
const defaultTitle = handler?.canZap ? formatMessage(messages.SendZap) : formatMessage(messages.SendSats);
|
const defaultTitle = handler?.canZap ? formatMessage(messages.SendZap) : formatMessage(messages.SendSats);
|
||||||
const title = target
|
const title = target
|
||||||
? formatMessage(messages.ToTarget, {
|
? formatMessage(messages.ToTarget, {
|
||||||
action: defaultTitle,
|
action: defaultTitle,
|
||||||
target,
|
target,
|
||||||
})
|
})
|
||||||
: defaultTitle;
|
: defaultTitle;
|
||||||
if (!(props.show ?? false)) return null;
|
if (!(props.show ?? false)) return null;
|
||||||
return (
|
return (
|
||||||
|
@ -45,9 +45,6 @@ export default function Layout() {
|
|||||||
const [pageClass, setPageClass] = useState("page");
|
const [pageClass, setPageClass] = useState("page");
|
||||||
const pub = useEventPublisher();
|
const pub = useEventPublisher();
|
||||||
useLoginFeed();
|
useLoginFeed();
|
||||||
useEffect(() => {
|
|
||||||
System.nip42Auth = pub.nip42Auth;
|
|
||||||
}, [pub]);
|
|
||||||
|
|
||||||
const shouldHideNoteCreator = useMemo(() => {
|
const shouldHideNoteCreator = useMemo(() => {
|
||||||
const hideOn = ["/settings", "/messages", "/new", "/login", "/donate", "/p/"];
|
const hideOn = ["/settings", "/messages", "/new", "/login", "/donate", "/p/"];
|
||||||
|
@ -6,7 +6,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||||||
import { faCheck, faClock, faXmark } from "@fortawesome/free-solid-svg-icons";
|
import { faCheck, faClock, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
import NoteTime from "Element/NoteTime";
|
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[] = [
|
export const WalletRoutes: RouteObject[] = [
|
||||||
{
|
{
|
||||||
@ -51,9 +51,7 @@ export default function WalletPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function createInvoice() {
|
async function createInvoice() {
|
||||||
const cfg = window.localStorage.getItem("wallet-lndhub");
|
if (wallet) {
|
||||||
if (cfg) {
|
|
||||||
const wallet = await openWallet(cfg);
|
|
||||||
const rsp = await wallet.createInvoice({
|
const rsp = await wallet.createInvoice({
|
||||||
memo: "test",
|
memo: "test",
|
||||||
amount: 100,
|
amount: 100,
|
||||||
|
84
packages/app/src/Wallet/LNCWallet.ts
Normal file
84
packages/app/src/Wallet/LNCWallet.ts
Normal 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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,4 @@
|
|||||||
import { EventPublisher } from "Feed/EventPublisher";
|
import { EventPublisher } from "Feed/EventPublisher";
|
||||||
import EventKind from "Nostr/EventKind";
|
|
||||||
import {
|
import {
|
||||||
InvoiceRequest,
|
InvoiceRequest,
|
||||||
LNWallet,
|
LNWallet,
|
||||||
@ -48,6 +47,10 @@ export default class LNDHubWallet implements LNWallet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
close(): Promise<boolean | WalletError> {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
async createAccount() {
|
async createAccount() {
|
||||||
return Promise.resolve(UnknownWalletError);
|
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> {
|
private async getJson<T>(method: "GET" | "POST", path: string, body?: any): Promise<T | WalletError> {
|
||||||
let auth = `Bearer ${this.auth?.access_token}`;
|
let auth = `Bearer ${this.auth?.access_token}`;
|
||||||
if (this.type === "snort") {
|
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());
|
auth = JSON.stringify(ev?.ToObject());
|
||||||
}
|
}
|
||||||
const rsp = await fetch(`${this.url}${path}`, {
|
const rsp = await fetch(`${this.url}${path}`, {
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
import useEventPublisher, { EventPublisher } from "Feed/EventPublisher";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import LNDHubWallet from "./LNDHub";
|
|
||||||
|
|
||||||
export enum WalletErrorCode {
|
export enum WalletErrorCode {
|
||||||
BadAuth = 1,
|
BadAuth = 1,
|
||||||
NotEnoughBalance = 2,
|
NotEnoughBalance = 2,
|
||||||
@ -70,32 +66,13 @@ export interface LNWallet {
|
|||||||
createAccount: () => Promise<Login | WalletError>;
|
createAccount: () => Promise<Login | WalletError>;
|
||||||
getInfo: () => Promise<WalletInfo | WalletError>;
|
getInfo: () => Promise<WalletInfo | WalletError>;
|
||||||
login: () => Promise<boolean | WalletError>;
|
login: () => Promise<boolean | WalletError>;
|
||||||
|
close: () => Promise<boolean | WalletError>;
|
||||||
getBalance: () => Promise<Sats | WalletError>;
|
getBalance: () => Promise<Sats | WalletError>;
|
||||||
createInvoice: (req: InvoiceRequest) => Promise<WalletInvoice | WalletError>;
|
createInvoice: (req: InvoiceRequest) => Promise<WalletInvoice | WalletError>;
|
||||||
payInvoice: (pr: string) => Promise<WalletInvoice | WalletError>;
|
payInvoice: (pr: string) => Promise<WalletInvoice | WalletError>;
|
||||||
getInvoices: () => Promise<WalletInvoice[] | WalletError>;
|
getInvoices: () => Promise<WalletInvoice[] | WalletError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function openWallet(config: string, publisher?: EventPublisher) {
|
export function useWallet(): LNWallet | undefined {
|
||||||
const wallet = new LNDHubWallet(config, publisher);
|
return undefined;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
18
yarn.lock
18
yarn.lock
@ -1681,6 +1681,19 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b"
|
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b"
|
||||||
integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==
|
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":
|
"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1":
|
||||||
version "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"
|
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"
|
shebang-command "^2.0.0"
|
||||||
which "^2.0.1"
|
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:
|
crypto-random-string@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
|
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user