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
pwltr
left a comment
There was a problem hiding this comment.
Rechecked commit 9f44c5b. A Spark send that failed while the app was closed now releases its held invoice when the activity sync records it, and a Spark funding kept for Payment Pending now completes the transfer from the funding screen once it lands, so dismissing Payment Pending no longer strands the confirm screen.
Low candidate — With Payment Pending open, two handlers settle a kept Spark funding and race (app/src/main/java/to/bitkit/ui/screens/transfer/SparkFundingViewModel.kt:344).
watchRetainedFunding follows the deposit at the confirm screen's speed and moves to Setting Up. If Payment Pending is still open when the attempt completes, its onRecoveredTransfer also runs followRecoveredSparkFunding (waitForMatureClaim) and navigates Home. Each call cancels the other's claim job, so the deposit ends Instant or Later, and the user lands on Setting Up or Home, depending on which runs last. Letting one path own the recovered funding would make the outcome deterministic.
Low candidate — The speed caption says Instant while the text says the transfer waits for confirmations (app/src/main/java/to/bitkit/ui/screens/transfer/SettingUpScreen.kt:230).
Unchanged: for Unavailable and NotSeen, whose text says the transfer is added after 3 confirmations, the caption still reads "Speed: Instant".
Review details
General review
Model: Claude Opus 5.5
Reasoning effort: high
|
@pwltr all four items from your two reviews on 9f44c5b (review, recheck) are fixed:
Fixed in fd854d3: the replay keeps a non-empty message already stored on the activity, such as an LNURL comment, and falls back to the invoice description only when there is none. Test:
Fixed in a88a943: a replay that isn't COMPLETED is skipped when the stored activity is already SUCCEEDED, whichever rail paid it. Test:
Fixed in aa61e5b: the open funding screen owns the kept funding, registering it when it starts watching and releasing it when the watch ends or the screen closes. While it owns it,
Fixed in aaafd92: Unavailable and NotSeen now read "Speed: Later". Tests: |
|
Two more fixes, from checking this PR against issues found on its iOS twin synonymdev/bitkit-ios#1128:
|
jvsena42
left a comment
There was a problem hiding this comment.
master, so I posted the code review only and didn't re-approve. I'll re-approve after the rebase if nothing above LOW remains.
Delta review of 8dee167..767f0eb:
- My two LOWs are fixed.
- One new LOW is inline: the QuickPay startup reconcile is lost when Spark syncs after the node.
- A residual of aa61e5b is in a reply on the
LightningRepo.kt:2047thread. - Checked and clean:
- fd854d3 keeps the payment note on a replay.
- a88a943 blocks the replay downgrade of a paid activity.
- aaafd92 is caption-only.
- 767f0eb refuses a sub-sat LNURL withdrawal before it requests anything.
- The merged QuickPay rows only settle; they are never used to decide a dispatch, so they can't cause a double payment.
Reviewer model: Claude Fable 5.1 (claude-fable-5-1), Claude Opus 5.5 (claude-opus-5-5)
Reasoning effort: not exposed
|
@ovitrif this conflicts with |
…ghtning-new # Conflicts: # app/src/main/java/to/bitkit/repositories/BackupRepo.kt
|
@jvsena42 about your rebase ask: I merged This push also has:
No Spark test calls the Blocktank regtest helpers that synonymdev/bitkit-android#1817 test: drop blocktank-dependent tests removed, so the merge needed no test changes. The E2E run on 767f0eb passed every suite. |
pwltr
left a comment
There was a problem hiding this comment.
Reviewed 7b7bdc0.
Medium — Pending Spark retry overwritten by an older failed channel attempt. CoreService.processBolt11 protects succeeded activity but allows bulk LDK synchronization to overwrite a pending Spark retry with the earlier channel failure at the same payment hash. Keep pending retry status authoritative until it settles.
High candidate — Channel retry admitted before Spark history has synchronized. SparkRepo.earlierLightningSend can treat a readable empty pre-sync list as permission to send through channels while a restored Spark payment may still be live. Require complete history or retained in-flight evidence before authorizing that retry. This identifies a duplicate-dispatch admission risk; duplicate settlement was not established.
Review details
General review
Model: GPT-6.1 Sol
Reasoning effort: high
jvsena42
left a comment
There was a problem hiding this comment.
Delta review of a180df6, 984a21b, 7b7bdc0 and 665641e, after the rebase onto master: no findings. Both my items are fixed.
- a180df6:
QuickPayRepoalso reconciles onisSparkSyncedturning true, so the startup reconcile runs once both rails can list. A reconcile running at the same time can't release a record that is still pending, since a live op or a non-FAILED row keeps it. - 984a21b: the funding screen keeps ownership until
onCleared. The ViewModel lives as long as theSparkFundingNavgraph, so while Setting Up is shown,onRecoveredTransferonly hides the sheet. No path leaves the graph without clearing it. - 7b7bdc0:
syncActivitiesreads Spark's list again before releasing. It skips hashes insendsInFlightand releases only those still FAILED, so a retry started during the sync keeps its hold. This matches iOS #1128's fix. - 665641e: drops the empty
super.onCleared().
The device gate passed earlier (gate_spark_1697). These commits change only the reconcile trigger, screen ownership and the release check, and unit tests cover each one.
Reviewer model: Claude Fable 5.1 (claude-fable-5-1), Claude Opus 5.5 (claude-opus-5-5)
Reasoning effort: not exposed
|
@pwltr both items from your review on 7b7bdc010 are fixed:
Fixed in f811bca:
Fixed in ecf9e53: @jvsena42 this push makes your approval at 665641e stale. A re-approval is needed only for:
Your iOS cross-check on the live failure release does not affect Android: the in-flight guard from 7b7bdc0 covers only the sync path, and the live |
jvsena42
left a comment
There was a problem hiding this comment.
Delta review of f811bca, ecf9e53 and d31e9f0. They answer pwltr's review on 7b7bdc0.
- ecf9e53 also holds a node retry while Spark is connected but hasn't synced its history. Spark that is unsupported, failed to start or is disconnected already took that path, so nothing new becomes permanently blocked, and the hold applies only to hashes in
sparkSendHashes. The cross-rail duplicate checks are unchanged. - f811bca: the one LOW is inline. Its time-based guard can also hide a node-only failure. Reproduced in a unit test.
- d31e9f0 only touches the regtest Esplora proxy script.
Reviewer model: Claude Fable 5.1 (claude-fable-5-1), Claude Opus 5.5 (claude-opus-5-5)
Reasoning effort: not exposed
| return | ||
| } | ||
| // A newer pending attempt, such as a Spark retry, stays until it settles; an older failure can't end it. | ||
| if (existingActivity is Activity.Lightning && existingActivity.v1.status == PaymentState.PENDING && |
There was a problem hiding this comment.
LOW: this guard compares a database write time with the payment's time, so a node-only failure can stay "Pending" until the invoice is retried (from f811bca)
bitkit-core 0.8.0-rc11 never stores the app's updatedAt. The column defaults to now on insert, and activities_update_trigger sets it to now on every UPDATE, including mark_activity_as_seen. So existingActivity.v1.updatedAt is when the row was last written, not when the payment changed. Any write to a PENDING node send's row that lands at least 1 s after ldk-node stamps it FAILED, but before the FAILED sync, makes this return. Such a write can be a late same-status resync, setContact, a comment write, or the restore seen sweep. The FAILED payment's latestUpdateTimestamp never changes, so every later sync drops it too, and the row shows Pending until the invoice is retried. Before this commit, a status change bypassed the timestamp check. No funds are at risk, because earlierNodeSend reads LDK's own list. The trigger is a timing race, so LOW.
Reproduced in ActivityServiceLightningMessageTest: a PENDING node send with updatedAt = 300 and empty sparkSendHashes, synced as FAILED with latestUpdateTimestamp = 200, fails with expected:<FAILED> but was:<PENDING>. This setup matches the commit's own "keeps a newer pending spark retry" test except for timestamp, which the guard doesn't read, so the guard can't tell the two cases apart.
Suggested fix: hold on Spark evidence instead of time: payment.id.lowercase() in cacheStore.data.first().sparkSendHashes. Spark adds the hash before sending and removes it only when it reports FAILED or NotFound.





Closes #1805
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 emergency exit lives in the stacked exit PR, #1782, and the Paykit route over Spark in #1754, both stacked on this one.
Description
What ships in 3.2
https://bitkit.to/electrs.SPARK_EXITbackup category.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.28.0) 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), its LNURL server (lnurlDomain = null) and its default external input parsers (useDefaultExternalInputParsers = false) in Spark's config on every network, so the app sends no traffic to Breez's data-sync orbreez.tipsLNURL servers, offers no Lightning address, and the SDK sends nothing the user scans to third-party parsers.https://bitkit.to/electrs) as its chain service, in place of the SDK's default mempool.space and blockstream.info; regtest and staging keep the SDK's default.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 addedBREEZ_API_KEYto thereleaseandrelease-internalenvironments, so Android mainnet builds ship with Spark on.SPARK_EXIT, encrypted like the other backups. It is exported once Spark has connected and synced, and again, at most once a minute, whenever the SDK reportsUnilateralExitStateChanged; until Spark has synced the backup waits and is not marked as backed up. The first upload in each app session reads the stored backup and merges it into Spark before replacing it, and fails rather than replace a backup it could not read, so a failed or skipped restore never loses the stored exit data. 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; a failed restore of it shows as an incomplete restore. 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.
UI pending design: design was requested in the #design-ux Spark 3.2 thread, and some screens may change after the designers answer. The exit UI polish list isn't part of this PR.
Preview
Transfer to Spending, Receive and Transfer to Savings on 9f44c5b, on an Android emulator at 896×1994 (2 px per dp) in dark mode, with Savings and Spark on Lightspark's regtest chain, at 2x speed. The Instant claim's adding, repriced and unavailable states and the two channel rows are staged with local debug-only hooks that are not in this PR; Instant, Later, the Pay after repricing and the Spark-only Transfer to Savings run for real.
p1.mp4
p2.mp4
p3.mp4
p4.mp4
p5.mp4
p6.mp4
p7.mp4
p8.mp4
p9.mp4
p10.mp4
p11.mp4
Screens
Every screen the PR adds or changes, on the same emulator and build in dark mode. Staged with local debug-only hooks that are not in this PR: the Instant adding, repriced, unavailable and not-seen states, the channel rows on Transfer to Savings and the Spark transfer failure. The hardware wallet screens use a Trezor emulator on a local regtest chain.
Transfer to Spending
Funds in transfer
Transfer to Spending from a hardware wallet
Transfer to Savings
Lightning Connections
QA Notes
Setup
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.Merged stored Spark exit databefore it replaces the stored backup — needs two installs and control of VSS reachability.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 is pendingSparkFundingViewModelTest.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, the Lightning address server and the default external input parsers on every network; the Later fee prices the mature claim at the half-hour rate (198 sats at 2 sat/vB); connect is refused during a wallet wipe; wipe deletes Spark's storage and a staged exit data restoreContentViewTest.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, defers without uploading until Spark has connected and synced, merges the stored backup into Spark before the first upload, never replaces a stored backup it could not read, reports a failed restore as incomplete, and uploads when Spark was ready and synced 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 close; Spark is never withdrawn at a fee the user was not shown, and is withdrawn once per confirmed transferSparkRepoTest.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 returned; a withdrawal above the confirmed fee, or leaving dust, never leaves; a failure inside a send ends as failed once Spark has synced and holds no record of it; an invoice in flight, pending or paid is not sent again; the exit backup waits for Spark's first sync; a wipe fails when Spark's storage is not deleted and clears Spark's state; a Max send keeps the routing fee Spark quotes for its whole balance; a Later deposit reads as claimed once Spark claims it; a payment event settles even when the balance read fails; a restore whose exit data Spark rejects fails and keeps it staged; a sent withdrawal succeeds when the refresh after it failsPaykitPaymentProofRepoTest.kt,PaykitAllowancePayerTest.kt— a Spark retry decides a Lightning payment the node failedSparkFundingViewModelTest.kt— a funding signed but not accepted opens its recovery instead of an errorLightningRepoTest.kt,AppViewModelSendFlowTest.kt— an earlier Spark failure never settles a retry the node is paying, a Spark retry of an invoice the node failed settles it, and the node's earlier failure never overwrites the Spark retry's successPaymentFailureReasonExtTest.ktcase — Spark and SDK failures show the existing payment failed copyNotifyPaymentReceivedHandlerTest.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
Each result at the revision it ran on. Dev builds ran on Android 15 and 16 emulators on Spark's hosted regtest.
spark-transfer-to-spending.xmlon 4e22b9c, Breez SDK 0.28.0: passed, with Savings funded with 100,000 sats on staging regtest. It passed before on ab1b09d, and by hand on 1adfc47 (recording in Preview).TransferViewModelTest.ktandNotifyPaymentReceivedHandlerTest.kt, not run on a device.SPARK_EXITmerge before the first upload; covered bySparkRepoTest.kt,TransferViewModelTest.ktandBackupRepoTest.ktonly. The default external input parsers change is not run on a device either.Mainnet tests (run by the author)
These run on real devices with mainnet funds, by the author. Reviewers and review bots don't need to run them, and they don't block review.
Setup: a mainnet build with
BREEZ_API_KEY=<key>inlocal.properties. Round 1 ran on a Pixel 10 Pro.Round 1
https://bitkit.to/electrs/fee-estimates, read-only check (result not posted).Round 2 (pending)
On the final heads, with a mainnet build and small amounts.
https://bitkit.to/electrs.Models used
claude-opus-5-5(reasoning:medium),claude-sonnet-5-5(reasoning:high)claude-opus-5-5(reasoning:medium)claude-sonnet-5-5(reasoning:high)Taken from the run logs of every session and subagent that planned, wrote or reviewed this PR, which record the model and reasoning effort per turn. The
claude-sonnet-5-5planning runs were research and audit subagents.