fix(wallet): gate lightning on CHANNELS, not just node state
Demo images / Build & push demo images (push) Successful in 3m24s
Demo images / Build & push demo images (push) Successful in 3m24s
A running LND with zero channels happily mints an invoice — it is simply unpayable, because nobody has a route in. So the state-only gate let receive through and handed the user a useless invoice, and let send walk to confirm. Neither errored, so the funding modal (wired to failures) never fired. requireLightningReady(direction) now asks lnd.listchannels and checks the liquidity that actually matters for the attempt: total_inbound to receive, total_outbound to send. It fails OPEN on an RPC error — a transient blip should not block a working wallet. The no-funds mode says plainly that a channel is needed, in the direction's own terms (inbound vs outbound), and offers both routes: "Open a channel" straight to the channels screen where the Zeus/Olympus flow is already prefilled, and "Setup Guide" to the run-lightning-node walkthrough for someone who wants the whole path explained. Buttons wrap rather than squeeze on narrow screens. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
700947ea3c
commit
c3d5bcd271
@@ -17,6 +17,7 @@
|
||||
// lnd container at all.
|
||||
import { ref } from 'vue'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { rpcClient } from '@/api/rpc-client'
|
||||
import { PackageState } from '@/types/api'
|
||||
|
||||
/** Package ids that provide a Lightning node.
|
||||
@@ -37,6 +38,8 @@ export type LightningStatus = 'absent' | 'stopped' | 'running' | 'no-funds'
|
||||
// global modal mounted in App.vue.
|
||||
const show = ref(false)
|
||||
const status = ref<LightningStatus>('absent')
|
||||
/** Which direction raised the funding modal, so the copy can be specific. */
|
||||
const fundingDirection = ref<'send' | 'receive'>('receive')
|
||||
|
||||
export function useLightningRequired() {
|
||||
const appStore = useAppStore()
|
||||
@@ -113,13 +116,44 @@ export function useLightningRequired() {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* The full readiness gate: node installed AND running AND with liquidity in
|
||||
* the direction being attempted.
|
||||
*
|
||||
* Node state alone is not enough — LND happily mints an invoice with zero
|
||||
* channels, so a state-only gate hands the user an invoice nobody can pay
|
||||
* (and a send that can only fail). `receive` needs inbound liquidity,
|
||||
* `send` needs outbound.
|
||||
*
|
||||
* Fails OPEN on an RPC error: if we cannot read the channel list we let the
|
||||
* attempt proceed rather than block a working wallet on a transient blip.
|
||||
*/
|
||||
async function requireLightningReady(direction: 'send' | 'receive'): Promise<boolean> {
|
||||
if (!requireLightningNode()) return false
|
||||
try {
|
||||
const res = await rpcClient.call<{ total_inbound?: number; total_outbound?: number }>({
|
||||
method: 'lnd.listchannels',
|
||||
timeout: 15000,
|
||||
})
|
||||
const liquidity = direction === 'receive' ? res?.total_inbound ?? 0 : res?.total_outbound ?? 0
|
||||
if (liquidity > 0) return true
|
||||
fundingDirection.value = direction
|
||||
openLightningFunding()
|
||||
return false
|
||||
} catch {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
show,
|
||||
fundingDirection,
|
||||
status,
|
||||
lightningStatus,
|
||||
hasLightningNode,
|
||||
requireLightningNode,
|
||||
openLightningFunding,
|
||||
requireLightningReady,
|
||||
handleLightningFailure,
|
||||
close,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user