Repository navigation
fix: confirm fixed Paykit requests directly - #1014
ben-kaufman wants to merge 31 commits into
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 |
|
Integrated actual master Final local validation on Xcode 26.1/iOS 26.5 passed 2,880 unit and 39 UI executions, with four explicit fixture skips and no failures or retries. Total: 2,923 executions, including 30 launch configurations; 2,894 unique cases include the four skips. The new pre-send backup/restore regression, all 16 backup lifecycle cases and camera case passed. The protected native USDT recovery class was excluded before fixture contact and remains unvalidated. SwiftFormat reports 0/824 violations, 100 skips; translations report zero errors and 15 warnings whose inputs match actual master. Complete structured, nested and runtime diagnostics were inspected. Retained: 21 structured QoS issues and one State issue, 22 raw QoS stacks, two raw State notices, 24 NaN notices, AppIntents metadata notices and symbolication limits. No introduced blocking finding was established; lower warning counts or passing tests do not establish a permanent fix for prior camera, launch, backup or runtime failures. The prior The existing native interactive Back, full J1/J2, fresh-install/channel/USDT recovery, provider and physical signing/biometric gaps remain explicit. Earlier human and automated QA keeps its reported revision and environment. Ovi's request and João's announced deferral through November 1 are preserved; Piotr's code-only review is not an approval. No merge or human-approval gate is claimed satisfied. |
|
Focused device evidence for A fixed 10,000-sat, Bitcoin/Savings-only payment request, Savings 0 sats, and a paired Trezor emulator with 1,000,000 sats reproduce both funding-selection concerns. Paykit enabled, USDT disabled. The issuer uses the existing
Expected: funding selection should settle before Savings coin preparation; a payable hardware request should remain on usable Confirm without the Savings failure toast, Amount detour or empty Savings picker. These observations need fixing or explaining before sign-off. Baseline runtime was not tested, so the evidence alone does not establish the first affected revision.
Automatic selection recording: hardware-auto-evidence.mp4Manual selection recording: hardware-manual.mp4Relevant runtime log: The final QA review will consolidate these results with the passing controls and remaining gaps. Logs from session: bitkit_logs_2026-10-30_12-23-07.zip |
piotr-iohk
left a comment
There was a problem hiding this comment.
QA review
Focused device result: two failed hardware-funding checks; approval is not established. The insufficient-Savings/no-hardware control and controlled background/foreground backup recovery passed. Native interactive Back remains unverified.
Tested fc916488, base 04a0a1f8, on the existing iPhone 17 / iOS 26.0 simulator with its wallet preserved. Installed the current CI app, artifact 12969464148, Bitkit 3.0.1 (202). Verified synthetic merge c1cbd167 and PR head have the same tree, 0a462801. Local Docker regtest; no app source modifications or payment authorization.
Actionable runtime finding: Savings coin preparation races hardware funding selection
Reproduction, screenshots, recordings and log excerpt.
Setup: Paykit enabled, USDT disabled, a fixed 10,000-sat BTC/Savings-only request, 0 sats Savings, and a paired Trezor emulator funded with 1,000,000 sats.
- Autopilot / Branch and Bound — failed: Confirm briefly opens, then “Please try again — Insufficient funds” appears and navigation falls back to Amount. The Trezor subsequently becomes selected, showing 999,890 sats available. Continue reaches confirmation. Reopening reproduced the Amount detour.
- Manual — failed: opening the same request pushes an empty Savings Coin Selection screen: required 10,000, selected 0, Continue disabled. Header Back reaches confirmation. A funded hardware source should not require this Savings picker.
The automatic log records onSwitchToOnchainWallet failing Savings UTXO selection at SendConfirmationView.swift:1908, alongside SendSheet.selectHardwareFundingSourceIfNeeded at line 933. This is consistent with confirmation starting Savings preparation before asynchronous hardware selection settles. That ordering is the suspected mechanism; no base-build runtime comparison was performed. Fix or explain these observed failures and retest both modes before sign-off.
Fixture boundary: these are real linked fixed-BTC requests from the existing rc65-shared issuer fixture. They are not USD-conversion quotes. The exact USD-quoted variants and physical hardware signing remain untested; the fixed-BTC failures are independently reproducible controls, not claimed completion of J1.
Other focused results
- Insufficient Savings, no hardware — passed for fixed BTC: after removing the disposable Trezor association, opening the same request twice produced one “Insufficient Savings” message per open and closed the sheet. The request stayed available. No extra Amount detour or generic failure toast was observed in the recording.
- Header Back / reopen / no payment — passed controls: header Back closed the automatic coin-failure fallback without looping; manual-picker Back returned to confirmation. Balances stayed Savings 0 and Spending 500,498 sats. The issuer still recorded the test request as
Proposed, with no payment proofs. No signing or payment swipe was performed. - Backup during quick background/foreground — passed controlled recovery: used this build's existing E2E localhost VSS/auth endpoint overrides to delay backup transport. After observing a SETTINGS write in flight, changed Manual back to Autopilot, pressed Home, and tapped Bitkit's Home-screen icon to resume the same process. In the final eight-second-delay run, the SETTINGS request was held at 12:06:36.499 UTC; the second setting change began at 12:06:36.607, Home at 12:06:37.896, and resume completed at 12:06:42.797, before the held response completed at 12:06:44.662. Logs show the active SETTINGS backup succeeding, then another SETTINGS write succeeding for the later change. Data Backups showed Settings recently synced, with no stale failure banner. The transient “Required” label was not captured; pending work is evidenced by the subsequent write. An earlier launch-command attempt cold-restarted the app and is excluded from this foreground-resume result; a longer-delay run also exercised transport retries. These controls do not prove restore, wallet-switch or production-network acceptance.
- Native interactive Back — blocked/unverified: short and longer edge-swipe attempts from the genuine automatic coin-failure Amount fallback did not establish a native transition. Touch-down hit target/interactive callbacks were not established. Neither cancellation nor completion is a pass, and no native-gesture product defect is inferred.
Retained code assessment and CI reconciliation
The saved full code assessment at fc916488 against 04a0a1f8 is retained: quoted-term binding, confirmation routing/lifecycle, coin and fee preparation, payment-time validation, backup observer generations/callers and exact amount formatting were reviewed. This device update does not claim a new full source review or erase earlier unverified concerns. The runtime finding above supersedes the earlier summary's implication that the proposed hardware cases had no demonstrated failure.
- Current-head unit/UI and integration passed.
- E2E #288, harness
611509ee, fixes the Connections-to-HeaderMenu navigation mismatch. It does not explain the current E2E run's two Lightning failures waiting 30 seconds for~Channelto disappear after channel close and mining six blocks. That assertion is atlightning.e2e.ts:281, before #288's NavigationBack step. Lightning passed the third built-in test attempt on the same app/harness tree. This is an intermittent failure, not a proven permanent fix. - Failure screenshots/recordings were generated on the runner but not uploaded after the final pass; only the app artifact is available. App/backend/harness root cause remains unproven. Prior device evidence at 06d3bd2b keeps its original revision and limits.
- The LOW journey-wording difference is already deferred to Android #1610, with the original discussion resolved. It is not an additional payment defect here.
Remaining gaps, without duplicate historical entries
- Fix/explain and retest both hardware-funding failures above; repeat their exact USD-quoted variants with a compatible issuer.
- Demonstrate cancelled and completed native interactive Back transitions after coin-preparation failure.
- Complete the outstanding J1/J2, provider USDT settlement, physical signing/biometrics and protected fresh-install/channel/USDT recovery coverage. The Trezor emulator and backup transport controls close only their stated cases.
- Reproduce the displayed-zero USDT toggle report with active eligibility/guard state captured. Unchanged guards and a successful fresh-fixture toggle do not diagnose that report.
- On another channel-removal failure, retain failure artifacts and channel/node state. Historical camera, launch and RBF-toast failures keep their original evidence; current passes do not establish their causes or permanent fixes.
The saved full-code-review model metadata below identifies that retained assessment. Focused device execution and this evidence reconciliation were performed in Codex. This is a COMMENT review, not approval or complete device acceptance.
Reviewer model: claude-opus-5-5
Reasoning effort: high
|
Master comparison: both hardware-funding navigation failures are regressions in #1014; the generic “Please try again” toast is pre-existing. This updates the baseline-attribution limit in the focused review and original reproduction/evidence. Tested master
The logs distinguish the paths:
The likely common cause is Savings preparation starting before hardware fallback finishes: the new Keep the two navigation failures on this PR, with both modes required for retesting. Treat the generic initial fee-estimation toast separately as an existing issue. The other testing gaps in the focused review remain open. Limits: master was locally built from clean pinned source; #1014 used its original CI artifact, so these are not identical-toolchain builds. Dependency pins and |
There was a problem hiding this comment.
Verdict: ⛔️ Request changes — the reported hardware-funding failures remain unresolved; native interactive Back is still unverified.
Findings: No new findings in this delta. The current-head hardware-funding report remains actionable: automatic selection detours to Amount after Savings preparation fails, and manual selection opens an empty Savings picker despite a funded Trezor emulator. The subsequent master comparison reproduces both navigation regressions and separates the pre-existing generic toast. I checked the competing preparation paths in the source; I did not repeat those device cases or duplicate the finding inline.
QA for fc91648:
Built the pinned head with Xcode 27.0 and tested on an iPhone 17 Pro simulator running iOS 26.5. The linked-request check used Bitcoin regtest and a staging issuer. No payment was authorized. Videos play at 2×.
Restore a manual Lightning payment from its pre-send journal ✅ Hosted assertions reconcile the restored attempt as successful.
The exact-head XCTest run captures the journal before sending, restores it through the production backup conversion, supplies successful node history and verifies the recorded outcome. Related assertions preserve attempts while node history is unavailable and reject duplicate admission. The recording shows the test host; the result bundle establishes these states. Node history and the SDK are controlled test doubles, so this does not prove fresh-install or channel recovery.
recovery.2x.mp4
Hosted journal and recovery regression.
Interactive Back from fallback Amount ⏭️ Neither a cancelled nor a completed native transition was established.
Real coin selection failed for a fixed 699,819-sat quote against 700,000 sats, returning to Amount. Short, full-width and preset edge gestures left Amount visible; the mid-drag capture and accessibility snapshots did not establish a native transition. Header Back closed the sheet once, and the unpaid request reopened with the same quote, checked in accessibility snapshots. Logs establish the coin-selection failure. The issuer still reported Proposed with zero proofs. These attempts establish neither a native Back pass nor a new product defect.
interactive-back.2x.mp4
Edge-gesture attempts on the established fallback and the header Back control; reopening was checked separately in accessibility snapshots.
Retained evidence for behavior unchanged by this delta:
- Previous review at 06d3bd2: refresh ordering, loading spinner, incoming requests during review and interrupted backup/sequential restore.
- Minimums, manual fees and preparation-failure Back.
- Request/subscription editing, serialization and exact USDT/bridge review.
- Locale and USDT preparation.
- Earlier regtest settlement and expiry/fresh-term results; no new settlement is claimed here.
Reviewed the contiguous 06d3bd2..fc91648 delta, all four changed files and reached callers. The confirmation-to-journal hash forwarding was checked in code; the executor/restore behavior ran above. Physical signing/biometrics, production-provider USDT settlement and fresh-install/channel/USDT recovery were not driven. Full J1/J2 completion is not claimed.
Pair PR synonymdev/bitkit-android#1581 matches direct fixed-Bitcoin confirmation. synonymdev/bitkit-android#1638 also keeps USDT selection on confirmation; iOS retains Amount for USDT and mixed LNURL/Savings selection.
Reviewed by gpt-6.1-sol-high
Keep fixed and quoted request confirmation idle until initial or fallback funding selection settles. Preserve saved Savings selection and hardware preview behavior without premature Amount or empty UTXO navigation. Preserve upstream backup subscription and metadata ordering safeguards, including metadata upload drain for superseded observation stops.
|
Fixed the two hardware-funding navigation regressions from review 5712051079 in Confirmation now waits for initial/fallback funding selection before preparing Savings coins. When hardware is selected, it stays on Confirm and calculates the hardware preview; when Savings remains selected, its saved automatic/manual selection resumes. The existing amount, expiry, authorization and cancellation checks remain. The generic fee-unavailable toast reported on master is separate and unchanged. A new controlled confirmation test reproduced the premature preparation before the payment fix on the staged integration (one failing method, four automatic/manual × fixed/quoted cases). The repaired candidate passed all 60 focused tests, including that regression and extended Savings/backup cases. Final canonical validation passed 2,945 unit and 39 UI executions, with five documented skips, no failures and no whole-test retries/restarts; XCTest retried one tap inside a passing USDT receive case. Complete logs and structured/nested diagnostics inspected; 28 structured QoS and one existing State notice, 32 raw QoS stacks, 72 NaN notices, three AppIntents metadata warnings and symbolication/runtime/simulator limits remain disclosed. SwiftFormat 0.64 reports 0/827 violations and 102 skips; translations report zero errors and 15 warnings with localization, validator and warning callsites identical to actual master This signed normal merge also integrates master The new hardware test uses controlled wallet/signer doubles and performs no PIN authorization, signing or broadcast. It does not replace the full SendSheet hardware, native interactive Back, provider/funded or full J1/J2 journeys. Those acceptance gaps and historical CI failures/recoveries remain disclosed. João’s deferral through November 1 and Ovi’s pending request remain intact. |
There was a problem hiding this comment.
Verdict: ⛔️ Request changes — pending funding setup still allows payment authorization before coin and fee preparation.
Findings:
- MEDIUM — Block authorization until request setup finishes.
Bitkit/Views/Wallets/Send/SendConfirmationView.swift:420: the new wait suppresses preparation while the explicit swipe remains usable. A controlled real-swipe test reached the payment-PIN callback with setup pending and no selected coins. The explicit submission path can proceed with an unreviewed fee and default coin selection. No payment was sent.
The previous hardware-navigation failures are fixed in the live checks below, including the added USD-quoted variants.
QA for 659a7c0:
Built 659a7c0 with Xcode 27.0 and tested an iPhone 17 Pro simulator on iOS 26.5. Live requests used linked disposable identities and Bitcoin regtest. Savings started empty; the paired Trezor emulator held 599,441 sats. No signing or payment was authorized. Videos play at 2×.
Payment stays disabled while funding setup is pending ❌ A real swipe reached the payment-PIN callback before setup finished.
An unpublished XCTest hosts the production confirmation with pending setup, valid quoted terms, a loaded fee rate and automatic Largest First selection. The recording shows the swipe and missing displayed fee; assertions establish the callback and state. The helper returns false at PIN, so it cannot send a payment.
setup=true pinRequests=1 preparations=0 selectedCoins=nil
XCTAssertTrue failed: Pending funding setup reached payment authorization before coin/fee preparation
This uses a controlled wallet and does not claim a completed send.
pending-authorization.2x.mp4
Unpublished diagnostic patch
diff --git a/BitkitTests/SendConfirmationLifecycleTests.swift b/BitkitTests/SendConfirmationLifecycleTests.swift
index d444e63e..599bb59b 100644
--- a/BitkitTests/SendConfirmationLifecycleTests.swift
+++ b/BitkitTests/SendConfirmationLifecycleTests.swift
@@ -867,6 +867,47 @@ final class SendConfirmationLifecycleTests: XCTestCase {
XCTAssertTrue(isSubmittingPayment, "The original payment owns the retained submission flag")
}
+ func testPendingRequestSetupMustNotAuthorizeDiagnostic() async throws {
+ snapshotAppDefaultsDomain()
+ snapshotAppGroupDefaults("home_screen_display_currency_code_v1", "home_screen_display_currency_symbol_v1")
+ let request = try onchainRequest()
+ let wallet = ControlledSendWallet(operation: .selection)
+ wallet.totalOnchainSats = 700_000
+ let app = AppViewModel()
+ let sheets = SheetViewModel()
+ sheets.activeSheetConfiguration = SheetConfiguration(id: .send, data: nil)
+ app.scannedOnchainInvoice = invoice
+ app.selectedWalletToPayFrom = .onchain
+ app.contactPaymentContext = ContactPaymentContext(publicKey: request.counterparty, incomingPaymentRequest: request)
+ app.paykitPaymentTerms = try request.payment(using: app.paykitPaymentMethod)
+ app.paykitReviewedAmount = app.paykitPaymentTerms?.amount
+ wallet.sendAmountSats = app.paykitPaymentTerms?.amount.atomic
+ let settings = SettingsViewModel.shared
+ let previousPin = settings.pinEnabled
+ addTeardownBlock { @MainActor in settings.pinEnabled = previousPin }
+ settings.pinEnabled = true
+ settings.requirePinForPayments = true
+ settings.useBiometrics = false
+ settings.coinSelectionMethod = .autopilot
+ settings.coinSelectionAlgorithm = .largestFirst
+ let setup = SendConfirmationTestSetup()
+ let navigation = SendConfirmationTestNavigation()
+ navigation.path = []
+ let window = host(app: app, wallet: wallet, sheets: sheets,
+ path: Binding(get: { navigation.path }, set: { navigation.path = $0 }),
+ navigation: navigation, setup: setup, confirmationIsRoot: true)
+ defer { close(window); wallet.resume(); ToastWindowManager.shared.hideToast() }
+ try await navigation.waitForAppearance(.confirm)
+ wallet.selectedFeeRateSatsPerVByte = 2
+ print("V4_PENDING_READY setup=\(setup.isPending) preparations=\(wallet.preparationCount)")
+ let deadline = ContinuousClock.now + .seconds(100)
+ while setup.pinChecks.isEmpty, ContinuousClock.now < deadline {
+ try await Task.sleep(for: .milliseconds(100))
+ }
+ print("V4_PENDING_RESULT setup=\(setup.isPending) pinRequests=\(setup.pinChecks.count) preparations=\(wallet.preparationCount) selectedCoins=\(wallet.selectedUtxos == nil ? "nil" : "set")")
+ XCTAssertTrue(setup.pinChecks.isEmpty, "Pending funding setup reached payment authorization before coin/fee preparation")
+ }
+
private func host(
app: AppViewModel,
wallet: WalletViewModel,The existing host denies its PIN callback. Production payment guards are unchanged.
Request funding waits for setup ✅ Hosted assertions passed for fixed BTC/USD requests in both coin modes.
The existing hardware-readiness regression confirms no Savings preparation before setup settles, then hardware fee preview without a Savings picker. Wallet and signer doubles are used; the result bundle establishes the assertions.
setup-readiness.2x.mp4
Fixed Bitcoin request selects hardware automatically ✅ The 10,000-sat request opened and reopened directly on Confirm.
Accessibility snapshots confirmed Trezor funding and the 141-sat fee. No Amount detour occurred; the request remained unpaid. The clip is the focused reopen.
hw-btc-auto.2x.mp4
Manual fixed Bitcoin request selects hardware ✅ Manual mode kept hardware Confirm without an empty Savings picker.
The coin-mode setting and accessibility snapshots confirmed Manual, 10,000 sats, Trezor funding and the 141-sat fee. Closing and reopening preserved the unpaid request; the clip shows the focused reopen.
hw-btc-manual.2x.mp4
USD quote selects hardware automatically ✅ The fixed 2.50-USD quote reopened directly at 10,000 sats.
Issuer terms fixed the BTC amount. Accessibility snapshots confirmed the selected Trezor and its 141-sat fee; no Amount detour occurred.
hw-usd-auto.2x.mp4
Manual USD quote selects hardware ✅ The same quote reached hardware Confirm without a Savings picker.
Manual mode was checked in Settings; accessibility snapshots confirmed 10,000 sats and the 141-sat hardware fee. The request stayed unpaid.
hw-usd-manual.2x.mp4
Empty Savings without hardware fails safely ✅ Both openings showed Insufficient Savings and closed.
After removing the disposable hardware association, the same plain-BTC request stayed available. No Amount detour or payment was observed.
no-hardware.2x.mp4
Savings preparation respects automatic and manual coins ✅ Saved selection and manual Back preserved the reviewed fee.
With 500,000- and 200,000-sat outputs, logs confirm Largest First selected the larger output; accessibility showed a 143-sat fee. Manual-picker Back retained Confirm and another swipe required coins. Selecting both outputs produced 212 sats on the second Confirm and after returning through the picker to the original root, checked in accessibility snapshots. No payment was submitted.
| Automatic selection | Manual selection |
|---|---|
savings-auto.2x.mp4 |
savings-manual.2x.mp4 |
Merged backup and address safety regressions ✅ Current-composition backup assertions passed.
Foreground recovery, backup service and Android backup normalization ran at this head. These controlled tests cover the merged observer/metadata-drain composition; the recording shows the host and XCTest assertions establish the result. They do not prove fresh-install recovery.
backup-integration.2x.mp4
Interactive Back from preparation-failure Amount ⏭️ Neither native cancellation nor completion was established.
Real coin selection failed for a 699,819-sat quote against 700,000 sats. Preset and short/full coordinate edge drags left Amount visible in accessibility snapshots and did not establish a native transition. Header Back closed the sheet once, and the unpaid quote reopened. These attempts establish neither a native Back pass nor a new product defect.
native-back.2x.mp4
Physical authorization and complete recovery acceptance ⏭️ Physical signing, provider settlement and full recovery remain unverified.
This run used disposable simulator/regtest state and stopped before signing. Physical biometric/hardware acceptance, mainnet-provider USDT settlement and fresh-install/channel/USDT recovery were not driven. Earlier supported in-place restore reports retain their stated limits.
Earlier passing evidence retained for implementations this delta leaves unchanged:
- Request/subscription editing, serialization and exact USDT/bridge review.
- Locale and locked USDT preparation.
- Savings/LNURL minimums and preparation-failure controls.
- Refresh ordering, review ownership and interrupted backup/sequential restore.
- Imported public/private sharing, rotation and supported restore results. Relevant sharing implementations and dependency pins match; the backup composition was exercised above.
- Imported Android-to-iOS restore results. Normalization and settings writers match.
- Earlier regtest settlement; no new settlement is claimed here.
Reviewed the contiguous fc91648..659a7c0 delta, all 62 changed files and reached callers, including the imported master changes and prior open hardware report. CI artifact-retention changes and journey wording were checked in code. Full J1/J2 completion is not claimed.
Twin comparison:
- bitkit-android#1581 fix: confirm fixed Paykit requests directly matches direct fixed-Bitcoin confirmation.
- bitkit-android#1638 fix: keep fixed paykit requests on confirmation with an asset switch also retains USDT selection on Confirm; iOS retains Amount for USDT and mixed LNURL/Savings selection.
Reviewed by gpt-6.1-sol-high
jvsena42
left a comment
There was a problem hiding this comment.
Re-reviewed to 659a7c0. No new findings.
0801b4f8: a below-minimum initial quote stays unbound on an Amount root, and a coin-prep failure goes throughreturnToAmountReview(). Every Back and cancel path from that Amount screen ends either by hiding the sheet or on a Confirm whose terms equalwallet.sendAmountSats: header Back, edge swipe, Continue → Confirm → Back → Back, X, the Amount-root pop, and the funding tap.3d48ab10and8aabaf77: the backup observation lifecycle traces clean.- Merge
659a7c07: addsisRequestSetupPending, and takes master'sBackupObservationSubscriptionsin theBackupServiceconflict.
On ovi-reviewer's open MEDIUM at SendConfirmationView.swift:420 (the swipe isn't blocked while setup is pending): the fix should gate submitPayment itself. Once .pin is pushed, the setup-complete .task can no longer prepare coins, because confirmationContext needs path.last to be nil or .confirm.
Device gate (partial): 659a7c0, not run yet. fixed-quoted-confirmation.xml now has a new action 4, which renumbers the old 2–6 to 2, 3, 5, 6, 7.
- My 629a874 results for 5, 6 and 7 stand.
- 2 and 3 have to be re-run, because
659a7c07re-times preparation. - Still pending: 4 (Trezor), 8–11, 13 (LNURL minimum), 14 (fee-shortfall Back), and all of
paykit-quoted-usdt.xml. - 12 needs a conversion-capable issuer, so it is left to e2e CI.
piotr-iohk
left a comment
There was a problem hiding this comment.
QA review
Scope: Full reassessment of the entire PR at 659a7c07 against merge base 3f14ea05, because the comparison base changed. No earlier coverage was inherited.
Code review completed with unverified earlier concerns.
Validation: Inspected hosted unit-test logs and integration logs for merge 03ec70c5, whose parents match these pins and whose tree equals the reviewed head. The affected regression tests passed; they do not exercise swiping during pending request setup. Local diff checks and parsing of all changed journey XML passed. Android and E2E comparisons were limited to the affected contracts at 6d99ad38 and 611509ee.
Device testing: not performed in this review. Local Swift/Xcode execution was unavailable; authored J1/J2 runtime acceptance and visual equivalence were not verified. Prior-findings reconciliation is deferred to the separate stage.
Suggested additional test cases
- iOS: with a running, funded Savings wallet, a fixed USD-to-BTC request, a non-default automatic coin algorithm and payment PIN disabled, hold the setup availability check after loading the fee rate. Attempt the confirmation swipe: no authorization or send should occur. Release setup and verify the saved coins and fee are prepared before the swipe enables; use a fee above 50% of the payment to verify its warning before sending.
Earlier findings reconciled at this revision: 1 still present, 14 verified fixed, 11 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 original displayed-zero report still lacks its active eligibility state. Unchanged guards and the reported fresh-fixture success do not diagnose that state or disprove the symptom. Code-only execution cannot settle it; no introduction or fix is claimed.
Could not verify earlier concern — Native interactive fallback Back remains unverified: Committed navigation writes are handled, but current-head reports establish neither cancelled nor completed native interactive transitions. Failed gesture initiation proves neither a defect nor a pass. Device execution is prohibited here.
Could not verify earlier concern — Camera-permission CI failure has no established cause: The matching test passes on the pinned tree, but the historical failures retain an unproven cause. Current success does not establish a permanent fix, and code-only execution cannot recover the missing runtime state.
Could not verify earlier concern — Remaining provider and physical-hardware acceptance is incomplete: Current-head hardware fallback and backup reports narrow the gaps but do not complete J1/J2, provider settlement, physical authorization or full recovery. These remain execution limitations.
Could not verify earlier concern — Native interactive fallback Back remains unverified: This separately retained item remains unverified. Source covers committed path changes, while current reports establish neither native cancellation nor completion. 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. Passing current CI does not explain the historical camera failures or verify a permanent fix.
Could not verify earlier concern — Remaining provider and physical-hardware acceptance is incomplete: This separately retained item remains an execution limitation. Attributed hardware-emulator and controlled backup checks do not complete the outstanding journeys, provider settlement, physical authorization or recovery coverage.
Could not verify earlier concern — Historical launch and E2E retry failures have unresolved causes: Historical launch and RBF-toast timeouts retain unproven causes. Current successes and retry recovery do not establish permanent fixes. The channel-navigation harness correction addresses a separate mechanism.
Could not verify earlier concern — Historical launch and E2E retry failures have unresolved causes: This separately retained item remains unresolved. Later passes and the known harness correction do not recover the historical runtime state or establish the timeout causes.
Could not verify earlier concern — Historical launch and E2E retry failures have unresolved causes: This separately retained item remains unresolved. Code-only inspection and later successful executions do not explain the earlier launch, RBF-toast or transient runtime failures.
Could not verify earlier concern — Contact-payment preparation latency remains unmeasured: The PR retains the latency concern without claiming a current reproduction or fix. Diagnostic timing hooks do not establish actual latency. No staging timing run occurred here.
Existing concerns
- [MEDIUM] Disable request payment until setup and coin preparation finish — still unresolved; already tracked.
- [LOW] Align fixed-quote journey wording across platforms — still unresolved; already tracked.
Reviewer model: gpt-6.1-sol
Reasoning effort: xhigh
There was a problem hiding this comment.
Verdict:
Findings: None in this delta. The pending-setup authorization finding is fixed at 1607a10.
QA for 1607a10:
Built this head with Xcode 27.0 and tested an iPhone 17 Pro simulator on iOS 26.5. Live requests used linked disposable identities and Bitcoin regtest; USDT entry used an unfunded test wallet. No payment or subscription was authorized. Videos play at 2×.
Payment waits for request setup ✅ Swipes during held setup reached no authorization.
An unpublished diagnostic hosts the production confirmation with valid quoted terms, a loaded fee rate and controlled wallet/setup state. Real swipes with payment PIN enabled and disabled left preparation at zero and selected coins unset. XCTest assertions establish those states; the recording shows the disabled/loading control.
pending-setup.2x.mp4
Payment waits for selected coins and fee ✅ The held preparation blocked authorization; a ready swipe reached the denied PIN callback.
While automatic coin selection was suspended, a swipe reached no PIN. After release, assertions verified Largest First, selected coins and fee preparation before the positive-control swipes reached the deliberately denied PIN callback. The callback always returned false, so this diagnostic could not authorize a payment.
coin-preparation.2x.mp4
Prepared high fee warns before payment ✅ The 1,500-sat fee on a 2,500-sat request triggered the over-50% warning.
With payment PIN disabled, Show Details displayed the controlled fee. A real swipe opened the fee warning, and Cancel returned to confirmation without sending.
high-fee-warning.2x.mp4
Manual selection and funding setup regressions ✅ The committed lifecycle and swipe assertions passed.
The result bundles cover automatic/manual and fixed-BTC/USD hardware setup, manual-picker Back, cancellation, minimum fallbacks and fee recovery. The focused replay shown here exercises the hardware setup wait and pending swipe-state test; XCTest assertions establish the broader result.
confirmation-regressions-focused.2x.mp4
Fixed request prepares the saved Savings coins ✅ The fixed $2.50 request opened and reopened directly at 5,000 sats with a 143-sat fee.
Settings and the device log confirmed Largest First selected the 500,000-sat output from the funded Savings wallet. The displayed fiat equivalent used the wallet rate. The request remained unpaid; the clip shows its reopen and details.
live-confirmation-focused.2x.mp4
Request denomination and receiving-method controls ✅ The USD/BTC control preserved 150.50 USD and method selection behaved independently.
Full-resolution captures verified one dark filled orange denomination control with its swap icon, and available Savings/USDT chips below expiry. Deselecting Savings changed its X to a dimmed plus while USDT stayed selected. Removing all methods disabled Send Request. The initial selection is shown separately; translated wrapping retains the earlier passing report below.
| Denomination and selection | Initial receiving methods |
|---|---|
request-layout-focused.2x.mp4 |
![]() |
USDT request sent state preserves exact USD amount ✅ Sent, the outgoing card and details all showed 150.50 USD.
The linked peer stored numeric 150.5 usd, only usdt-arbitrum-address, and zero proofs. A new creator retained typed 10.00 USD, verified in its accessibility snapshot. The recording shows the outgoing card/details and new entry; the still shows Sent.
| Card, details and trailing zeros | Sent |
|---|---|
outgoing-request-focused.2x.mp4 |
![]() |
Subscription defaults and receiving-method layout ✅ Creation and incoming review preserved 150.50 USD with USDT selected.
USD/BTC round trips and editing retained the exact amount. The creator placed USDT-selected/Savings-unselected chips below frequency, with description and icon side by side. Its sent proposal and peer record preserved the numeric amount. A separate incoming monthly proposal showed 150.50 USD on its card and review, USDT-only methods and a teal subscribe control. It was closed without subscribing.
| Creator controls | Incoming recurrence and review |
|---|---|
subscription-create-focused.2x.mp4 |
subscription-incoming.2x.mp4 |
Private USDT amount matches direct Send layout ✅ Private entry offered only USDT and labeled the balance AVAILABLE.
The device log confirms a private payment list was opened. At 0.01 USDT, full-resolution captures verified matching shared amount, balance, chip, keypad and Continue typography and bottom layout against direct Send. No quote or payment was started.
private-usdt-layout-focused.2x.mp4
Interactive Back from fallback Amount ⏭️ Neither a cancelled nor a completed native transition was established.
Actual coin selection failed for a 699,819-sat quote against 700,000 sats and opened Amount. Short and full coordinate edge drags and the native gesture preset left Amount in place without an observable transition. Header Back closed the sheet. These attempts establish neither a native Back pass nor a new product defect.
native-back-focused.2x.mp4
Earlier passing evidence retained for implementations this three-file delta leaves unchanged:
- Hardware fallback, saved/manual selection and backup composition. Its failed pending-setup case is replaced by the current-head checks above.
- Savings/LNURL minimums and preparation-failure controls.
- Request/subscription serialization, exact USDT/bridge review and translated wrapping.
- Locale and locked USDT preparation.
- One-micro request and six-decimal subscription display.
- Refresh ownership and supported interrupted-backup/sequential restore.
- Earlier regtest settlement; no new settlement is claimed here.
Reviewed the contiguous 659a7c0..1607a10 delta, all three changed files and reached preparation, fee, authorization and cancellation callers, plus the prior open finding. The early setup-to-task submission guard was also checked in code. Native interactive Back remains the unrun journey subcase; full J1/J2 completion is not claimed. Physical authorization, provider USDT settlement and fresh-install recovery were not driven.
Twin comparison:
- synonymdev/bitkit-android#1581 fix: confirm fixed Paykit requests directly matches direct fixed-Bitcoin confirmation.
- synonymdev/bitkit-android#1638 fix: keep fixed paykit requests on confirmation with an asset switch also keeps USDT selection on Confirm; iOS retains Amount for USDT and mixed LNURL/Savings selection.
Reviewed by gpt-6.1-sol-high
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]
| Logger.error("Failed to calculate actual fee: \(error)") | ||
| transactionFee = 0 | ||
| if app.contactPaymentContext?.incomingPaymentRequest != nil { | ||
| returnToAmountReview() |
There was a problem hiding this comment.
MEDIUM — keep a single active confirmation after fee-failure retry
On aac68853, after real automatic selection succeeds and the subsequent fee calculation fails, this new fallback clears terms and sets the path to [.amount] while the original Confirm remains the root. Real Amount Continue then appends .confirm (SendAmountView:441), leaving Confirm(root) → Amount → Confirm(retry). Both confirmations share the path and presentation; confirmationContext/sendContext (lines 65–85) accept either instance when the path ends in .confirm. Wallet-switch deduplication is per-view @State, so it cannot prevent both instances preparing coins.
Device result: FAIL. After Continue → Spending → Savings, one tap caused two UTXO selections and two SDK fee calculations at 11:52:30.648–.652 UTC; a full dismissal/reopen control selected/calculated once. Back → Amount → Back exposed the older confirmation. The valid-terms path also resets hasShownAmountReview at line1813, so the immediate-failure dismissal guard does not cover this recovered sequence. See the accompanying review's recordings and the timestamped log excerpt in the review summary. The duplicate work is log evidence; no duplicate payment or funds loss was observed.
Ensure only the active review owns preparation when retrying this fallback (retire/reuse the old confirmation or enforce instance ownership), and add a real fee failure → Continue → switch methods → Back regression. testRequestFeeFailureAfterCoinSelectionReturnsToAmountReview currently tests immediate Back only; its host renders Amount as Color.clear, so it misses the failing continuation. The original resolved fee-fallback finding is fixed; this is the newly exercised retry path reached by the added catch branch.




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
aac68853J1 action 14 now returns fixed/quoted requests to Amount after a post-selection fee failure, clears reviewed terms and closes safely on Back. The same regression failed before the fix and passed afterward, including authorization/submission tripwires.
Hosted attempt 1 on
8405acaf/ tree1123cafc(parents831ec3e6/aac68853): 2,954 unit + 39 UI passes, five fixture skips; 30 integration passes; E2E 13 passes and one hardware skip. Zero test failures or test-case reruns. E2E used its first workflow step, with one internal tap retry and four log-stream restarts. Full diagnostics and prior validation history are retained. Newer master82925a86is not covered by this combined-tree result.Final local checks on the same tree: 2,993 passes/five skips, two independent source reviews, SwiftFormat 0/828 and translations zero errors/15 verified base warnings. All 31 publication-range signatures verified locally/on GitHub against base
831ec3e6. Hosted static checks passed too.Both hosted XCTest builds retain 286 unique linker warnings/571 occurrences; keyboard/NaN/RGS/node notices remain. E2E retains 85 harness dependency findings (7 critical) and lacks media/structured test reports. No fresh master-runtime or warning-free claim. Historical entitlement/launch/E2E failures, skips, recoveries and missing local-only evidence retain their original attribution.
Bot QA reports stress-hook fee fallback/header Back/reopen; natural-rate shortfall and native interactive Back remain unverified. Zero current human approvals; all three named reviewers pending. Native interactive Back, full linked-request/provider/funded/physical-device and recovery acceptance remain incomplete. No 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.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.