Repository navigation
fix: confirm fixed Paykit requests directly - #1014
Conversation
|
Triage: milestone 3.1.0, the iOS twin of synonymdev/bitkit-android#1581. Fixed Paykit requests go straight to confirmation instead of a mostly disabled amount screen. It isn't a release blocker, and it closes no issue, so this PR carries the milestone itself. |
[Medium impact] The changes reach incoming Paykit request preparation and payment confirmation. The PR appears safe to merge, with a non-blocking issue where direct on-chain confirmation ignores the saved coin-selection algorithm. Findings
SummaryFixed quoted Bitcoin requests now bind their exact payment terms before opening confirmation, skipping the locked amount screen when no enabled USDT choice is offered.
Diagram%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Resolve incoming request] --> B{USDT enabled and offered?}
B -->|Yes| C[Choose asset and review amount]
B -->|No| D[Bind exact Bitcoin terms]
C --> E[Confirmation]
D --> E
E --> F[Wait for wallet readiness]
F --> G[Review fee and approve]
G --> H[Recheck request and send]
Reviews (1) · Last reviewed commit: "fix: confirm fixed Paykit requests direc..." · Reviewed by Greptile |
talosmachina
left a comment
There was a problem hiding this comment.
1 finding (1 P2). Opens a fixed-quoted Bitcoin Paykit request straight on Confirm by preparing the payment terms during scan handling. Reviewed 72d89f3, full tier, reasoned from the code and CI (iOS does not build here).
What I checked, and 3 candidates I ruled out
Read in full: handleScannedData (asset-selection branch), PaymentNavigationHelper.openPaymentSheet / appropriateSendRoute / contactPaymentRoute, PaykitRequestPricing.payment(requested:endpoint:period:at:quoteId:), the incoming-request preparation block in AppScene.swift, SendSheet.shouldShowSyncOverlay and the post-setup validation
Call sites traced: every handleScannedData(_, claimedContactPaymentContext:) caller (AppScene, SubscriptionsView, SendSheet retry, SendContactSelectView, AddContactView, PaymentNavigationHelper)
CI: validate, detect-changes and Greptile green. Run Tests, Integration Tests and build-local were still pending when I reviewed.
Ruled out
- Sync overlay bypassed on Confirm:
shouldShowSyncOverlaynow exempts asset selection only whilecurrentRoot == .amount, so Confirm waits for the node. The renamedtestBelowMinimumQuotedRequestWaitsForSyncBeforeFallbackasserts that. - Stale terms after a wallet switch: the send path rechecks
terms == app.paykitPaymentTermsandisValidbefore paying (SendConfirmationView.swift:1688), andresetSendStateclears both fields (asserted in the new test). - USDT-capable requests losing the asset picker: the block is gated on
!canSelectUsdt, andtestDollarRequestOffersAssetSelectionOnlyWhenUsdtIsEnabledcovers both settings.
Merge confidence: 4/5, one P2 on a narrow path, and the test suites had not finished at review time.
jvsena42
left a comment
There was a problem hiding this comment.
Requesting changes: one MEDIUM (Back from the manual coin picker), inline with a failing test.
Checked and clean:
contactPaymentRoutemaps.quickpayand.amountto Confirm for incoming requests, so QuickPay can't auto-pay a request whose terms are now bound.- Scanner and Enter Manually call
appropriateSendRoutewithout a claimed context, so the terms branch never runs there. - Converted initial subscriptions stay manual (
conversion == nil). returnToAmountReviewIfNeededre-validates terms on a wallet switch.- The rate-unavailable case is fixed by 963d810.
The android twin synonymdev/bitkit-android#1581 prepares coin selection at swipe, so this shape is absent there.
fixed-quoted-confirmation.xml is byte-identical to Android.
Device gate: not run (MEDIUM open).
|
@ovitrif Fixed the Spending/Savings switch in 6c79bae. Already-reviewed Bitcoin requests stay on Confirm with the selected rail’s exact quoted amount, refreshed fees and a required swipe. Missing/expired terms and amounts below the Lightning minimum return to Amount; BTC/USDT selection and hardware safeguards remain. The hosted test switches between 2,500 and 5,000 sats and verifies fee preparation, with separate fallback cases. The journey now matches Android and also covers Back from manual coin selection. All 47 focused tests pass; SwiftFormat and translation validation passed with no introduced diagnostics. |
jvsena42
left a comment
There was a problem hiding this comment.
The MEDIUM is fixed at 6c79bae6. Back from the manual coin picker now stays on Confirm, and manual selection still runs before paying. I resolved the thread.
Wallet-switch rebind in the same commit, clean:
- It needs USDT off, no hardware wallet, not submitting, the previous BTC terms equal to
paykitReviewedAmount, valid new terms andmeetsMinimum. - It sets
paykitPaymentTerms,paykitReviewedAmountandsendAmountSatstogether. acceptsPaymentAmountandacceptsLightningInvoiceAmountstill re-check at pay time.- Unavailable or expired terms, or an amount below the minimum, return to Amount.
fixed-quoted-confirmation.xml is byte-identical to synonymdev/bitkit-android#1581.
Device gate (partial): not run yet. Pending: payment-requests/fixed-quoted-confirmation.xml, cross-device with synonymdev/bitkit-android#1581, plus Back from the manual coin picker on device. Code scan: no HIGH/MEDIUM open. The approval follows the device run.
|
@ben-kaufman thanks, rechecked at 6c79bae. On a Spending ↔ Savings switch, |
|
Fixed the Xcode 16.4 type-checking failure in The custom-fee lifecycle test also now allows SwiftUI to cancel and restart preparation during navigation. It still requires exactly one successful preparation, rejects cancelled results, and checks the final manual-UTXO or confirmation route. Validation: local simulator build and all 50 confirmation, swipe, lifecycle and pricing tests passed; SwiftFormat passed on all eight PR Swift files. Translation validation has no errors and no added warnings compared with master. Every PR commit verifies locally and on GitHub. Local validation used Xcode 27; the Xcode 16.4 CI rerun is pending. The cross-device journey remains a separate reviewer device gate. |
|
The hosted Xcode 16.4 compiler still timed out on the remaining confirmation modifier chain. In 05beeb5 I split that expression into smaller private computed views. Inlining them reproduces the original expression byte-for-byte, preserving task IDs, modifier order, state and payment safeguards. All 50 focused confirmation, lifecycle and pricing tests pass locally. SwiftFormat is clean for all eight changed Swift files; full translation/compiler diagnostics contain no findings introduced by this fix. Independent review is clean, and all six PR commits are verified. Reviews are re-requested; the new hosted CI run still needs to confirm the Xcode 16.4 build. |
jvsena42
left a comment
There was a problem hiding this comment.
The delta since my last review, b58c196, is clean. It only moves code: confirmationContent → confirmationLayout with the same modifier chain in the same order, so payment and authorization behaviour is unchanged. The testCustomFeeWalletSwitchSurvivesFeeScreenNavigation relaxation (successfulCoinPreparations == 1) is safe, because cancelled preparations throw before they assign anything.
CI is red because of this change, so I'm holding the approval. Unit tests on Xcode 16.4 (run 39308712256) still stop at the compiler is unable to type-check this expression in reasonable time, now at confirmationLayout (SendConfirmationView.swift:393), so no test ran. The ~15-modifier chain is still one expression. Splitting the onChange/.task group into its own computed property or a ViewModifier should get it under the limit.
Device gate (partial): not run yet. Pending: payment-requests/fixed-quoted-confirmation.xml cross-device with synonymdev/bitkit-android#1581, and Back from the manual coin picker.
|
@jvsena42 The latest commit |
jvsena42
left a comment
There was a problem hiding this comment.
Re-reviewed to 1607a10. No new findings.
1607a10 gates submitPayment itself, as I asked in my last review. The guard now requires preparingRequest == nil, !isRequestSetupPending, walletSwitchContext == nil and prepared coins (or the automatic path) before any warning, PIN, biometrics or hardware validation.
Checked:
- No payment before setup finishes. While
isSetupPendingholds,sendContextis nil, the swipe is disabled and loading, and a swipe that gets through throwsCancellationErrorbeforeisSubmittingPaymentis set.SwipeButtonresets, so a rejected swipe never leaves the view busy. - The pending state cannot stick.
flow.isSetupPendingis cleared in the setup task'sdefer. The task is cancelled only bycleanup()(the app-lock retain path re-runssetUpSend()) and bysetUpSend()itself, which re-arms it. The fee-rate retry is bounded, and on failure the Confirm shows the retry row with the swipe disabled. - A swipe in the gap between setup and coin preparation sets
walletSwitchContextand starts preparation once:startWalletSwitchdedupes the view.taskrun for the same context, so no second picker. - Displayed amount equals paid amount. The delta does not touch it, and the amount validation and terms-equality checks still run before the send.
The journey parity thread stays deferred to synonymdev/bitkit-android#1610. The new sentence in fixed-quoted-confirmation.xml action 2 belongs on that list.
Performance: this adds a user wait. For an incoming request the swipe stays loading until setup ends. The longest leg is the hardware-funding estimate when Savings cannot cover the quote. The gate times PaymentRequestConfirm shown → swipe enabled, with a paired Trezor and insufficient Savings.
Device gate (partial): 1607a10 — not run yet. Pending: payment-requests/fixed-quoted-confirmation.xml (action 2's new mid-setup swipe check included) cross-device with Android, paykit-quoted-usdt.xml, and the timing above. Device results will follow in a separate review.
jvsena42
left a comment
There was a problem hiding this comment.
Device gate result. Requesting changes for one MEDIUM, inline: journey action 14 fails, because a fee calculation that fails after coin selection leaves Confirm swipeable.
Device gate: 1607a10 — iPhone 17e simulator (payer, PIN required for payments) against Android master 1337f12 on Pixel_9 (requester), the two Paykit contacts linked both ways, Trezor emulator paired. perf: see below. l10n: n/a.
payment-requests/fixed-quoted-confirmation.xml, iOS paying Android requests:
- 1: pass.
- 2: pass, including the new mid-setup swipe. With Savings, 7 swipes during preparation opened nothing; the first after setup opened the PIN. With hardware, 5 swipes during pending setup opened no PIN and nothing was signed. 3,202 sats for $2.50, fee 143 shown before payment.
- 3: pass. Largest First selection and fee; with manual selection the picker opens first, Back keeps Confirm, and returning through the picker recalculates the fee.
- 4: pass. With Savings too small, BTC and USD-quoted requests settle on hardware Confirm with a fee and no Amount detour, in auto and manual modes; every request reopens.
- 5, 6, 7, 9: pass.
- 8: partial. A 1-hour request closed itself at expiry with nothing sent; Android uses the request expiry as the payment deadline, so the fresh-terms retry cannot be reached.
- 10: partial. A Savings + BOLT11 request with USDT on opens straight to Confirm; switching rails and the Lightning minimum need a channel on the requester.
- 12: adapted. 546 sats closes with the existing dust toast; 547 goes to Confirm; a $0.42 quote (538 sats) opens Amount with Continue disabled and stays open, and the Back arrow closes the sheet without reopening it.
- 14: fail (inline).
Android paying an iOS $2.50 request: paid with one swipe (Android master has no PIN); parity is tracked in synonymdev/bitkit-android#1610.
paykit-quoted-usdt.xml: action 17 creator part passes ($150.50 keeps 150.50 through Edit). An Android USDT-only $150.50 request opens a locked Amount with USDT only and Continue disabled, and Back closes without any Bitcoin confirmation.
perf, Trezor paired with Savings too small, Confirm shown → swipe accepted:
- head: 14.5–16.5 s over 6 samples (BTC and USD quotes).
- master f3e97b0: ≤0.8 s, because master accepts the swipe while hardware setup is still running, which is what this PR gates.
- The 15 s is two sequential hardware
composeTransactioncalls of about 7.5 s each; master runs the same two. The spinner ends after the first, and the arrow stays dimmed until the second ends. The duplicate compose is pre-existing, so it is not a finding here.
Skipped:
⚠️ Actions 11 and 13: need an LNURL requester.⚠️ paykit-quoted-usdt.xmlsix-decimal step: needs paykit-fixture-sender; left to e2e CI. Funded USDT review, fee and comma-region checks were not run.⚠️ The interactive edge-swipe Back in actions 10 and 12: the simulator gesture never popped Amount in three tries, so it is not established.
Out of scope (same on master): with a Trezor paired, Savings max-sendable estimation fails by one sat (LDK "Available: 779999sats, Required: 780000sats"), so selectHardwareFundingSourceIfNeeded routes requests Savings could pay to the hardware wallet.
piotr-iohk
left a comment
There was a problem hiding this comment.
QA review
Scope: Follow-up review at 1607a10c. It re-reviews all changes since the previous reviewed head 659a7c07, plus the code paths those changes affect. Coverage of the unchanged rest of the PR carries over from the baseline review. Git confirms that the merge base is still 3f14ea05 and that the previous head is an ancestor of the current head. The update is one commit (1607a10c, "block payment while request setup is pending"). It changes three files: SendConfirmationView.swift, SendConfirmationSwipeTests.swift (new to the PR diff, which now has 23 paths) and the fixed-quoted-confirmation.xml journey.
Code review completed with unverified earlier concerns.
No new actionable code findings in the follow-up.
What the update changes:
- Swipe blocked during request setup. For incoming requests, the payment swipe is now disabled and shows a loading state while SendSheet setup or fallback funding is still pending.
- Manual swipe guard.
submitPaymentnow rejects a manual swipe in three cases: request setup is pending, a coin preparation is in progress, or automatic coin selection is configured but the request has no coins selected yet. It throwsCancellationErrorbefore the warnings, PIN, biometric, hardware-signing and broadcast steps. In the last case it starts the existing preparation run instead.
Why this holds up:
- Swipe reset.
SwipeButtonresets after any thrown error. - Preparation scope. The preparation only runs for Savings payments of incoming requests, with a hardware wallet not active, a known fee rate and no selected coins.
- No retry loop. A successful run always sets
selectedUtxos, so the swipe cannot loop. - Manual coin selection. After Back from the coin picker,
manualCoinsRequestedContextmakes the swipe go throughrequireManualCoinSelectionand reopen the coin picker. - Setup flag clears.
isSetupPendinggoes back to false when the setup task finishes and is reset when setup is restarted, so the new swipe-disable condition does not add a new stuck state. - Single caller. The swipe is the only manual caller of
submitPayment. The automatic path already waited onisSetupPendingand is unchanged.
Validation: source analysis only. There is no Swift toolchain in this Linux environment, so nothing was compiled and no tests ran. That includes the updated SendConfirmationSwipeTests, which I only read. I did not check the matching CI run. Device testing: not performed in this review. I did not re-check the new swipe guard against Android; the Android comparison from the baseline review covers the earlier head only.
Suggested additional test cases:
- iOS, automatic coin selection with a non-default algorithm. Open a fixed Savings-only BTC request on a payer with funded Savings. If a manual swipe happens before the coins are chosen (for example, right after a fee-rate change), verify that the swipe resets and no PIN or biometric prompt appears. Then verify the selected coins and fee update, and that a second swipe pays the quoted amount.
- iOS, the same request with a hardware fallback funding source. While the fallback funding source is still being chosen, verify the swipe stays disabled with a spinner. Once a funding source is chosen, verify it becomes usable.
Earlier findings reconciled at this revision: 2 still present, 6 verified fixed, 12 could not be verified.
Reconciliation incomplete: the following earlier concerns remain unverified. This report does not establish that they are fixed or invalid, or establish approval or device readiness.
Could not verify earlier concern — USDT toggle reportedly cannot be disabled at displayed zero balance: The report at #1014 (comment) (USDT toggle cannot be disabled at a displayed zero balance) still lacks the active guard state that the author asked for in comment 6303103440. The toggle and eligibility guards are identical to base 3f14ea0, and the 659a7c0..1607a10 delta does not touch them. Unchanged code neither identifies which guard was active nor disproves the symptom. The comment count is still 40 and no newer comment supplies the guard booleans. Review 5722769833 exercised USDT entry only with an unfunded test wallet, not the original state. Code-only execution cannot recover that state. No introduction or fix is claimed.
Could not verify earlier concern — Native interactive fallback Back remains unverified: The source handles a committed pop from the fallback Amount to an empty path. Two current-head device reports still establish neither a cancelled nor a completed native interactive transition: ovi-reviewer review 5722769833 and jvsena42 review 5729110725. Both say edge drags and gesture presets never popped Amount, while header Back closed the sheet. Failure to start the gesture proves neither a defect nor a pass. Device execution is prohibited in this stage.
Could not verify earlier concern — Camera-permission CI failure has no established cause: The matching UI test passes again in current-head CI. The cause of the historical camera-permission failures is still unproven, and a later pass does not establish a permanent fix. That CI run merges the head into current master f3e97b0, not into the supplied base, so it is current-head evidence and not an exact-tree run of the supplied pins. No local Swift/Xcode execution was possible.
Could not verify earlier concern — Remaining provider and physical-hardware acceptance is incomplete: Current-head device reports narrow the gaps but do not complete acceptance. jvsena42's review 5729110725 passes J1 actions 1-7 and 9. Action 8 is partial, actions 10 and 12 are partial or adapted, and action 14 fails (see externalConcern ext-fee-calc-failure-swipe-enabled). Actions 11 and 13 were skipped because they need an LNURL requester, and the paykit-quoted-usdt six-decimal and funded USDT steps were not run. ovi-reviewer's review 5722769833 states that physical authorization, provider USDT settlement and fresh-install recovery were not driven. These are execution limitations. Code-only execution cannot close them.
Could not verify earlier concern — Native interactive fallback Back remains unverified: This separately retained item has the same unresolved limitation. The source covers committed path changes, but both current-head reports leave native cancellation and completion unestablished. It is not marked duplicate because the counterpart lineage is not open. Device execution is prohibited.
Could not verify earlier concern — Camera-permission CI failure has no established cause: This separately retained item still has no established cause. The current-head CI pass does not explain the earlier camera failures or prove a permanent fix.
Could not verify earlier concern — Remaining provider and physical-hardware acceptance is incomplete: This separately retained item is still an execution limitation. Current-head reports leave LNURL-requester actions, six-decimal and funded USDT steps, physical signing and biometrics, provider settlement and full recovery unverified, and journey action 14 fails on device.
Could not verify earlier concern — Historical launch and E2E retry failures have unresolved causes: The historical launch and RBF success-toast timeouts still have unproven causes. The RBF symptom recurred at the current head. In E2E run 41198752500, the first invocation failed '@boost_2 - Can do RBF' with BoostSuccessToast not displayed after 30000 ms (12 passing, 1 failing), and it passed on the built-in second invocation. The run concluded success. Retry recovery does not diagnose the cause. Unit and integration runs at this head succeeded.
Could not verify earlier concern — Historical launch and E2E retry failures have unresolved causes: This separately retained item is still unresolved. The current-head E2E run reproduced the RBF success-toast timeout on its first invocation and recovered only through the built-in retry, so neither the cause nor a permanent fix is established.
Could not verify earlier concern — Historical launch and E2E retry failures have unresolved causes: This separately retained item is still unresolved. Code inspection and a current-head retry recovery do not explain the earlier launch, RBF-toast or transient runtime failures.
Could not verify earlier concern — Contact-payment preparation latency remains unmeasured: The contact-payment preparation latency concern in #868 (still open) has no staging measurement at this head. jvsena42's current-head timing (14.5-16.5 s from Confirm shown to swipe accepted, Trezor paired, Savings too small) measures the hardware-funding setup wait that this delta adds, not the shared-Paykit-runtime contact-payment latency of #868. The reviewer attributes it to two pre-existing hardware composeTransaction calls. No timing run occurred here.
Could not verify earlier concern — Contact-payment preparation latency remains unmeasured: This separately retained item is the same unresolved latency concern. Issue #868 is open, and no staging contact-payment timing exists at this head.
Existing concerns
- [LOW] Align fixed-quote journey wording across platforms — still unresolved; already tracked.
- [MEDIUM] Fee-calculation failure after coin selection leaves request Confirm swipeable — still unresolved; already tracked.
Reviewer model: claude-opus-5-5
Reasoning effort: high
Integrate current master and route incoming payment requests back to Amount when fee calculation fails after coin selection. Clear reviewed terms before authorization and cover fixed/quoted requests plus Back dismissal.
|
Addressed the J1 action 14 fee-calculation failure in The unchanged regression reproduced the defect for fixed BTC and quoted USD: eight expected assertion failures in one execution. The same test passes after the fix, including Back dismissal and the existing authorization/submission tripwires. This uses controlled test UTXOs and the actual confirmation-root host; it does not prove a funded or physical-device journey. Final validation: 2,954 unit and 39 UI executions passed; five fixture cases skipped, zero failures or test retries. The 2,993 passes include 30 distinct launch configurations. Complete logs and nested reports were inspected. Ten runtime warnings remain (nine QoS notices and one State-binding warning in an existing PR-added test), plus 72 ignored CoreGraphics NaN messages, stale-RGS/background failures and simulator/framework notices; no fresh master-runtime baseline is claimed. Xcode 27.2 (27B52), iOS 26.5 (23F77), isolated iPhone 17 simulator; frozen tree The first red invocation crashed before the fee boundary because its simulator build omitted app-group entitlements. After preserving and auditing that failure, one bounded signing correction reached the intended regression. A build-only invocation-option failure and an artifact-copy status-label mismatch are also retained; neither caused a test retry. All prior fixture, provider, funded, hardware and shared-navigation acceptance limits remain. Previous Current validation section, preserved verbatim at its original revisionCurrent validation — published
|
There was a problem hiding this comment.
Verdict:
Findings: No new defect found. The post-selection fee-failure finding is fixed at aac6885.
QA for aac6885:
Built this head with Xcode 27.0 and tested an iPhone 17 Pro simulator on iOS 26.5, using a linked disposable issuer and funded Bitcoin regtest. No payment was authorized. Videos play at 2×.
Fixed Bitcoin request fee failure ✅ A real fee error after selection returned the request to Amount.
For J1 action 14, the fixed 779,586-sat request selected real coins from a 780,000-sat wallet. An unpublished one-shot fee-rate stress hook then made LDK return InsufficientFunds. The request moved to locked Amount without a PIN prompt; one header Back closed it without another preparation attempt. Accessibility snapshots verified its unpaid reopen with the original amount and a 414-sat fee.
fee-btc-stress.2x.mp4
Post-selection fee failure and header Back.
Quoted dollar request fee failure ✅ The USD-to-BTC quote used the same safe fallback.
The fixed 2.50 USD request quoted exactly 779,586 sats. The same stress input caused a real LDK fee error after successful selection, followed by locked Amount and one header Back. Snapshots verified reopening at 779,586 sats with a 414-sat fee; the issuer retained the proposed request with zero proofs, and its regtest address received nothing.
fee-usd-stress.2x.mp4
Quoted request fee failure and header Back.
Fresh review after fee recovery ✅ A new swipe was required after reopening.
After restoring the unmodified head, snapshots verified that the previously failed quote reopened on Confirm at 779,586 sats with a 414-sat fee. The recording shows a fresh real swipe opening the existing over-half-balance warning; Cancel returned to unpaid confirmation.
fee-recovery.2x.mp4
Fresh swipe and warning cancellation on the unmodified head.
Native Back from fallback Amount ⏭️ Neither a cancelled nor a completed interactive transition was established.
On the unmodified head, real automatic selection failed for a 779,675-sat quote and opened Amount. Valid short, slow and fast left-edge gestures left Amount in place. Header Back closed the sheet without reopening it. This remains an unrun acceptance subcase; the attempts establish neither a native Back pass nor a new product defect.
native-back.2x.mp4
Fallback Amount, attempted edge gestures and header Back.
Confirmation lifecycle regressions ✅ The committed fee-failure, lifecycle, swipe and navigation assertions passed.
The exact-head XCTest results cover fixed BTC and quoted USD fee failures after successful selection, cleared review terms, Back without repeated preparation, cancellation, manual coins and funding setup. Assertions establish these results; the focused recording shows the hosted test execution rather than a full payment journey.
fee-regression-focus.2x.mp4
Focused replay of the new fee-failure regression.
Unpublished fee-rate test hook
The hook changes only a one-shot test input after real coin selection: 1 → 10,000 sat/vB. That stress rate makes both LDK's exact-payment calculation and its send-all fallback fail. It replaces no SDK result, fallback, navigation or authorization decision. The natural 779,586-sat quote was payable here at a 414-sat fee, so the stress run is not a literal reproduction of the earlier normal-rate shortfall. The committed regression separately covers the original error boundary with controlled wallet state. The hook was removed before the recovery and native Back checks.
diff --git a/Bitkit/ViewModels/WalletViewModel.swift b/Bitkit/ViewModels/WalletViewModel.swift
index 940e92d2..276de8e1 100644
--- a/Bitkit/ViewModels/WalletViewModel.swift
+++ b/Bitkit/ViewModels/WalletViewModel.swift
@@ -757,6 +757,14 @@ class WalletViewModel: ObservableObject {
}
guard !Task.isCancelled, isCurrentSend() else { throw CancellationError() }
selectedUtxos = utxos
+ #if DEBUG // test hook
+ let feeHookURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("review.raiseFee") // test hook
+ if FileManager.default.fileExists(atPath: feeHookURL.path) { // test hook
+ try? FileManager.default.removeItem(at: feeHookURL) // test hook
+ Logger.info("REVIEW_FEE_HOOK: real selection completed; raising fee rate from \(selectedFeeRateSatsPerVByte) to 10000", context: "ReviewFeeHook") // test hook
+ self.selectedFeeRateSatsPerVByte = 10000 // test hook
+ } // test hook
+ #endif // test hook
Logger.info("Selected UTXOs: \(String(describing: selectedUtxos))")
}The device log records the sequence for both denominations:
Quoted USD: real selection completed; fee rate 1 -> 10000
Failed to calculate actual fee: Insufficient funds
Hiding sheet send reason: Back from the request amount review
Fixed BTC: real selection completed; fee rate 1 -> 10000
Failed to calculate actual fee: Insufficient funds
Hiding sheet send reason: Back from the request amount review
Earlier passing evidence is retained only for behavior later pushes leave unchanged:
- Pending setup, saved/manual coins, hardware fallback and J1 controls at 1607a10. Its failed fee-calculation case is replaced by the checks above.
- Request/subscription controls and private-USDT layout at 1607a10.
- Savings/LNURL minimums and preparation-failure controls at 0801b4f.
- Expiry/fresh review, exact USDT review and serialization at d5e88de.
- Initial funded asset choice and scrolling USDT details at 20cfeff.
- Passing comma-region entry and cents checks at 9dacc08. Its six-decimal failure is superseded by the later passing precision report.
- Passing six-decimal and one-micro request/subscription display at 629a874.
Reviewed the contiguous 1607a10..aac6885 delta, its three PR-specific files and reached fee, preparation, cancellation, manual and hardware callers, plus the prior open finding. Base-only allowance, contact-refresh and artwork changes received no separate device case. Full J1/J2 completion, physical signing and provider settlement are not claimed.
Cross-platform comparison:
- synonymdev/bitkit-android#1581 fix: confirm fixed Paykit requests directly matches direct Bitcoin confirmation; the documented iOS LNURL/Savings selector remains on Amount.
- synonymdev/bitkit-android#1638 fix: keep fixed paykit requests on confirmation with an asset switch also keeps USDT selection on Confirm, while iOS retains Amount there.
- synonymdev/bitkit-android#1610 Align fixed-quote journeys with both payment flows retains the deferred journey wording follow-up.
Reviewed by gpt-6.1-sol-high
|
Hosted runtime CI for XCTest jobs used Xcode 16.4 on iPhone 16 / iOS 26.2; E2E used Xcode 26.2 on iPhone 17 / iOS 26.2. These are different toolchains from local Xcode 27.2 / iOS 26.5 validation. Each runtime checkout is merge
Both hosted XCTest builds retain 286 unique duplicate-symbol warnings / 571 occurrences. Project, dependency lockfile and package-action inputs match actual base E2E's external harness used Node 22.23.4/npm 10.9.9. New automated QA at this head reports fixed-BTC and quoted-USD post-selection fee fallback, header Back and unpaid reopening on an iPhone 17 Pro simulator. Its disclosed, unpublished one-shot fee-rate input (1 to 10,000 sat/vB) reached a real LDK InsufficientFunds catch; it did not literally reproduce the original normal-rate shortfall. Recovery on the unmodified head required a fresh swipe and allowed warning cancellation. Native edge gestures still established neither a completed nor a cancelled interactive Back transition. These are reviewer-reported results, not independent re-execution or fixture authority; the bot comment is not a human approval. Five video links are preserved in that review; no independent visual inspection is claimed here. A read-only xcresult export initially hit a TestReport cache permission error; bounded reader recovery succeeded without rerunning any build or test. All three named reviewers remain pending; no approval, repeated request or dismissal is inferred from green CI. Native interactive Back, full linked-request/provider/funded/physical-device and recovery acceptance still require their documented prerequisites. Prior failures, recovered E2E failures and missing local-only diagnostic disclosures retain their original attribution. Previous Current validation section, preserved verbatimCurrent validation — published
|
piotr-iohk
left a comment
There was a problem hiding this comment.
QA review
Scope: Re-reviewed the full PR diff for bitkit-ios#1014 at aac68853 as a full reassessment. No verified baseline was supplied, and the saved comparison base changed from 3f14ea0 to 831ec3e6, which is also the merge base. Inherited coverage was not used.
Code review completed with unverified earlier concerns.
Valid fixed and quoted requests bind above-minimum terms and open confirmation directly. Missing, expired, and below-minimum quotes stay on Amount. A Spending/Savings switch rebinds that rail's quote before refreshing fees; coin preparation waits until funding setup finishes; fee or coin-preparation failure clears reviewed terms and returns to Amount; Back from that fallback closes the sheet. Exact USDT review text keeps six decimals. Backup start/stop requests are ordered so a newer request wins and an in-flight metadata upload and writer drain can finish.
Device testing: not performed in this review. The new lifecycle, navigation, amount, and backup tests were inspected and not executed. This pass did not compile the app.
Shared journey text was compared with bitkit-android 1337f121. Both open a valid fixed quote on confirmation and send unavailable, expired, or below-minimum Lightning terms back to Amount. iOS keeps LNURL-plus-Savings and Bitcoin-plus-USDT on Amount, and returns a below-minimum Savings quote there; Android keeps those choices on confirmation and disables authorization below its Savings minimum. Android fee-failure recovery was not traced in Kotlin.
Suggested additional test cases
- iOS, funded regtest payer, fixed USD request offering Savings and BOLT11, with coin selection succeeding and the following fee calculation failing. From the fallback Amount, tap Continue, switch Spending/Savings, and swipe only after the new quote and fee are shown. Expected: one confirmation, the selected rail's exact quote and fee, a single coin selection, and no payment for the other rail's amount.
Earlier findings reconciled at this revision: 1 still present, 1 verified fixed, 12 could not be verified.
Reconciliation incomplete: the following earlier concerns remain unverified. This report does not establish that they are fixed or invalid, or establish approval or device readiness.
Could not verify earlier concern — USDT toggle reportedly cannot be disabled at displayed zero balance: The report at #1014 (comment) says the USDT toggle could not be turned off at a displayed zero balance. The toggle and eligibility guards at aac6885 are identical to base 831ec3e, so this pull request does not change them. Unchanged code neither identifies which guard was active nor disproves the symptom. The author asked for that guard state in comment 6303103440. All 42 conversation comments were scanned and none after that comment reports the guard booleans. A later fresh-fixture success remains limited to that fixture. This code-only pass has no Swift toolchain and cannot reproduce the missing runtime state. Focused open and closed issue search found no usdtDisableBlocked issue, so tracked_separately is unavailable. No introduction or fix is claimed.
Could not verify earlier concern — Native interactive fallback Back remains unverified: The source handles a committed pop from the fallback Amount to an empty path. The current-head report #1014 (review) still establishes neither a cancelled nor a completed native interactive transition: edge gestures left Amount in place, while header Back closed the sheet. Failure to start the gesture proves neither a defect nor a pass. Device execution is prohibited in this stage.
Could not verify earlier concern — Camera-permission CI failure has no established cause: The matching UI test passes on the current-head merge. The cause of the historical camera-permission failures is still unproven, and a later pass does not establish a permanent fix. The unit run merges the head into the supplied base and its tree equals HEAD, so it is current-head evidence. No local Swift or Xcode execution was possible.
Could not verify earlier concern — Remaining provider and physical-hardware acceptance is incomplete: Current-head reports narrow the fee-failure and header-Back cases but do not complete acceptance. Review 5730839316 at aac6885 does not claim a full J1/J2 pass, physical signing, or production-provider USDT settlement. The earlier current-lineage device report at 1607a10 left LNURL-requester actions, six-decimal and funded USDT steps, and native edge-swipe Back unverified. These are execution limitations. Code-only execution cannot close them.
Could not verify earlier concern — Native interactive fallback Back remains unverified: This separately retained item has the same unresolved limitation. The source covers committed path changes, but the current-head report leaves native cancellation and completion unestablished. It is not marked duplicate because the counterpart lineage is not open. Device execution is prohibited.
Could not verify earlier concern — Camera-permission CI failure has no established cause: This separately retained item still has no established cause. The current-head CI pass does not explain the earlier camera failures or prove a permanent fix. It is not marked duplicate because the counterpart lineage is not open.
Could not verify earlier concern — Remaining provider and physical-hardware acceptance is incomplete: This separately retained item is still an execution limitation. The current-head review leaves full J1/J2, physical signing, and provider settlement unverified. It is not marked duplicate because the counterpart lineage is not open.
Could not verify earlier concern — Historical launch and E2E retry failures have unresolved causes: The historical launch timeout and the parent-head RBF success-toast timeout still have unproven causes. E2E run 41356864579 at this head completed successfully: the log records 13 passing and no failing count, and @boost_2 dragged BoostSuccessToast rather than timing out. An address-type toast retry and four log-stream restarts are separate from that historical timeout. Later success does not explain comments 6444464437, 6497736273, 6507674422, and 6510561206. The historical runtime state cannot be recovered from code.
Could not verify earlier concern — Historical launch and E2E retry failures have unresolved causes: This separately retained item is still unresolved. The current-head E2E success does not explain the historical launch or RBF timeouts. It is not marked duplicate because the counterpart lineage is not open.
Could not verify earlier concern — Historical launch and E2E retry failures have unresolved causes: This separately retained item is still unresolved. Code inspection and a current-head E2E pass do not explain the earlier launch, RBF-toast, or transient runtime failures. It is not marked duplicate because the counterpart lineage is not open.
Could not verify earlier concern — Contact-payment preparation latency remains unmeasured: The contact-payment preparation latency concern in #868 remains open and has no staging measurement at this head. The 14.5-16.5 s timing in review 5729110725 measures the hardware-funding setup wait at 1607a10, which that reviewer attributes to two pre-existing composeTransaction calls. It is not the shared-Paykit-runtime staging measurement named by issue 868. No timing run occurred here.
Could not verify earlier concern — Contact-payment preparation latency remains unmeasured: This separately retained item is the same unresolved latency concern. Issue 868 is open, and no staging contact-payment timing exists at this head. It is not marked duplicate because the counterpart lineage is not open.
Findings
- [LOW] Align fixed-quote journey wording across platforms — now at
journeys/payment-requests/fixed-quoted-confirmation.xml:14. Still present from the earlier review.
Reviewer model: grok-4.7
Reasoning effort: high
piotr-iohk
left a comment
There was a problem hiding this comment.
Focused device QA — fee failure, retry and wallet switching
Tested aac68853 on the existing iPhone 17 / iOS 26.0 simulator, using local regtest/Electrum/LND/Pubky and a distinct Android requester. Installed the CI artifact from run 41356864579; its merge checkout 8405acaf has the same source tree as this head. Wallets were preserved.
Outcome: the focused run FAILED the retry/single-preparation assertion. One MEDIUM finding is attached inline. Hold sign-off for a correction and retest of that sequence. No incorrect amount, duplicate payment, authorization bypass or funds loss was observed. This is the complete result of this focused session, not a full J1/J2 or whole-PR device pass.
Setup and controlled failure
Created one fixed $250 request offering Savings and Spending/BOLT11, note QA101-fee-retry, payable as 320,921 sats on both methods. Payer balances: Savings 474,714, Spending 502,998 sats. Opened the request on Spending, then switched to Savings with automatic Branch and Bound selection.
After real selection succeeded with one 474,714-sat UTXO, a one-call LLDB change raised only the LightningService.calculateTotalFee fee argument from 1 to 10,000 sat/vB. The real SDK returned InsufficientFunds. Amount, recipient, navigation and authorization were not mocked; no app source/binary patch was installed. The breakpoint was disabled immediately and the debugger detached before the switch comparison. Subsequent calculations used normal fees. This reproduces the error branch under controlled stress, not a natural fee-rate shortfall.
Executed results
| Check | Result | Observation |
|---|---|---|
| Successful selection → fee failure → locked Amount | PASS | Real selection at 11:51:04 UTC, fee error at 11:51:31. “Please try again”; locked 320,921-sat Amount; no authorization prompt. The source clears reviewed terms; private state was not independently read from memory. |
| Continue and inspect selected-method fees | PASS | Recovered Savings showed 143 sats; Spending showed ~1 sat; returning to Savings restored 143 sats. Amount remained 320,921 sats. |
| One preparation/active confirmation after retry and switching | FAIL — MEDIUM | One Spending → Savings tap produced two selection starts, two selected-UTXO results and two real fee calculations at 11:52:30.648–.652 UTC. Header Back → Amount → Back revealed the older confirmation. |
| Fresh swipe and cancellation | PASS within this boundary | A new swipe was required and opened the “over 50% of your total balance” warning. Cancel returned to confirmation. No PIN/biometric or final payment was attempted. |
| Full dismissal/reopen control | PASS | Reopened the same unpaid request, then switched Spending → Savings: one selection and one 143-sat calculation at 11:55:12 UTC. |
| Unpaid state and cleanup | PASS | Both balances unchanged; requester still showed the same request pending. No duplicate request/payment observed. Dismissed only this test request afterward; requester showed Rejected. |
The original post-selection fee-failure thread is marked resolved, and its fallback fix passes here. The new failure is the subsequent Continue → wallet-switch lifecycle; this does not re-report the former enabled-confirmation-after-error defect.
Evidence and where to look
Short Back-navigation clip (40 seconds): about 00:13, Back returns to Amount; about 00:31, the second Back reveals the older confirmation.
back-after-retry.mp4
Complete focused-run recording (5:39, normal speed): fee failure around 01:14, Continue around 01:31, switch back to Savings around 02:14, first Back at 03:18–03:19, second Back at 03:36–03:37, clean-reopen Savings control around 04:55.
fee-retry.gh.mp4
Timestamped app-log excerpt below — inspected, scoped app-log excerpt. Duplicate preparation is established by the logs, not visually distinguishable in the recording: the two fee calculations finish a millisecond apart. Compare 11:52:30 with the single preparation at 11:55:12.
Required retest and remaining scope
After correction, repeat successful selection → fee failure → Amount → Continue → Savings/Spending/Savings; assert one active confirmation/preparation owner, one preparation per switch, correct fees, and Back without resurfacing the older review. Add this exact continuation to the lifecycle regression: the existing test checks fallback → immediate Back, and its Amount destination is a placeholder, so it never exercises real Continue and switching.
Native interactive Back, natural fee shortfall, quote expiry/replacement, PIN/biometrics, settlement, hardware and the wider PR journey matrix were not tested in this session. Header Back is the navigation evidence. No new master runtime comparison was performed; the inline explanation identifies the newly added fee-error fallback as the source-level entry into this failing retry sequence.
Timestamped app logs: failure, retry, duplicate preparation and clean-reopen control
[2026-11-02 11:51:04.543 UTC] INFOℹ️: Selecting UTXOs with algorithm: branchAndBound, target amount: 320921 sats, fee rate: 1 sats/vbyte [WalletViewModel.swift: setUtxoSelection(coinSelectionAlgorythm:isCurrentSend:listSpendable:selectForAmount:) line: 742]
[2026-11-02 11:51:04.552 UTC] INFOℹ️: Selected UTXOs: Optional([LDKNode.SpendableUtxo(outpoint: LDKNode.OutPoint(txid: "b5cc711319dc7595156a1143d0aab81ee7d59a998878adc29d071cecf4e90bd0", vout: 0), valueSats: 474714)]) [WalletViewModel.swift: setUtxoSelection(coinSelectionAlgorythm:isCurrentSend:listSpendable:selectForAmount:) line: 761]
[2026-11-02 11:51:31.235 UTC] ERROR❌: Failed to calculate actual fee: AppError(message: "Insufficient funds", debugMessage: Optional("The available funds are insufficient to complete the given operation."), paymentFailureReason: nil, underlyingError: Optional(LDKNode.NodeError.InsufficientFunds(message: "The available funds are insufficient to complete the given operation."))) [SendConfirmationView.swift: calculateTransactionFee() line: 2057]
[2026-11-02 11:51:47.942 UTC] INFOℹ️: Selecting UTXOs with algorithm: branchAndBound, target amount: 320921 sats, fee rate: 1 sats/vbyte [WalletViewModel.swift: setUtxoSelection(coinSelectionAlgorythm:isCurrentSend:listSpendable:selectForAmount:) line: 742]
[2026-11-02 11:51:47.944 UTC] INFOℹ️: Selected UTXOs: Optional([LDKNode.SpendableUtxo(outpoint: LDKNode.OutPoint(txid: "b5cc711319dc7595156a1143d0aab81ee7d59a998878adc29d071cecf4e90bd0", vout: 0), valueSats: 474714)]) [WalletViewModel.swift: setUtxoSelection(coinSelectionAlgorythm:isCurrentSend:listSpendable:selectForAmount:) line: 761]
[2026-11-02 11:51:47.971 UTC] INFOℹ️: Calculated transaction fee: 143sats for sending to address bcrt1q7nhtndqq07h602qwc8qv0qequsjcdgyl793npw - [LDK] [ldk_node::wallet:1931] [Logger.swift: log(record:) line: 173]
[2026-11-02 11:52:30.648 UTC] INFOℹ️: Selecting UTXOs with algorithm: branchAndBound, target amount: 320921 sats, fee rate: 1 sats/vbyte [WalletViewModel.swift: setUtxoSelection(coinSelectionAlgorythm:isCurrentSend:listSpendable:selectForAmount:) line: 742]
[2026-11-02 11:52:30.648 UTC] INFOℹ️: Selecting UTXOs with algorithm: branchAndBound, target amount: 320921 sats, fee rate: 1 sats/vbyte [WalletViewModel.swift: setUtxoSelection(coinSelectionAlgorythm:isCurrentSend:listSpendable:selectForAmount:) line: 742]
[2026-11-02 11:52:30.650 UTC] INFOℹ️: Selected UTXOs: Optional([LDKNode.SpendableUtxo(outpoint: LDKNode.OutPoint(txid: "b5cc711319dc7595156a1143d0aab81ee7d59a998878adc29d071cecf4e90bd0", vout: 0), valueSats: 474714)]) [WalletViewModel.swift: setUtxoSelection(coinSelectionAlgorythm:isCurrentSend:listSpendable:selectForAmount:) line: 761]
[2026-11-02 11:52:30.650 UTC] INFOℹ️: Selected UTXOs: Optional([LDKNode.SpendableUtxo(outpoint: LDKNode.OutPoint(txid: "b5cc711319dc7595156a1143d0aab81ee7d59a998878adc29d071cecf4e90bd0", vout: 0), valueSats: 474714)]) [WalletViewModel.swift: setUtxoSelection(coinSelectionAlgorythm:isCurrentSend:listSpendable:selectForAmount:) line: 761]
[2026-11-02 11:52:30.651 UTC] INFOℹ️: Calculated transaction fee: 143sats for sending to address bcrt1q7nhtndqq07h602qwc8qv0qequsjcdgyl793npw - [LDK] [ldk_node::wallet:1931] [Logger.swift: log(record:) line: 173]
[2026-11-02 11:52:30.652 UTC] INFOℹ️: Calculated transaction fee: 143sats for sending to address bcrt1q7nhtndqq07h602qwc8qv0qequsjcdgyl793npw - [LDK] [ldk_node::wallet:1931] [Logger.swift: log(record:) line: 173]
[2026-11-02 11:53:53.809 UTC] INFOℹ️: Calculated transaction fee: 143sats for sending to address bcrt1q7nhtndqq07h602qwc8qv0qequsjcdgyl793npw - [LDK] [ldk_node::wallet:1931] [Logger.swift: log(record:) line: 173]
[2026-11-02 11:55:12.217 UTC] INFOℹ️: Selecting UTXOs with algorithm: branchAndBound, target amount: 320921 sats, fee rate: 1 sats/vbyte [WalletViewModel.swift: setUtxoSelection(coinSelectionAlgorythm:isCurrentSend:listSpendable:selectForAmount:) line: 742]
[2026-11-02 11:55:12.221 UTC] INFOℹ️: Selected UTXOs: Optional([LDKNode.SpendableUtxo(outpoint: LDKNode.OutPoint(txid: "b5cc711319dc7595156a1143d0aab81ee7d59a998878adc29d071cecf4e90bd0", vout: 0), valueSats: 474714)]) [WalletViewModel.swift: setUtxoSelection(coinSelectionAlgorythm:isCurrentSend:listSpendable:selectForAmount:) line: 761]
[2026-11-02 11:55:12.221 UTC] INFOℹ️: Calculated transaction fee: 143sats for sending to address bcrt1q7nhtndqq07h602qwc8qv0qequsjcdgyl793npw - [LDK] [ldk_node::wallet:1931] [Logger.swift: log(record:) line: 173]
Replace the failed confirmation root when Amount continues, and prevent its retired instance from preparing coins or starting payment. Cover the retry, wallet switch and Back path while retaining quoted amounts and manual coin selection. Integrate current master 6861aa7 before final validation.
jvsena42
left a comment
There was a problem hiding this comment.
Code scan of the two new commits since 1607a10c (aac68853, 86f2dfe7; both are merge commits that carry code). One MEDIUM: the retired root confirmation can still be swiped when Amount's Continue fails before it pushes. Two LOWs on the new retire binding and on a pushed confirmation's fee failure. Device gate not run, since a MEDIUM is open.
|
Moved to the 3.2.0 milestone: this PR missed the 3.1.0 cut and has no fix in v3.1.0, so it ships in the next minor release. |
piotr-iohk
left a comment
There was a problem hiding this comment.
QA review
Scope: Re-reviewed the full PR at dd2cb0b0 against merge base 6861aa73. This is a full reassessment because the comparison base moved from 831ec3e6 to 6861aa73, and the target-branch changes touch files this PR also changes (BackupService.swift, PaykitPaymentRequestServiceTests.swift). No earlier coverage is inherited.
Code review completed with unverified earlier concerns.
No new actionable code findings. Paths traced in this pass:
- Up-front terms binding in
AppViewModel.handleScannedData, including the Savings minimum (dust limit 547 sats), the LNURLminSendablecheck and the USDT exclusion. - Routing in
PaymentNavigationHelper.openPaymentSheet/contactPaymentRoute, including the LNURL+Savings funding-choice exception. - SendSheet setup of the amount from the bound terms.
- The confirmation state machine:
- the pending-setup gate;
- the
isAmountReviewRequired/hasShownAmountReviewgate on send context, swipe, automatic start andsubmitPayment; - rebinding terms when switching between Spending and Savings;
- up-front coin preparation through the wallet-switch run;
- the fallback from fee or coin failure to Amount;
- retiring the failed root confirmation in
amountNavigationPath; - Back from the fallback Amount closing the sheet.
- Automatic payment gating: first-subscription automatic pay stays blocked after manually chosen coins because of
hasManuallyChosenCoins. - App-lock review retention is not reachable while the amount-review flag is set.
- BackupService observation ordering: the request generations make the outcome independent of how the start/stop tasks are scheduled. Each install and remove is guarded by its observation generation. Stops that were superseded still wait for the metadata upload and the writers to drain. Listeners are only removed after the drain, and
scheduleBackupstays guarded byisObservingand the foreground generation. - Exact USDT display formatting and the duration-only preparation timing log.
Validation:
- At this exact head, unit tests and integration tests passed. E2E was still pending when checked.
- I inspected the new and changed tests but did not run them:
SendConfirmationLifecycleTests,BackupForegroundRecoveryTests,PaymentNavigationHelperTests, and the routing/minimum tests inPaykitPaymentRequestServiceTests. - Parity: a scoped source check against Android master
44bf50ec(not a PR twin) found the same on-chain minimum boundary (amount > 546). The PR documents that Android shows confirmation but disables authorization below the minimum, where iOS keeps Amount.
Limitations:
- The PR description mixes requirements with fix and validation history. Only its Description section was used as neutral requirements.
- J1 and J2 still need runtime execution.
Device testing: not performed in this review.
Suggested additional test cases
- iOS, fee-speed change after automatic coin pre-selection.
- Setup: automatic coin selection, and a fixed-quote Savings request whose automatically selected coins only just cover amount plus fee at Normal speed.
- Action: open the request, which goes directly to Confirm, then change the fee speed to Fast.
- Expected: the fee refreshes for the new speed, or the Amount review appears and Continue recovers. Payment never becomes authorizable with a zero or hidden fee.
- iOS, plain-BTC unified request below the on-chain minimum.
- Setup: a 100-sat BTC request offering Savings and BOLT11 Spending, Spending selected, no channels.
- Action: wait for the sync timeout fallback.
- Expected: the sheet closes with the minimum-amount toast, as before. The previous unit test for this case was repurposed to a quoted request in this PR.
- iOS, rapid node stop/start.
- Setup: a running wallet.
- Action: trigger a node stop immediately followed by a restart, then change a setting and a widget.
- Expected: the settings and widgets backup categories become required and upload afterward, with no duplicate observers (a single upload per change).
Earlier findings reconciled at this revision: 1 still present, 4 verified fixed, 12 could not be verified.
Reconciliation incomplete: the following earlier concerns remain unverified. This report does not establish that they are fixed or invalid, or establish approval or device readiness.
Could not verify earlier concern — USDT toggle reportedly cannot be disabled at displayed zero balance: The report at #1014 (comment) says the USDT toggle could not be turned off at a displayed zero balance. At dd2cb0b the toggle and eligibility guards are identical to base 6861aa7, so this PR does not change them. Unchanged code neither identifies which guard was active nor disproves the symptom. The author asked for the guard state in comment 6303103440. The 43-comment snapshot has no later report of those booleans; the only comment added since the previous reconciliation (6604460671) is a milestone move. A code-only pass without a Swift toolchain cannot reproduce the missing runtime state. A focused issue search found no matching issue, so tracked_separately is unavailable. No introduction or fix is claimed.
Could not verify earlier concern — Native interactive fallback Back remains unverified: The source handles a committed pop from the fallback Amount to an empty path. The navigationPath observer calls returnToAmountReviewIfNeeded, which hides the captured presentation while a review is required. The review requirement now also lives on the shared presentation state (flow.isAmountReviewRequired). The latest current-lineage device report (#1014 (review) at aac6885) explicitly did not test native interactive Back; header Back is its only navigation evidence. The author's dd2cb0b validation (discussion_r4456284815) also says native interactive Back retains its recorded prerequisite. No one has established either a cancelled or a completed native transition. Device execution is prohibited in this stage.
Could not verify earlier concern — Camera-permission CI failure has no established cause: The matching UI test passes in current-head CI on a merge whose tree equals HEAD. The cause of the historical camera-permission failures is still unproven, and a later pass does not prove a permanent fix. No local Swift or Xcode execution was possible.
Could not verify earlier concern — Remaining provider and physical-hardware acceptance is incomplete: Current-lineage reports narrow the gaps but do not complete acceptance. Review 5736400910 at aac6885 is a focused fee-failure/retry session. It explicitly excludes native interactive Back, natural fee shortfall, quote expiry, PIN/biometrics, settlement, hardware and the wider journey matrix. Its retry failure is fixed at dd2cb0b (see externalConcern ext-fee-retry-two-confirmations), but there is no device retest at this head. The author's dd2cb0b validation describes controlled host, unit and UI checks only, not full linked-request, provider, funded, physical-hardware or recovery acceptance. These are execution limitations that a code-only pass cannot close.
Could not verify earlier concern — Native interactive fallback Back remains unverified: This separately retained item has the same unresolved limitation. The source covers committed Amount-to-empty pops, but neither a cancelled nor a completed native interactive transition has been established at or near this head. It is not marked duplicate because the counterpart lineage is not open. Device execution is prohibited.
Could not verify earlier concern — Camera-permission CI failure has no established cause: This separately retained item still has no established cause. The current-head CI pass does not explain the earlier camera failures or prove a permanent fix. It is not marked duplicate because the counterpart lineage is not open.
Could not verify earlier concern — Remaining provider and physical-hardware acceptance is incomplete: This separately retained item is still an execution limitation. The latest device review and the author's current-head validation leave full J1/J2, physical signing, provider settlement and recovery unverified. It is not marked duplicate because the counterpart lineage is not open.
Could not verify earlier concern — Historical launch and E2E retry failures have unresolved causes: The historical launch timeout and the parent-head RBF success-toast timeout still have unproven causes. Unit and integration runs at dd2cb0b succeeded. E2E run 41657547060 at this head is still in progress, with no conclusion. E2E run 41635404470 at the parent head 86f2dfe concluded success; the author reports 13 unique passes and no test-case retries (attributed, not inspected here). Later success does not explain comments 6444464437, 6497736273, 6507674422 and 6510561206, and the historical runtime state cannot be recovered from code.
Could not verify earlier concern — Historical launch and E2E retry failures have unresolved causes: This separately retained item is still unresolved. Current-head unit and integration successes and the parent-head E2E success do not explain the historical launch or RBF timeouts, and the current-head E2E run is still pending. It is not marked duplicate because the counterpart lineage is not open.
Could not verify earlier concern — Historical launch and E2E retry failures have unresolved causes: This separately retained item is still unresolved. Code inspection and later successful executions do not explain the earlier launch, RBF-toast or transient runtime failures. It is not marked duplicate because the counterpart lineage is not open.
Could not verify earlier concern — Contact-payment preparation latency remains unmeasured: The contact-payment preparation latency concern in #868 remains open, and there is no staging measurement at this head. The only cited timing (review 5729110725 at 1607a10) measured the hardware-funding setup wait, not the shared-Paykit-runtime staging latency that issue 868 names. Discovery notes that AppScene now logs preparation durations only, which is a diagnostic hook, not a measurement. No timing run occurred here.
Could not verify earlier concern — Contact-payment preparation latency remains unmeasured: This separately retained item is the same unresolved latency concern. Issue 868 is open, and no staging contact-payment timing exists at this head. It is not marked duplicate because the counterpart lineage is not open.
Existing concerns
- [LOW] Align fixed-quote journey wording across platforms — still unresolved; already tracked.
Reviewer model: claude-opus-5-5
Reasoning effort: high
|
Current-head CI inspection for Unit run 41657545762 passed 2,984 unit + 39 UI executions, with five fixture skips and zero test failures. All 22 lifecycle and 16 swipe cases passed. Integration run 41657545453 passed 30 unique tests, zero skips or failures. Both workflow attempt 1 jobs checked merge Neither native run had test-case retries or crashes. The integration configuration allows two iterations, but every case passed on its first execution. The five unit/UI skips remain the owned recovery fixture, three disabled Trezor-emulator cases and the isolated Arbitrum fixture. Retained diagnostics: 571 duplicate-symbol warning occurrences (286 unique titles) in each native build, two raw SwiftUI State notices from the PR-added static snapshot test, and 48 ignored CoreGraphics NaN notices. Structured runtime warning records were zero; this does not erase the raw notices. Integration retains RGS age/IgnoreError, wallet-sync/stop timeout and controlled fixture messages. Logged source sites were checked against actual master; no new blocking production diagnostic was identified, and no fresh base-runtime or warning-free claim is made. Two stdout test-start lines were interleaved with app logs; native markers and typed execution records reconcile completely. Uploaded artifacts contain xcresult bundles, not the executed app/test binaries. Both ZIP digests and all 12,563 evidence files were verified; binary/installed-entitlement verification is not claimed. E2E run 41657547060 also completed on workflow attempt 1, using the same merge/tree and harness The retained failure was the RBF test's 30-second wait for All five executed E2E job logs, both attempts' simulator logs, failure screenshot/video and source/artifact provenance were inspected. The results archive digest is Piotr's current-head review 5755252259 is COMMENTED, reports no new actionable code finding and no device testing. João and Ovi remain pending; there are no current human approvals. The historical LOW journey-wording concern retains its existing Android #1610 deferral; alignment is not claimed. |
There was a problem hiding this comment.
Verdict:
Findings: No new defect found. The fixes for the duplicate confirmation after a fee retry, the retired confirmation payable after a failed Continue, the retire on ordinary sends and the child confirmation fee-failure loop hold in the code at dd2cb0b. The device cases below confirm the last three, and the Back half of the first.
QA for dd2cb0b:
I built this head with Xcode 27.0 and tested on an iPhone 17 Pro simulator running iOS 26.5:
- Wallet: 600,000 sats Savings in three 200,000-sat coins, and 500,000 sats Spending over a regtest channel.
- Requests: a linked disposable issuer sent fixed
2.50 USDrequests quoted at 50,000 sats. - Fee failures: two one-shot test hooks, described below, only fire when armed.
- Evidence: counts of coin selections and fee calculations come from the app log, not the video.
No payment was authorized. Videos play at 2×.
Retry after a failed fee keeps one confirmation ✅ Continue prepared once, and Back → Amount → Back closed the sheet without the failed Confirm.
This is J1 action 14 on Savings.
- The request opened on Confirm.
- After real coin selection, the hooked fee calculation made LDK return
InsufficientFunds. The sheet showed the locked Amount at 50,000 sats with "Please try again". - Continue logged one coin selection and one 143-sat fee, and opened a Confirm at 50,000 sats.
- Back returned to Amount. Back again closed the sheet, with no new selection and no failed confirmation shown. The request stayed pending.
The middle of the video shows the Speed list, where taps on Fast had no effect (see Not driven).
fee-retry.2x.mp4
Fee failure, Continue, Back and Back.
Back after a failed Amount retry cannot pay ✅ Continue failed before navigating, and Back closed the sheet with no swipeable Confirm.
- After the fee fallback, the second hook made coin selection throw on Amount's Continue. The log line comes after the tap. Amount stayed on screen.
- Back closed the sheet. The log reads
Hiding sheet send reason: Back from the request amount review. - Reopening the request ran a fresh selection and showed Confirm with a 143-sat fee.
The issuer's regtest address received nothing.
continue-fails.2x.mp4
Failed Continue, Back, and an unpaid reopen.
Fee failure after picking coins manually closes on Back ✅ The child Confirm's fee failure led to Amount, and Back closed the sheet.
- With manual coin selection, the request's Confirm pushed the coin picker.
- I picked one 200,000-sat coin and continued. The child Confirm's hooked fee calculation failed and the sheet showed Amount at 50,000 sats.
- Back closed the sheet instead of pushing Amount again. The log reads
Back from the request amount review.
manual-child.2x.mp4
Manual coins, child fee failure and Back.
Editing an ordinary send's amount keeps its confirmation ✅ Back returned to Amount, then to the original Confirm.
- A unified invoice (regtest address plus a 30,000-sat lnd BOLT11) opened Confirm on Spending.
- I switched to Savings, opened Amount from the amount, entered 30,005 and continued. A pushed Confirm with a Back arrow showed 30,005.
- Back returned to Amount, and Back again returned to the original root Confirm.
ordinary.2x.mp4
Ordinary amount edit and its Back path.
Test hooks (not part of the PR)
Both hooks are DEBUG-only and one-shot. Each fires only when its trigger file exists in the app's Documents directory, and does nothing otherwise.
review.feeFailraises the fee rate passed to onecalculateTotalFeecall to 10,000 sat/vB after real coin selection. LDK then returnsInsufficientFunds. This is a stress input, not a natural shortfall.review.selectFailmakes onesetUtxoSelectioncall throw before selecting, standing in for a transient node or Electrum error.
No navigation, fallback or authorization decision is replaced.
Not driven:
- Switch wallets after retrying a failed fee ⏭️ (J1 action 14's BOLT11 part). The fixture issuer's private payment list carries only its on-chain endpoint, so every request opened Savings-only with the From button disabled.
- I tried a plain and a route-hinted lnd invoice across three requests.
- The Spending channel itself worked: a plain lnd invoice opened on Spending.
- Change the fee speed after automatic pre-selection ⏭️, from @piotr-iohk's review. Element and coordinate taps on the Fast and Slow rows had no effect, on request and ordinary on-chain Confirms alike.
- The rows report enabled, and the selection did not change.
- I have not established whether the tooling or the app is at fault.
SendFeeRateandFeeItemare unchanged by this PR.
- Native swipe Back from the fallback Amount ⏭️. Neither the simulator's edge-swipe preset nor a raw edge drag popped even an ordinary Settings screen, so the interactive pop could not be produced.
- Not attempted: @piotr-iohk's suggested 100-sat plain-BTC sync-timeout case and the rapid node stop/start backup case. The routing paths are not in this delta. The merge's
BackupServicechange only passeshasLedgerto the Paykit payment-state restore, so it does not touch observation start/stop.
Relied on: @piotr-iohk's device QA at aac6885 for the original duplicate-preparation failure. Earlier passing cases listed in our aac6885 review cover paths this delta leaves unchanged.
Scope: reviewed the PR's own changes in aac6885..dd2cb0b: SendAmountView, SendConfirmationView, SendSheet, their tests and the J1 journey text. The master merge in 86f2dfe (allowance, backup and layout changes) is not reviewed here and no device case drove it.
Twin: synonymdev/bitkit-android#1581 and #1638 (both merged) match direct confirmation. Android keeps the asset and LNURL/Savings choice on Confirm, while iOS keeps it on Amount, as the PR documents.
Reviewed by claude-opus-5-5-medium
jvsena42
left a comment
There was a problem hiding this comment.
Approving at dd2cb0b0. All three findings from my last review are fixed:
- MEDIUM (retired Confirm payable):
isAmountReviewRequirednow lives onSendPresentationStateand is bound into every Confirm. It blockssubmitPayment(CancellationError),isSwipeDisabled,sendContextand auto-pay. Only a forward push from Amount clears it. A Continue that binds terms and then fails leaves the flag set, so Back closes the sheet. ThereboundTermsvariants intestRequestFeeFailureAfterCoinSelectionReturnsToAmountReviewandtestRequiredAmountReviewKeepsSwipeDisabledWithKnownFeescover it. - LOW (retire on ordinary sends): the retire now requires
isAmountReviewRequired, which only incoming requests set. Covered bytestOrdinaryAmountContinuationPreservesConfirmationRoot. - LOW (pushed Confirm loop): the shared flag lets the root take the hide branch. Covered by
testManualRequestFeeFailureBackClosesConfirmationRoot.
No new findings. I also traced the Lightning, manual and hardware variants of a failed Continue, app lock, and retry after a payment failure. CI green. Paykit request journeys not run locally; they are left to e2e CI.
|
Merging this PR: it meets the merge rule at |
Description
resolveMsandsetupMs) to separate endpoint resolution from subsequent setup without logging payment details.Out of Scope
Cross-platform fixed-quote journey wording and LNURL/Savings coverage: Align fixed-quote journeys with both payment flows bitkit-android#1610 (review thread). Android #1581 is already merged; the remaining gap is in the shared test instructions and platform notes.
Preparation latency (Contact payment latency on staging with the shared Paykit runtime: pin paykit-rs#171 and re-measure #868): the reported several-second delay has not been reproduced with timings. Endpoint resolution and fee/balance preparation are possible contributors; this PR does not claim to fix their latency.
Initial subscription authorization: this does not remove the extra review required for converted subscription terms.
Design
Reuses the existing send confirmation without changing its layout. Android's screen map identifies
Send (Paste) (On-chain) → Confirm Send Onchainin the Bitkit design file. The exact current frame node is unverified.Preview
Before: Preparing → Amount (locked) → Confirm.
After, when no separate funding choice is required: Preparing → Confirm. LNURL + Savings keeps its existing selection step.
Current validation — published
dd2cb0b0Failed request review blocks payment and closes on Back; ordinary sends retain their confirmation. Request retries preserve the exact amount. Repair/local evidence.
Local tree
d8d2f720, master6861aa73: 2,984 unit + 39 UI passes/five fixture skips/zero failures or retries; all 38 focused cases pass. SwiftFormat 0/828, translations zero errors/15 verified base-source warnings, two scoped reviews and all 33 local/GitHub signatures complete.Hosted merge
44f7c359has the same tree: 2,984 unit + 39 UI passes/five skips; integration 30 passes/zero skips. E2E: 13 unique eventual passes in 14 executions, including one failed boost-toast wait and its passing built-in retry; one permanent hardware skip. Full attempt-1 audits, retained warnings/failures and capture limits: CI evidence. No warning-free or fresh-base-runtime claim.Automated simulator QA reports four repaired retry/Back paths with one-shot DEBUG faults. Wallet switching after retry, fee-speed changes and native interactive Back were not driven. No payment was authorized; this is reviewer-reported evidence, not independent execution or human approval.
Full J1/J2 linked-request/provider, funded, physical-hardware, two-wallet settlement and recovery acceptance remain incomplete. Native interactive Back retains its ownership prerequisite. João/Ovi pending, Piotr COMMENTED, zero current human approvals. No acceptance gate is waived.
QA Notes
Setup
Use the existing fixed-quote journey prerequisites: two disposable identities, a fresh fixed $2.50 Bitcoin quote, funded regtest Savings and a usable Spending channel for live switching. For the minimum boundary, use controlled rates yielding exactly 546 and 547 Savings sats as specified in
journeys/paykit-quoted-usdt.xml. Owner validation remains unfunded; the linked reviewer-reported regtest payments retain their recorded revision and environment. For the added J1 hardware subcase, reuse an explicitly owned, already funded regtest hardware-emulator wallet with empty Savings; check automatic/manual selection for both fixed BTC and quoted requests, then close without signing or sending.The XCTest USDT fixture is separate:
UsdtWalletUITestsnow defaults to unavailable loopback endpoints. An explicitly owned disposable fork must be selected through test-runnerBITKIT_TEST_USDT_RPC_URL/BITKIT_TEST_USDT_BUNDLER_URL; its test funds its wallet.BITKIT_TEST_USDT_OFFLINE_RPC_URLmust be unavailable. Never use another task's shared fixture. Seejourneys/README.md.Journeys
J1: new
journeys/payment-requests/fixed-quoted-confirmation.xml— LNURL/Savings selection, quoted amount and wallet valuation, saved/manual coin selection and initial hardware fallback, cancel/reopen, delayed preparation, insufficient funds, expiry and BTC/USDT choice. Partial linked-regtest execution is reported in Ovi’s d181 QA, including payment, wallet switching, manual coins, header Back/reopen and expiry/fresh terms. Native interactive Back remains unverified; earlier LNURL evidence retains its original revision. Manual failure, plain-BTC failure and fresh-review controls retain their earlier programmatic evidence.J2: updated
journeys/paykit-quoted-usdt.xml— iOS dot-decimal checks in comma-region settings preserve exact values, cents, trailing zeros and six-decimal precision. Partial d5e reviewer QA covers six-decimal review, request/subscription serialization and the 546/547-sat boundary; full J2 is not claimed. Incoming fixed USDT terms stay locked: action17 now separately checks editable USD creator amounts and fixture-provided six-decimal USDT review. Android retains confirmation but disables authorization below its equivalent minimum.Manual Tests
N/A
Models used
For this repair and validation update (verified session metadata; earlier implementation attribution is not inferred):
gpt-6-astra(reasoning:xhigh)gpt-6-astra(reasoning:xhigh)gpt-6.1-sol(reasoning:high)Earlier validation, failures and diagnostic history
Exact USDT review amounts —
629a874e(October 28): Fixed the six-decimal display finding from review 5678141014. The actual payer confirmation header and Show Details payment, fee, recipient amount and maximum total now use the existing exact USDT formatter. Request/subscription amount text also preserves all six decimals. Quote values, serialized amounts and payment authorization are unchanged; balance overviews retain their established formatting.The rendered regression reproduced
$0.12/$<0.01for0.123456/0.000001before the fix (one failed test, ten assertions). All ten focused amount/display tests now pass. Saved native screenshots show exact headers, fee0.000001, and totals0.123457/0.000002. These tests host the real production review builders directly and verify static rendering; they bypass full view lifecycle/navigation/quote loading and do not authorize or send a payment. Their new SwiftUI constant-binding warning is retained as an explicit test boundary, not a production-race or warning-free claim.Final canonical local validation passed 2,934 unit and 39 UI executions, with four documented fixture skips and zero failures or retries. Complete diagnostics were inspected: two AppIntents warnings, ten structured QoS warnings plus the new static-builder State warning, eleven raw QoS backtraces, and 72 NaN notices remain explicitly retained alongside symbolication and simulator notices; this is not a warning-free claim. Full SwiftFormat 0.64.0: 0/846 files require formatting, 83 skipped. Translation validation: zero errors, 15 warnings matching the verified actual-base warning multiset. Two independent source perspectives covered the component and actual payer path; an initial component-only coverage gap was caught before publication and the actual payer regression added. Required source signatures: all 20 commits in the current-base PR range verified locally and matched the exact GitHub set with
verified=true/reason=valid. Validation applies to local PR tree88d9c2e3295b93490a3395510ed3605f1ce1b565, not a new combined master tree. Master68bede62added only disjoint profile journey documentation since the earlier base; no unrelated integration was performed.J1/J2 retain their IDs. Their six-decimal request case now explicitly needs fixture-provided USDT terms, because the iOS editor creates BTC/USD terms. Full linked-request, native Back, fresh-terms retry, provider/funded/hardware/settlement acceptance remains incomplete. No pending human request was recreated; João's announced absence through November 1 is preserved. Current work: implementation
gpt-6-astra/xhigh, scoped independent reviews and diagnostic auditgpt-6.1-sol/high, verified from execution metadata.Hosted checks for
629a874eare complete on workflow attempt 1: unit passed 2,960 unit and 39 UI executions, with four fixture skips and no failures; integration passed 30 tests once each, with four existing Blocktank staging skips. Verified checkouta52bbce2/ treeefec63fbcombines actual master68bede62with629a874e; this differs from local PR tree88d9c2e3. Complete logs, artifact digests, execution trees, case summaries and nested diagnostics were inspected. The payer display regression passed in 11.611 seconds. Its two introduced State/constant-Binding messages remain explicit: direct builder hosting proves static text, not the full view lifecycle or authorization flow. The 286 structured / 571 raw duplicate-symbol warnings match the verified prior warning multisets. Runtime/mock/fixture/tooling notices, symbolication limits, and two passing integration tests' internal sync/deposit recoveries remain recorded; no fresh-base runtime or warning-free claim.E2E ultimately passed 13 unique cases across 14 executions: 12 passed initially, while Lightning timed out after 30 seconds waiting for
ActivityShort-0to show9 000after restored-channel closure. The selective second built-in step passed Lightning; step 3 was skipped. One existing hardware-wallet case remained disabled. This was not a first-pass clean run or a manual retry. Failure screenshots/video were generated on the runner, but their upload was skipped after recovery; no downloadable failed visuals, device/app logs or runtime report were available. Root cause remains unproven, with no actual-base reproduction or permanent-fix claim. All five job logs, pinned harness4c0cbef0, cases and app-artifact provenance were inspected. The 76 stale-element notices, five log-stream restarts and pinned-harness dependency findings remain explicit. Formatter and translations were already inspected: 0/850 files needed formatting (87 skipped); zero translation errors and 15 verified base-matching warnings.Automated reviewer QA at this head reports exact six-decimal and one-micro request displays, a six-decimal subscription, and Savings-only proposal selection using linked test wallets and a local fixture, without authorization. This is reviewer-reported evidence, not an independent rerun or human approval. Neither edge gesture established an interactive Back transition; expiry removed the request before fresh-terms retry could be exercised. Editing/serialization, live bridge, provider, hardware and settlement gaps remain. No new delta defect was reported. Shared-navigation ownership coordination and the existing remaining acceptance gates are still required.
Current documentation correction —
9dacc089(October 28): Corrected stale J2 comma-decimal expectations to the existing iOS dot-decimal convention, including comma-region settings. All 19 actions and exact-value, trailing-zero, six-decimal and serialized-value checks remain. No app behavior changed. XML/action/reference checks,git diff --check, full SwiftFormat 0.64.0 (0/846), and one independent documentation review passed. All 19 current-base commits verify locally and on GitHub. App tests were not rerun for this documentation-only delta; earlier local/hosted results keep their original inputs. Automated review 5676643246 reports additional partial device evidence at20cfeffa, not human approval or independent execution by this monitor. Native Back, expiry retry and full J1/J2 acceptance remain incomplete. The former comma-expectation result is preserved as a failure of the earlier journey wording, not converted into a completed new journey run.Current repair —
20cfeffa(October 27): The initially-unreviewed Back assertion reported in automated review 5670593898 reproduced independently on unchangedb53e300ewith Xcode 27.1/iOS 26.5. The four-line fix observes Amount being popped to an empty navigation path and reuses the existing invalid-terms, presentation-owned dismissal guard. All 15 unchanged lifecycle tests now pass. No assertion or production payment guard was weakened; the journey also names interactive Back as an acceptance check.Final canonical validation on Xcode 26.1/iOS 26.1 passed 2,932 unit executions and 39 UI executions, four fixture skips, zero failures, including all 35 watch-only tests. Full SwiftFormat 0.64 passed (0/846; 83 exclusions); translations report zero errors and 15 warnings whose individual call/missing-language sets match actual master
543d9389. Complete structured/action/raw diagnostics and an independent frozen correctness/quality review were inspected. Eight aggregate QoS warning cases represent 19 warning-bearing executions; three raw Paykit/pubky initialization backtraces, two AppIntents build notices, simulator/negative-case/runtime messages and the unavailable-fork skip remain explicit. All 18 current-base commits verify locally and match GitHub's exactverified=true,reason=validset. Tested local PR tree:e286b0d9; the new hosted unit and integration results below cover the combined tree. No warning-free or fresh-master-runtime claim.Hosted checks at
20cfeffa: Unit 40559037614 and integration 40559037349 passed on workflow attempt 1. Unit: 2,953 unit + 39 UI executions passed, four fixture skips, zero failures, including all 15 confirmation lifecycle and 35 watch-only tests. The repaired initially-unreviewed Back regression passed. Integration: 30 unique passes in 30 executions, four existing Blocktank staging skips, zero failures or test-case reruns. Both checked out verified merged1ec3668(543d9389/20cfeffa), tree370b70b7, which differs from the local PR tree; 21 additional upstream unit cases account for the count difference. Full archive hashes, test trees, all UI/integration case summaries, build/action/runtime diagnostics and job logs were inspected. The three Trezor opt-in cases and isolated Arbitrum case remain skipped; integration retains the four staging skips.The 286 structured and 571 build-log duplicate-symbol warning multisets match the previously inspected hosted result. Runtime output still includes the keyboard queue timeout with
Last Exception Backtrace: No stack!, socket/haptic/launch notices, injected negative-case diagnostics, integration RGS/node/address-search/teardown messages and the version-probe broken-pipe exception. Associated source comparisons are retained; this is not a warning-free run or a fresh actual-base runtime reproduction. E2E 40559038210 also passed on workflow attempt 1: 13 unique passes in 13 executions, one existing hardware-transfer skip, zero failures. The first built-in test step passed; steps 2/3 were skipped. Build and runtime used the same verifiedd1ec3668/370b70b7combined tree. The executed harness was4c0cbef0(two commits beyond the previous harness), on iPhone 17/iOS 26.2. The exact pinned hardware spec retains its Blocktank-staging skip. All five full logs, case outcomes and artifact provenance were inspected. Artifact12761095558SHA-256b59616212d303b5441c1053ac883fbd32ba4af1593cb9c98e93c85816accc1a6matches API/upload/runner download; no local ZIP or runtime-report/video artifact was inspected. Retained diagnostics include 66 stale-element notices, four log-stream restarts, an address-type tap helper retry that reached its expected toast, Node/npm/tool/AppIntents notices and 76 pinned-harness dependency findings. No manual CI retry or warning-free claim. These hosted passes do not close the supplementary native gesture or other acceptance gaps below.Supplementary native check remains partial: an unpublished animated test host used real Amount with fixed-quote terms that were valid at a historical time and expired before appearance. Header Back closed the owned sheet state with an empty path and no preparation, authorization or submission. The first diagnostic execution failed an exclusive-visible-route readiness assumption and timed out before an edge gesture was delivered. One bounded rerun corrected only that temporary readiness check; actual Amount/path assertions passed, but two edge swipes did not pop Amount and the diagnostic timed out. Both failures and full diagnostics/screenshots are retained. The cause is unproven; completed/cancelled interactive Back, real linked-request setup, dismissal animation and reopening are not accepted by this check. Temporary test code was restored byte-for-byte before signing. No funds, payment, wallet reset or shared-fork access occurred. The repaired existing lifecycle regression is green; this additional gesture gap is not presented as a pass.
A later bounded prerequisite check on the same isolated iOS 26.5 simulator found two native controllers, an enabled recognizer,
allowSwipeBack=trueand a key window. The temporary host's 390×844 window differed from the 402×874 screen. After correcting only that host geometry and observing native transition state, one cancel-shaped edge drag and one full edge swipe both produced recognizerfailedstate before any interactive transition. The 100-second diagnostic ended with one failed case/four assertions: Amount/path and sheet state remained, no completed or cancelled pop occurred, and routing/preparation counts stayed zero. The geometry change did not resolve the gap; the reason for gesture rejection is still unproven, and no production defect or fix is inferred. Complete xcresults, action logs, state trace and screenshots are retained. Temporary code was restored byte-for-byte; published source remains20cfeffa. Further gesture acceptance needs the production sheet presentation to reach that boundary, rather than another unchanged synthetic-host retry.At
b53e300e, the bot reported rendered confirmation, saved/manual coin selection (143/212-sat fees), manual-picker Back, 546-sat fallback/Back/reopen, 547-sat confirmation, cancellation during preparation and LNURL/Savings selection, stopping before payment. Those remain reviewer-reported partial J1/J2 results at that head. Piotr's fee-refresh thread is externally resolved. On October 27, Ovi dismissed João's older changes request, noting its resolved threads and João's absence until November 1; this monitor did not dismiss it. Piotr/Ovi requests remain pending, João's comment retains assignment and his absence is respected, and there are zero current-head human approvals. A bot comment or resolved thread is not approval. Current shipped/provider/funded/physical-hardware/full-linked-request/two-wallet settlement acceptance remains incomplete.Earlier validation —
b53e300e(October 27): The conflict with then-current masterd2d9d28dis resolved in a signed normal merge, tested tree4516c8e1. Master's cache display and test-preference isolation changes are preserved; the USDT UI helper retains its explicit endpoint overrides and unavailable defaults. Final canonical validation passed 2,932 unit tests and 39 UI executions, four fixture skips, zero failures. The previously failing launch test passed locally, which does not establish the cause or a permanent fix for its earlier hosted timeout. Full SwiftFormat 0.64.0 passed (0/846); translations found zero errors and 15 warnings with all 20 inputs identical to actual master. Complete diagnostics and an independent frozen integration review were inspected. All 17 current-base commits verified locally and match GitHub's exact set withverified=true,reason=valid. Hosted unit 40519925128 and integration 40519925139 passed on workflow attempt 1. Unit/UI: 2,932 unit and 39 UI executions passed, four fixture skips, zero failures; all 35 watch-only account tests passed, and the earlier launch case passed in 13.385s. Integration: 30 unique passes in 30 executions, four existing Blocktank staging skips, zero test failures or test-case reruns. Both used verified checkout3c59760a(d2d9d28d/b53e300e), tree4516c8e1, exactly matching final local validation. Full artifact digests, execution trees, UI/skip summaries, structured diagnostics and compile/action/runtime logs were inspected. Hosted E2E 40519926046 also passed on workflow attempt 1: 13 unique passing cases in 13 executions, one existing hardware-case skip, zero failures. Its first test step passed; steps 2/3 were skipped. Build and runner checked out the same3c59760a/4516c8e1tree, with pinned harnessec01c02f. No manual CI retry. All three existing human requests are preserved; João's changes request remains. The later fee-thread disposition is recorded above. No approval, merge readiness or funded/provider acceptance is inferred.Final diagnostics retain 16 structured QoS warnings, three AppIntents compile notices, 12 UIKit appearance events, 24 CoreGraphics NaN lines in app stdout, and platform/negative-case runtime messages. Full address stacks are saved; no raw symbolicated backtrace was exported. No fresh base runtime or warning-free claim. Effective runner/helper and built endpoints were verified at unavailable 295xx ports; the fork skipped before funding. J1/J2, physical hardware, full linked-request/two-wallet settlement and rendered 546/547 gaps remain as documented below.
Hosted diagnostics retain the exact prior 286 structured / 571 build-log duplicate-warning multisets. Integration's existing sync helper recovered from a first-attempt Taproot sync timeout inside a passing test; no whole test was repeated. Unit runtime logged a keyboard-queue timeout with
Last Exception Backtrace: No stack!, plus socket/haptic and negative-case messages. These observations are retained without a cause or permanent-fix claim. Integration's 22 associated inputs match actuald2d9d28d; of 40 unit-runtime source files, 38 match and AppViewModel/SendConfirmationView retain this PR's changes. No fresh actual-base runtime or warning-free claim.E2E diagnostics retain 66 stale-element notices, four log-stream restarts (statuses 143/137/124/149), Node/npm deprecations and the pinned harness's 76 dependency-audit findings (8 low, 17 moderate, 44 high, 7 critical). Build/action output retains AppIntents metadata, destination/tool and ignored-base-input notices. All five job logs and 14 case outcomes were inspected. App artifact
12741962657(141,017,046 bytes) has SHA-25681978458fa352095f683215ca76521bd97cd1c7fe1a8e73e62477a4e59891883, matching API metadata, upload and runner download logs; the archive was not downloaded locally. The successful workflow did not upload runtime-report/video artifacts. These results do not establish a warning-free run or erase earlier failures and acceptance gaps.Actual master at the latest audit is
85e9d4c6: three commits and eight paths beyond the hosted base543d9389. The only overlap with the PR diff is AppScene's foreground-notification hunk, disjoint from this PR's request-preparation timing changes. The PR remains mergeable; no integration was performed. Hosted unit/integration/E2E results cover543d9389/20cfeffa; the newer85e9d4c6combined tree is unvalidated. Local results retain their separate repaired PR-tree attribution.Earlier validation —
20f54449(October 27): The unit CI crash at811b4335was traced to concurrent access in the watch-only test fake: an assertion read its tracking Set while the authorization task inserted into it. The two affected tests now await the existing actor-isolated gate after tracking has finished. All assertions and all 35 tests remain; production code is unchanged. The coordinated repair passed 35 focused tests, 2,928 unit executions and 39 UI executions, with four explicit fixture skips and zero failures. Full SwiftFormat 0.64.0 passed (0/845 files need changes); translation validation found zero errors and 15 warnings with unchanged actual-base inputs. Complete diagnostics and independent frozen review were inspected. All 16 commits against actual masterb666ccb1verify locally and match GitHub’s exact commit set withverified=true,reason=valid. Hosted integration 40501754529 passed on attempt 1: 30 unique tests passed in 30 executions, four existing Blocktank staging skips, zero failures or retries. Verified checkouta7cb2f52(b666/20f54449) has treec1e3cd27, exactly matching final local validation. The archive digest, all 34 full case summaries, tree, action/runtime output and compile/test log were inspected. Structured warning/error arrays are empty; retained 571 duplicate-symbol compile warnings match the prior integration multiset, alongside the version-probe broken-pipe exception, tool/destination notices and embedded RGS/node/address-search/teardown/fixture/launch-metric messages. All 22 associated source inputs match actual base; no fresh base runtime or warning-free claim. Hosted unit40501754193subsequently failed on attempt 1: all 2,928 unit executions (including all 35 watch-only tests) passed, but UI had 38 passes, four skips and one failure inBitkitUITests/testExampleatapp.launch(). XCTest timed out acquiring a background assertion; the failure video shows splash then Terms. Root cause remains unproven. E2E40501754542passed 13 unique cases in 13 executions on its first step, with one existing hardware skip and zero failures. The complete logs, artifact digests, test trees, failure video and diagnostics were inspected; no manual CI retry. Both use the verifieda7cb2f52/c1e3cd27checkout. The 286 structured/571 compile warnings match the preceding unit run; E2E retains 70 stale-element notices, log-stream restarts and pinned-harness dependency warnings. All three existing human requests are preserved; João’s changes request and Piotr’s fee-refresh thread remain in force.Earlier validation at
9a807ef3(October 27): Conflicts with actual masterb666ccb1are resolved in signed normal merge9a807ef3, tested tree0ce4fc92. The combined tree preserves fixed-quote amount/expiry/dust/fee and authorization safeguards, duration-only diagnostics, and master's SDK/request/proof lifecycle changes. Final local gates passed: 15 focused tests, 2,928 unit executions and 39 UI executions, four fixture skips, zero failures. Full SwiftFormat 0.64.0 reports 0/845 files needing changes (81 skipped); translations report zero errors and 15 warnings whose inputs match actual master. Complete test trees, compile/action logs, 19 structured QoS warnings, 21 raw backtraces and remaining platform notices were inspected and retained; this is not a warning-free claim or runtime baseline reproduction. All 14 commits against actual master verified locally and matched GitHub's exact set, with every signatureverified=true,reason=valid. Hosted unit, integration and E2E workflows all passed on attempt 1 at the exact locally tested merge tree. Hosted unit/UI: 2,928 unit and 39 UI executions passed, with four fixture skips. Integration: 30 passed and four Blocktank skips. E2E: 13 passed and one existing hardware-case skip; its first test step passed and steps 2/3 were skipped. Full diagnostic and helper-retry limits are recorded below. Full evidence is recorded below. All three existing human review requests remain pending; no changes request was dismissed.The canonical run verified built RPC/bundler endpoints at unavailable 29545/29546 and the fork skip before app launch or funding. No shared fork access, funding or transfer occurred. Funded/provider/hardware/two-wallet and rendered 546/547 acceptance gaps remain; the historical incidents and hosted retry/skip limits below retain their original attribution.
Earlier validation (October 26, retained at its original head): Conflicts with actual master
b8b7f3d9are resolved in signed normal merge63ea2034(tested tree21fb1809). The combined tree preserves master's wallet-switch lifecycle fix and this PR's quoted-payment guards. Final local validation passed: 2,894 unit executions and 39 UI executions, with four fixture skips and no failures. All 13 current-base PR commits verify locally and on GitHub. Hosted unit/integration checks passed. E2E completed successfully after two built-in retries of the Lightning case; 13 unique cases passed and one hardware-transfer case stayed skipped. The two earlier Lightning timeouts remain unexplained. J1/J2 and the funded/provider/hardware acceptance gaps below remain open.Fixed quoted Bitcoin requests open in the existing confirmation screen when no separate funding-source selection is needed. A fixed $2.50 Savings request skips the amount screen with disabled controls. Requests offering LNURL Spending alongside Savings retain the existing funding-choice screen because LNURL confirmation has no wallet selector.
Earlier master integration and local validation —
63ea2034The normal integrations retain the previous dust and fixture-endpoint fix, merged #1003's Receive coverage, and merged #1098's single wallet-switch run and resumed-preparation behavior. Quote identity, exact amounts, expiry, selected-rail minimums, routing-fee cancellation and authorization checks remain intact. No assertions were removed or weakened, and no new exclusion or authored test was added by conflict resolution.
b8b7f3d9tree, using Xcode 26.1 and an iPhone 17 Pro Max simulator on iOS 26.1, one worker and private build/package paths. The six canonical integration exclusions match the workflow; separate integration, E2E, provider or hardware completion is not claimed by this local run.b8b7f3d9matched the local range and reportedverification.verified=true,reason=validon GitHub. Both integrations are additive signed commits, with authors and prior work preserved.The earlier
536abb44candidate also passed its own canonical run before master advanced, but that result is distinct from the final run above. The earlier shared-fork incident and incomplete rendered 546/547-sat boundary, provider, physical-biometric, hardware and two-wallet settlement journeys remain explicitly unvalidated.Earlier dust/fixture fix validation —
9acc68e2Quoted Savings terms now require at least 547 sats before confirmation can rebind them. Exact valid amounts, expiry, quote identity and authorization guards are preserved. This addresses Piotr's finding.
test_fundWallet, and observed 1,000 fake USDT. Quote preparation failed before confirmation; no transfer/settlement is evidenced. This was an isolation mistake, not accepted funded QA. No fixture repair, reset or compensating write followed.2ce21025verify locally and asverified=true,reason=validon GitHub. This is an additive signed commit, with no history rewrite. At that point hosted CI was blocked by the master conflict; the current signed integration above supersedes that hold.J1/J2 and live provider, physical biometric, hardware and two-wallet settlement gaps remain open. The final safe fixture skip does not validate the failed funded journey.
Automated Checks
PaymentNavigationHelperTests.swift— exact converted amount, expiry, real BTC/USDT choice, routing, unavailable/expired-rate fallback and reviewed-term mismatch protection.PaykitPaymentRequestServiceTests.swift— hosted confirmation waits for wallet sync before rejecting a below-minimum quote.SendConfirmationLifecycleTests.swift— hosted fixed-request preparation, rail-specific quoted amounts and fees, invalid-term fallback, manual picker Back/retry, UTXO failure and fee failure/recovery, including single-owner Amount Continue and method switching.Historical validation before
ad30f8be: the 48-test focused result above predates this integration. The prior all-target compile, whitespace-only ActivityRow formatting repair, 14 verified base translation warnings and CoreSimulator boot blocker remain historical evidence.Historical author validation at
ad30f8be(tested treef76406a5, integrated master53c5ec0d):xcodebuildmcp simulator test --jsonfocused selection: 439 passed, zero failures/skips. The canonical direct Xcode fallback used the same six CI integration exclusions and one worker: 2,881 units plus 43 UI executions (39 passed, four fixture skips), zero failures. The UI skips require the disabled Trezor emulator or local Arbitrum fixture; they are not passes. Existing unfunded iPhone 17 Pro simulator, iOS 26.1.53c5ec0d. Independent combined integration review found no verified issue; PR patches and master recovery/sign-out behavior are preserved.e4bf44a2through maintainer merges of fix: unify usdt balance and amount presentation #1077/fix: reuse received payment artwork layout #1092. Those 36 paths are disjoint from this PR's 13-path delta; a clean read-only merge retained the exact PR blobs and all current-base changes. Local runtime evidence applies to the published tree. Hosted unit and integration checks subsequently passed on CI mergea6deea4d, whose tree053473bcexactly matches that reviewed combination withe4bf44a2.e4bf44a2verified locally and the exact GitHub commit set reportsverified=true,reason=validfor every commit.Hosted validation for head
ad30f8be:e4bf44a2run 40028545050. AppIntents metadata notices also occur on both. Project, workflow and dependency pins match that base; no new warning was found. The hosted xcresult records no runtime warnings.@lightning_1timed out after 30 seconds waiting for “Transfer Initiated” after closing a restored channel (lightning.e2e.ts:276at pinned E2E revisionbcf91fee). The second attempt reran only that case and passed; the other 13 were skipped and attempt 3 did not run. Complete workflow logs and per-case reporters were inspected. The close-confirmation view, transfer/wallet view models, Lightning service, toast components and E2E workflow match actual basee4bf44a2; this comparison does not prove that the timeout is pre-existing or establish its cause. After the successful retry, the workflow skipped runtime/screenshot/video artifact upload; only the built app artifact remains, so the saved failure visuals and app logs could not be inspected. Startup log-stream wrapper restarts are separate from the test retry. No manual workflow retry or code change was made, and the passing retry does not establish a permanent fix. All CI workflows for that earlier tested revision passed; this historical result does not describe the latest head. The full payment-journey limitations below remain.External device QA at exact head
ad30f8beis attributed to review 5635086275, which reports no introduced finding. On an iPhone 17 Pro / iOS 26.5 simulator with regtest funds, the reviewer checked direct 5,000-sat confirmation and a 143-sat fee, Largest First selection, manual-picker Back, cancellation during preparation, LNURL/Savings choice, LNURL-only direct confirmation, BOLT11/Savings switching at 2,500/5,000/2,500 sats, the fixed $2.50 BTC/USDT choice, and insufficient/expired-term guards. Swiping after expiry left both Bitcoin balances unchanged; fresh terms required confirmation. Hosted assertions covered invalid-term and fee/UTXO failure recovery. Videos and exact limitations are in that review.This external subset does not report a completed two-wallet fixed-quote settlement. USDT funding/submission, subscription authorization and hardware-wallet signing were not exercised; provider and physical-biometric validation remain incomplete. The full journey above stays unchecked. That earlier author-validation pass made no funded payment or wallet reset. Simulator data was preserved and the native window released.
Hosted unit/integration completion —
63ea2034, 2026-10-26Unit40410064946 and integration40410064928 succeeded on workflow attempt 1. Both checked out merge
a9eb51a06724ccd6a0cbff62487fcd8b5bf0bc77, with verified parents actual masterb8b7f3d9/ published63ea2034and tree21fb1809bb1af4f626c437015f8780139a9320b7, exactly matching the locally tested published tree. Unit: 2,894 passed. UI: 43 executions, 39 passed / four fixture skips / zero failures. Integration: 30 passed / four existing Blocktank staging-retirement skips/zero failures, with no retry execution. The four integration guards are byte-identical to actual master. Skips are not successful provider/hardware journeys.Artifact ZIP digests, all execution refs/skip reasons, complete logs and structured build/action reports were inspected. Unit has 286 structured duplicate-symbol warnings and 571 build-log warning messages; the structured multiset equals the historical b261 baseline. Project/pin/unit/integration workflow inputs match actual b8, but no actual b8 runtime baseline was run. Both full compile logs retain 571 duplicate-symbol warnings plus tool/multiple-destination notices; the integration version probe retains its broken-pipe exception. RuntimeWarnings/testFailures arrays and action warning/error nodes are empty. Integration's test-without-building status does not prove warning-free compilation. Xcode 16.4 compile paths, iPhone 16 / iOS 26.2.
E2E workflow 40410065359 completed on workflow attempt 1 using the same verified
a9eb51amerge/tree above, Xcode 26.2 (17C52), iPhone 17 / iOS 26.2 and E2E revisionec01c02ff611797c112e65b5c87c936d25720bac. Of 14 selected cases, 13 unique cases passed across 15 executions and three built-in test attempts. Attempt 1: 12 passed,@lightning_1failed, and hardware-wallet transfer-to-Spending stayed skipped. Attempt 2 reran only Lightning and failed; attempt 3 passed it. Both failures timed out after 30 seconds waiting forActivityShort-0to contain9 000after closing the restored channel (lightning.e2e.ts:287). No root cause or actual-base runtime reproduction is established; passing the third attempt does not establish a fix. The hardware skip is explicit in the pinned test source because Blocktank staging no longer creates orders.All five job logs, per-case reporters and complete available diagnostics were inspected. The workflow marks the first two steps successful through
continue-on-error; their raw exit-1 failures remain recorded here. Failure screenshots/videos were named in the runner log but were not uploaded after final success; only the app artifact remains, so those visuals and runtime reports could not be inspected. Log-stream restarts (143/137/124/124/143/143), 80 stale-element warnings, the address-type helper retry, dependency notices/audit output and the AppIntents/multiple-destination build notices remain retained. No new source change or manual CI retry was made. Rendered 546/547, funded/provider/hardware/two-wallet and other documented acceptance gaps remain explicit.Hosted integration completion —
9a807ef3, October 27Integration workflow40453530944 passed on attempt1: 30 unique tests /30 passing executions, four existing Blocktank skips, zero failures. All34 case summaries and the full execution tree confirm no retry execution. Empty runtimeWarnings/testFailures arrays and no structured action warning/error nodes. Embedded action output still contains RGS gossip failures, node/address-search/teardown diagnostics and the orphaned-RN-keychain fixture: all45 exact diagnostic groups were inspected, and all nine associated source/test files match actualb666. Hierarchy copies duplicate these log lines; this is not a warning-free or actual-base runtime reproduction claim. Skipped refund-address lifecycle/create-CJIT/orders/channel-purchase cases cite the closed staging service; their source and workflow match actual master.
The job checked out merge
a27dd5a89eba7dd6c2fd9e753131800383c565cc, with verifiedb666ccb1/9a807ef3parents and tree0ce4fc92, exactly matching the locally validated tree. Artifact digest, full job log, test trees and structured diagnostics were inspected. Compile output retains571 duplicate-symbol warnings (286 unique), the same complete multiset as the earlier actual-cc5 baseline. Current project/pins match actualb666, but no new b666 runtime reproduction is claimed. Test-without-building buildStatus=notRequested/no exported build log is not warning-free evidence; the full separate compile output is retained. The version-probe broken-pipe exception and tool/destination notices remain explicit. Runner: Xcode16.4 compile environment, iPhone16/iOS26.2; local26.1 results remain distinct.Unit and E2E completion is recorded in the following addendum; no manual retry. Funded/provider/hardware/two-wallet/rendered546/547 acceptance and historical retry/skip limits remain. Full report:
/private/tmp/usdt-monitor-20261027-0534/ci/completion-report.md.Hosted unit and E2E completion —
9a807ef3, October 27Unit 40453530938 and E2E 40453531272 passed on workflow attempt 1. Unit, E2E build and E2E runtime logs identify merge
a27dd5a89eba7dd6c2fd9e753131800383c565cc, with verified actual-masterb666ccb1/ published9a807ef3parents and tree0ce4fc929ce5dcb22d64b5800dc7451466705bb3, matching final local validation.ec01c02f. This does not establish physical-hardware acceptance.The unit archive digest, complete execution trees, all UI summaries, structured build/action diagnostics, all six unit/E2E job logs and available artifact metadata were inspected. Unit structured runtimeWarnings/testFailures are empty. Its 286 structured duplicate-symbol warnings and 571 build-log warning messages exactly match the recorded actual-cc5 multisets; current build inputs match b666, but no fresh b666 runtime baseline was run. Embedded action output retains negative-case offline/auth/storage/identity/hardware/durable-recovery and fee/UTXO diagnostics, synthetic Blocktank UUID-order failures, and simulator haptic/socket notices. Associated source comparisons and complete groups are retained; this is not a warning-free or blanket pre-existing claim.
E2E retains 68 stale-element notices, log-stream restarts 143/137/124/124, and one address-type tap/toast helper retry, separately from test retries. Tool/AppIntents/Node/npm notices remain; the pinned external E2E harness reports 76 dependency vulnerabilities (8 low, 17 moderate, 44 high, 7 critical), identical to its earlier same-revision log, with no uploaded advisory report. Only the app artifact was uploaded, and its metadata digest matches the build-upload and runner-download logs; no runtime-report or video artifact was available after success. Unit used Xcode 16.4 / iPhone 16 / iOS 26.2; E2E used Xcode 26.2 / iPhone 17 / iOS 26.2.
No manual CI retry or source change. Earlier Lightning failures/recovery and fixture incidents retain their original attribution. Funded/provider/physical-hardware/two-wallet settlement and rendered 546/547 acceptance remain incomplete. Full evidence:
/private/tmp/usdt-monitor-20261027-0636/ci/completion-report.md.Quoted review Back fix —
811b4335, October 27Returning from fallback Amount to a fixed quoted request’s Confirm root now closes that presentation, so Back no longer immediately pushes Amount again. The first fallback—including initially unreviewed terms—still requires amount review; valid terms reset the marker. The existing presentation-scoped dismissal preserves a replacement sheet. Seven production lines changed; two existing lifecycle tests were extended with a true Confirm-root host, with no new/removed tests. The existing journey now includes the iOS fallback Back and manual-coin return steps.
Unchanged
9a807ef3failed the Back/dismiss assertions for all four invalid quote variants; the frozen fix passed all 15 focused lifecycle tests and the workflow-selected canonical suite: 2,928 unit + 39 UI executions passed, four fixture skips, zero failures. Three Trezor cases remain opt-in; the isolated fork skipped at unavailable29546before app launch/funding. Exact helper/runner forwarding and built endpoints were checked. SwiftFormat 0.64: 0/845 files need changes; translations: zero errors/15 warnings, with all 21 relevant inputs identical to actualb666ccb1. Independent frozen-diff review found no verified issue. All 15 current-base PR commits verified locally and match GitHub’s exact set withverified=true/reason=valid. Normal additive push; no history rewrite.Complete logs, execution trees and 47 warning/UI/skip summaries were inspected. The 13 structured QoS warnings and 16 raw backtraces are different diagnostic surfaces; raw traces cover Pubky/mainline initialization and LDK status/sync. Two AppIntents compile notices, 11 UIKit appearance notices, simulator haptic diagnostics and 48 CoreGraphics NaN notices during passing USDT Receive/manual-entry cases remain recorded. No NaN backtrace or fresh actual-base runtime reproduction is available; those cases do not enter the changed quoted Bitcoin Confirm path. The changed source’s UTXO-unavailable logs come from an unchanged negative-case method. No blanket warning-free/pre-existing claim. Test-without-building metadata is not compile-clean evidence.
The manual-coin regression also passed with Confirm as the actual navigation root: after Continue to the second confirmation and Back through the picker, the original root recalculated using the selected coin without a fee-rate change. This proves the controlled fee-preparation call, not rendered fee text or a funded/provider journey. New-head hosted CI is pending; full linked-request/provider/physical-hardware/two-wallet settlement and rendered 546/547 acceptance remain incomplete. Previous evidence retains its original head attribution. Planning/implementation/review for this fix:
gpt-6-astra, reasoningxhigh, verified from actual execution metadata.Hosted integration at 811b433
Integration 40486729719 passed on workflow attempt 1: 30 tests passed in 30 executions, four existing Blocktank staging skips, zero failures or retry executions. Checkout
156e44dbhas actualb666ccb1/811b4335parents and treee5409e4f, exactly matching final local validation. The archive digest, all 34 case summaries, test tree, action/runtime output and full compile/test logs were inspected.Structured warning/error arrays are empty, but test-without-building is not proof of warning-free compilation. Retained 571 duplicate-symbol compile warnings (matching the prior hosted integration multiset), the version-probe broken-pipe exception, tool/destination notices and embedded RGS/node/address-search/teardown/fixture diagnostics. Associated inputs match actual base; no fresh base runtime reproduction is claimed. The subsequent unit failure and E2E completion for this parent are recorded in the crash-repair addendum below. Provider/funded/physical-hardware/full linked-request/two-wallet/rendered-fee acceptance limits and Piotr's open finding remain unchanged.
CI crash repair and diagnostic limits —
20f54449At parent
811b4335, unit workflow 40486729701 built successfully but had 2,927 unit passes and one crash intestAuthorizationSerializesReconciliationUntilTrackingStateIsPersisted; UI had 39 passes/four skips. The crash report showsXCTAssertEqualreadingtrackedAccountIndexesconcurrently with the fake’s insertion. The test and service inputs match actualb666ccb1, but no actual-base runtime reproduction is claimed. The narrow synchronization repair was coordinated with the file’s owning work before editing. Parent E2E 40486730476 passed 13 unique cases in 13 executions, with one existing hardware-case skip and no failures. Both checked out merge156e44db/ treee5409e4f(b666/811b); these results retain that parent attribution, not the new head.Final local repair validation used tree
c1e3cd27, Xcode 26.1 and the owned iPhone 17 Pro Max / iOS 26.1 simulator with one worker and private products. The complete execution tree confirms 2,928 unit + 39 UI passes; the summary groups 30 launch configurations into one. Three Trezor opt-in cases and the unavailable isolated fork skipped. Built endpoints and runner/helper forwarding were verified; runtime fork availability failed at unavailable29546before app launch or funding. No shared fixture was accessed.Full reports retain 16 structured QoS warnings and 16 raw backtraces (14 Paykit/Pubky initialization, two LDK status), two AppIntents compile notices, UIKit appearance and simulator keyboard/haptic/graphics/network notices, and negative-case storage/auth/recovery/fee diagnostics. Relevant QoS wrappers/pins match actual
b666ccb1; no fresh base runtime or warning-free claim. The passing UI run emitted 24 CoreGraphics NaN notices; the earlier identical-production811brun retains 48. No causal or permanent-fix claim is made for those notices. Hosted parent E2E retains 68 stale-element notices, helper/log-stream retries, pinned-harness dependency warnings and absent runtime-report upload. These checks do not complete J1/J2, production-provider, physical-hardware, funded settlement, full linked-request or rendered 546/547 acceptance.Native Back prerequisite diagnostic — October 27, 21:24 UTC
A temporary, isolated diagnostic at
20cfeffanow mounted the productionSendSheetthrough the same modal binding asMainNav, with setup-complete state seeded and unavailable fixture endpoints. This excludes real linked-request setup and persistence, authorization, and funded/provider acceptance. The modal, two-controller stack, enabled native recognizer, key window and hit-test were verified. One cancellation-shaped drag and one completion swipe still failed before any interactive transition began; the diagnostic failed all four relevant assertions. Adding the modal wrapper did not close the gap or establish a production defect.A standard UIKit navigation control on the same iOS 26.5 simulator did reach and cancel a genuine interactive Back transition. Its first run and one bounded recovery both expired before the completion gesture could be delivered because of turn interruption/context compaction; both remain failed, with two assertions each. No completed native pop or J1 acceptance is claimed. Full logs, nested diagnostics, failed assertions and screenshots are retained, including UIKit transition fallback and platform/accessibility notices. The temporary source was restored byte for byte, the checkout is clean, and scoped simulator services are stopped with data preserved. No production edit, source push, CI rerun or additional review request was made.
Native Back control follow-up — October 27, 22:28 UTC
The final bounded recovery of the interrupted UIKit control passed using identical built products: both cancelled and completed native transitions were observed, and the stack returned to its root. Fresh UI and log guards kept both gestures inside one live test window. Earlier interrupted failures remain retained.
The remaining prerequisite also fails in minimal SwiftUI and UIKit modals with hidden navigation bars and no payment/request/wallet objects (four and three assertions respectively). Each received one cancellation drag; the guard withheld completion when no interactive transition began. Separate SendSheet observations found both the edge and iOS 26 content-pop recognizers enabled, with delegates permitting a start. These values were unchanged by temporarily showing the bar. Both shared swipe-extension files match current master
85e9d4c6; this is source comparison, not a base runtime test.The controls do not yet separate navigation-bar visibility, modal presentation and runtime behavior, so no product cause/fix or completed J1 native Back acceptance is claimed. All logs, nested diagnostics, failure stacks and screenshots are retained. Temporary source is restored exactly and the checkout remains clean at
20cfeffa; no source push, CI rerun or reviewer notification. Existing provider, funded, physical-device and full linked-request limits remain.Native Back visibility control — October 27, 23:28 UTC
The isolated UIKit modal control passed its one selected test in 8.209 seconds on the same owned iOS 26.5 / Xcode 27.1 setup after changing only the navigation bar from hidden to visible (plus its state assertion/log and test name). Native cancellation and completion callbacks were
[true, false], and the stack returned to its root. No SendSheet/payment/request/wallet objects participated. The prior hidden-bar modal failures remain retained.This isolates visibility-dependent behavior in that control; it does not establish the production cause/fix or complete SendSheet J1 native Back acceptance. Shared swipe code matches actual master
85e9d4c6, by source comparison only. A shared-navigation follow-up requires ownership reconciliation. Full build/test trees, nested diagnostics, raw logs and screenshots were inspected. Structured warning/error arrays are empty; SDK/accessibility notices and two UIKit transition-fallback notices remain. Temporary source is restored exactly, the checkout remains clean at20cfeffa, and scoped simulator services are stopped with data preserved. No production edit, source push, CI rerun or reviewer request. Existing provider, funded, physical-device and linked-request limits remain.