PortalBTCLib/examples/portalbtc-cli.ts
2025-07-09 16:47:19 +02:00

172 lines
4.8 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bun
// Example CLI for interacting with PortalBtcWallet
// Usage examples:
// bun run examples/portalbtc-cli.ts balance
// bun run examples/portalbtc-cli.ts invoice 1000
// bun run examples/portalbtc-cli.ts pay <destination> [amountSat]
// bun run examples/portalbtc-cli.ts redeem-token <cashu-token>
// bun run examples/portalbtc-cli.ts list [limit] [offset]
//
// Environment variables required:
// MNEMONIC BIP39 seed words for the wallet (string, required)
// BREEZ_API_KEY Breez SDK API key (string, required)
// NETWORK "mainnet" | "testnet" (default: "testnet")
import PortalBtcWallet from "../index";
import { PaymentStatus } from "../paymentStatus";
import type { CashuStore, CashuTxn } from "../paymentStatus";
import type { Proof } from "@cashu/cashu-ts";
import fs from "node:fs";
import path from "node:path";
interface CashuPersistenceFile {
proofs: Proof[];
txns: CashuTxn[];
lastRedeemedCashuQuoteTimestamp: number;
}
class FileCashuStore implements CashuStore {
private data: CashuPersistenceFile;
constructor(private filePath: string) {
if (fs.existsSync(filePath)) {
this.data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
} else {
this.data = {
proofs: [],
txns: [],
lastRedeemedCashuQuoteTimestamp: 0,
};
this.flush();
}
}
private flush() {
fs.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2));
}
async getProofs(): Promise<Proof[]> {
return this.data.proofs;
}
async getTxns(): Promise<CashuTxn[]> {
return this.data.txns;
}
async getLastRedeemedCashuQuoteTimestamp(): Promise<number> {
return this.data.lastRedeemedCashuQuoteTimestamp;
}
async persistProofs(proofs: Proof[]): Promise<void> {
this.data.proofs = proofs;
this.flush();
}
async persistTxns(txns: CashuTxn[]): Promise<void> {
this.data.txns = txns;
this.flush();
}
async persistLastRedeemedCashuQuoteTimestamp(
timestamp: number,
): Promise<void> {
this.data.lastRedeemedCashuQuoteTimestamp = timestamp;
this.flush();
}
}
function usage() {
console.log(`Usage:
balance Show wallet balances (on-chain + Cashu)
invoice <amountSat> Generate a Lightning invoice
pay <destination> [amountSat] Pay a destination (invoice / LNURL / address)
redeem-token <cashu-token> Redeem a Cashu token
list [limit] [offset] List recent payments (default limit 20)
`);
process.exit(1);
}
async function main() {
const [cmd, ...rest] = process.argv.slice(2);
if (!cmd) usage();
const mnemonic = process.env.MNEMONIC;
const network = (process.env.NETWORK ?? "testnet") as "mainnet" | "testnet";
if (!mnemonic) {
console.error(
"Error: MNEMONIC environment variable is required.",
);
process.exit(1);
}
const statePath = path.resolve(
path.dirname(new URL(import.meta.url).pathname),
".portalbtc_state.json",
);
const cashuStore = new FileCashuStore(statePath);
const wallet = await PortalBtcWallet.create(
mnemonic,
cashuStore,
false,
network,
);
switch (cmd) {
case "balance": {
console.log("Balance (sats):", wallet.balance);
console.log(" Cashu balance:", wallet.cashuBalance);
console.log(" Pending balance:", wallet.pendingBalance);
console.log(" Lightning address:", wallet.lightningAddress);
break;
}
case "invoice": {
const amountSat = Number.parseInt(rest[0] ?? "0", 10);
if (!amountSat) {
console.error("Amount (sat) required for invoice command");
process.exit(1);
}
const invoice = await wallet.generateBolt11Invoice(amountSat);
console.log("Generated invoice:", invoice);
break;
}
case "pay": {
const destination = rest[0];
const amountArg = rest[1] ? Number.parseInt(rest[1], 10) : 0;
if (!destination) {
console.error("Destination required for pay command");
process.exit(1);
}
for await (const status of wallet.pay(destination, amountArg)) {
console.log("→", PaymentStatus[status.status], status);
}
break;
}
case "redeem-token": {
const token = rest[0];
if (!token) {
console.error("Cashu token required for redeem-token command");
process.exit(1);
}
await wallet.redeemToken(token);
console.log("Token redeemed successfully");
break;
}
case "list": {
const limit = rest[0] ? Number.parseInt(rest[0], 10) : 20;
const offset = rest[1] ? Number.parseInt(rest[1], 10) : 0;
const payments = await wallet.listPayments(limit, offset);
console.log(payments);
break;
}
default:
usage();
}
}
main().catch((err) => {
console.error(err);
process.exit(1);
});