Repository navigation
Conversation
|
Pushed |
Regtest APKDownload bitkit-dev-debug universal APK (expires in 30 days). |
|
Added to the 3.2.0 milestone: Spark via Breez SDK is the 3.2.0 highlight, and this PR stacks on #1697. |
ab34526 to
ab1b09d
Compare
Rebuilt on the split Spark head; carries the Pubky Swaps route (#1732) as it was merged into this branch.
c08023a to
f00fb41
Compare
# Conflicts: # gradle/libs.versions.toml
Codex Review Summary: ✅ CompletedThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Ready for review: Paykit payments to contacts work when the balance sits in Spark. It's stacked on #1697 and builds on bitkit-core 0.8.0-spark.3, a prerelease of synonymdev/bitkit-core#193. Every bitcoin Paykit payment goes through the core router with the contact's full endpoint list. On-chain endpoints and requests can be paid from Spark. The QA notes list each Paykit flow with its result on Spark-only wallets and the revision it ran on. Flows we couldn't stage are marked ⏭️ with the reason; the on-chain Spark withdrawal after the fee fix is covered by unit tests only. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d49fc3f496
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
[Critical impact] The PR extends wallet payments to Spark withdrawals across Paykit contact and request flows. Fix the fee limit and withdrawal recovery before merging; a payer can be charged more than shown or have an unpaid request become impossible to retry. Findings
SummaryRoutes Bitcoin contact payments through bitkit-core and adds Spark withdrawals for on-chain endpoints.
Review checks were source-based. No runtime or device verification was performed. Diagram%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Contact payment] --> B[bitkit-core router]
B --> C[Direct endpoint]
B --> D[Spark service leg]
B --> E[No route message]
D --> F[Confirm amount and fee]
F --> G[Prepare withdrawal again]
G --> H[Mark request proof started]
H --> I[Save withdrawal journal]
I --> J[Send with Spark]
J --> K[Save Sent activity]
J --> L[Complete request proof]
M[Restart recovery] --> N[Spark payment history]
N --> K
N --> L
Reviews (1) · Last reviewed commit: "fix: keep contact withdrawals out of tra..." · Reviewed by Greptile |
|
Triage: in 3.2.0. This PR closed no issue, so I filed #1807 to carry the milestone, moved 3.2.0 from this PR to that issue and added |
talosmachina
left a comment
There was a problem hiding this comment.
1 finding (1 P1). Routes Paykit contact payments through bitkit-core's router and pays an on-chain-only contact from Spending with a Spark withdrawal. Reviewed d49fc3f, full tier, statically: on the Bitkit track an auto review does not build the branch.
What I checked
Read: the PR diff of AppViewModel.kt, SparkRepo.kt, PaykitRouteRepo.kt, PaykitPaymentProofRepo.kt, LightningRepo.kt, SparkService.kt, plus cancelPreparation, failOnchainPayment, prepare, reconcileProof, handleOnchainPaymentFailure and PaykitPaymentProofStore.inFlightRequestIds
Tests read: the Spark withdrawal cases in SparkRepoTest.kt and PaykitPaymentProofRepoTest.kt; none drives a failure after the proof is started
CI: still running at review time; compose-ui / ui-tests failed in the emulator step (adb exit 1, no test results produced), which I could not tie to this change
Cold reader: a second model from another family read the finding against the checkout and it stood
Merge confidence: 3/5, one P1.
Reviewer model: claude-opus-5-5
Reasoning effort: Not exposed (the runner reports no effort setting)
Closes #1807
Twin: synonymdev/bitkit-ios#1191
Dependency:
This PR routes Paykit contact payments through bitkit-core's Paykit router, so a wallet whose spending balance sits on Spark pays contacts directly over Lightning, or through a Spark withdrawal to a contact who only accepts on-chain bitcoin.
Description
paykitRoutePayment) how to pay: on amount Continue, when a payment request opens its confirm screen directly, and when the source is switched on a request's confirm screen. The router gets the contact's full endpoint list and what the selected source can spend:Directpays the endpoint it names: Lightning (bolt11 first, then LNURL) or the on-chain address.Serviceopens the confirm screen withRoute: Sparkand the leg's fee in the fee row. The swipe withdraws exactly the amount to the contact's address, and the contact receives exactly the amount entered.Noneshows "Insufficient balance" when the amount exceeds what the source can spend, else "Can't pay this contact"; the router's reason goes to the log.SparkRepo.withdrawalRouteLeg).0.8.0-spark.3, a prerelease of feat: pay contacts via spark bitkit-core#193.What it needs to ship
Out of Scope
Design
N/A — no design available.
Preview
A wallet whose spending balance is held on Spark pays a contact who accepts only on-chain bitcoin. The router picks a Spark withdrawal, the confirm screen shows
Route: Spark, and the contact receives exactly 20,000 sats on Spark's regtest chain (2x speed).android-spark-j12x.mp4
QA Notes
Setup
bitkit://contact?pubky=<pubky>)../lsp POST /regtest/chain/deposit(cases 10, 11).mainwith the marketplace and payment-requests profiles, itsfixture-issueras the contact, and the app built withE2E=true E2E_BACKEND=local E2E_LOCAL_HOST=127.0.0.1 E2E_HOMESERVER_PUBKY=$(./pubky-marketplace info | jq -r .homeserver_z32)andadb reverse6286, 6287, 6288, 15411, 15412, 60001, 23735, 23000 and 23010.Journeys
paykit-spark-route.xml— a wallet whose spending balance is on Spark pays an on-chain-only contact through a Spark withdrawal, and the contact receives exactly 20,000 satsManual Tests
Two contacts as above, both Spark only (no channel); either one can play payer or payee.
./lsp POST /regtest/chain/deposit): Spending is the default source; switching to Savings pays the contact's on-chain endpoint, which is the payee's Savings address, and the payee's receive is linked to the payer.Route: Sparkwith the withdrawal's fee, the withdrawal pays exactly the amount to the payee's address, and the payee's request shows "Proof submitted".Evidence
Each case ran with dev debug builds on two regtest Android emulators (API 35), with Spark on Lightspark's hosted regtest and no channel. Both wallets were Spark only, except in cases 10 and 11, where the payer's Savings was funded too.
1cbb4b6c9: 300 sats paid; the payer showed Sent 301 (fee 1) to the contact with its preimage, and the payee showed Received 300 from the contact. Again through the router, Direct bolt11: 120 sats atbd755b839, and 110 sats at25b40e0fdon bitkit-core0.8.0-spark.3.case 1
case 1 on spark.3
1cbb4b6c9: after the payee's relaunch the payer opened list version 5 after 0 and paid 150 sats.f11-rotate2x.mp4
1cbb4b6c9: the force-stopped payee received 250 sats; on relaunch Home showed Received 250 from the contact and no duplicate rows.case 3 payer
case 3 payee
1cbb4b6c9: a 200-sat request was paid, and the payee showed "Proof submitted".case 4 payee
case 4 payer
bd755b839: both sides showed Rejected.f5-reject-payer2x.mp4
bd755b839: the 300-sat request was paid automatically ("Paid automatically $0.23"); a 2,000-sat request was kept manual ("the allowance limit is reached"); after End Allowance, a 200-sat request was presented for manual payment.case 6 payer
case 6 payee
1cbb4b6c9: with 4,473 sendable, 6,000 and 44,731 could not be entered; 4,473 was paid, quoted at ± ₿ 12.f9-above2x.mp4
bd755b839: two kills before Spark dispatched left the balance unchanged and the request payable; a kill after dispatch could not be timed, since Spark sends finish in under a second.case 8
case 8 relaunch
25b40e0fd: the first 100-sat period was paid from Spark and its proof queued; after a force-stop and relaunch the payer listed the subscription as Active, renewing the next day. Later periods not observed yet.25b40e0fd: from Savings, the router paid the payee's on-chain endpoint 15,000 sats at Normal (₿ 141); the payee showed Received 15,000 from the contact in Savings.f4-onchain2x.mp4
47a1484a8's fee fix: switching to Spending showedRoute: Spark; Spark's withdrawal365dc1df…paid exactly 2,000 sats to the payee's address and confirmed on Lightspark's chain (the payee's node runs on staging's chain, so its Savings doesn't show it); the proof was delivered and the payee showed "Proof submitted"; the payer's activity showed the contact. The confirm screen quoted ± ₿ 750 while Spark charged 2,190, and the activity read as a transfer: both fixed in47a1484a8.f5-onchain-payer2x.mp4
47a1484a8: the confirm screen opened from Spending at Instant (± ₿ 1); 200 sats sent and received.f5-router-request22x.mp4
47a1484a8: Spark refused the send before it left ("Spark could not prepare the payment"), the payment-failed screen showed, and after reconnecting no payment had been made.f9-offline2x.mp4
28a63f8on bitkit-core0.8.0-swaps.3, before the move to the router: Spark's withdrawal56263be8…paid exactly 20,000 sats to the contact. Not rerun since.Paykit flows with a Spark balance
Flows from the v0.1 specs at pubky/paykit-rs
cb8cba5. State a is Spark only, with no channel. State b is a channel too small for the amount, with Spark covering the rest. The manual case numbers above give the evidence.LightningRepoTest,PaykitAllowanceRepoTest).0.8.0-spark.3; ⏭️ public endpoint, because both test wallets hold a private link, which is paid first.28a63f8before the router.Automated Checks
AppViewModelSendFlowTestcases: router Direct Lightning, Direct on-chain from Savings, Service leg, None with a short balance and None with an unpayable contact; the Spark leg runs only from Spending; a request's confirm screen pays the endpoint the router picks; an on-chain request paid through the Spark leg starts and completes its proof, and a failed leg releases an unsent proof; a source switch on confirmation reroutes; Spark leg fees count in the warnings; the confirm tags reach the withdrawal; an on-chain request Savings can't cover goes to SparkPaykitPaymentStateBackupTestcase: a Spark withdrawal proof keeps its rail through backupPaykitPaymentProofRepoTestcases: an on-chain proof completed with a Spark withdrawal's txid, one settled from Spark's payment list on reconcile, and one released when its withdrawal never sentSparkRepoTestcases: a withdrawal quoted as a route leg, no leg the balance cannot cover, a withdrawal recorded with its contact and fee, a second withdrawal refused while one is unsettled, a journaled withdrawal settled or dropped on start, a withdrawal refused right before it sends or above the confirmed debit, the journal kept when the activity fails to save, an earlier withdrawal never settling a new attempt, tags on a recovered withdrawal, a contact withdrawal not marked as a transfer, and kept off the Spark screen's transfersjust compile,just test,just lintand the androidTest compile on bitkit-core0.8.0-spark.3: passModels used
claude-opus-5-5(reasoning:medium),claude-sonnet-5-5(reasoning:low)claude-opus-5-5(reasoning:medium)