Skip to content

[Bug]: CJIT at suggested minimum yields unspendable channel while app reports 354 sats received #1265

Description

@piotr-iohk

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. 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:

Screen Shows Reality
Receive sheet + activity item Received +354 165 pushed
Lightning Connections → spending balance / Connection 1 ↑354 0 spendable
Home → Spending 0 correct

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):

  1. 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).
  2. 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

  1. New mainnet wallet, no channels.
  2. Receive → enter an amount → choose the spending-balance (CJIT) option at the suggested minimum amount (2000 sats today).
  3. Note the confirmation screen shows amount − fee (165 sats here; Blocktank total fee 1835).
  4. Pay the invoice from another wallet.
  5. 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).

Logs / Screenshots / Recordings

Home: 0 balance, activity shows Received +354

CJIT details: channel 671850, total fee 1835 (network 1056, service 779)

Lightning Connections: spending balance 354, receiving 664307

Logcat excerpt (UTC), channel 18ca78cf…, LSP 02a37103… (Blocktank-LND4), CJIT 41b441a1-998b-45ee-a378-70b4ed8e5ba7:

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'

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

  • 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.swift amountOnClose (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.
  • Related: [Bug]: CJIT received sheet & activity item have wrong amount #626 (closed — CJIT sheet/activity wrong amount; amountOnClose was the fix and still overstates in this edge case), LN Balance not correct #615 (open — channel balance ≠ home balance).
  • Not a 2.5.0 regression; both formulas pre-date it. Money-losing at the app's own suggested minimum on mainnet, though.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions