#!/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 [amountSat] // bun run examples/portalbtc-cli.ts redeem-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 { return this.data.proofs; } async getTxns(): Promise { return this.data.txns; } async getLastRedeemedCashuQuoteTimestamp(): Promise { return this.data.lastRedeemedCashuQuoteTimestamp; } async persistProofs(proofs: Proof[]): Promise { this.data.proofs = proofs; this.flush(); } async persistTxns(txns: CashuTxn[]): Promise { this.data.txns = txns; this.flush(); } async persistLastRedeemedCashuQuoteTimestamp( timestamp: number, ): Promise { this.data.lastRedeemedCashuQuoteTimestamp = timestamp; this.flush(); } } function usage() { console.log(`Usage: balance – Show wallet balances (on-chain + Cashu) invoice – Generate a Lightning invoice pay [amountSat] – Pay a destination (invoice / LNURL / address) redeem-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); });