feat(wallet): LND wedge watchdog + Fedi send rail + token QRs + Auto hidden
Demo images / Build & push demo images (push) Failing after 33s
Demo images / Build & push demo images (push) Failing after 33s
- LND watchdog: framework-pt's LND sat wedged 14h (RPC up, server never ready, channels inactive) with nothing noticing. 15 consecutive bad minutes now bounce the container automatically (30min cooldown) — silent multi-hour Lightning outages become self-healed blips. - Send modal: Auto tab hidden; Ecash splits into Cashu and Fedi (new wallet.fedimint-send RPC wrapping the existing spend_from_any); both rails render the generated token as a scannable QR alongside the copyable text. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
e68fe071a7
commit
df9b9905b6
@@ -3,12 +3,12 @@
|
||||
<!-- Method tabs -->
|
||||
<div class="flex gap-1 mb-4 p-1 bg-white/5 rounded-lg">
|
||||
<button
|
||||
v-for="m in (['auto', 'lightning', 'onchain', 'ecash', 'ark'] as const)"
|
||||
v-for="m in (['lightning', 'onchain', 'ecash', 'fedimint', 'ark'] as const)"
|
||||
:key="m"
|
||||
@click="sendMethod = m"
|
||||
class="flex-1 px-2 py-1.5 rounded text-xs font-medium capitalize transition-colors"
|
||||
:class="sendMethod === m ? 'bg-white/15 text-white' : 'text-white/50 hover:text-white/80'"
|
||||
>{{ m === 'onchain' ? t('sendBitcoin.onChain') : m === 'lightning' ? t('sendBitcoin.lightning') : m === 'ecash' ? t('sendBitcoin.ecash') : m === 'ark' ? 'Ark' : t('sendBitcoin.auto') }}</button>
|
||||
>{{ m === 'onchain' ? t('sendBitcoin.onChain') : m === 'lightning' ? t('sendBitcoin.lightning') : m === 'ecash' ? 'Cashu' : m === 'fedimint' ? 'Fedi' : 'Ark' }}</button>
|
||||
</div>
|
||||
|
||||
<div v-if="sendMethod === 'auto'" class="mb-3 p-2 bg-white/5 rounded-lg">
|
||||
@@ -49,8 +49,13 @@
|
||||
<textarea v-model="dest" rows="2" :placeholder="effectiveMethod === 'lightning' ? 'lnbc...' : effectiveMethod === 'ark' ? 'tark1… / lnbc… / user@lnaddress' : 'bc1...'" class="w-full input-glass font-mono"></textarea>
|
||||
</div>
|
||||
|
||||
<div v-if="ecashToken && effectiveMethod === 'ecash'" class="mb-3 p-2 bg-white/5 rounded-lg">
|
||||
<div v-if="ecashToken" class="mb-3 p-2 bg-white/5 rounded-lg">
|
||||
<p class="text-white/50 text-xs mb-1">{{ t('sendBitcoin.tokenShareLabel') }}</p>
|
||||
<!-- QR so the recipient can scan the token straight off this screen
|
||||
(animated multi-frame not needed: qrcode handles these sizes). -->
|
||||
<div class="flex justify-center my-2">
|
||||
<canvas ref="tokenQrCanvas" class="rounded-lg bg-white p-2"></canvas>
|
||||
</div>
|
||||
<p class="text-xs font-mono text-white/80 break-all">{{ ecashToken }}</p>
|
||||
<button @click="copyText(ecashToken)" class="mt-2 text-xs text-orange-400 hover:text-orange-300">{{ t('common.copy') }}</button>
|
||||
</div>
|
||||
@@ -83,7 +88,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { ref, computed, watch, nextTick } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { rpcClient } from '@/api/rpc-client'
|
||||
import BaseModal from '@/components/BaseModal.vue'
|
||||
@@ -93,7 +98,9 @@ const { t } = useI18n()
|
||||
const props = defineProps<{ show: boolean }>()
|
||||
const emit = defineEmits<{ close: []; sent: []; scan: [] }>()
|
||||
|
||||
const sendMethod = ref<'auto' | 'lightning' | 'onchain' | 'ecash' | 'ark'>('auto')
|
||||
// 'auto' remains in the type for the effectiveMethod logic but is no longer
|
||||
// offered as a tab (hidden per operator request 2026-07-22).
|
||||
const sendMethod = ref<'auto' | 'lightning' | 'onchain' | 'ecash' | 'fedimint' | 'ark'>('lightning')
|
||||
const amount = ref<number>(0)
|
||||
const dest = ref('')
|
||||
const processing = ref(false)
|
||||
@@ -142,6 +149,17 @@ function copyText(text: string) {
|
||||
navigator.clipboard.writeText(text).catch(() => {})
|
||||
}
|
||||
|
||||
const tokenQrCanvas = ref<HTMLCanvasElement | null>(null)
|
||||
watch(ecashToken, async (token) => {
|
||||
if (!token) return
|
||||
await nextTick()
|
||||
if (!tokenQrCanvas.value) return
|
||||
try {
|
||||
const QRCode = await import('qrcode')
|
||||
await QRCode.toCanvas(tokenQrCanvas.value, token, { width: 220, margin: 1 })
|
||||
} catch { /* QR is a convenience — the copyable text is authoritative */ }
|
||||
})
|
||||
|
||||
async function send() {
|
||||
if (processing.value) return
|
||||
if (!amount.value && !isSweep.value) return
|
||||
@@ -169,6 +187,13 @@ async function send() {
|
||||
params: { amount_sats: amount.value },
|
||||
})
|
||||
ecashToken.value = res.token
|
||||
} else if (method === 'fedimint') {
|
||||
const res = await rpcClient.call<{ token: string }>({
|
||||
method: 'wallet.fedimint-send',
|
||||
params: { amount_sats: amount.value },
|
||||
timeout: 60000,
|
||||
})
|
||||
ecashToken.value = res.token
|
||||
} else if (method === 'lightning') {
|
||||
if (!dest.value.trim()) { error.value = t('web5.pasteInvoice'); return }
|
||||
const res = await rpcClient.call<{ payment_hash: string }>({
|
||||
|
||||
Reference in New Issue
Block a user