refactor: extract wallet system
This commit is contained in:
1
packages/wallet/README.md
Normal file
1
packages/wallet/README.md
Normal file
@ -0,0 +1 @@
|
||||
# wallet
|
35
packages/wallet/package.json
Normal file
35
packages/wallet/package.json
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "@snort/wallet",
|
||||
"version": "0.1.0",
|
||||
"description": "Snort wallet system package",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"module": "src/index.ts",
|
||||
"repository": "https://git.v0l.io/Kieran/snort",
|
||||
"author": "v0l",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "rm -rf dist && tsc",
|
||||
"test": "jest --runInBand"
|
||||
},
|
||||
"files": [
|
||||
"src",
|
||||
"dist"
|
||||
],
|
||||
"packageManager": "yarn@4.1.1",
|
||||
"dependencies": {
|
||||
"@cashu/cashu-ts": "^1.0.0-rc.3",
|
||||
"@lightninglabs/lnc-web": "^0.3.1-alpha",
|
||||
"@scure/base": "^1.1.6",
|
||||
"@snort/shared": "workspace:^",
|
||||
"@snort/system": "workspace:^",
|
||||
"debug": "^4.3.4",
|
||||
"eventemitter3": "^5.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/debug": "^4.1.12",
|
||||
"@webbtc/webln-types": "^3.0.0",
|
||||
"typescript": "^5.4.5"
|
||||
}
|
||||
}
|
202
packages/wallet/src/AlbyWallet.ts
Normal file
202
packages/wallet/src/AlbyWallet.ts
Normal file
@ -0,0 +1,202 @@
|
||||
import { base64 } from "@scure/base";
|
||||
import { unixNow, unwrap } from "@snort/shared";
|
||||
|
||||
import {
|
||||
InvoiceRequest,
|
||||
LNWallet,
|
||||
prToWalletInvoice,
|
||||
WalletError,
|
||||
WalletErrorCode,
|
||||
WalletEvents,
|
||||
WalletInfo,
|
||||
WalletInvoice,
|
||||
WalletInvoiceState,
|
||||
} from ".";
|
||||
import EventEmitter from "eventemitter3";
|
||||
|
||||
export interface OAuthToken {
|
||||
access_token: string;
|
||||
created_at: number;
|
||||
expires_in: number;
|
||||
refresh_token: string;
|
||||
scope: string;
|
||||
token_type: string;
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
}
|
||||
|
||||
export default class AlbyWallet extends EventEmitter<WalletEvents> implements LNWallet {
|
||||
#token: OAuthToken;
|
||||
constructor(token: OAuthToken) {
|
||||
super();
|
||||
this.#token = token;
|
||||
}
|
||||
|
||||
isReady() {
|
||||
return true;
|
||||
}
|
||||
|
||||
canAutoLogin() {
|
||||
return true;
|
||||
}
|
||||
|
||||
canGetInvoices() {
|
||||
return this.#token.scope.includes("invoices:read");
|
||||
}
|
||||
|
||||
canGetBalance() {
|
||||
return this.#token.scope.includes("balance:read");
|
||||
}
|
||||
|
||||
canCreateInvoice() {
|
||||
return true;
|
||||
}
|
||||
|
||||
canPayInvoice() {
|
||||
return true;
|
||||
}
|
||||
|
||||
async getInfo() {
|
||||
const me = await this.#fetch<GetUserResponse>("/user/me");
|
||||
return { alias: me.lightning_address } as WalletInfo;
|
||||
}
|
||||
|
||||
async login() {
|
||||
return true;
|
||||
}
|
||||
|
||||
close() {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
async getBalance() {
|
||||
await this.#refreshToken();
|
||||
const bal = await this.#fetch<GetBalanceResponse>("/balance");
|
||||
return bal.balance;
|
||||
}
|
||||
|
||||
async createInvoice(req: InvoiceRequest) {
|
||||
const inv = await this.#fetch<CreateInvoiceResponse>("/invoices", "POST", {
|
||||
amount: req.amount,
|
||||
memo: req.memo,
|
||||
});
|
||||
|
||||
return unwrap(prToWalletInvoice(inv.payment_request));
|
||||
}
|
||||
|
||||
async payInvoice(pr: string) {
|
||||
const pay = await this.#fetch<PayInvoiceResponse>("/payments/bolt11", "POST", {
|
||||
invoice: pr,
|
||||
});
|
||||
|
||||
return {
|
||||
...prToWalletInvoice(pay.payment_request),
|
||||
fees: pay.fee,
|
||||
preimage: pay.payment_preimage,
|
||||
state: WalletInvoiceState.Paid,
|
||||
direction: "out",
|
||||
} as WalletInvoice;
|
||||
}
|
||||
|
||||
async getInvoices() {
|
||||
const invoices = await this.#fetch<Array<GetInvoiceResponse>>("/invoices?page=1&items=20");
|
||||
return invoices.map(a => {
|
||||
return {
|
||||
...prToWalletInvoice(a.payment_request),
|
||||
memo: a.comment,
|
||||
preimage: a.preimage,
|
||||
state: a.settled ? WalletInvoiceState.Paid : WalletInvoiceState.Pending,
|
||||
direction: a.type === "incoming" ? "in" : "out",
|
||||
} as WalletInvoice;
|
||||
});
|
||||
}
|
||||
|
||||
async #fetch<T>(path: string, method: "GET" | "POST" = "GET", body?: object) {
|
||||
const req = await fetch(`https://api.getalby.com${path}`, {
|
||||
method: method,
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
authorization: `Bearer ${this.#token.access_token}`,
|
||||
...(body ? { "content-type": "application/json" } : {}),
|
||||
},
|
||||
});
|
||||
const json = await req.text();
|
||||
if (req.ok) {
|
||||
return JSON.parse(json) as T;
|
||||
} else {
|
||||
if (json.length > 0) {
|
||||
throw new WalletError(WalletErrorCode.GeneralError, JSON.parse(json).message as string);
|
||||
} else {
|
||||
throw new WalletError(WalletErrorCode.GeneralError, `Error: ${json} (${req.status})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
async #refreshToken() {
|
||||
if (this.#token.created_at + this.#token.expires_in < unixNow()) {
|
||||
const params = new URLSearchParams();
|
||||
params.set("refresh_token", this.#token.refresh_token);
|
||||
params.set("grant_type", "refresh_token");
|
||||
|
||||
const req = await fetch("https://api.getalby.com/oauth/token", {
|
||||
method: "POST",
|
||||
body: params,
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
authorization: `Basic ${base64.encode(
|
||||
new TextEncoder().encode(`${this.#token.clientId}:${this.#token.clientSecret}`),
|
||||
)}`,
|
||||
},
|
||||
});
|
||||
const json = await req.json();
|
||||
if (req.ok) {
|
||||
this.#token = {
|
||||
...(json as OAuthToken),
|
||||
created_at: unixNow(),
|
||||
};
|
||||
this.emit("change", JSON.stringify(this.#token));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface GetBalanceResponse {
|
||||
balance: number;
|
||||
currency: string;
|
||||
unit: string;
|
||||
}
|
||||
|
||||
interface CreateInvoiceResponse {
|
||||
expires_at: string;
|
||||
payment_hash: string;
|
||||
payment_request: string;
|
||||
}
|
||||
|
||||
interface PayInvoiceResponse {
|
||||
amount: number;
|
||||
description?: string;
|
||||
destination: string;
|
||||
fee: number;
|
||||
payment_hash: string;
|
||||
payment_preimage: string;
|
||||
payment_request: string;
|
||||
}
|
||||
|
||||
interface GetInvoiceResponse {
|
||||
amount: number;
|
||||
comment?: string;
|
||||
created_at: string;
|
||||
creation_date: number;
|
||||
currency: string;
|
||||
expires_at: string;
|
||||
preimage: string;
|
||||
payment_request: string;
|
||||
settled: boolean;
|
||||
settled_at: string;
|
||||
type: "incoming" | "outgoing";
|
||||
}
|
||||
|
||||
interface GetUserResponse {
|
||||
lightning_address: string;
|
||||
}
|
104
packages/wallet/src/Cashu.ts
Normal file
104
packages/wallet/src/Cashu.ts
Normal file
@ -0,0 +1,104 @@
|
||||
import { CashuMint, Proof } from "@cashu/cashu-ts";
|
||||
|
||||
import { InvoiceRequest, LNWallet, WalletEvents, WalletInfo, WalletInvoice } from ".";
|
||||
import EventEmitter from "eventemitter3";
|
||||
|
||||
export type CashuWalletConfig = {
|
||||
url: string;
|
||||
keys: Record<string, string>;
|
||||
keysets: Array<string>;
|
||||
proofs: Array<Proof>;
|
||||
};
|
||||
|
||||
export class CashuWallet extends EventEmitter<WalletEvents> implements LNWallet {
|
||||
#wallet: CashuWalletConfig;
|
||||
#mint: CashuMint;
|
||||
|
||||
constructor(wallet: CashuWalletConfig) {
|
||||
super();
|
||||
this.#wallet = wallet;
|
||||
this.#mint = new CashuMint(this.#wallet.url);
|
||||
}
|
||||
|
||||
getConfig() {
|
||||
return { ...this.#wallet };
|
||||
}
|
||||
|
||||
canGetInvoices() {
|
||||
return false;
|
||||
}
|
||||
|
||||
canGetBalance() {
|
||||
return true;
|
||||
}
|
||||
|
||||
canAutoLogin() {
|
||||
return true;
|
||||
}
|
||||
|
||||
isReady() {
|
||||
return true;
|
||||
}
|
||||
|
||||
canCreateInvoice() {
|
||||
return true;
|
||||
}
|
||||
|
||||
canPayInvoice() {
|
||||
return true;
|
||||
}
|
||||
|
||||
async getInfo() {
|
||||
return {
|
||||
alias: "Cashu mint: " + this.#wallet.url,
|
||||
} as WalletInfo;
|
||||
}
|
||||
|
||||
async login(): Promise<boolean> {
|
||||
if (this.#wallet.keysets.length === 0) {
|
||||
const keys = await this.#mint.getKeys();
|
||||
this.#wallet.keys = keys;
|
||||
this.#wallet.keysets = [""];
|
||||
this.onChange(this.#wallet);
|
||||
}
|
||||
await this.#checkProofs();
|
||||
return true;
|
||||
}
|
||||
|
||||
close(): Promise<boolean> {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
async getBalance() {
|
||||
return this.#wallet.proofs.reduce((acc, v) => (acc += v.amount), 0);
|
||||
}
|
||||
|
||||
async createInvoice(req: InvoiceRequest) {
|
||||
const rsp = await this.#mint.requestMint(req.amount);
|
||||
return {
|
||||
pr: rsp.pr,
|
||||
} as WalletInvoice;
|
||||
}
|
||||
|
||||
payInvoice(): Promise<WalletInvoice> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
getInvoices(): Promise<WalletInvoice[]> {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
async #checkProofs() {
|
||||
if (this.#wallet.proofs.length == 0) return;
|
||||
|
||||
const checks = await this.#mint.check({
|
||||
proofs: this.#wallet.proofs.map(a => ({ secret: a.secret })),
|
||||
});
|
||||
|
||||
const filteredProofs = this.#wallet.proofs.filter((_, i) => checks.spendable[i]);
|
||||
this.#wallet.proofs = filteredProofs;
|
||||
if (filteredProofs.length !== checks.spendable.length) {
|
||||
this.emit("change", JSON.stringify(this.#wallet));
|
||||
}
|
||||
}
|
||||
}
|
182
packages/wallet/src/LNCWallet.ts
Normal file
182
packages/wallet/src/LNCWallet.ts
Normal file
@ -0,0 +1,182 @@
|
||||
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<WalletEvents> 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<WalletError | Login> {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
async getInfo(): Promise<WalletInfo> {
|
||||
const nodeInfo = await this.#lnc.lnd.lightning.getInfo();
|
||||
return {
|
||||
nodePubKey: nodeInfo.identityPubkey,
|
||||
alias: nodeInfo.alias,
|
||||
} as WalletInfo;
|
||||
}
|
||||
|
||||
close(): Promise<boolean> {
|
||||
if (this.#lnc.isConnected) {
|
||||
this.#lnc.disconnect();
|
||||
}
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
async login(password?: string): Promise<boolean> {
|
||||
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<number> {
|
||||
const rsp = await this.#lnc.lnd.lightning.channelBalance();
|
||||
this.#log(rsp);
|
||||
return parseInt(rsp.localBalance?.sat ?? "0");
|
||||
}
|
||||
|
||||
async createInvoice(req: InvoiceRequest): Promise<WalletInvoice> {
|
||||
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<WalletInvoice> {
|
||||
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<WalletInvoice[]> {
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
209
packages/wallet/src/LNDHub.ts
Normal file
209
packages/wallet/src/LNDHub.ts
Normal file
@ -0,0 +1,209 @@
|
||||
import { throwIfOffline } from "@snort/shared";
|
||||
|
||||
import {
|
||||
InvoiceRequest,
|
||||
LNWallet,
|
||||
prToWalletInvoice,
|
||||
Sats,
|
||||
WalletError,
|
||||
WalletErrorCode,
|
||||
WalletEvents,
|
||||
WalletInfo,
|
||||
WalletInvoice,
|
||||
WalletInvoiceState,
|
||||
} from ".";
|
||||
import EventEmitter from "eventemitter3";
|
||||
|
||||
const defaultHeaders = {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
export default class LNDHubWallet extends EventEmitter<WalletEvents> implements LNWallet {
|
||||
type: "lndhub";
|
||||
url: URL;
|
||||
user: string;
|
||||
password: string;
|
||||
auth?: AuthResponse;
|
||||
|
||||
constructor(url: string) {
|
||||
super();
|
||||
if (url.startsWith("lndhub://")) {
|
||||
const regex = /^lndhub:\/\/([\S-]+):([\S-]+)@(.*)$/i;
|
||||
const parsedUrl = url.match(regex);
|
||||
if (!parsedUrl || parsedUrl.length !== 4) {
|
||||
throw new Error("Invalid LNDHUB config");
|
||||
}
|
||||
this.url = new URL(parsedUrl[3]);
|
||||
this.user = parsedUrl[1];
|
||||
this.password = parsedUrl[2];
|
||||
this.type = "lndhub";
|
||||
} else {
|
||||
throw new Error("Invalid config");
|
||||
}
|
||||
}
|
||||
|
||||
isReady(): boolean {
|
||||
return this.auth !== undefined;
|
||||
}
|
||||
|
||||
canAutoLogin() {
|
||||
return true;
|
||||
}
|
||||
|
||||
canGetInvoices() {
|
||||
return true;
|
||||
}
|
||||
|
||||
canGetBalance() {
|
||||
return true;
|
||||
}
|
||||
|
||||
canCreateInvoice() {
|
||||
return true;
|
||||
}
|
||||
|
||||
canPayInvoice() {
|
||||
return true;
|
||||
}
|
||||
|
||||
close(): Promise<boolean> {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
async getInfo() {
|
||||
await this.login();
|
||||
return await this.getJson<WalletInfo>("GET", "/getinfo");
|
||||
}
|
||||
|
||||
async login() {
|
||||
if (this.auth) return true;
|
||||
|
||||
const rsp = await this.getJson<AuthResponse>("POST", "/auth?type=auth", {
|
||||
login: this.user,
|
||||
password: this.password,
|
||||
});
|
||||
this.auth = rsp as AuthResponse;
|
||||
this.emit("change");
|
||||
return true;
|
||||
}
|
||||
|
||||
async getBalance(): Promise<Sats> {
|
||||
await this.login();
|
||||
const rsp = await this.getJson<GetBalanceResponse>("GET", "/balance");
|
||||
const bal = Math.floor((rsp as GetBalanceResponse).BTC.AvailableBalance);
|
||||
return bal as Sats;
|
||||
}
|
||||
|
||||
async createInvoice(req: InvoiceRequest) {
|
||||
await this.login();
|
||||
const rsp = await this.getJson<UserInvoicesResponse>("POST", "/addinvoice", {
|
||||
amt: req.amount,
|
||||
memo: req.memo,
|
||||
});
|
||||
|
||||
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) {
|
||||
await this.login();
|
||||
const rsp = await this.getJson<PayInvoiceResponse>("POST", "/payinvoice", {
|
||||
invoice: pr,
|
||||
});
|
||||
|
||||
const pRsp = rsp as PayInvoiceResponse;
|
||||
return {
|
||||
pr: pr,
|
||||
paymentHash: pRsp.payment_hash,
|
||||
preimage: pRsp.payment_preimage,
|
||||
state: pRsp.payment_error
|
||||
? WalletInvoiceState.Failed
|
||||
: pRsp.payment_preimage
|
||||
? WalletInvoiceState.Paid
|
||||
: WalletInvoiceState.Pending,
|
||||
} as WalletInvoice;
|
||||
}
|
||||
|
||||
async getInvoices(): Promise<WalletInvoice[]> {
|
||||
await this.login();
|
||||
const rsp = await this.getJson<UserInvoicesResponse[]>("GET", "/getuserinvoices");
|
||||
return (rsp as UserInvoicesResponse[])
|
||||
.sort((a, b) => (a.timestamp > b.timestamp ? -1 : 1))
|
||||
.slice(0, 50)
|
||||
.map(a => {
|
||||
const decodedInvoice = prToWalletInvoice(a.payment_request);
|
||||
if (!decodedInvoice) {
|
||||
throw new WalletError(WalletErrorCode.InvalidInvoice, "Failed to parse invoice");
|
||||
}
|
||||
return {
|
||||
...decodedInvoice,
|
||||
state: a.ispaid ? WalletInvoiceState.Paid : decodedInvoice.state,
|
||||
paymentHash: a.payment_hash,
|
||||
memo: a.description,
|
||||
} as WalletInvoice;
|
||||
});
|
||||
}
|
||||
|
||||
private async getJson<T>(method: "GET" | "POST", path: string, body?: unknown): Promise<T> {
|
||||
throwIfOffline();
|
||||
const auth = `Bearer ${this.auth?.access_token}`;
|
||||
const url = `${this.url.pathname === "/" ? this.url.toString().slice(0, -1) : this.url.toString()}${path}`;
|
||||
const rsp = await fetch(url, {
|
||||
method: method,
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
headers: {
|
||||
...defaultHeaders,
|
||||
Authorization: auth,
|
||||
},
|
||||
});
|
||||
const json = await rsp.json();
|
||||
if ("code" in json && !rsp.ok) {
|
||||
const err = json as ErrorResponse;
|
||||
throw new WalletError(err.code, err.message);
|
||||
}
|
||||
return json as T;
|
||||
}
|
||||
}
|
||||
|
||||
interface AuthResponse {
|
||||
refresh_token?: string;
|
||||
access_token?: string;
|
||||
token_type?: string;
|
||||
}
|
||||
|
||||
interface GetBalanceResponse {
|
||||
BTC: {
|
||||
AvailableBalance: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface UserInvoicesResponse {
|
||||
amt: number;
|
||||
description: string;
|
||||
ispaid: boolean;
|
||||
type: string;
|
||||
timestamp: number;
|
||||
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 };
|
||||
}
|
||||
|
||||
interface ErrorResponse {
|
||||
code: number;
|
||||
message: string;
|
||||
}
|
349
packages/wallet/src/NostrWalletConnect.ts
Normal file
349
packages/wallet/src/NostrWalletConnect.ts
Normal file
@ -0,0 +1,349 @@
|
||||
/* eslint-disable max-lines */
|
||||
import { dedupe } from "@snort/shared";
|
||||
import { Connection, EventBuilder, EventKind, NostrEvent, PrivateKeySigner } from "@snort/system";
|
||||
import debug from "debug";
|
||||
|
||||
import {
|
||||
InvoiceRequest,
|
||||
LNWallet,
|
||||
WalletError,
|
||||
WalletErrorCode,
|
||||
WalletEvents,
|
||||
WalletInfo,
|
||||
WalletInvoice,
|
||||
WalletInvoiceState,
|
||||
} from ".";
|
||||
import EventEmitter from "eventemitter3";
|
||||
|
||||
interface WalletConnectConfig {
|
||||
relayUrl: string;
|
||||
walletPubkey: string;
|
||||
secret: string;
|
||||
}
|
||||
|
||||
interface QueueObj {
|
||||
resolve: (o: string) => void;
|
||||
reject: (e: Error) => void;
|
||||
}
|
||||
|
||||
interface WalletConnectResponse<T> {
|
||||
result_type?: string;
|
||||
result?: T;
|
||||
error?: {
|
||||
code:
|
||||
| "RATE_LIMITED"
|
||||
| "NOT_IMPLEMENTED"
|
||||
| "INSUFFICIENT_BALANCE"
|
||||
| "QUOTA_EXCEEDED"
|
||||
| "RESTRICTED"
|
||||
| "UNAUTHORIZED"
|
||||
| "INTERNAL"
|
||||
| "OTHER";
|
||||
message: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface GetInfoResponse {
|
||||
alias?: string;
|
||||
color?: string;
|
||||
pubkey?: string;
|
||||
network?: string;
|
||||
block_height?: number;
|
||||
block_hash?: string;
|
||||
methods?: Array<string>;
|
||||
}
|
||||
|
||||
interface ListTransactionsResponse {
|
||||
transactions: Array<{
|
||||
type: "incoming" | "outgoing";
|
||||
invoice: string;
|
||||
description?: string;
|
||||
description_hash?: string;
|
||||
preimage?: string;
|
||||
payment_hash?: string;
|
||||
amount: number;
|
||||
feed_paid: number;
|
||||
settled_at?: number;
|
||||
created_at: number;
|
||||
expires_at: number;
|
||||
metadata?: object;
|
||||
}>;
|
||||
}
|
||||
|
||||
interface MakeInvoiceResponse {
|
||||
invoice: string;
|
||||
payment_hash: string;
|
||||
}
|
||||
|
||||
const DefaultSupported = ["get_info", "pay_invoice"];
|
||||
|
||||
export class NostrConnectWallet extends EventEmitter<WalletEvents> implements LNWallet {
|
||||
#log = debug("NWC");
|
||||
#config: WalletConnectConfig;
|
||||
#conn?: Connection;
|
||||
#commandQueue: Map<string, QueueObj>;
|
||||
#info?: WalletInfo;
|
||||
#supported_methods: Array<string> = DefaultSupported;
|
||||
|
||||
constructor(cfg: string) {
|
||||
super();
|
||||
this.#config = NostrConnectWallet.parseConfigUrl(cfg);
|
||||
this.#commandQueue = new Map();
|
||||
}
|
||||
|
||||
static parseConfigUrl(url: string) {
|
||||
const uri = new URL(url.replace("nostrwalletconnect://", "http://").replace("nostr+walletconnect://", "http://"));
|
||||
return {
|
||||
relayUrl: uri.searchParams.get("relay"),
|
||||
walletPubkey: uri.host,
|
||||
secret: uri.searchParams.get("secret"),
|
||||
} as WalletConnectConfig;
|
||||
}
|
||||
|
||||
canAutoLogin(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
isReady(): boolean {
|
||||
return this.#conn !== undefined;
|
||||
}
|
||||
|
||||
canGetInvoices() {
|
||||
return this.#supported_methods.includes("list_transactions");
|
||||
}
|
||||
|
||||
canGetBalance() {
|
||||
return this.#supported_methods.includes("get_balance");
|
||||
}
|
||||
|
||||
canCreateInvoice() {
|
||||
return this.#supported_methods.includes("make_invoice");
|
||||
}
|
||||
|
||||
canPayInvoice() {
|
||||
return this.#supported_methods.includes("pay_invoice");
|
||||
}
|
||||
|
||||
async getInfo() {
|
||||
await this.login();
|
||||
if (this.#info) return this.#info;
|
||||
|
||||
const rsp = await this.#rpc<WalletConnectResponse<GetInfoResponse>>("get_info", {});
|
||||
if (!rsp.error) {
|
||||
this.#supported_methods = dedupe(["get_info", ...(rsp.result?.methods ?? DefaultSupported)]);
|
||||
this.#log("Supported methods: %o", this.#supported_methods);
|
||||
const info = {
|
||||
nodePubKey: rsp.result?.pubkey,
|
||||
alias: rsp.result?.alias,
|
||||
blockHeight: rsp.result?.block_height,
|
||||
blockHash: rsp.result?.block_hash,
|
||||
chains: rsp.result?.network ? [rsp.result.network] : undefined,
|
||||
} as WalletInfo;
|
||||
this.#info = info;
|
||||
return info;
|
||||
} else if (rsp.error.code === "NOT_IMPLEMENTED") {
|
||||
// legacy get_info uses event kind 13_194
|
||||
return await new Promise<WalletInfo>((resolve, reject) => {
|
||||
this.#commandQueue.set("info", {
|
||||
resolve: (o: string) => {
|
||||
this.#supported_methods = dedupe(["get_info", ...o.split(",")]);
|
||||
this.#log("Supported methods: %o", this.#supported_methods);
|
||||
const info = {
|
||||
alias: "NWC",
|
||||
} as WalletInfo;
|
||||
this.#info = info;
|
||||
resolve(info);
|
||||
},
|
||||
reject,
|
||||
});
|
||||
this.#conn?.queueReq(["REQ", "info", { kinds: [13194], limit: 1 }], () => {
|
||||
// ignored
|
||||
});
|
||||
});
|
||||
} else {
|
||||
throw new WalletError(WalletErrorCode.GeneralError, rsp.error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async login() {
|
||||
if (this.#conn) return true;
|
||||
|
||||
await new Promise<void>(resolve => {
|
||||
this.#conn = new Connection(this.#config.relayUrl, { read: true, write: true });
|
||||
this.#conn.on("connected", () => resolve());
|
||||
this.#conn.on("auth", async (c, r, cb) => {
|
||||
const eb = new EventBuilder();
|
||||
eb.kind(EventKind.Auth).tag(["relay", r]).tag(["challenge", c]);
|
||||
const ev = await eb.buildAndSign(this.#config.secret);
|
||||
cb(ev);
|
||||
});
|
||||
this.#conn.on("event", (s, e) => {
|
||||
this.#onReply(s, e);
|
||||
});
|
||||
this.#conn.connect();
|
||||
});
|
||||
await this.getInfo();
|
||||
this.emit("change");
|
||||
return true;
|
||||
}
|
||||
|
||||
async close() {
|
||||
this.#conn?.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
async getBalance() {
|
||||
await this.login();
|
||||
const rsp = await this.#rpc<WalletConnectResponse<{ balance: number }>>("get_balance", {});
|
||||
if (!rsp.error) {
|
||||
return (rsp.result?.balance ?? 0) / 1000;
|
||||
} else {
|
||||
throw new WalletError(WalletErrorCode.GeneralError, rsp.error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async createInvoice(req: InvoiceRequest) {
|
||||
await this.login();
|
||||
const rsp = await this.#rpc<WalletConnectResponse<MakeInvoiceResponse>>("make_invoice", {
|
||||
amount: req.amount * 1000,
|
||||
description: req.memo,
|
||||
expiry: req.expiry,
|
||||
});
|
||||
if (!rsp.error) {
|
||||
return {
|
||||
pr: rsp.result?.invoice,
|
||||
paymentHash: rsp.result?.payment_hash,
|
||||
memo: req.memo,
|
||||
amount: req.amount * 1000,
|
||||
state: WalletInvoiceState.Pending,
|
||||
} as WalletInvoice;
|
||||
} else {
|
||||
throw new WalletError(WalletErrorCode.GeneralError, rsp.error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async payInvoice(pr: string) {
|
||||
await this.login();
|
||||
const rsp = await this.#rpc<WalletConnectResponse<WalletInvoice>>("pay_invoice", {
|
||||
invoice: pr,
|
||||
});
|
||||
if (!rsp.error) {
|
||||
return {
|
||||
...rsp.result,
|
||||
pr,
|
||||
state: WalletInvoiceState.Paid,
|
||||
} as WalletInvoice;
|
||||
} else {
|
||||
throw new WalletError(WalletErrorCode.GeneralError, rsp.error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async getInvoices() {
|
||||
await this.login();
|
||||
const rsp = await this.#rpc<WalletConnectResponse<ListTransactionsResponse>>("list_transactions", {
|
||||
limit: 50,
|
||||
});
|
||||
if (!rsp.error) {
|
||||
return (
|
||||
rsp.result?.transactions.map(
|
||||
a =>
|
||||
({
|
||||
pr: a.invoice,
|
||||
paymentHash: a.payment_hash,
|
||||
memo: a.description,
|
||||
amount: a.amount,
|
||||
fees: a.feed_paid,
|
||||
timestamp: typeof a.created_at === "string" ? new Date(a.created_at).getTime() / 1000 : a.created_at,
|
||||
preimage: a.preimage,
|
||||
state: WalletInvoiceState.Paid,
|
||||
direction: a.type === "incoming" ? "in" : "out",
|
||||
}) as WalletInvoice,
|
||||
) ?? []
|
||||
);
|
||||
} else {
|
||||
throw new WalletError(WalletErrorCode.GeneralError, rsp.error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async #onReply(sub: string, e: NostrEvent) {
|
||||
if (sub === "info") {
|
||||
const pending = this.#commandQueue.get("info");
|
||||
if (!pending) {
|
||||
throw new WalletError(WalletErrorCode.GeneralError, "No pending info command found");
|
||||
}
|
||||
pending.resolve(e.content);
|
||||
this.#commandQueue.delete("info");
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.kind !== 23195) {
|
||||
throw new WalletError(WalletErrorCode.GeneralError, "Unknown event kind");
|
||||
}
|
||||
|
||||
const replyTo = e.tags.find(a => a[0] === "e");
|
||||
if (!replyTo) {
|
||||
throw new WalletError(WalletErrorCode.GeneralError, "Missing e-tag in command response");
|
||||
}
|
||||
|
||||
const pending = this.#commandQueue.get(replyTo[1]);
|
||||
if (!pending) {
|
||||
throw new WalletError(WalletErrorCode.GeneralError, "No pending command found");
|
||||
}
|
||||
|
||||
pending.resolve(e.content);
|
||||
this.#commandQueue.delete(replyTo[1]);
|
||||
this.#conn?.closeReq(sub);
|
||||
}
|
||||
|
||||
async #rpc<T>(method: string, params: Record<string, string | number | undefined>) {
|
||||
if (!this.#conn) throw new WalletError(WalletErrorCode.GeneralError, "Not implemented");
|
||||
this.#log("> %o", { method, params });
|
||||
if (!this.#supported_methods.includes(method)) {
|
||||
const ret = {
|
||||
error: {
|
||||
code: "NOT_IMPLEMENTED",
|
||||
message: `get_info claims the method "${method}" is not supported`,
|
||||
},
|
||||
} as T;
|
||||
this.#log("< %o", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
const payload = JSON.stringify({
|
||||
method,
|
||||
params,
|
||||
});
|
||||
const signer = new PrivateKeySigner(this.#config.secret);
|
||||
const eb = new EventBuilder();
|
||||
eb.kind(23194 as EventKind)
|
||||
.content(await signer.nip4Encrypt(payload, this.#config.walletPubkey))
|
||||
.tag(["p", this.#config.walletPubkey]);
|
||||
|
||||
const evCommand = await eb.buildAndSign(this.#config.secret);
|
||||
this.#conn.queueReq(
|
||||
[
|
||||
"REQ",
|
||||
evCommand.id.slice(0, 12),
|
||||
{
|
||||
kinds: [23195 as EventKind],
|
||||
authors: [this.#config.walletPubkey],
|
||||
["#e"]: [evCommand.id],
|
||||
},
|
||||
],
|
||||
() => {
|
||||
// ignored
|
||||
},
|
||||
);
|
||||
await this.#conn.sendEventAsync(evCommand);
|
||||
return await new Promise<T>((resolve, reject) => {
|
||||
this.#commandQueue.set(evCommand.id, {
|
||||
resolve: async (o: string) => {
|
||||
const reply = JSON.parse(await signer.nip4Decrypt(o, this.#config.walletPubkey));
|
||||
this.#log("< %o", reply);
|
||||
resolve(reply);
|
||||
},
|
||||
reject,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
127
packages/wallet/src/WebLN.ts
Normal file
127
packages/wallet/src/WebLN.ts
Normal file
@ -0,0 +1,127 @@
|
||||
import { barrierQueue, processWorkQueue, unwrap, WorkQueueItem } from "@snort/shared";
|
||||
|
||||
import {
|
||||
InvoiceRequest,
|
||||
LNWallet,
|
||||
prToWalletInvoice,
|
||||
Sats,
|
||||
WalletError,
|
||||
WalletErrorCode,
|
||||
WalletEvents,
|
||||
WalletInfo,
|
||||
WalletInvoice,
|
||||
WalletInvoiceState,
|
||||
} from ".";
|
||||
import EventEmitter from "eventemitter3";
|
||||
|
||||
const WebLNQueue: Array<WorkQueueItem> = [];
|
||||
processWorkQueue(WebLNQueue);
|
||||
|
||||
export class WebLNWallet extends EventEmitter<WalletEvents> implements LNWallet {
|
||||
isReady(): boolean {
|
||||
return window.webln !== undefined && window.webln !== null;
|
||||
}
|
||||
|
||||
canCreateInvoice() {
|
||||
return true;
|
||||
}
|
||||
|
||||
canPayInvoice() {
|
||||
return true;
|
||||
}
|
||||
|
||||
canGetInvoices() {
|
||||
return false;
|
||||
}
|
||||
|
||||
canGetBalance() {
|
||||
return window.webln?.getBalance !== undefined;
|
||||
}
|
||||
|
||||
canAutoLogin(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
async getInfo(): Promise<WalletInfo> {
|
||||
await this.login();
|
||||
if (this.isReady()) {
|
||||
const rsp = await barrierQueue(WebLNQueue, async () => await window.webln?.getInfo());
|
||||
if (rsp) {
|
||||
return {
|
||||
nodePubKey: rsp.node.pubkey,
|
||||
alias: rsp.node.alias,
|
||||
} as WalletInfo;
|
||||
} else {
|
||||
throw new WalletError(WalletErrorCode.GeneralError, "Could not load wallet info");
|
||||
}
|
||||
}
|
||||
throw new WalletError(WalletErrorCode.GeneralError, "WebLN not available");
|
||||
}
|
||||
|
||||
async login(): Promise<boolean> {
|
||||
if (window.webln) {
|
||||
await window.webln.enable();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
close(): Promise<boolean> {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
async getBalance(): Promise<Sats> {
|
||||
await this.login();
|
||||
if (window.webln?.getBalance) {
|
||||
const rsp = await barrierQueue(WebLNQueue, async () => await unwrap(window.webln?.getBalance).call(window.webln));
|
||||
return rsp.balance;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
async createInvoice(req: InvoiceRequest): Promise<WalletInvoice> {
|
||||
await this.login();
|
||||
if (this.isReady()) {
|
||||
const rsp = await barrierQueue(
|
||||
WebLNQueue,
|
||||
async () =>
|
||||
await window.webln?.makeInvoice({
|
||||
amount: req.amount,
|
||||
defaultMemo: req.memo,
|
||||
}),
|
||||
);
|
||||
if (rsp) {
|
||||
const invoice = prToWalletInvoice(rsp.paymentRequest);
|
||||
if (!invoice) {
|
||||
throw new WalletError(WalletErrorCode.InvalidInvoice, "Could not parse invoice");
|
||||
}
|
||||
return invoice;
|
||||
}
|
||||
}
|
||||
throw new WalletError(WalletErrorCode.GeneralError, "WebLN not available");
|
||||
}
|
||||
|
||||
async payInvoice(pr: string): Promise<WalletInvoice> {
|
||||
await this.login();
|
||||
if (this.isReady()) {
|
||||
const invoice = prToWalletInvoice(pr);
|
||||
if (!invoice) {
|
||||
throw new WalletError(WalletErrorCode.InvalidInvoice, "Could not parse invoice");
|
||||
}
|
||||
const rsp = await barrierQueue(WebLNQueue, async () => await window.webln?.sendPayment(pr));
|
||||
if (rsp) {
|
||||
invoice.state = WalletInvoiceState.Paid;
|
||||
invoice.preimage = rsp.preimage;
|
||||
invoice.fees = "route" in rsp ? (rsp.route as { total_fees: number }).total_fees : 0;
|
||||
return invoice;
|
||||
} else {
|
||||
invoice.state = WalletInvoiceState.Failed;
|
||||
return invoice;
|
||||
}
|
||||
}
|
||||
throw new WalletError(WalletErrorCode.GeneralError, "WebLN not available");
|
||||
}
|
||||
|
||||
getInvoices(): Promise<WalletInvoice[]> {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
}
|
1
packages/wallet/src/custom.d.ts
vendored
Normal file
1
packages/wallet/src/custom.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="@webbtc/webln-types" />
|
155
packages/wallet/src/index.ts
Normal file
155
packages/wallet/src/index.ts
Normal file
@ -0,0 +1,155 @@
|
||||
import { decodeInvoice, unwrap } from "@snort/shared";
|
||||
import AlbyWallet from "./AlbyWallet";
|
||||
import { CashuWallet } from "./Cashu";
|
||||
import { LNCWallet } from "./LNCWallet";
|
||||
import LNDHubWallet from "./LNDHub";
|
||||
import { NostrConnectWallet } from "./NostrWalletConnect";
|
||||
import { WebLNWallet } from "./WebLN";
|
||||
import EventEmitter from "eventemitter3";
|
||||
|
||||
export enum WalletKind {
|
||||
LNDHub = 1,
|
||||
LNC = 2,
|
||||
WebLN = 3,
|
||||
NWC = 4,
|
||||
Cashu = 5,
|
||||
Alby = 6,
|
||||
}
|
||||
|
||||
export enum WalletErrorCode {
|
||||
BadAuth = 1,
|
||||
NotEnoughBalance = 2,
|
||||
BadPartner = 3,
|
||||
InvalidInvoice = 4,
|
||||
RouteNotFound = 5,
|
||||
GeneralError = 6,
|
||||
NodeFailure = 7,
|
||||
}
|
||||
|
||||
export class WalletError extends Error {
|
||||
code: WalletErrorCode;
|
||||
|
||||
constructor(c: WalletErrorCode, msg: string) {
|
||||
super(msg);
|
||||
this.code = c;
|
||||
}
|
||||
}
|
||||
|
||||
export const UnknownWalletError = {
|
||||
code: WalletErrorCode.GeneralError,
|
||||
message: "Unknown error",
|
||||
} as WalletError;
|
||||
|
||||
export interface WalletInfo {
|
||||
fee: number;
|
||||
nodePubKey: string;
|
||||
alias: string;
|
||||
pendingChannels: number;
|
||||
activeChannels: number;
|
||||
peers: number;
|
||||
blockHeight: number;
|
||||
blockHash: string;
|
||||
synced: boolean;
|
||||
chains: string[];
|
||||
version: string;
|
||||
}
|
||||
|
||||
export interface Login {
|
||||
service: string;
|
||||
save: () => Promise<void>;
|
||||
load: () => Promise<void>;
|
||||
}
|
||||
|
||||
export interface InvoiceRequest {
|
||||
amount: Sats;
|
||||
memo?: string;
|
||||
expiry?: number;
|
||||
}
|
||||
|
||||
export enum WalletInvoiceState {
|
||||
Pending = 0,
|
||||
Paid = 1,
|
||||
Expired = 2,
|
||||
Failed = 3,
|
||||
}
|
||||
|
||||
export interface WalletInvoice {
|
||||
pr: string;
|
||||
paymentHash: string;
|
||||
memo: string;
|
||||
amount: MilliSats;
|
||||
fees: number;
|
||||
timestamp: number;
|
||||
preimage?: string;
|
||||
state: WalletInvoiceState;
|
||||
direction: "in" | "out";
|
||||
}
|
||||
|
||||
export function prToWalletInvoice(pr: string) {
|
||||
const parsedInvoice = decodeInvoice(pr);
|
||||
if (parsedInvoice) {
|
||||
return {
|
||||
amount: parsedInvoice.amount ?? 0,
|
||||
memo: parsedInvoice.description,
|
||||
paymentHash: parsedInvoice.paymentHash ?? "",
|
||||
timestamp: parsedInvoice.timestamp ?? 0,
|
||||
state: parsedInvoice.expired ? WalletInvoiceState.Expired : WalletInvoiceState.Pending,
|
||||
pr,
|
||||
direction: "in",
|
||||
} as WalletInvoice;
|
||||
}
|
||||
}
|
||||
|
||||
export type Sats = number;
|
||||
export type MilliSats = number;
|
||||
|
||||
export interface WalletEvents {
|
||||
change: (data?: string) => void
|
||||
}
|
||||
|
||||
export type LNWallet = EventEmitter<WalletEvents> & {
|
||||
isReady(): boolean;
|
||||
getInfo: () => Promise<WalletInfo>;
|
||||
login: (password?: string) => Promise<boolean>;
|
||||
close: () => Promise<boolean>;
|
||||
getBalance: () => Promise<Sats>;
|
||||
createInvoice: (req: InvoiceRequest) => Promise<WalletInvoice>;
|
||||
payInvoice: (pr: string) => Promise<WalletInvoice>;
|
||||
getInvoices: () => Promise<WalletInvoice[]>;
|
||||
|
||||
canAutoLogin: () => boolean;
|
||||
canGetInvoices: () => boolean;
|
||||
canGetBalance: () => boolean;
|
||||
canCreateInvoice: () => boolean;
|
||||
canPayInvoice: () => boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load wallet by kind
|
||||
* @param kind The wallet kind to create
|
||||
* @param data Opaque data
|
||||
*/
|
||||
export function loadWallet(kind: WalletKind, data: string | undefined) {
|
||||
switch (kind) {
|
||||
case WalletKind.LNC: {
|
||||
return LNCWallet.Empty();
|
||||
}
|
||||
case WalletKind.WebLN: {
|
||||
return new WebLNWallet();
|
||||
}
|
||||
case WalletKind.LNDHub: {
|
||||
return new LNDHubWallet(unwrap(data));
|
||||
}
|
||||
case WalletKind.NWC: {
|
||||
return new NostrConnectWallet(unwrap(data));
|
||||
}
|
||||
case WalletKind.Alby: {
|
||||
return new AlbyWallet(JSON.parse(unwrap(data)));
|
||||
}
|
||||
case WalletKind.Cashu: {
|
||||
return new CashuWallet(JSON.parse(unwrap(data)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { LNCWallet, WebLNWallet, LNDHubWallet, NostrConnectWallet, AlbyWallet, CashuWallet }
|
18
packages/wallet/tsconfig.json
Normal file
18
packages/wallet/tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src",
|
||||
"target": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"esModuleInterop": true,
|
||||
"noImplicitOverride": true,
|
||||
"module": "ESNext",
|
||||
"strict": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"inlineSourceMap": true,
|
||||
"outDir": "dist",
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["./src/**/*.ts"],
|
||||
"exclude": ["**/*.test.ts"]
|
||||
}
|
3
packages/wallet/typedoc.json
Normal file
3
packages/wallet/typedoc.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"entryPoints": ["src/index.ts"]
|
||||
}
|
Reference in New Issue
Block a user