Skip to content

feat: stop offering new lightning channels - #1102

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

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

Conversation

@ovitrif

@ovitrif ovitrif commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #1097
Twin: synonymdev/bitkit-android#1670
Companion: synonymdev/bitkit-e2e-tests#284

This PR stops the app from buying new Lightning channels from Blocktank, points users who want a channel to third-party providers, and tells users with Blocktank channels the date their channels close, so they can move their spending balance to savings first.

Description

  • Turns Transfer To Spending on the Savings wallet, Transfer from Savings on the empty Spending wallet and Transfer To Spending on the hardware wallet into a "Transfer Unavailable" sheet ("Manual setup required"), so no button starts a Blocktank channel order; the buttons keep their old visibility rules.
  • Adds a "Choose a Lightning provider" screen behind the sheet's "Continue" (Figma "Third-party LSP › Choose LSP"): LNBIG, ZEUS Olympus, Megalithic and Amboss Magma open their channel order pages in the browser, "QR Code" opens the scanner for their LNURL channel or node QR, and "Manual Setup" opens the existing manual channel setup.
  • Removes the Home "Spend" suggestion card, the other way into the transfer flow.
  • Points Lightning Connections › add connection (bottom button and the + in the navigation bar) at Funding Advanced (LNURL channel and manual setup), which never creates a Blocktank order.
  • Removes instant receive (CJIT) from Receive: the "Receive Lightning Funds" button and Spending onboarding are gone, the Receive button on the Spending wallet no longer opens the CJIT amount screen, and the Spending tab, like Auto, appears 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 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); a gift code scan or link now shows the generic unsupported toast.
  • Removes the Blocktank calls that only served purchases (geocheck, order polling and watching, refund addresses, trusted peers from Blocktank info); the calls the remaining channels need stay. See Blocktank API calls below.
  • Adds a Home banner, "CHANNELS CLOSE ON DEC 15, 2026", for wallets with a channel to Blocktank (open or pending), with or without activity; 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 app language's medium date style.
  • Keeps send, receive over an existing channel and transfer to savings unchanged. New strings are translated in every locale, and the strings and the icon only the removed flows used are deleted.
  • Updates the receive liquidity doc to the Savings fallback that replaces CJIT.
  • Retires the journeys for removed flows, updates the ones whose route or setup used them, and adds journeys for the notice, the provider screen and on-chain-only receive.

The purchase screens (funding options, transfer intro, the spending amount, confirm and advanced screens, the hardware-wallet signing screens, and the CJIT amount, confirm and liquidity screens) stay in code with no UI entry point.

Out of Scope

  • Views/Transfer, Views/Wallets/Receive/ReceiveCjit*, TransferViewModel, BlocktankViewModel: 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 a built-in provider; the Choose LSP providers are links to third-party sites.
  • Choose LSP embedded provider pages: the design shows them inside the app; this PR opens them in the browser, like Android.
  • Notice while the node is not running: the banner and notice read the node's channel list, so they are absent during node startup and after a node start error.
  • Gift codes: a dedicated "no longer supported" message instead of the generic unsupported toast.
  • Spending wallet empty state: its onboarding copy is unchanged.

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. It matches synonymdev/bitkit-android#1670.

Removed:

  • Geocheck (/api/geocheck): the region check that gated channel purchases, with the geo-blocked state, its UI variants, the onboarding geo note and the CHECK_GEOBLOCK build flag.
  • Trusted peers from Blocktank info: the node always uses the hardcoded Blocktank peers in Env.trustedLnPeers, which were already the fallback. The third mainnet peer's address is updated to the one Blocktank's info lists now (34.65.147.55). The LDK debug screen's peer simulation, which only faked that fetch, is gone.
  • Order and CJIT polling: the two-minute loop and the info refresh when Receive opens.
  • Order and CJIT reads come from the local cache only: the orders list, the pending channels on Lightning Connections and Channel Detail, and recovering an interrupted transfer payment.
  • Opening a paid order's channel: the app no longer watches pending orders at start or calls open for them.
  • Refund addresses: the app no longer allocates or sends one. The saved address stays in the settings backup, 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, 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 (/notifications/api): receiving while the app is closed, until the channels close.
  • React Native migration: a user migrating from the old Bitkit app has their past orders read, to rebuild channel and transfer history. Removing that needs migration work; open for Ovi and Blocktank.
  • Dev screens: the regtest tools and the 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 paid before the upgrade, 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.

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:

They live in LightningProvider.all. LNURL-channel claims from these providers go through the existing LNURL channel flow; I didn't open a mainnet channel or try a claim with any of them.

Design

  • Choose LSP: Third-party LSP › Choose LSP (section Third-party LSP).
  • Transfer Unavailable sheet: no Figma frame yet; it follows the flow board the design lead shared with the team, built on the shared intro sheet layout (as the High Balance sheet).
  • Banner and Spending notice: N/A — no design available; the banner reuses the Transfer In Progress banner.

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, then the sheet to the provider list, on regtest (2x):

j32x.mp4

Closing notice for a Blocktank channel, and a wallet without channels (2x):

Banner and notice Without a channel
j1c2x.mp4
j22x.mp4

An existing channel keeps working (2x):

Receive Transfer to savings
recv2x.mp4
savingsc2x.mp4

QA Notes

Setup

  • Debug build on regtest against the staging Blocktank (api.stag0.blocktank.to). Fund and mine with the sibling Android checkout's helper: ../bitkit-android/lsp POST /regtest/chain/deposit and ../bitkit-android/lsp POST /regtest/chain/mine.
  • Wallet with a Blocktank channel: install a master build from before this PR (2464865), onboard, fund, open a channel with Savings › Transfer To Spending, mine 6 blocks, 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 ../bitkit-android/lsp GET /info) also counts as a Blocktank channel.
  • Wallet without channels: a fresh wallet on this build, funded on-chain only.
  • 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; 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, no banner, and both transfer buttons open the Transfer Unavailable sheet
  • J3: new choose-lsp-from-transfer.xml — the sheet's Continue opens Choose LSP: provider rows open the browser, QR Code opens the scanner, Manual Setup opens the manual channel setup
  • J4: updated external-amount-over-max.xml — the route starts from Funding Advanced
  • 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 sheet
  • J8: updated receive-tabs-wraparound.xml — runs on a wallet without channels (Savings and USDT)
  • J9: updated usdt-receive-swipe-tabs.xml — without a channel, swiping right from USDT lands on Savings
  • J10: updated usdt-wallet-navigation.xml — the Spending tab shows only over a channel
  • J11: updated receive-above-ten-percent-of-channel.xml — its channel comes from a pre-change build instead of CJIT

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 notification-permission suite (its routes went through the CJIT and Spending Confirm screens) and spending-confirm-amount-change.xml; their flows no longer exist.

Manual Tests

N/A

Automated Checks

  • added ChannelsClosingTests.swift — the close date formats in the medium date style per locale
  • updated ReceiveLiquidityDecisionTests.swift — keeps the Lightning-invoice checks, drops the CJIT routing ones
  • updated ReceiveEditTests.swift — drops the route into the CJIT amount screen
  • updated PubkyAuthURLSchemeTests.swift — gift code links no longer bypass the node start gate
  • removed TabBarReceiveTests.swift — its subject, the Spending wallet's Receive shortcut into CJIT, is gone

Evidence

Debug builds on an iPhone 17 Pro simulator (iOS 26.5), regtest against the staging Blocktank.

  • J1 on a wallet upgraded from master (2464865) with a 150 000 sat Blocktank channel, at 7ead1a4, and again at f15e3bb on a wallet with a manual 200 000 sat channel to the LSP node (the recording), where the banner title fits on one line: banner "CHANNELS CLOSE ON DEC 15, 2026", tap opens the Spending wallet with the notice and Transfer To Savings; Savings › Transfer To Spending opens the Transfer Unavailable sheet, and Cancel returns to Savings.
  • J2 on a fresh wallet funded with 200 000 sats on-chain only, at 7ead1a4, and again at 9ae1235 (the recording): no banner and no Spend card on Home; the Receive sheet shows the Savings tab only, with no CJIT button; Edit Invoice with 100 sats returns to the Savings QR; Transfer To Spending and Transfer from Savings both open the Transfer Unavailable sheet.
  • J3 on the same wallet, at 3802622: Continue opens Choose LSP with the four providers; Megalithic opens https://megalithic.me/get_a_channel in Safari; QR Code opens the scanner; Manual Setup opens the manual channel setup with empty fields.
  • Receive over the existing channel, at cd99830 (receive code unchanged since): the Spending tab is offered, and an invoice paid by the LSP received 5 000 sats.
  • Transfer to savings, at 7ead1a4 and again at 9ae1235 (the recording): moved 155 000 sats to savings through the existing flow, and the banner went away once the channel closed.
  • swiftformat --lint on the changed Swift files and node scripts/validate-translations.js pass.
  • Unit and UI suite with CI's selection at 8cda3c4, rebased on master, on a local simulator: one failure, SendConfirmationLifecycleTests.testCustomFeeWalletSwitchSurvivesFeeScreenNavigation, which fails the same way on master locally and passes in CI. CI's unit-tests run passes at 4adaf7f (https://github.com/synonymdev/bitkit-ios/actions/runs/40117909346).
  • CI at 4adaf7f, started by hand while this PR is a draft: integration tests pass (https://github.com/synonymdev/bitkit-ios/actions/runs/40120383042), and E2E against test: cover transfer to spending without blocktank channels bitkit-e2e-tests#284's branch passes, 14 tests (https://github.com/synonymdev/bitkit-ios/actions/runs/40120385287).
  • J4–J11 at 4adaf7f on an iOS 26.5 simulator, regtest against the staging Blocktank:
    • J4 passes: nine 9s show the over-max toast, the field caps at ₿99 999, and deleting drops it to 9 999.
    • J5 passes on the simulator: a manual 200 000 sat channel to the LSP node shows the "Spending balance ready" toast about 10 s after 3 blocks.
    • J6 passes with a Trezor emulator over Bridge: a hardware Transfer To Spending made on a pre-change build and two receives show with blue icons in the Home, All Activity and tab filters.
    • J7 passes: the hardware wallet's Transfer To Spending opens the Transfer Unavailable sheet, the activity row opens, and Remove forgets the device.
    • J8 and J9 pass: without a channel the Receive tabs are Savings and USDT, swipes wrap, and only a tap copies.
    • J10 passes the steps a wallet without USDT funds can run (opt-in, layout, address copy, invalid address, six decimals, separate Bitcoin Receive, no Spending tab without a channel).
    • J11 passes at a smaller amount: on a channel opened on LDK Node rc.77 (627 300 sats, receiving 470 367), a single 100 000 sat payment (15.9%) arrives; the same payment to a channel opened on rc.76 fails with no route.

Gaps

  • J5's Notification Center steps need a physical device and were not run.
  • J10's steps that need funded USDT, saved USDT activity or an offline provider were not run.
  • J11 was run at 15.9% of the channel, not 30%: the LSP helper failed to decode the 188 000 sat invoices, which is a helper problem.
  • Send over an existing channel was not run; the send flow is untouched.
  • No channel was claimed from a Choose LSP provider: the providers run on mainnet, so the LNURL-channel claim from a third-party LSP is untested.

Models used

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

Remove Transfer To Spending, instant receive (CJIT), gift codes and the Spend card, and show wallets with Blocktank channels the date the channels close.
Savings, Spending and hardware wallet Transfer To Spending buttons stay and open a sheet saying new channels are no longer offered, with the close date for wallets with Blocktank channels.
The sheet now says automatic transfers are gone and opens a Choose LSP screen with third-party providers, manual setup and the scanner.
The sheet becomes Transfer Unavailable (manual setup required) with Cancel and Continue to Choose LSP, per the final design; the close date stays on the banner and Spending notice.
Drops the geocheck, the trusted-peer list from Blocktank info (peers come from Env), the refund address for new orders, the order polling timer and the info refresh when Receive opens. Info for fee estimates, FX rates, push registration and the watch of already paid orders stay.
Order and CJIT reads use the local cache only; the app no longer polls a paid order or opens its channel at start, so Blocktank opens or refunds orders paid before the upgrade.
@ovitrif

ovitrif commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased on master and pushed 4adaf7f:

CI's unit-tests run passes at 4adaf7f: https://github.com/synonymdev/bitkit-ios/actions/runs/40117909346

@ovitrif

ovitrif commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator Author

Replaced by #1127, 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