Initial Commit

This commit is contained in:
Danny Morabito 2025-07-09 12:45:59 +02:00
commit 2d858727b0
Signed by: dannym
GPG key ID: 7CC8056A5A04557E
10 changed files with 1128 additions and 0 deletions

172
examples/portalbtc-cli.ts Normal file
View file

@ -0,0 +1,172 @@
#!/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);
});