archy/neode-ui/src/components/SendBitcoinModal.vue

222 lines
9.2 KiB
Vue
Raw Normal View History

<template>
<BaseModal :show="show" :title="t('web5.sendBitcoinTitle')" max-width="max-w-2xl" content-class="max-h-[90vh] overflow-y-auto" @close="close">
<!-- Method tabs -->
<div class="flex gap-1 mb-4 p-1 bg-white/5 rounded-lg">
<button
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' ? 'Cashu' : m === 'fedimint' ? 'Fedi' : 'Ark' }}</button>
</div>
<div v-if="sendMethod === 'auto'" class="mb-3 p-2 bg-white/5 rounded-lg">
<p class="text-xs text-white/50">{{ t('sendBitcoin.autoMethodDesc') }}</p>
</div>
<div class="mb-3">
feat(setup): Zeus lightning journey — fund-wallet step, IBD timer + finish-setup toast, channel suggestions Every Lightning setup (Open a Shop, Accept Payments, Run a Lightning Node) gains a guided path to a working channel: - New "Fund Your Bitcoin Wallet" step, gated on the blockchain finishing its initial sync: while syncing it shows a live progress bar with a counting-down time-remaining estimate; once synced it shows the on-chain balance and a "Fund Wallet" button that opens the receive modal with a fresh address, QR, and the Zeus channel limits (min 150,000 / max 1,500,000 sats) noted. - When the sync finishes while a Lightning setup is mid-flight, a toast pops with a "Finish setup" link straight back to the wizard (toasts now support action links). If several setups are in flight, one is chosen — the shared steps complete the lightning part of any of them. - Channel steps are Zeus-branded (logo + copy) and land on the Lightning Channels screen, which now carries an "Open a channel with Zeus" card that prefills the open-channel modal with the Olympus peer URI, 150k sats, and private-channel checked. "Get Zeus" links to zeusln.com. - Setup completion now actually requires walking the manual steps (fund, open channel, configure) — previously any step whose app was installed was silently auto-ticked and running apps marked the whole goal done. - Completion CTAs now go to the app you just set up ("Go to my shop (BTCPay)" etc.) instead of the generic services list; iframe apps open in the on-top app overlay. - On-chain send modal gains a "Send all funds" sweep toggle, backed by LND's send_all on the backend (amount no longer required when sweeping). - Demo/mock: bitcoin.getinfo now returns the block_height/sync_progress contract the UI reads and simulates a ~90s IBD ramp per visitor so the timer, toast, and fund flow can all be demoed live; channel list data fixed (status/channel_point/liquidity totals — the panel previously crashed on the missing status field); sendcoins supports send_all. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 21:49:09 -04:00
<div class="flex items-center justify-between mb-1">
<label class="text-white/60 text-sm">{{ t('sendBitcoin.amountSats') }}</label>
<button
v-if="sendMethod === 'onchain'"
@click="toggleSendAll"
class="text-xs px-2 py-0.5 rounded border transition-colors"
:class="sendAll
? 'bg-orange-500/20 border-orange-500/40 text-orange-300'
: 'bg-white/5 border-white/15 text-white/60 hover:text-white/90'"
>
Send all funds
</button>
</div>
<input
v-model.number="amount"
type="number"
min="1"
:placeholder="sendAll ? '' : '1000'"
:disabled="sendAll"
class="w-full input-glass disabled:opacity-50"
/>
<p v-if="sendAll" class="text-xs text-white/50 mt-1">
Sweeps your entire on-chain balance{{ onchainBalance !== null ? ` (~${onchainBalance.toLocaleString()} sats)` : '' }} minus network fees.
</p>
</div>
<div v-if="effectiveMethod !== 'ecash'" class="mb-3">
<label class="text-white/60 text-sm block mb-1">
{{ effectiveMethod === 'lightning' ? t('sendBitcoin.lightningInvoice') : effectiveMethod === 'ark' ? 'Ark address, invoice or lightning address' : t('sendBitcoin.bitcoinAddress') }}
</label>
<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" 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>
<div v-if="resultTxid" class="mb-3 alert-success">
<p class="text-xs">{{ t('sendBitcoin.sentTx', { txid: resultTxid }) }}</p>
</div>
<div v-if="resultHash" class="mb-3 alert-success">
<p class="text-xs">{{ t('sendBitcoin.paidHash', { hash: resultHash }) }}</p>
</div>
<div v-if="resultArk" class="mb-3 alert-success">
<p class="text-xs">{{ resultArk }}</p>
</div>
<div v-if="error" class="mb-3 alert-error">{{ error }}</div>
<div class="flex gap-3">
<button @click="close" class="flex-1 glass-button px-4 py-2 rounded-lg text-sm">{{ t('common.close') }}</button>
<button @click="$emit('scan')" class="flex-1 glass-button px-4 py-2 rounded-lg text-sm font-medium flex items-center justify-center gap-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8V6a2 2 0 012-2h2M3 16v2a2 2 0 002 2h2m10-16h2a2 2 0 012 2v2m-4 12h2a2 2 0 002-2v-2M7 12h10" />
</svg>
Scan
</button>
feat(setup): Zeus lightning journey — fund-wallet step, IBD timer + finish-setup toast, channel suggestions Every Lightning setup (Open a Shop, Accept Payments, Run a Lightning Node) gains a guided path to a working channel: - New "Fund Your Bitcoin Wallet" step, gated on the blockchain finishing its initial sync: while syncing it shows a live progress bar with a counting-down time-remaining estimate; once synced it shows the on-chain balance and a "Fund Wallet" button that opens the receive modal with a fresh address, QR, and the Zeus channel limits (min 150,000 / max 1,500,000 sats) noted. - When the sync finishes while a Lightning setup is mid-flight, a toast pops with a "Finish setup" link straight back to the wizard (toasts now support action links). If several setups are in flight, one is chosen — the shared steps complete the lightning part of any of them. - Channel steps are Zeus-branded (logo + copy) and land on the Lightning Channels screen, which now carries an "Open a channel with Zeus" card that prefills the open-channel modal with the Olympus peer URI, 150k sats, and private-channel checked. "Get Zeus" links to zeusln.com. - Setup completion now actually requires walking the manual steps (fund, open channel, configure) — previously any step whose app was installed was silently auto-ticked and running apps marked the whole goal done. - Completion CTAs now go to the app you just set up ("Go to my shop (BTCPay)" etc.) instead of the generic services list; iframe apps open in the on-top app overlay. - On-chain send modal gains a "Send all funds" sweep toggle, backed by LND's send_all on the backend (amount no longer required when sweeping). - Demo/mock: bitcoin.getinfo now returns the block_height/sync_progress contract the UI reads and simulates a ~90s IBD ramp per visitor so the timer, toast, and fund flow can all be demoed live; channel list data fixed (status/channel_point/liquidity totals — the panel previously crashed on the missing status field); sendcoins supports send_all. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 21:49:09 -04:00
<button @click="send" :disabled="processing || (!amount && !isSweep)" class="flex-1 glass-button glass-button-warning px-4 py-2 rounded-lg text-sm font-medium disabled:opacity-50">
{{ processing ? t('common.sending') : t('common.send') }}
</button>
</div>
</BaseModal>
</template>
<script setup lang="ts">
import { ref, computed, watch, nextTick } from 'vue'
import { useI18n } from 'vue-i18n'
import { rpcClient } from '@/api/rpc-client'
import BaseModal from '@/components/BaseModal.vue'
const { t } = useI18n()
const props = defineProps<{ show: boolean }>()
const emit = defineEmits<{ close: []; sent: []; scan: [] }>()
// '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)
const error = ref('')
const resultTxid = ref('')
const resultHash = ref('')
const resultArk = ref('')
const ecashToken = ref('')
feat(setup): Zeus lightning journey — fund-wallet step, IBD timer + finish-setup toast, channel suggestions Every Lightning setup (Open a Shop, Accept Payments, Run a Lightning Node) gains a guided path to a working channel: - New "Fund Your Bitcoin Wallet" step, gated on the blockchain finishing its initial sync: while syncing it shows a live progress bar with a counting-down time-remaining estimate; once synced it shows the on-chain balance and a "Fund Wallet" button that opens the receive modal with a fresh address, QR, and the Zeus channel limits (min 150,000 / max 1,500,000 sats) noted. - When the sync finishes while a Lightning setup is mid-flight, a toast pops with a "Finish setup" link straight back to the wizard (toasts now support action links). If several setups are in flight, one is chosen — the shared steps complete the lightning part of any of them. - Channel steps are Zeus-branded (logo + copy) and land on the Lightning Channels screen, which now carries an "Open a channel with Zeus" card that prefills the open-channel modal with the Olympus peer URI, 150k sats, and private-channel checked. "Get Zeus" links to zeusln.com. - Setup completion now actually requires walking the manual steps (fund, open channel, configure) — previously any step whose app was installed was silently auto-ticked and running apps marked the whole goal done. - Completion CTAs now go to the app you just set up ("Go to my shop (BTCPay)" etc.) instead of the generic services list; iframe apps open in the on-top app overlay. - On-chain send modal gains a "Send all funds" sweep toggle, backed by LND's send_all on the backend (amount no longer required when sweeping). - Demo/mock: bitcoin.getinfo now returns the block_height/sync_progress contract the UI reads and simulates a ~90s IBD ramp per visitor so the timer, toast, and fund flow can all be demoed live; channel list data fixed (status/channel_point/liquidity totals — the panel previously crashed on the missing status field); sendcoins supports send_all. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 21:49:09 -04:00
// "Send all funds" — sweeps the whole on-chain balance (explicit on-chain tab only)
const sendAll = ref(false)
const onchainBalance = ref<number | null>(null)
const isSweep = computed(() => sendMethod.value === 'onchain' && sendAll.value)
function toggleSendAll() {
sendAll.value = !sendAll.value
if (sendAll.value && onchainBalance.value === null) {
rpcClient.call<{ balance_sats: number }>({ method: 'lnd.getinfo', timeout: 5000 })
.then((res) => { onchainBalance.value = res.balance_sats || 0 })
.catch(() => { /* balance hint is best-effort */ })
}
}
// Leaving the on-chain tab disarms the sweep so it can never apply elsewhere
watch(sendMethod, (m) => { if (m !== 'onchain') sendAll.value = false })
const effectiveMethod = computed(() => {
if (sendMethod.value !== 'auto') return sendMethod.value
const amt = amount.value || 0
if (amt <= 0) return 'lightning'
if (amt < 1000) return 'ecash'
if (amt > 500000) return 'onchain'
return 'lightning'
})
function close() {
error.value = ''
resultTxid.value = ''
resultHash.value = ''
resultArk.value = ''
ecashToken.value = ''
emit('close')
}
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() {
feat(setup): Zeus lightning journey — fund-wallet step, IBD timer + finish-setup toast, channel suggestions Every Lightning setup (Open a Shop, Accept Payments, Run a Lightning Node) gains a guided path to a working channel: - New "Fund Your Bitcoin Wallet" step, gated on the blockchain finishing its initial sync: while syncing it shows a live progress bar with a counting-down time-remaining estimate; once synced it shows the on-chain balance and a "Fund Wallet" button that opens the receive modal with a fresh address, QR, and the Zeus channel limits (min 150,000 / max 1,500,000 sats) noted. - When the sync finishes while a Lightning setup is mid-flight, a toast pops with a "Finish setup" link straight back to the wizard (toasts now support action links). If several setups are in flight, one is chosen — the shared steps complete the lightning part of any of them. - Channel steps are Zeus-branded (logo + copy) and land on the Lightning Channels screen, which now carries an "Open a channel with Zeus" card that prefills the open-channel modal with the Olympus peer URI, 150k sats, and private-channel checked. "Get Zeus" links to zeusln.com. - Setup completion now actually requires walking the manual steps (fund, open channel, configure) — previously any step whose app was installed was silently auto-ticked and running apps marked the whole goal done. - Completion CTAs now go to the app you just set up ("Go to my shop (BTCPay)" etc.) instead of the generic services list; iframe apps open in the on-top app overlay. - On-chain send modal gains a "Send all funds" sweep toggle, backed by LND's send_all on the backend (amount no longer required when sweeping). - Demo/mock: bitcoin.getinfo now returns the block_height/sync_progress contract the UI reads and simulates a ~90s IBD ramp per visitor so the timer, toast, and fund flow can all be demoed live; channel list data fixed (status/channel_point/liquidity totals — the panel previously crashed on the missing status field); sendcoins supports send_all. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 21:49:09 -04:00
if (processing.value) return
if (!amount.value && !isSweep.value) return
processing.value = true
error.value = ''
ecashToken.value = ''
resultTxid.value = ''
resultHash.value = ''
resultArk.value = ''
const method = effectiveMethod.value
try {
if (method === 'ark') {
if (!dest.value.trim()) { error.value = 'Enter an Ark address, invoice or lightning address'; return }
await rpcClient.call<{ sent: boolean }>({
method: 'wallet.ark-send',
params: { destination: dest.value.trim(), amount_sats: amount.value },
// Ark sends can wait on round participation.
timeout: 130000,
})
resultArk.value = `Sent ${amount.value.toLocaleString()} sats via Ark`
} else if (method === 'ecash') {
const res = await rpcClient.call<{ token: string }>({
method: 'wallet.ecash-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 }>({
method: 'lnd.payinvoice',
params: { payment_request: dest.value.trim() },
})
resultHash.value = res.payment_hash
} else {
if (!dest.value.trim()) { error.value = t('web5.enterBitcoinAddress'); return }
const res = await rpcClient.call<{ txid: string }>({
method: 'lnd.sendcoins',
feat(setup): Zeus lightning journey — fund-wallet step, IBD timer + finish-setup toast, channel suggestions Every Lightning setup (Open a Shop, Accept Payments, Run a Lightning Node) gains a guided path to a working channel: - New "Fund Your Bitcoin Wallet" step, gated on the blockchain finishing its initial sync: while syncing it shows a live progress bar with a counting-down time-remaining estimate; once synced it shows the on-chain balance and a "Fund Wallet" button that opens the receive modal with a fresh address, QR, and the Zeus channel limits (min 150,000 / max 1,500,000 sats) noted. - When the sync finishes while a Lightning setup is mid-flight, a toast pops with a "Finish setup" link straight back to the wizard (toasts now support action links). If several setups are in flight, one is chosen — the shared steps complete the lightning part of any of them. - Channel steps are Zeus-branded (logo + copy) and land on the Lightning Channels screen, which now carries an "Open a channel with Zeus" card that prefills the open-channel modal with the Olympus peer URI, 150k sats, and private-channel checked. "Get Zeus" links to zeusln.com. - Setup completion now actually requires walking the manual steps (fund, open channel, configure) — previously any step whose app was installed was silently auto-ticked and running apps marked the whole goal done. - Completion CTAs now go to the app you just set up ("Go to my shop (BTCPay)" etc.) instead of the generic services list; iframe apps open in the on-top app overlay. - On-chain send modal gains a "Send all funds" sweep toggle, backed by LND's send_all on the backend (amount no longer required when sweeping). - Demo/mock: bitcoin.getinfo now returns the block_height/sync_progress contract the UI reads and simulates a ~90s IBD ramp per visitor so the timer, toast, and fund flow can all be demoed live; channel list data fixed (status/channel_point/liquidity totals — the panel previously crashed on the missing status field); sendcoins supports send_all. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 21:49:09 -04:00
params: isSweep.value
? { addr: dest.value.trim(), send_all: true }
: { addr: dest.value.trim(), amount: amount.value },
})
resultTxid.value = res.txid
}
emit('sent')
} catch (err: unknown) {
error.value = err instanceof Error ? err.message : t('web5.sendFailed')
} finally {
processing.value = false
}
}
</script>