feat(wallet-ui): Ark send/receive tabs + Ark pay-with option on peer files

Send modal gains an Ark tab (address/invoice/lightning-address via
wallet.ark-send), Receive gains an Ark address + QR tab, and the peer
file purchase confirm offers Ark alongside Cashu/Fedimint. Demo mock
exposes ark_sats in wallet.ecash-balance and deducts the chosen rail on
paid downloads.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-07-15 09:53:53 +01:00
co-authored by Claude Fable 5
parent 46f7ac3fcf
commit 97488c83f7
4 changed files with 75 additions and 22 deletions
+15 -12
View File
@@ -441,16 +441,16 @@
switch to the other if it has enough balance. -->
<div class="space-y-2">
<button
v-for="b in (['cashu', 'fedimint'] as const)"
v-for="b in (['cashu', 'fedimint', 'ark'] as const)"
:key="b"
@click="ecashPlan.chosen = b"
:disabled="ecashBalanceOf(b) < getItemPrice(payItem.access)"
class="w-full px-4 py-3 rounded-xl flex items-center gap-3 text-left border transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
:class="ecashPlan.chosen === b ? 'border-green-400/70 bg-green-400/10' : 'border-white/10 bg-white/5 hover:bg-white/10'"
>
<span class="text-xl shrink-0">{{ b === 'cashu' ? '🥜' : '🤝' }}</span>
<span class="text-xl shrink-0">{{ b === 'cashu' ? '🥜' : b === 'fedimint' ? '🤝' : '' }}</span>
<span class="flex-1 min-w-0">
<span class="block text-base text-white">{{ b === 'cashu' ? 'Cashu' : 'Fedimint' }}</span>
<span class="block text-base text-white">{{ b === 'cashu' ? 'Cashu' : b === 'fedimint' ? 'Fedimint' : 'Ark' }}</span>
<span class="block text-xs text-white/50">Balance: {{ ecashBalanceOf(b).toLocaleString() }} sats<span v-if="ecashBalanceOf(b) < getItemPrice(payItem.access)"> · not enough</span></span>
</span>
<svg v-if="ecashPlan.chosen === b" class="w-5 h-5 text-green-400 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -707,10 +707,11 @@ const payMode = ref<'choose' | 'ecash-confirm' | 'qr'>('choose')
// Ecash confirmation step: after the user picks "pay from this node's ecash",
// we look at both balances, decide which backend covers the price, and show a
// confirm screen so they see (and can switch) which ecash is spent (#3).
type EcashBackend = 'cashu' | 'fedimint'
type EcashBackend = 'cashu' | 'fedimint' | 'ark'
const ecashPlan = ref<{
cashu: number
fedimint: number
ark: number
total: number
chosen: EcashBackend | null
} | null>(null)
@@ -1135,7 +1136,7 @@ async function pollOnchain(address: string) {
/** Spendable balance for a given ecash backend in the current plan. */
function ecashBalanceOf(b: EcashBackend): number {
if (!ecashPlan.value) return 0
return b === 'cashu' ? ecashPlan.value.cashu : ecashPlan.value.fedimint
return b === 'cashu' ? ecashPlan.value.cashu : b === 'fedimint' ? ecashPlan.value.fedimint : ecashPlan.value.ark
}
/**
@@ -1152,23 +1153,25 @@ async function prepareEcashPay() {
try {
let cashu = 0
let fedimint = 0
let ark = 0
try {
const res = await rpcClient.call<{ cashu_sats?: number; fedimint_sats?: number; total_sats?: number; balance_sats?: number }>({
const res = await rpcClient.call<{ cashu_sats?: number; fedimint_sats?: number; ark_sats?: number; total_sats?: number; balance_sats?: number }>({
method: 'wallet.ecash-balance',
})
cashu = res?.cashu_sats ?? res?.balance_sats ?? 0
fedimint = res?.fedimint_sats ?? 0
ark = res?.ark_sats ?? 0
} catch {
// Couldn't read balances — let the user try anyway (auto backend).
}
const total = cashu + fedimint
// Prefer Cashu when it covers the price, else Fedimint, else leave null
// (insufficient — shown in the confirm screen, Confirm disabled).
const total = cashu + fedimint + ark
// Prefer Cashu when it covers the price, else Fedimint, else Ark, else
// leave null (insufficient — shown in the confirm screen, Confirm disabled).
const chosen: EcashBackend | null =
cashu >= price ? 'cashu' : fedimint >= price ? 'fedimint' : null
ecashPlan.value = { cashu, fedimint, total, chosen }
cashu >= price ? 'cashu' : fedimint >= price ? 'fedimint' : ark >= price ? 'ark' : null
ecashPlan.value = { cashu, fedimint, ark, total, chosen }
if (!chosen) {
purchaseError.value = `Not enough ecash: Cashu ${cashu} + Fedimint ${fedimint} sats, need ${price}. Fund a wallet, or pay another way.`
purchaseError.value = `Not enough funds: Cashu ${cashu} + Fedimint ${fedimint} + Ark ${ark} sats, need ${price}. Fund a wallet, or pay another way.`
}
payMode.value = 'ecash-confirm'
} finally {