Repository navigation
Conversation
bf469f4 to
ef7130e
Compare
01334a9 to
4ed36e0
Compare
a1412d5 to
a1c333c
Compare
9982a55 to
d964a5f
Compare
eab67dd to
567e1a8
Compare
|
QA: 🟢 Restore Spark exit dataExit data backs up as its own category. After a wipe and restore, the Pixel's Spark balance and claimed deposits came back, but the log counted 0 imported leaves because the exit data was restored after Spark had already synced. Ran on 567e1a8; the order is fixed in f96bae9, and a rerun is pending.
🟢 Claim a depositTransfer to Spending: Spark claimed each deposit after 3 confirmations, keeping 297 sats of 15,000 and 198 of 2,500. Ran on an earlier head of this PR, before the 27 Oct rebase. 🟢 Receive with zero balance, then payWith no channel and a 0 Spark balance, the Pixel received 1,024 sats over Lightning and paid 128 from them two minutes later: Lightning received into Spark is spendable at once. The send's activity showed a fee of 0, fixed in 972e5ec. Ran on 567e1a8.
🟢 Move Spending to SavingsTransfer to Savings withdrew 12,973 sats from Spark for a 1,470 sat fee; the withdrawal showed in Savings unconfirmed after 22 seconds and confirmed after 31 minutes. Ran on an earlier head of this PR, before the 27 Oct rebase; a later run on a build of #1710 lists the withdrawal as a Transfer from Spending.
🟢 Fees against BlocktankOn the same wallet, a 15,000 sat Transfer to Spending cost 907 sats in all through Spark (6.0%) and 1,584 through Blocktank (10.6%). Moving back to Savings cost 1,470 sats through Spark, where Blocktank channel closes cost 183 to 1,113. 🔴 Pay a Paykit request over SparkThe iPhone paid this Pixel's 204 sat Paykit request over Spark and it arrived at once, but the iPhone stayed on Payment Pending; fixed in 024b6bb and synonymdev/bitkit-ios@735e972e, not rerun on a device yet. An earlier attempt failed because this Pixel republished a private invoice already paid through Spark, fixed in 972e5ec.
|
901ecd8 to
2ba664e
Compare
|
Removed from the 3.1.0 milestone: this draft is the deliverable of a finished Spark experiment and does not ship in 3.1.0. |
024b6bb to
d7be600
Compare
|
Retargeted this draft to master before merging its base, #1696, so deleting that branch would not close it. |
There was a problem hiding this comment.
detekt found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.
|
Rebuilt this branch on master after the Blocktank sunset (#1696). Master removed the old Transfer to Spending flow, so Spark now has its own amount, confirm and deposit screens, reached from Funding and the transfer buttons while Spark is ready. The deposit screen offers Breez's early claim at the fee the provider quotes for that deposit. The history was rewritten into new commits, so the shas under Evidence predate the rebuild. This PR stays a draft. |
cab0f89 to
fa49b5a
Compare
fea5a92 to
171b250
Compare
|
I restored this branch to |
Regtest APKDownload bitkit-dev-debug universal APK (expires in 30 days). |
927472a to
5edd5a8
Compare
|
A push meant for the Move to Spark branch landed on this PR's branch by mistake, fast-forwarding it to |
ab34526 to
ab1b09d
Compare
|
QA after the split, on ab1b09d: dev build on an Android 15 emulator, Spark on its hosted regtest, Savings funded with 200,000 sats on staging regtest.
J1 to the confirm screen and Learn more, then Savings › Transfer To Spending with no intro (2x speed).
|





Twin: synonymdev/bitkit-ios#1128
This PR keeps Lightning in Bitkit after Blocktank stopped selling channels: on top of the Blocktank sunset (#1696), Spark becomes the easy option where Transfer to Spending now leads to manual setup. Savings to Spending funds a Breez SDK on Spark wallet through the restored transfer screens, Lightning sends and receives fall back to it whenever no channel can carry the payment. Existing channels keep working until they close, and ldk-node keeps running the on-chain wallet.
The Spark emergency exit moved to its own PR, #1782, stacked on this one.
Description
Transfer to Spending on Spark
HwSendViewModel.signAndBroadcasttakes the address from its caller), so the restored intro, amount, sign and signed screens send to the Spark deposit address, priced from the device's account, with the same speed choice and setting-up screen. The hardware amount step reuses the restored amount screen with its old test tags, instead of a second copy of it. The send is recorded as a transfer.hasSeenTransferIntroandhasSeenSpendingIntroin settings and their migration from the React Native app, removed by the sunset.Spark wallet and Lightning
breez_sdk_spark:bindings-android:0.27.2) withSparkServiceandSparkRepo, so that a Spark wallet starts beside the node from the same recovery phrase and passphrase. Spark derives its own keys onm/8797555', so there is no new phrase to back up.realTimeSyncServerUrl = null) and its LNURL server (lnurlDomain = null) in Spark's config on every network (3e60f04), so the app sends no traffic to Breez's data-sync orbreez.tipsLNURL servers and offers no Lightning address.BREEZ_API_KEYbuild property, so that mainnet builds without a key keep Spark off; regtest builds use Lightspark's hosted regtest, which needs no key.ESPLORA_URLbuild property for regtest builds, which puts the node's chain source on an Esplora endpoint, plusscripts/spark-regtest-esplora.pyandscripts/spark-regtest-fund.py, so a dev build runs on Spark's regtest chain and QA can fund it and test Savings to Spending and the claim without mainnet.BREEZ_API_KEYfrom thereleaseenvironment in the release workflow and fails the release when it is missing, so Spark can't ship switched off; internal release builds pass it through when therelease-internalenvironment has it. A repo admin has to addBREEZ_API_KEYto thereleaseenvironment (and torelease-internalfor internal builds with Spark).SPARK_EXIT, encrypted like the other backups. It is exported again when Spark connects and whenever the SDK reportsUnilateralExitStateChanged, and on restore it is imported right after Spark connects, without contacting the operators, so a wallet restored on a new device can still exit on-chain while the operators are unreachable. The category shows in Backup settings only where Spark runs.Transfer to Savings selection
Paykit on Spark
Screen layout
Out of Scope
Risks for review
SPARK_EXIT; upstream VSS accepts request bodies up to 1 GiB by default, but a large wallet's upload against Synonym's VSS is untested.250 × sat/vB + 750sats.CLAIM_TX_SIZE_VBYTES,deposits.rs), so it is a flat cost that weighs heavily on small transfers. The confirm screen shows it as Spending fee, with To spending after it, for the speed the user picks.libbreez_sdk_spark_bindings.soadds about 11 MB per ABI (arm64, uncompressed).ESPLORA_URL.Design
Restored transfer screens, on the latest
Bitkit - Handoffpage of the Bitkit Figma file (frame names fromdocs/screens-map.md; the Spark speed rows, Spending fee and setting-up states have no frame yet):Figma has no Spark or unlimited-receiving state for the Lightning Connections header, so No limit uses the same style and arrow as the spending figure beside it; @aldertnl, a design for this state is still open.
Preview
Transfer to Spending on Spark's regtest chain: the Spend card, both intros, Funding, the amount pad with 25%, Max and delete, confirm with Instant and Later, and Learn more; then an Instant transfer Spark added after 1 confirmation (
1adfc47).t1-intro-amount-confirm2x.mp4
t2-instant-settingup2x.mp4
The setting-up screen for a Later transfer (
1adfc47), and for an Instant transfer the provider offered no early claim for (3e60f04).t3-later-settingup2x.mp4
t4-instant-unavailable2x.mp4
Earlier Spark cases: the Spending invoice from Spark without channels (1,
7aa44f6), and Spark in Lightning Connections with a pending deposit (10,07a1df7, balance staged with a local hook).r1-receive2x.mp4
r72x.mp4
Transfer to Savings selection on Spark's regtest chain (
b0169b275, channel rows staged with a local hook): Spark alone goes straight to confirm with its fee; with channels, Advanced lists Spark and each channel, all on, and confirm shows only what is selected.case1-spark-only-no-channels2x.mp4
case2-channels-and-spark2x.mp4
case3-partial-selection2x.mp4
Channels selected with Spark off (no Spark fee), nothing selected (Continue disabled), and channels with no Spark balance (no Spark row) (
b0169b275).case3b-channels-selected-spark-off2x.mp4
case4-nothing-selected2x.mp4
case6-channels-only-no-spark2x.mp4
Spark only picked from the list beside two channels, then swiped (
5edd5a8ce): no channel closes, and Savings receives 97,711, the 99,901 balance less the 2,190 Spark fee shown.case5-spark-only-selected-swipe2x.mp4
Screens
Transfer to Spending
Transfer to Savings
Lightning Connections
QA Notes
Setup
BREEZ_API_KEY=<key>inlocal.properties, and small amounts.python3 scripts/spark-regtest-esplora.py --port 3999. It fronts Spark's regtest mempool API (the SDK's own regtest default,sdk_builder.rs) and adds the two Esplora calls it lacks and ldk-node needs:/fee-estimatesat start and/block/<hash>/statusfor Lightning sync.ESPLORA_URL=http://10.0.2.2:3999 ./gradlew assembleDevDebug, so the node's chain source is Esplora on Spark's chain instead of Electrum on Blocktank's staging chain. For a phone, run the proxy with--bind 0.0.0.0and use the host's address.FAUCET_USERNAME=… FAUCET_PASSWORD=… python3 scripts/spark-regtest-fund.py <Savings address> 100000through Lightspark's API faucet (credentials issued by Lightspark; Breez's CI uses the same,faucet.rs).POST /txwithsendrawtransaction RPC error: {"code":-1}, whilePOST /v1/txs/packageaccepted the same transaction. If a Savings send fails that way, forward/txto the package endpoint (a one-element JSON array) in the proxy.TransferViewModelTest.kt. Channels with no Spark balance was opened through the debug screen linkbitkit://screen/savings-availability, since Spending was 0.ESPLORA_URLstays on Blocktank's staging chain: Spark start-up, invoices and screens work, but its Savings to Spending deposit is never seen by Spark.Journeys
transfer/spark-transfer-to-spending.xml— with Spark ready, the suggestion card opens the Spending Balance intro and Funding, Transfer from Savings opens the Transfer to Spending intro and the amount screen; 25% and Max fill the amount, confirm shows the four tiles with Instant and Later, Later updates the tiles, Learn more opens and returns; the intros show once.transfer/transfer-unavailable-sheet.xml— now needs Spark not running, since a ready Spark replaces the sheet.amount-limits/transfer-spending-over-max.xml— the amount pad caps at Max with the Spending Balance Maximum toast.amount-limits/transfer-spending-preset-delete.xml— after 25%, Max or a unit switch, each delete removes one digit.transfer/spending-confirm-amount-change.xml— a lower price at swipe pays at once; a higher one asks for another swipe.hardware-wallet/transfer-to-spending.xml— a paired Trezor transfers into Spark through amount, sign and signed to the setting-up screen.hardware-wallet/passphrase-transfer-to-spending.xml— a hidden wallet signs with its live session, re-prompts after the session drops, and refuses a wrong passphrase.hardware-wallet/transfer-to-spending-node-warmup.xml— a hardware transfer started during node warm-up reaches the sign screen.transfer/savings-select-funds.xml— Select funds lists Spark and each channel, all on; confirm shows only what is selected, with Spark fee only while Spark is selected; Transfer all restores everything; nothing selected disables Continue; stops before the swipe.Manual Tests
ESPLORA_URLon Spark's regtest chain, Savings funded from the faucet → Transfer to Spending; after 1 confirmation Spark claims the deposit and Spending shows it; Lightning Connections lists it as claimed — Spark's regtest chain.Imported Spark exit databefore Spark is used, and Spark's balance and deposits match — needs two installs.Automated Checks
SparkRepoTest.ktcases — Instant claims early within the ceiling and reaches Claimed, asks above it, shows the repriced fee from a repriced claim, pays a repriced claim at the new fee, is unavailable with only the standard claim or a declined claim, says Spark has not seen a deposit never listed, and Later makes no claim call; a restored exit backup is imported once Spark connects, one from another network is skipped, a send recorded with fee 0 is re-recorded with the fee a later sync reports, a withdrawal is listed with its fee and its arrival marked as a transfer, and a Spark send refused before it leaves (fee over the limit, past its deadline) is a typed refusal while a failure inside the send stays uncertainSparkFundingViewModelTest.kt— Max is Savings less the network fee and 25% a quarter of it, Max is 0 without Savings, Continue refuses an amount under the Instant fee, prices and opens confirm, the swipe asks again when the fee rose and refuses when Savings no longer cover it, sends at the quoted fee rates and follows the claim at the chosen speed, and a hardware transfer prices from the device and hands signing a transfer requestSparkClaimOfferTest.kt— a quote maps to the standard claim, an automatic early claim within the cap, or a choice above the cap or with automatic claiming off, and each claim outcome maps to claimed, waiting, repriced or declinedSparkServiceTest.kt— Lightning sends return before completion; the config turns off Breez data sync and the Lightning address server on every network (3e60f04); the Later fee prices the mature claim at the half-hour rate (198 sats at 2 sat/vB)ContentViewTest.kt— the spending start route shows the intro until seen, for Savings and hardware wallets, and setting up drops the confirm screen from the back stackPrivatePaykitRepoTest.kt— a private invoice paid through Spark is replaced on the next refreshPaykitPaymentProofRepoTest.kt,PaykitAllowancePayerTest.kt,PaykitAllowanceRepoTest.kt,AppViewModelSendFlowTest.kt— a Lightning proof settles from Spark when its event was missed and is removed when Spark failed the send, a send neither rail knows was never made, a Spark send is never read as unsent before Spark has synced, Spark becoming ready or syncing settles open allowance attempts and republishes the Paykit endpoints, Spark sends keep the deadline and fee limit, a receive recorded without its event gets its contact, and the send limit leaves room for Spark's service feeBackupRepoTest.kt—SPARK_EXITuploads the exported state and restores it, uploads nothing while Spark is not connected, and uploads when Spark was ready before observing startedEditInvoiceVMTest.kt— Receive asks for no liquidity or CJIT while Spark can receiveDeriveBalanceStateUseCaseTest.kt— Spark balance counts toward Spending, caps max send, and pending deposits count as in transferTransferViewModelTest.kt— a selection moves only the picked channels and Spark: Spark only closes no channel and doesn't wait for a peer, channels only leave Spark untouched, both move both; a swap skips Spark when it is deselected and is sized by the selected channels; a quote left from a channel selection never swaps a Spark-only transfer; Spark's fee is priced once per balance and gives up after 15 s; a failed Spark withdrawal is reported while the selected channels still closeSparkRepoTest.kt— a Spark withdrawal pays the quote its fee estimate priced while it holds and asks again once it expired or the address changed; its arrival is marked a transfer by the txid its send returnedNotifyPaymentReceivedHandlerTest.kt— an on-chain receive from a Spark withdrawal is a transfer with no received sheetLightningRepoTest.kt— a Spark send is refused while a wallet restore is pending, or when one begins during authorizationLightningRepoTest.kt,WalletRepoTest.kt,WalletViewModelTest.kt— construct the new Spark dependency, idle, so existing channel paths are unchangedQA
Tested on Android 15 and Android 16 emulators on Spark's regtest, and on a phone on mainnet for case 3.
🟢 Transfer to Spending intros, amount and confirm, Instant transfer, Later transfer
Transfer to Spending intros, amount and confirm
On 1adfc47 on Spark's regtest chain (J1's steps by hand): the Spend card opened the Spending Balance intro, Funding and the Transfer to Spending intro; 25%, Max and delete worked on the pad, confirm for 50,000 sats showed 458 network fee, up to 397 Spending fee, 49,603 to Spending and 50,458 total, Later and Instant switched the rows, and Learn more opened and returned. Recording in Preview.
Instant transfer
On 1adfc47, 20,000 sats with Instant: the setting-up screen waited for Spark to see the deposit, and about a minute after the swipe, after 1 confirmation, Spark claimed it (99 sat standard claim; the provider offered no early claim), Funds moved to spending showed, and the Spark screen listed the deposit as claimed (2, 13). Recording in Preview.
Later transfer
On 1adfc47, 10,000 sats with Later: confirm showed about 99 Spending fee and 9,901 to Spending, the setting-up screen said Spark adds it after 3 confirmations, and the Spark screen then listed it as claimed with a 99 sat fee. Recording in Preview.
🟢 Earlier Spark cases (1, 3, 10, Spark's chain)
Receive without channels (1)
On 7aa44f6, a new wallet without channels: Receive showed Auto and Spending, and the Spending invoice's payee was Spark's node with a Spark route hint, not the LDK node.
Mainnet Transfer to Spending (3)
On ef7130e, on a phone: 15,000 sats to the Spark deposit address showed as in transfer to Spending within a minute, and Spark claimed it with the standard claim after 3 confirmations.
Spark in Lightning Connections (10)
On 07a1df7 and a93f3dc: Lightning Connections listed Spark with its balance and a pending deposit at 1/3 confirmations from the app's own broadcast, 26 seconds after the swipe; the detail showed provider, trust note, deposits with their claim fee and Transfer to Savings. Recording in Preview.
Node on Spark's chain
On 5f88733, a dev build with
ESPLORA_URLstarted its node on Spark's regtest, synced on-chain and Lightning to the tip with no failed calls at the proxy, and Spark connected beside it.🟢 Transfer to Savings selection
Spark only, no channels
On b0169b2 on Spark's regtest chain, with 99,901 on Spark and no channels: confirm showed 99,901 and a 2,190 Spark fee, with no Advanced button.
Channels and Spark, partial and empty selections
On b0169b2 with two staged channels (60,000 and 25,000): confirm showed 184,901 with the Spark fee and Advanced; Select funds listed Spark, Connection 1 and Connection 2, all on. Spark and Connection 1 gave 159,901 with the Spark fee and Transfer all; channels with Spark off gave 85,000 with no Spark fee; nothing selected left Continue disabled.
Spark only from the list, swiped
On 5edd5a8 with the two staged channels: Spark alone gave 99,901 and a 2,190 Spark fee; the swipe reached Funds moved to savings at once, no channel close was attempted, and Savings received 97,711.
Channels with no Spark balance
On b0169b2 after the withdrawal: Select funds listed only the two channels, confirm showed 85,000 with no Spark fee, and deselecting one left 60,000.
🟠 Mainnet cases, Two devices and Paykit, Setting-up repricing, Transfer to Savings failures and arrival
Mainnet cases
Not run: 4 to 9, 16 to 18, 22 to 26 need mainnet funds, a mainnet channel, a Breez key or a Trezor; 21 needs a build without a Breez key.
Two devices and Paykit
Not run: 12, 14 and 15 need two installs or two Paykit contacts; the Paykit fixes are covered by the unit tests above.
Setting-up repricing
Not run on a device: Spark's regtest provider offered no early claim, so Pay and Wait (24) and Spark hasn't seen your transfer show only in the
SettingUpScreenpreviews andSparkRepoTest.kt.Transfer to Savings failures and arrival
Not run on a device: 37 and 38 landed in d6d853d after the recordings;
TransferViewModelTest.ktandNotifyPaymentReceivedHandlerTest.ktcover them. Before that fix, the case 5 withdrawal still showed a Received Bitcoin sheet on Home.Models used
claude-opus-5-5(reasoning:low)claude-opus-5-5(reasoning:low)