203 lines
4.8 KiB
TypeScript
203 lines
4.8 KiB
TypeScript
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;
|
|
}
|