import { CashuMint, type Proof } from "@cashu/cashu-ts"; import { CashuWallet } from "@cashu/cashu-ts"; import NpubCash from "./npubCash"; import type { CashuStore, CashuTxn } from "./paymentStatus"; import PortalBtcWallet from "."; export default class PortalBtcWalletCashu { private npubCash: NpubCash; private cashuTxns: CashuTxn[] = []; private proofs: Proof[] = []; private onBalanceUpdated: (() => void) | null = null; get txns() { return this.cashuTxns; } get cashuWallet() { const mint = new CashuMint(PortalBtcWallet.MINT_URL); return new CashuWallet(mint); } get balance() { return this.proofs.reduce((sum, p) => sum + p.amount, 0); } constructor( mnemonic: string, private cashuStore: CashuStore, ) { this.npubCash = new NpubCash(mnemonic); } async init(onBalanceUpdated: () => void) { this.cashuTxns = await this.cashuStore.getTxns(); this.proofs = await this.cashuStore.getProofs(); this.onBalanceUpdated = onBalanceUpdated; } async redeemCashuQuotes() { const attempt = this.npubCash.tryRedeemUnredeemedCashuQuotes( await this.cashuStore.getLastRedeemedCashuQuoteTimestamp(), ); for await (const quote of attempt) { this.cashuTxns.push({ txId: `cashu-quote-${quote.quoteId}`, paymentType: "receive", amountSat: quote.amountSat, timestamp: quote.timestamp, status: "complete", }); } const proofs = await attempt.next(); if (!proofs.done || typeof proofs.value === "undefined") return []; this.proofs.push(...proofs.value); await this.persistState(); this.onBalanceUpdated?.(); return proofs.value; } async persistState() { await this.cashuStore.persistProofs(this.proofs); await this.cashuStore.persistTxns(this.cashuTxns); await this.cashuStore.persistLastRedeemedCashuQuoteTimestamp( this.cashuTxns[this.cashuTxns.length - 1]?.timestamp ?? 0, ); } async meltProofsToPayInvoice(invoice: string) { const meltQuote = await this.cashuWallet.createMeltQuote(invoice); const amountToMelt = meltQuote.amount + meltQuote.fee_reserve; const { keep, send } = await this.cashuWallet.send( amountToMelt, this.proofs, { includeFees: true, }, ); const { change } = await this.cashuWallet.meltProofs(meltQuote, send); this.proofs = [...keep, ...change]; this.cashuTxns.push({ txId: `cashu-send-${Date.now()}`, paymentType: "send", amountSat: amountToMelt, timestamp: Math.floor(Date.now() / 1000), status: "complete", }); await this.persistState(); this.onBalanceUpdated?.(); } async redeemToken(token: string): Promise { if (!token.trim()) throw new Error("Token is empty"); const received = await this.cashuWallet.receive(token.trim()); this.proofs.push(...received); const amountReceived = received.reduce((sum, p) => sum + p.amount, 0); this.cashuTxns.push({ txId: `cashu-receive-${Date.now()}`, paymentType: "receive", amountSat: amountReceived, timestamp: Math.floor(Date.now() / 1000), status: "complete", }); await this.persistState(); this.onBalanceUpdated?.(); } }