You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fresh mainnet wallet (after wipe). Receive → spending balance setup (CJIT), amount = the app's suggested minimum 2000 sats. Paid the invoice from another Bitkit.
Blocktank quoted a total fee of 1835 sats (network 1056 + service 779) and opened a 0-conf channel of 671 850 sats with push_msat: 165000 — i.e. exactly 2000 − 1835 = 165 sats pushed to us. That is below the 354-sat channel reserve and below the 354-sat dust limit, so ldk-node reports outbound_capacity_msat = 0 and ClaimableOnChannelClose.amount_satoshis = 0. The 165 sats are neither spendable nor claimable on close.
The app then shows three different numbers for the same channel:
The 354 comes from ChannelDetails.amountOnClose = outboundCapacity + unspendablePunishmentReserve (0 + 354), which assumes we always hold at least the reserve. It coincidentally equals the reserve, not anything the user received.
Root causes (both in app code, both shared with iOS):
Minimum amount ignores the channel reserve.BlocktankRepo.refreshMinCjitSats: minCjitSats = ceil(feeSat × 1.1 / 1000) × 1000. This only guarantees ~10 % of the fee is left after fees (~180 sats at today's ~1.8k fee), which is below the 354-sat reserve/dust. Whether the user ends up with a usable channel depends on where Blocktank's fee lands at that moment — the same 2000-sat CJIT on iOS an hour earlier happened to leave >354 and was spendable. The estimate also uses getDefaultLspBalance(clientBalance = 0) while the real order uses amount + lspBalance, so the actual fee (1835) can exceed the fee the minimum was derived from (≤ 1818).
amountOnClose overstates when balance < reserve (ext/ChannelDetails.kt, used by ActivityRepo.insertActivityFromCjit, NotifyChannelReadyHandler, WakeNodeWorker, Lightning Connections). There is already a // TODO: use channelDetails.claimableOnCloseSats.
Expected behavior
The suggested minimum CJIT amount guarantees a usable result: amount − feeSat ≥ channel reserve (354) + a sane margin (e.g. ≥ 1000 sats net), or the flow refuses/warns when amount − fee would be unspendable.
The received sheet, activity item and Lightning Connections show the real amount: invoiceSat − feeSat (165) for the CJIT receive, and claimableOnCloseSats / outbound for balances — consistent with Home (0 spendable).
Steps to Reproduce
New mainnet wallet, no channels.
Receive → enter an amount → choose the spending-balance (CJIT) option at the suggested minimum amount (2000 sats today).
Note the confirmation screen shows amount − fee (165 sats here; Blocktank total fee 1835).
Pay the invoice from another wallet.
Receive sheet / activity show +354; Lightning Connections shows ↑354; Home shows Spending 0; nothing can be sent.
Deterministic whenever Blocktank's fee is within 354 sats of the suggested minimum (minCjitSats − feeSat ≤ 354).
07:24:29 DEBUG [BlocktankRepo.kt:251] Updated minCjitSats to: 2000
07:24:49 TRACE Received message OpenChannel(… funding_satoshis: 671850, dust_limit_satoshis: 354, … push_msat: 165000, channel_reserve_satoshis: 354 })
07:24:49 TRACE Handling event OpenChannelRequest { … funding_satoshis: 671850, channel_negotiation_type: PushMsat(165000), … }
07:24:50 INFO Accepting inbound 0conf Anchor channel of 671850sats from trusted peer 02a37103…
07:24:50 TRACE Building commitment transaction number 281474976710655 … for us … including to_remote output with value 668436 ← no to_local output
07:24:50 INFO Channel 18ca78cf… ready to be used with funding_txo aecb6598…:0
07:24:51 VERBOSE Balances in ldk-node: {"totalLightningBalanceSats":0,"lightningBalances":[{"type":"…ClaimableOnChannelClose","channelId":"18ca78cf…","amountSatoshis":0,…}]}
07:24:56 INFO Synced 1 payments successfully (CJIT activity inserted with amountOnClose = 0 + 354)
07:27:18 INFO channel support summary: channelId='18ca78cf…', ready='true', usable='false', outboundMsat='0', inboundMsat='664307000'
Mainnet, new wallet right after a wipe, Lightning / CJIT.
No HTLC is involved — Blocktank delivers CJIT via push_msat, so the LSP behaved as quoted; both problems are on the app side.
Also affects iOS (same logic): BlocktankViewModel.swift:455 (ceil(Double(fees.feeSat) * 1.1 / 1000) * 1000) and ViewModels/Extensions/ChannelDetails.swiftamountOnClose (outboundCapacityMsat / 1000 + unspendablePunishmentReserve), used by TransferViewModel for the CJIT receive. The same 2000-sat CJIT on iOS was spendable only because the fee at that time left more than 354 sats.
What happened?
Fresh mainnet wallet (after wipe). Receive → spending balance setup (CJIT), amount = the app's suggested minimum 2000 sats. Paid the invoice from another Bitkit.
Blocktank quoted a total fee of 1835 sats (network 1056 + service 779) and opened a 0-conf channel of 671 850 sats with
push_msat: 165000— i.e. exactly2000 − 1835 = 165 satspushed to us. That is below the 354-sat channel reserve and below the 354-sat dust limit, so ldk-node reportsoutbound_capacity_msat = 0andClaimableOnChannelClose.amount_satoshis = 0. The 165 sats are neither spendable nor claimable on close.The app then shows three different numbers for the same channel:
The 354 comes from
ChannelDetails.amountOnClose = outboundCapacity + unspendablePunishmentReserve(0 + 354), which assumes we always hold at least the reserve. It coincidentally equals the reserve, not anything the user received.Root causes (both in app code, both shared with iOS):
BlocktankRepo.refreshMinCjitSats:minCjitSats = ceil(feeSat × 1.1 / 1000) × 1000. This only guarantees ~10 % of the fee is left after fees (~180 sats at today's ~1.8k fee), which is below the 354-sat reserve/dust. Whether the user ends up with a usable channel depends on where Blocktank's fee lands at that moment — the same 2000-sat CJIT on iOS an hour earlier happened to leave >354 and was spendable. The estimate also usesgetDefaultLspBalance(clientBalance = 0)while the real order usesamount + lspBalance, so the actual fee (1835) can exceed the fee the minimum was derived from (≤ 1818).amountOnCloseoverstates when balance < reserve (ext/ChannelDetails.kt, used byActivityRepo.insertActivityFromCjit,NotifyChannelReadyHandler,WakeNodeWorker, Lightning Connections). There is already a// TODO: use channelDetails.claimableOnCloseSats.Expected behavior
amount − feeSat ≥ channel reserve (354) + a sane margin(e.g. ≥ 1000 sats net), or the flow refuses/warns whenamount − feewould be unspendable.invoiceSat − feeSat(165) for the CJIT receive, andclaimableOnCloseSats/ outbound for balances — consistent with Home (0 spendable).Steps to Reproduce
amount − fee(165 sats here; Blocktank total fee 1835).Deterministic whenever Blocktank's fee is within 354 sats of the suggested minimum (
minCjitSats − feeSat ≤ 354).Logs / Screenshots / Recordings
Logcat excerpt (UTC), channel
18ca78cf…, LSP02a37103…(Blocktank-LND4), CJIT41b441a1-998b-45ee-a378-70b4ed8e5ba7:Full excerpt (122 lines) staged as
2026-09-15-cjit-minimum-unspendable-logcat-excerpt.txt
Bitkit Version
2.5.0 (mainnet,
to.bitkit)Device / OS
Samsung Galaxy S22 (SM-S901B), Android 16 (One UI 8)
Reproducibility
Sometimes (<50%)
Additional context
push_msat, so the LSP behaved as quoted; both problems are on the app side.BlocktankViewModel.swift:455(ceil(Double(fees.feeSat) * 1.1 / 1000) * 1000) andViewModels/Extensions/ChannelDetails.swiftamountOnClose(outboundCapacityMsat / 1000 + unspendablePunishmentReserve), used byTransferViewModelfor the CJIT receive. The same 2000-sat CJIT on iOS was spendable only because the fee at that time left more than 354 sats.amountOnClosewas the fix and still overstates in this edge case), LN Balance not correct #615 (open — channel balance ≠ home balance).