feat(wallet): pay for peer files from BOTH Cashu and Fedimint ecash (#3)

Paying for a peer file minted a Cashu-only token, so a node whose ecash balance
lived in Fedimint couldn't pay even with funds. Now both backends are tried:

- payer (content.download-peer-paid): mint a Cashu token first; on failure fall
  back to spending Fedimint notes. Only error if BOTH backends can't cover it.
- seller (verify_and_receive_payment): accept Fedimint notes as well as Cashu —
  anything not starting with "cashu" is redeemed via reissue_into_any.
- new fedimint_client::spend_from_any() — spend from whichever joined federation
  has the balance, returning the notes + federation id (mirrors reissue_into_any).
- wallet.ecash-balance now also reports fedimint_sats + combined total_sats; the
  pay-for-file pre-check uses the combined total so a Fedimint-funded node isn't
  wrongly blocked.

Compiles (cargo check + vue-tsc). Live cross-node federation validation pending
(dual-ecash phase 6) — needs two nodes sharing a federation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-06-20 08:13:23 -04:00
co-authored by Claude Opus 4.8
parent b3633ec525
commit 8f06d88fbf
5 changed files with 138 additions and 9 deletions
+6 -4
View File
@@ -916,14 +916,16 @@ async function payWithEcash() {
downloading.value = item.id
purchaseError.value = null
try {
// Check ecash balance first
// Check ecash balance first — across BOTH backends (Cashu + Fedimint), since
// the payment tries each in turn (#3). Fall back to the Cashu-only field for
// older backends that don't report `total_sats`.
try {
const balanceRes = await rpcClient.call<{ balance_sats?: number }>({
const balanceRes = await rpcClient.call<{ total_sats?: number; balance_sats?: number }>({
method: 'wallet.ecash-balance',
})
const balance = balanceRes?.balance_sats ?? 0
const balance = balanceRes?.total_sats ?? balanceRes?.balance_sats ?? 0
if (balance < price) {
purchaseError.value = `Insufficient ecash balance (${balance} sats). Need ${price} sats. Fund your wallet, or pay from another wallet via QR.`
purchaseError.value = `Insufficient ecash balance (${balance} sats across Cashu + Fedimint). Need ${price} sats. Fund either wallet, or pay from another wallet via QR.`
closePayModal()
return
}