Skip to content

feat: stop offering new lightning channels - #1670

Closed
ovitrif wants to merge 8 commits into
masterfrom
feat/stop-new-channels
Closed

ovitrif wants to merge 8 commits into
masterfrom
feat/stop-new-channels

Conversation

@ovitrif

@ovitrif ovitrif commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #1669
Twin: synonymdev/bitkit-ios#1102
Companion: synonymdev/bitkit-e2e-tests#284

This PR stops the app from buying new Lightning channels from Blocktank and tells users with Blocktank channels the date their channels close, so they can move their spending balance to savings first.

Description

  • Makes Transfer To Spending on the Savings wallet, Transfer from Savings on the Spending wallet and Transfer To Spending on the hardware wallet open the "Transfer Unavailable" sheet from @aldertnl's design instead of the transfer flow, so users who try to move savings to spending learn why and no Blocktank order is created. The sheet reads "MANUAL SETUP REQUIRED" and "Automatic transfers to your spending balance are no longer available. Use your own node, or set up a connection with another provider.", with Cancel and Continue.
  • Adds a "Choose a Lightning provider" screen, built from the Third-party LSP design, that Continue leads to. It lists the design's four third-party LSPs, each opening its own channel order page in the browser. Its footer offers Manual Setup (the existing manual channel setup) and QR Code (the existing scanner, which claims the LNURL channel or node QR the provider gives).
  • Removes the Home "Spend" suggestion card, which promoted the transfer and funding flow.
  • Points Lightning Connections › add connection at Funding Advanced (LNURL channel and manual setup), which opens channels to a node of the user's choice, never a Blocktank order.
  • Removes instant receive (CJIT) from the Receive sheet: the "Receive Lightning funds" button and onboarding are gone, and the Spending and Auto tabs appear only when the wallet can invoice over a ready channel with enough inbound liquidity, so a wallet without one gets the on-chain Savings tab only.
  • Removes the CJIT offer from Edit Invoice: an amount above inbound liquidity now updates the invoice and returns to the QR, where the sheet falls back to Savings.
  • Removes gift codes end to end (sheet, claim and gift order); scanning one shows the generic QR error toast.
  • Adds a Home banner, "CHANNELS CLOSE ON DEC 15, 2026", for wallets with a channel to Blocktank (open or pending); tapping it opens the Spending wallet, which shows "Your Lightning channels will be closed on Dec 15, 2026. Move your spending balance to savings before then." above Transfer To Savings. Channels to other nodes do not show it.
  • Sets the date in one constant, CHANNELS_CLOSE_DATE, with a placeholder of Dec 15, 2026 until the shutdown date is final, formatted in the locale's medium date style.
  • Keeps send, receive over an existing channel and transfer to savings unchanged; the new strings are translated in every locale, and the strings and drawable only the removed flows used are deleted.
  • Removes the app's Blocktank order, CJIT, geocheck and refund-address calls and the trusted-peer lookup, so Blocktank can drop those endpoints; the list below names each removed and kept call.
  • Updates the hardcoded address of Blocktank's LND4 node to the one Blocktank's info now lists, since the node list no longer comes from info.nodes; with the old address, channels with LND4 could not reconnect.
  • Retires the journeys for removed flows, updates the ones whose route or setup used them, and adds journeys for the notice and for on-chain-only receive.

The purchase screens (Funding, Transfer intro, the spending amount, confirm and advanced screens, the hardware-wallet signing screens, and the receive amount, confirm and liquidity screens) stay in the navigation graph with no UI entry point. Release builds disable screen deep links, so only dev mode in a debug build reaches them.

Out of Scope

  • ui/screens/transfer, TransferViewModel, BlocktankRepo: deleting the dormant purchase screens and the order and CJIT code they call; this PR only removes every way to reach them.
  • Blocktank channels: closing them, and stopping orders on Blocktank's server.
  • Lightning replacement: swaps or other providers.
  • Notice while the node is not running: the banner and notice read the node's channel list, so they are absent during node startup, after a node start error and in recovery mode.
  • Fee estimates and FX rates: they still come from Blocktank's info and FX endpoints until another source replaces them.
  • Home "Spend" suggestion card: it stays removed rather than opening the Transfer Unavailable sheet.
  • Gift codes: a dedicated "no longer supported" message instead of the generic QR error.
  • Spending wallet empty state: its "Send bitcoin to your spending balance" copy is unchanged.

Third-party LSPs

The list and order are the Third-party LSP design's, and the team removes any it doesn't want to be associated with at review. Each link answered HTTP 200 on 22 Oct:

I didn't open a mainnet channel with any of them. They can be removed by deleting their entries in THIRD_PARTY_LSPS.

LNURL-channel claims from these providers go through the existing LNURL channel flow. That flow connects to whatever node the QR names, sends the channel request, and doesn't limit it to Blocktank. LDK accepts the inbound channel from a peer it doesn't trust (the anchor reserve per channel is 1 sat). Zero-conf stays limited to Blocktank's nodes, so a third-party channel becomes usable after it confirms. I didn't try a claim against a live provider.

Blocktank API calls

This PR removes every order and CJIT call outside the dev screens and the React Native migration, so Blocktank can drop those endpoints (Ovi's scope, confirmed by Blocktank).

Removed:

  • Geocheck (/api/geocheck): the region check that gated channel purchases. The geo-blocked state, its UI variants and the onboarding geo note are gone with it.
  • Trusted peers from info.nodes: the node now always uses the hardcoded Blocktank peers in Env.trustedLnPeers, which were already the fallback.
  • Order and CJIT polling: the two-minute loop, the refresh at app start and on every foreground, the pull-to-refresh fetch on Lightning Connections and Channel Detail, and the order fetch for transfers still pending.
  • Order and CJIT reads now come from the local cache only:
    • the CJIT lookup on channel ready
    • order lookups for activity and channel details
    • recovering an interrupted transfer payment
  • Opening a paid order's channel: neither app start nor the push wake for a paid order calls open any more.
  • Refund addresses: the app no longer allocates or sends one. The cached pointer stays in the shared backup shape, and native SegWit monitoring stays required so refunds of past orders still arrive.
  • Order creation, CJIT and gift codes: no UI reaches order creation or CJIT (see above), and the gift-code calls are deleted.

Kept:

  • info(): fee estimates for every on-chain send (info.onchain.feeRates). Removing it needs another fee source first.
  • FX rates (/api/fx/rates/btc): fiat conversion. Needs another source first.
  • Push registration and wake (/api/notifications): receiving while the app is closed, until the channels close in about six weeks.
  • React Native migration: a user migrating from the old Bitkit app has their past orders read once, to rebuild channel and transfer history. Removing that needs migration work; open for Ovi and Blocktank.
  • Dev screens: the regtest tools and channel orders screen.

Not Blocktank API but hosted on Blocktank domains, unchanged: RGS snapshots, the scorer, VSS backups, LNURL auth, and the staging Electrum and mempool servers.

Orders pending at upgrade: an order that was paid before the upgrade, and whose channel the app had not yet asked Blocktank to open, is no longer opened by the app. Blocktank has to open or refund it from its side. A transfer whose channel already exists still settles when the channel becomes ready.

Design

  • Choose a Lightning provider: Bitkit - Final Review v63 › Third-party LSP › Choose LSP, from the Third-party LSP section. The frame is not on the Handoff page yet, so docs/screens-map.md lists the screen as todo. The design embeds the provider's page in the app; this PR opens it in the browser.
  • Transfer Unavailable sheet: @aldertnl's design for this flow, in the Blocktank wind-down thread and the same Third-party LSP section. It drops the close-date line the sheet had earlier; the Home banner and the Spending notice keep the date.
  • The banner and the Spending notice have no design.

Preview

Next to the design, each pair with the Figma frame on the left and the app on the right:

Choose a Lightning provider Transfer Unavailable

Transfer To Spending without Blocktank, on regtest (2x):

Sheet to provider list Manual setup to an LSP node
j12-transfer-unavailable-2xs.mp4
j10-manual-from-choose-lsp-2xs.mp4

Closing notice for a Blocktank channel (the banner title shrinks to fit one line), and a wallet without channels (2x):

Banner and notice Without a channel
j1b-banner-2xs.mp4
j2-onchain-only-2xs.mp4

An existing channel keeps working (2x):

Receive Send Transfer to savings
j3-receive-2xs.mp4
j4-send-2xs.mp4
to-savings-m5a-2xs.mp4

QA Notes

Setup

  • Dev build (just build, regtest) against the staging regtest LSP; fund and mine with ./lsp POST /regtest/chain/deposit and ./lsp POST /regtest/chain/mine.
  • Wallet with a Blocktank channel: install a master build from before this PR (d5526d4), onboard, fund, open a channel with Savings › Transfer To Spending, then install this PR's build over it. A manual channel to the staging LSP node (Settings › Advanced › Lightning Connections › add connection › Manual Setup, node from ./lsp GET /info) also counts as a Blocktank channel.
  • Wallet without channels: a fresh wallet on this build, funded on-chain only.
  • Payee for the send case: ./lsp POST /cjit for another wallet's node id gives a Lightning invoice to pay; hand it to the app as lightning:<invoice> with adb shell am start -a android.intent.action.VIEW -d.
  • journeys/README.md › Getting a Lightning channel has the same recipes.

Journeys

  • J1: new channels-closing-notice.xml — Home banner with the date opens the Spending wallet notice; Transfer To Savings stays, and Savings › Transfer To Spending opens the Transfer Unavailable sheet
  • J2: new receive-onchain-only-without-channel.xml — a wallet without channels gets a Savings-only Receive sheet, no CJIT button, no Spend card and no banner; Transfer To Spending and Transfer from Savings open the Transfer Unavailable sheet
  • J3: updated external-amount-over-max.xml — the route starts from Funding Advanced
  • J4: updated restore-keeps-tags-and-closed-channels.xml — channel set up through manual setup to the LSP node
  • J5: updated non-cjit-channel-no-payment-notification.xml — channel set up through manual setup to the LSP node
  • J6: updated activity-blue-icons.xml — precondition notes the transfer row needs a pre-change build
  • J7: updated detail-overview.xml — hardware wallet Transfer To Spending opens the Transfer Unavailable sheet
  • J8: updated cancelled-node-restart.xml — channel precondition says how to get one
  • J9: updated receive-auto-tab-selection.xml — channel precondition; without a channel the sheet shows Savings only
  • J10: updated receive-tabs-wraparound.xml — runs on a wallet without channels (Savings and USDT)
  • J11: updated usdt-receive-swipe-tabs.xml — without a channel, swiping right from USDT lands on Savings
  • J12: updated transfer-to-savings-returns-home.xml — channel precondition says how to get one
  • J13: updated usdt-wallet-navigation.xml — the Spending tab shows only over a channel
  • J14: new choose-lsp-from-transfer.xml — the sheet's Continue leads to Choose a Lightning provider; provider rows open their sites, and Manual Setup and QR Code open the existing manual setup and scanner

Retired, listed in journeys/README.md › Retired journeys: the transfer-to-spending amount-limit journeys, the CJIT notification journeys for CJIT orders, the hardware-wallet transfer-to-spending journeys, the CJIT and transfer notification-toggle journeys, spending-confirm-amount-change.xml and closed-channel-transfer-settles.xml; the transfer, CJIT and gift flows behind them no longer open.

Manual Tests

N/A

Automated Checks

  • added HomeViewModelTest.kt — the channels-closing banner shows with Blocktank channels and hides without them
  • updated ReceiveLiquidityDecisionTest.kt — keeps the Lightning-invoice checks
  • updated SuggestionsRepoTest.kt — no transfer dependency now that the Spend card is gone
  • updated ContentViewTest.kt — drops the hardware-wallet transfer start route
  • updated ReceiveAutoTabSelectionTest.kt — picks Savings, since a wallet without a ready channel has no Spending tab
  • updated BlocktankRepoTest.kt — orders and CJIT entries load from the local cache only; orders submit without a refund address
  • updated TransferRepoTest.kt — pending transfers settle without fetching orders
  • updated LightningRepoTest.kt, WalletRepoTest.kt — no geocheck or trusted-peer fetch on start and invoice refresh
  • removed refund-address allocation, CJIT server-refresh and order-fetch tests in BlocktankRepoTest.kt and TransferRepoTest.kt, and the trusted-peers-from-Blocktank tests in LightningRepoTest.kt — those calls are gone
  • removed EditInvoiceVMTest.kt — its view model only decided CJIT offers for Edit Invoice
  • ran just compile, just test, just lint and compileDevDebugAndroidTestKotlin on the rebased branch — pass
  • ran CI's build and unit tests by workflow dispatch on 6f86a1b, since draft PRs skip them — pass
  • updated E2E specs in test: cover transfer to spending without blocktank channels bitkit-e2e-tests#284 for the sheet, the provider list and the removed Blocktank purchase
  • ran E2E by workflow dispatch on 6f86a1b against the test: cover transfer to spending without blocktank channels bitkit-e2e-tests#284 branch — all seven local suites pass (settings, send, onchain/boost/receive/widgets, onboarding/backup/numberpad, multi-address, LNURL/transfer, lightning/security)

Evidence

Dev builds on a Pixel 9 emulator (Android 16) against the staging regtest LSP. On 24 Oct the branch was rebased onto master; the shas below from before that date are the pre-rebase commits.

  • On 6f86a1b (rebased, with the Blocktank call removal): Savings › Transfer To Spending opened the Transfer Unavailable sheet, Continue showed the four providers, and Receive offered Savings only. A manual 100 000 sat channel to the staging LSP node showed the banner and the Spending notice, and Transfer to savings moved the balance back and cleared the banner. The app log for that run has no order refresh, channel open, geocheck or trusted-peer fetch, only the local order cache load, and no crash.

Earlier runs: The Transfer Unavailable sheet ran on d749648; Choose a Lightning provider and Manual Setup on c0aae97; cases 1 and 4 ran on 46e67b5; 2, 3 and 5 ran on an earlier build of this branch, before the review fixes that limited the notice to Blocktank channels and moved the banner out of the activity block, which do not touch those flows.

  • J1 on a wallet upgraded from master with a 100 000 sat Blocktank channel: banner "CHANNELS CLOSE ON DEC 15, 2026", tap opens the Spending wallet with the notice and Transfer To Savings, and Savings has no Transfer To Spending.
  • J2 on a fresh wallet funded on-chain only: the Receive sheet shows the Savings tab only, with no CJIT button. Edit Invoice with 10 000 sats returns to the Savings QR, and neither wallet screen shows a transfer button or the notice.
  • Receive over the existing channel: an invoice from the Spending tab, paid by the LSP, received 5 000 sats.
  • Send over the existing channel: paid a 20 000 sat invoice from a lightning: URI.
  • Transfer to savings: moved 85 000 sats to savings through the existing flow, and the banner went away once the channel closed.
  • A second wallet that gained a Blocktank channel showed the banner after upgrading to the head build.
  • On d749648, on a wallet without channels: Savings › Transfer To Spending opened the Transfer Unavailable sheet as designed. Continue led to Choose a Lightning provider with the four providers, and QR Code opened the scanner.
  • On c0aae97: Megalithic opened its channel page in Chrome, and back returned to the screen. Manual Setup opened empty manual setup. A 100 000 sat channel to the staging LSP node opened with no service fee, and the wallet then showed the banner.
  • On 1de408c (previous sheet copy): Transfer from Savings on an empty Spending wallet opened the sheet, and Cancel closed it in place.

Gaps

  • E2E for the hardware wallet (@hardware_wallet_3) did not run: the dispatched E2E matrix has no Trezor emulator suite.
  • The updated journeys J3–J13 were not run on a device; the edits are to preconditions and routes, checked against the code.
  • Hardware wallet Transfer To Spending (J7) was not run: no Trezor emulator was set up. It calls the same sheet as the other two buttons.
  • No mainnet channel was ordered from the four third-party LSPs, and their LNURL channel QRs were not scanned.
  • API < 33 with a per-app language: the banner and notice format the date with the default locale, which was not checked on an older API level.

Models used

  • Planning/scoping: claude-opus-5-5 (reasoning: medium)
  • Implementation: claude-opus-5-5 (reasoning: medium), claude-sonnet-5-5 (reasoning: Not exposed)
  • Review: claude-opus-5-5 (reasoning: Not exposed)

Metadata note: the session's model and effort come from the app's session record. The journey-update and review subagents reported their models from their own prompts; their reasoning settings were not exposed in any record available here.

@ovitrif ovitrif added this to the 3.1.0 milestone Oct 22, 2026
@ovitrif ovitrif self-assigned this Oct 22, 2026
@ovitrif

ovitrif commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed 1de408c: Transfer To Spending (Savings and hardware wallet) and Transfer from Savings (empty Spending wallet) are back, and they now open a "New Channels Unavailable" sheet instead of the transfer flow. Following the request in the Blocktank wind-down thread, users who try to move savings to spending now see why they can't. For wallets with a Blocktank channel the sheet also shows the close date. No Blocktank order or CJIT entry is created from anywhere.

  • Journeys channels-closing-notice.xml, receive-onchain-only-without-channel.xml and detail-overview.xml now step through the sheet.
  • Ran just compile, just test and just lint; they pass. The sheet was driven from all three entry points on device except the hardware wallet one, with recordings in Preview.

@ovitrif

ovitrif commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed c0aae97. The savings-to-spending sheet now uses @aldertnl's copy ("No automatic transfer") and offers Cancel and Open Channel, following the Blocktank wind-down thread. Open Channel leads to a new "Choose a Lightning provider" screen built from the Third-party LSP design:

  • LNBIG, ZEUS Olympus, Megalithic and Amboss Magma, each opening its own channel order page. The picks and reasons are in the description, so any we don't want to be associated with can be dropped.
  • Manual Setup opens the existing manual channel setup.
  • QR Code opens the scanner for the provider's LNURL channel QR.

Still no Blocktank order or CJIT entry is created from anywhere. New journey choose-lsp-from-transfer.xml. just compile, just test and just lint pass, and the flow was driven on a regtest emulator, including a manual channel to the LSP node, with recordings in Preview.

@ovitrif

ovitrif commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed d749648. The savings-to-spending sheet now follows @aldertnl's full design (thread):

  • It is titled "Transfer Unavailable" and shows the lightning bolt with "MANUAL SETUP REQUIRED", the new body text, and Cancel and Continue.
  • Continue opens Choose a Lightning provider.
  • The sheet no longer shows the close date; the Home banner and Spending notice still do.

The provider links (LNBIG, ZEUS Olympus, Megalithic, Amboss Magma) were checked and return 200. The description notes how LNURL-channel claims from these providers work and that no claim was tried against a live provider. just compile, just test and just lint pass. A recording and the design-vs-app comparison are in Preview.

@ovitrif

ovitrif commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed 8e42a49: the Home banner "CHANNELS CLOSE ON DEC 15, 2026" no longer wraps onto two lines. The banner component gets an optional autoSizeTitle flag, used only by this banner: its title starts at the usual 20 sp and shrinks only as far as needed to fit on one line, down to 12 sp (Compose TextAutoSize.StepBased). The transfer banners are unchanged. just compile, just test and just lint pass, and the banner cell in Preview has the new recording.

@ovitrif

ovitrif commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator Author

Pushed 6086f96: the app no longer calls Blocktank for orders, CJIT entries, geocheck or refund addresses, and no longer reads trusted peers from Blocktank's info.nodes, following the agreed scope. Order and CJIT data now come from the local cache only. Kept: info() for fee estimates, FX rates, push registration and wake, the dev screens, and a one-time order read when migrating from the old React Native app (open question in the description). One consequence, noted in the description: an order paid before the upgrade whose channel the app had not yet opened is left for Blocktank to open or refund. just compile, just test and just lint pass.

@ovitrif

ovitrif commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto master (no conflicts) and pushed 1adc54a, which adds one fix: the hardcoded address of Blocktank's LND4 node was stale (34.65.153.174), and Blocktank's info now lists 34.65.147.55 for the same node. With trusted peers no longer read from info.nodes, channels with LND4 could not have reconnected.

On the rebased head, just compile, just test and just lint pass. The dispatched CI build and unit tests passed on 6f86a1b; a run on 1adc54a is in progress. A device run on the rebased build checked the sheet, the provider list, Savings-only receive, the banner and notice on a manual LSP channel, and transfer to savings, with no Blocktank order, CJIT or geocheck call in the app log. E2E is running against synonymdev/bitkit-e2e-tests#284, which updates the specs for the sheet and the removed purchase.

@ovitrif

ovitrif commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator Author

Replaced by #1696, which keeps the Transfer Unavailable sheet and drops the provider list and closing banner.

@ovitrif ovitrif closed this Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: stop offering new lightning channels and warn of channel closing

1 participant