Repository navigation
Conversation
This comment has been minimized.
This comment has been minimized.
|
I pushed e447862 with original Shop and order follow-up recovery, exact native observation acknowledgement, earlier-payment isolation, shared verified proof metadata and original transfer accounting across wallet changes. The focused simulator build and 90 tests passed. This addresses accepted payments becoming stuck after local proof/activity/tracking failures without funding again. Process loss before a durable result still remains guarded. Corrected artifacts, hardware proof integration and current consumer journeys remain draft gates. |
|
I pushed 9c84067 with the hardware Shop acceptance gates. A Core txid remains an original lookup candidate until a fresh exact outgoing observation in the original hardware wallet verifies it. Only that positive result can create Shop proof, Sent activity or Success; Pending retains the original payer/request/wallet and does not inspect an unrelated Savings guard. The frozen batch passed its simulator build and 70 focused tests with no failures or skips. The UI, payer and activity regressions failed before their fixes; mocks do not establish a real hardware acceptance journey. Physical hardware and native fault fixtures remain QA gaps. This remains draft while current rc68 remote consumption, affected tests and fixed/Manual Max native journeys finish. Historical rc67 media is labelled accordingly. |
|
I pushed f16736d with the production rc68 package pin and current journey documentation. The normal SwiftPM revision is 773792d; the published archive, Package.swift checksum and extracted/resolved simulator framework independently match the canonical artifact. The current simulator build and 101 affected software, hardware and backup tests passed. The matching installed app completed funded fixed 1,000-sat and Manual Max 198,745-sat native sends; both exact UI transaction IDs matched native successful-broadcast logs and independent backend lookup. Current Preview replaces the historical accepted-send media. The hardware Shop journey is specified and parsed but unrun; controlled native refusal/response-loss and physical USB/BLE remain explicit QA gaps. Autopilot Max remains outside this fix. |
jvsena42
left a comment
There was a problem hiding this comment.
One HIGH, one MEDIUM and two LOW inline. I reviewed this as a funds change. The refusal lockout and the legacy-proof gap are shared with synonymdev/bitkit-android#1384.
Checked and clean:
- Only
NodeErrormaps to.preDispatch; in rc.68send_with_broadcast_resulterrors only before submission. Panics and other errors stay.unresolved, and the non-cancellable queue records the outcome. - Success is shown only on
.accepted, and Electrum returnsAcceptedonly for a matching txid. Observation promotion needs BDK sync events. HW Shop needs a matchinggetTransactionDetailwithsent > 0. admitis synchronous, so a concurrent send sees.pending.- The new keychain entry holds no key material and uses the existing accessibility/group, and full wipes cover it.
- The ldk-node rc.66 → rc.68 bump carries the broadcast-result API with no storage migrations. It also brings rc.67 coin-selection changes.
Pre-existing and out of scope: ordinary HW sends and Boost still use the legacy broadcast.
|
Two independent reviews. needs changing before merge
worth doing, does not block
|
|
Pushed 2039d39 to address callback admission, proof mutation races, and delayed/reopened hardware follow-up. Callback errors clear only the exact attempt before native dispatch; a failed clear remains guarded. Proof mutations now serialize without holding the lock through SDK network work. Delayed verification restores original Sent/contact/tags before delivery, and Pending can reopen after the resolution event was consumed. Accepted payments are never broadcast again. Local verification: simulator build and 99 affected tests passed against published rc68 (0 failures/skips). The three regressions failed on the preceding head. Geographic test state is isolated while the production check remains unchanged. Prior native fixed/Manual Max media is historical for this batch. Hardware journey and controlled native faults remain unrun. Previous-head E2E failures remain unresolved; new-head CI is pending. Refusal lockout and software legacy migration remain open review discussions. |
jvsena42
left a comment
There was a problem hiding this comment.
Re-checked 2039d39. The callback-failure MEDIUM and the lock LOW are resolved, and the greptile threads on delayed HW verification and the pending screen check out. No new findings.
Still open, waiting on my decision: the refusal lockout thread and the legacy-proof migration thread, where you asked whether they block this PR.
|
Pushed ec99bd0 to fix the reported Electrum refusal formats. The parser now accepts supported JSON-string envelopes and coded RPC -25/-26 messages, including the complete replacement-fee refusal. Recognized refusals permit leaving the sheet while retaining the original signed payment and wallet guard; malformed and unknown responses remain guarded. Focused refusal/navigation, original-byte retention and restart reconciliation regressions passed on the corrected source. The device report was from Android; actual iOS device refusal and funded Shop acceptance remain unrun. Current-head CI and human review are pending. |
The reported Electrum JSON-string/coded RPC refusal finding is addressed in ec99bd0 with focused navigation and retained-payment regressions; its thread is resolved. Dismissing the superseded verdict; current-head human review and device acceptance remain pending.
|
Pushed 1067305 to restore access to the original refused hardware payment after restart. The original request stays accessible, and reopening uses its hardware wallet, recipient, amount, signed bytes and fee without preparing a new payment. Request identity/amount/billing period/endpoint checks and durable guards stay enforced; retries still require explicit authorization. Focused reload/reopen and original-payment guard regressions passed. Actual hardware restart, funded Shop and fresh-wallet acceptance remain unrun; current-head CI and human review are pending. |
jvsena42
left a comment
There was a problem hiding this comment.
Device gate run on an iPhone 17 simulator with the Trezor emulator over the Bridge, an Android emulator as the linked issuer, and a second wallet holding the same hardware-account coins to make the conflicting spend. No finding from the run. The read of the last two commits is still in progress, so this is a comment and not yet an approval.
Device gate: ec99bd0 for the first two parts, 1067305 for the third.
- Normal path, passed. A 100,000 sat request opened with the Trezor as the funding source. The sign screen and the Trezor showed the issuer's address, 0.001 and a 141 sat fee. One approval ended on the success screen, and the backend has one transaction,
bc2b0cd85baa…, paying exactly 100,000 sats to that address. - Refusal, passed. For a 30,000 sat request, the hardware-account coin was spent by the other wallet after the Trezor showed the recipient. The server answered
insufficient fee, rejecting replacement. The button turned to Retry, Back returned to confirmation and swipe-down closed the sheet. - Reopen after relaunch, passed. At
ec99bd0the relaunched app showed the request as Pending with no action. At1067305its details offer Pay and Dismiss; Pay reopened the confirmation, Retry reused the signed bytes without a Trezor prompt, the server answeredbad-txns-inputs-missingorspent, and the sheet could be left. Nothing was paid to the issuer's address for this request.
jvsena42
left a comment
There was a problem hiding this comment.
Read of the last two commits finished at 1067305: one MEDIUM, inline, reproduced by a unit test and seen on the simulator. The device results from my previous comment stand.
Checked and clean in the same read:
- Refusal parser: still an allow-list. The JSON-string and object forms, the
-25/-26prefixes and the anchored replacement pattern cannot match a connectivity error, a longer message or another error code; those stay guarded. - Reopen: address, amount and wallet come from the retained proof, the funding source cannot be switched, and the fee shown is the signed transaction's fee. The stored bytes are reused, so there is no second signature.
- The retained retry is bound to the same identity, request id, amount and endpoint; a Lightning or software payment cannot reach this path, and an identity switch clears the cache.
- Closing the reopened sheet, during preparation or after, leaves the proof and the request as they were.
|
Pushed 72814b0 to preserve access to the original unpaid recurring hardware payment after restart. Retained retries now match the original payer, subscription period, amount and endpoint. Dismiss is hidden and cancellation is rejected while the original signed payment remains unresolved, preserving its retry route and durable guard. This addresses the recurring restart and retained-payment dismissal findings. Focused restart, original-byte retention, paid/changed-period exclusion and cancellation regressions passed. Actual funded Shop, fresh-device wallet/VSS and physical hardware acceptance remain unrun; current-head CI and human review are pending. |
The retained-payment dismissal finding is addressed in 72814b0 with focused regression evidence and verified thread resolution. Current-head review and acceptance checks remain pending.
jvsena42
left a comment
There was a problem hiding this comment.
Re-read at 72814b0: the Dismiss finding is fixed and I have no new finding in this commit. No thread of mine is open.
Checked in the commit:
- A retained payment cannot be dismissed, for one-time and recurring requests, at the manager and in the three views. Requests that could be dismissed before still can.
- Recurring retained retries are matched on the request id, which includes the period start, plus amount, billing period and endpoint. A paid or locally completed period is excluded, and a one-time proof cannot satisfy a period.
- The pay path re-validates wallet, address and amount against the retained proof.
Device gate (partial): 72814b0 — not run yet at this head. The refusal, reopen and retry steps passed on the simulator at 1067305 (my earlier comment). The hidden Dismiss needs a fresh refused payment on the simulator; I will run it and edit this line.
Platform note, for a follow-up and not a finding here: with this commit iOS reopens a refused hardware payment for a subscription period, while Android keeps such a payment guarded and unreachable after a restart (synonymdev/bitkit-android#1384).
jvsena42
left a comment
There was a problem hiding this comment.
Holding this before merge: on the simulator, sending the maximum on-chain amount fails at 72814b0 and works on a master-based build with the same wallet. MEDIUM, regression. I am posting the device result now because the PR is approved; the root cause and a unit test follow in a reply.
Steps, iPhone 17 simulator, regtest, plain software wallet with 100,000 sats in Savings as two confirmed coins (60,000 and 40,000), automatic coin selection, no Paykit request involved:
- Open
bitcoin:<a regtest address>so the Amount screen appears. - Tap AVAILABLE. The amount fills with 99,819.
- Tap Continue.
Result at 72814b0: a toast "Send Error — Coin selection failed", and the screen stays on Amount. Repeating Continue gives the same result.
Result on a master-based build (0304889, the head of #911) with the same wallet and the same steps: the Confirm screen opens with 99,819.
I first saw it at 1067305 with a different wallet (one coin of about 24M sats); a fixed amount of 1,000,000 from that wallet went through to Confirm, so it is the maximum amount that fails. The app log at 72814b0 shows repeated estimateSendAllFee calls and one-input PSBTs alternating between the two coins, with no error line.
The Android twin is not affected: at b33dc94 the same steps reach Confirm with the full balance.
piotr-iohk
left a comment
There was a problem hiding this comment.
QA review
Scope: Follow-up review of the changes since ace62523, including affected payment paths and prior findings, at 72814b0c. Inherited baseline: previous QA review.
1 actionable finding — resolve or provide an evidence-backed rebuttal.
The pre-dispatch deadline and cancellation recheck, quoted Electrum RPC refusal parsing, recurring-period reopen, and retained-payment dismiss block match the current source. The deadline thread, refusal-envelope thread, recurring-reopen thread, and dismiss thread are addressed at this revision. The reopened confirmation still leaves the fee control editable, unlike the retained-payment lock on Android b33dc941.
Validation: the new refusal, expiry, and reopen regressions were inspected and not executed here. The unit-test run succeeded for this revision. Android comparison was limited to refusal parsing and retained confirmation terms.
Device testing: not performed in this review.
Suggested additional test cases
- iOS, retained refused hardware Shop receipt: open Pay, change the fee speed, and return to confirmation. The speed, confirmation estimate, and sat fee stay on the original signed transaction, and swipe broadcasts those same bytes. After that refusal, change the speed again and swipe without leaving the sheet. The second attempt still submits the original transaction.
Findings
- [LOW] Lock the fee on a reopened hardware payment — inline at
Bitkit/Views/Wallets/Send/SendConfirmationView.swift:1612.
@jvsena42 Fixed in b24a470: Max uses the spendable drain inputs instead of running fixed-amount selection against an amount that already includes the drain fee. Normal fixed-amount selection keeps its chosen algorithm. The focused regression uses the reported 60,000/40,000 sat case at the selection boundary; a funded UI retest remains pending. The same push fixes @piotr-iohk's retained hardware fee finding: confirmation uses the original signed fee rate, prevents fee edits, and repeats explicitly authorized retries with the same signed bytes even if the wallet preset changes. Focused regressions reproduced the old failures and passed on the corrected source. Funded Shop, fresh-device wallet/VSS and physical hardware acceptance remain unrun; new-head CI and human review are pending. |
Superseded by b24a470: the reported Max selection regression is fixed with focused failing/passing evidence. Funded UI retest and current-head review remain pending.
There was a problem hiding this comment.
Re-reviewed at b24a470: the send-max regression is fixed and confirmed on the simulator. No new finding from me in this commit. The hardware part still gets a device run, so this is a comment for now.
Send max, iPhone 17 simulator at b24a470, plain software wallet with 60,000 + 40,000 sats:
- Available, Continue: Confirm opens with 99,819 and a 181 sat fee. The broadcast transaction
02d3cc841799…spends both coins into one output of 99,819. - Available, then edited down to 10,000, Continue: sent as an ordinary payment. Transaction
3c28e3093195…spends the 40,000 coin into 10,000 plus 29,857 change, fee 143.
Root cause, for the record: the ldk-node bump in this PR (rc.66 to rc.71) makes selectUtxosWithAlgorithm add recipient overhead to its target, so "balance minus send-all fee" no longer fits as an exact amount and the selector reports CoinSelectionFailed. setUtxoSelection now takes every spendable output for a max send, and the actual send still goes through sendAllToAddress after shouldUseMaxOnchainSend re-checks the amount.
Read of the commit, clean:
isMaxAmountSendis recomputed on every Continue, so an edited amount, a unit switch or Back to Amount cannot leave it stale; it is reset with the send state.- The hardware and transfer flows never call
setUtxoSelection. - On a reopened retained hardware payment the confirmation shows the signed fee and the fee row cannot be opened.
Not posted, no reproduction: in the same sheet, Retry after a refusal now passes ceil(signed fee rate) where the first attempt passed the preset. If those two ever differ, the retained-payment check in HwFundingSigner.swift:543-549 throws operationInProgress until the sheet is reopened. On the Trezor emulator the signed fees I saw were exact multiples of the preset (141 at 1 sat/vB), so I could not make them differ. The device run below includes a same-sheet Retry.
Device gate: b24a470 — the hardware run is done and passed; the result is in my approval on this PR.
@jvsena42 Checked against the pinned Bitkit Core producer: |
jvsena42
left a comment
There was a problem hiding this comment.
Approving at b24a470: every finding I raised is fixed and confirmed on the simulator, and no thread of mine is open.
Device gate: b24a470 — passed, iPhone 17 simulator with the Trezor emulator over the Bridge, an Android emulator as the linked issuer, and a second wallet holding the same hardware-account coins for the conflicting spend.
- Send max, software wallet: passed, details in my previous comment.
- Hardware refusal: a 30,000 sat request was signed, the coin was spent by the other wallet first, and the server answered
insufficient fee, rejecting replacement. The button turned to Retry with the back arrow available. - Retry in the same sheet: no Trezor prompt, a second broadcast and a second refusal, no "in progress" error. The concern I mentioned about the retained rate did not show: the signed fee is an exact multiple of the preset.
- Leaving: Back returned to confirmation and swipe-down closed the sheet.
- After relaunch: the request's details show Pay and no Dismiss. Pay reopened the confirmation with the Trezor as source and the fee shown as
1 ₿/vbyte (₿ 141)with no edit control; tapping the fee row did nothing. - Retry after reopen, one block later: no Trezor prompt, the server answered
bad-txns-inputs-missingorspent, and the sheet could be left. Nothing was paid to the issuer's address for this request.
|
Pushed 0e00f06 to integrate master 91fffad and its reviewed Paykit Simulator compilation and affected payment/recovery checks ran against the published packages. One lifecycle test remains failing: Current-head dev approval and required hosted CI are pending. Fresh-device wallet/VSS restore, funded Shop proof/paid-order acceptance and physical hardware acceptance remain unrun. |
|
Published bb02e83: adds the local-only Paykit fallback journey and setup recipe. Remote staging stays the default; fallback is conditional on a recorded pairing failure. The docs distinguish rc11 server/rc72 client matching from actual pairing, payment observation and Shop order completion. XML and configuration references checked; the journey remains unrun. No application code changed. |
Closes #717
Twin: synonymdev/bitkit-android#1384
Refs:
Description
Keeps the original unpaid recurring hardware payment accessible after restart with exact payer, period, amount and endpoint checks; retained payments cannot be dismissed or canceled, and the request UI hides Dismiss while the original signed payment remains unresolved.
Recognizes actual Electrum refusal envelopes and preserves dismissal after an expired retry, while retaining the signed hardware payment and durable guard.
Releases abandoned unsigned Shop preparation on restart only when no signed receipt or dispatch could exist, restoring sends and wallet backups.
Persists the actual signed mining fee for the original candidate; a zero saved estimate requires observed winning-fee evidence before local completion.
Bounds initial Max funding by the original approved total, including the actual signed mining fee from native rc71; missing or excessive fees fail before candidate retention or broadcast.
Shows the original guarded payment when a differently keyed Shop request is blocked, preserving its amount, transaction and request rather than displaying the unsent new request.
Serializes ordinary accepted-payment activity completion across awaited fee and storage operations.
Preserves the precise recurring billing-period timestamp in an active payment backup by using its matching original proof.
Releases definitely unsigned ordinary sends and transfers left by process death before a prepared receipt exists; live preparation, signed candidates and Shop proof guards remain protected.
Keeps the original software payment guard if authorization cleanup cannot durably remove its started Shop proof.
Preserves the prior consumed private payment-list boundary with the original receipt and backup, so cancelling an unsent version cannot reopen older payment details.
Keeps the captured private payment-list version in hardware receipts and backups; releases only that exact version before deleting a definitely unsent proof, so interrupted cleanup remains retryable.
Restores whether the original hardware Shop transaction ever reached dispatch; interrupted authorization or expiry before dispatch clears only its exact unsent proof, while attempted payments stay guarded.
Retains the original signed hardware Shop payment and request after broadcast failure. A recognized backend refusal permits leaving the sheet; connectivity or unknown outcomes keep navigation guarded. Explicit retry reuses the original signed transaction without another signature.
Defers the full wallet backup while an ordinary send or channel funding operation is admitted but unsigned; backups retain the exact signed receipt needed for original-payment recovery.
Refreshes the visible original Pending operation from durable acceptance when a completion event is missed; completed results suppress stale Unknown and Retry.
Reset funding confirmation’s swipe when its retained Pending sheet opens, without advancing setup or showing a generic error toast.
Rejected channel funding opens the exact retained Pending operation directly, with setup incomplete and explicit retry available.
Integrates shared-state Paykit
0.1.0-rc72with LDK0.7.0-rc.71, retaining original payment guards, captured proof app IDs and cross-platform backup state.Carries the original request deadline into native submission and hardware signing while retaining typed broadcast outcomes and original payer/wallet checks.
Opens the exact retained Payment Pending operation when channel funding has an unknown broadcast outcome; confirmation does not advance to setup or leave recovery hidden behind a generic error toast.
Required for Bitkit 2.6.0 Shop support. The identified recovery fixes are published and this PR is open for human review. LDK rc71 is merged and published; the original signed transaction ID, actual inputs and recipient amount are durable before submission. Current-head human approval and the remaining wallet/merchant acceptance checks are still required before merging.
0.7.0-rc.71prepared-send bindings at daeee4d2, including the signed mining-fee receipt. Current simulator compilation and affected service regressions use this package; earlier rc69/rc70 checks below are historical coverage.Why
Required for Shop support in Bitkit 2.6.0: buyers must be able to tell whether an on-chain payment was accepted and recover an uncertain checkout without paying twice.
These are release acceptance requirements. The app PRs include the selected Paykit updates for 2.6.0. Final validation must pay a Shop order with these app builds and verify that the merchant receives the payment proof and the order becomes paid on the existing Shop server.
Out of Scope
Design
N/A — no design available for the new unresolved-send state.
Preview
Historical rc70/rc65 funded simulator run at 182b0a0: the original broadcast was accepted with its acknowledgement withheld. An explicitly PIN-authorized higher-fee retry used the same exact input set and 1,000-sat recipient amount. The open Pending sheet resolved to the accepted successor and enabled Details without reopening; Details showed its actual 626-sat fee.
Controlled rc69 UI fixtures: synthetic unresolved candidate, fee entry and invalid empty input. These are actual simulator UI captures, not funded retry, PIN, backend refusal or response-loss evidence. The fee capture precedes the final wording change adding the
satsunit.Historical rc68 candidate before the current feedback batch: fixed 1,000-sat and Manual Max 198,745-sat regtest sends using the actual published and resolved LDK package. Both exact UI transaction IDs matched native successful-broadcast logs and independent backend lookup; Max spends the original fixed-send change output with no change remaining.
QA Notes
Merchant acceptance (10 October)
amount_matched: trueand one confirmation. Locks completed verification, issued an access credential, and returned the exact protected content through an authenticated read. This Android result is separate from the iOS result below and does not establish Shop order completion.f33ff0363066237f61ce8e6e1db6bd845f226cafc9d36774058284712cf9a013has one original funding input, a 1,000-sat seller output and a 141-sat fee. Confirmed transaction bytes and native signed-input evidence were retained; this does not claim a pre-broadcast snapshot or an interrupted-send retry test.creatorparameter. Shop then called the fork-only/v0/accounts/pubky<seller>route and received HTTP 404; listing creation and Accept Bitcoin remained gated.paykit_invoice_creation_failed, including on one authorized new-bundle retry. The buyer's App Registry and identity-signed Noise authorization were present and verified. The exact remote deployment/configuration cause remains unknown; the successful isolated purchase does not identify it or prove the same Shop order becomes paid.ec0d05e9-9238-4c7d-a156-69a9be3c7908became confirmed withamount_matched: true; requesta7e5d0aa-7099-4af5-939a-5838f78e1ef3reachedproof_submitted. Transaction:283f408b3435eaf04258b5f929eed73a846f78ad21ff1990c2dcd203397b9a97.0KGP41FQ0W0F27EWPA2YBGJD6R, issued an access credential and served the exact protected content through an independently repeated authenticated read (73 bytes; SHA-25666126e151321afc148b133e059dd634f6f7beb1bf1b8b64c36097d021da21724). This proves iOS Locks merchant acceptance; it does not prove a Shop order-paid transition or wallet restore.SharedStateBusysession error cleared after stopping the payer app for 82 seconds and relaunching normally; no remote lock was edited. The simulator and app/log helpers were stopped after evidence capture; the backend remains available for the remaining tests.c51f229cac163ea802a5495dd3c2bbdcbdee113382b7948dc254f65897722d1c), recovered the original payer and requeste470dd63-92e8-4b13-8584-fcab299e5f27, and queued its original proof at 03:28:08 UTC. The server independently changed that request fromacceptedtoproof_submitted; transaction34d03ad637596414c1f8bdb0f3c9a54c6d4ff820986a31f2c80ae8934852377ewas paid once. No application data or hold marker was copied. This covers ordinary pending-proof restore, not an active-guard crash window or a Shop order-paid transition. The Android PR includes the temporary hook and repeatable journey.380b66e9b7d9a85c02432ad6a71b7cf175800c5c89aa839c94cae1c0990e29f9). The retained proof kept request7c73247a-47aa-47c2-8486-3c5fce98ddd6, the original payer, 1,000-sat amount and transactiona6e55ae0b09afa69f216d3753d3fd040af5302d5c56ccd23ab7d4ee7a28bfcab. Normal reconciliation submitted the original proof; an independent server read verifiedproof_submittedandamount_matched: true. No new transaction was created or broadcast by the restored wallet. The disposable hook only deferred source proof delivery and logged ordinary backup/restore data; its marker was absent on the fresh simulator. Both owned simulators and helpers were stopped. Temporary XML, exact diff and reproduction steps are in the iOS PR.89ca8c49-65d5-423f-b2f8-3fd877601dd1becamepaid; payment4320c5ed-b2a1-4c05-a22b-fa3cf86042bebecameconfirmedthrough the service’s independent Locks worker. Paykit invoice26c3a793-e0f3-49d9-a49b-3a40c869293breportedamount_matched: trueand request8b2aaf89-6c93-4b08-a1e1-37b78440af45reachedproof_submitted. Locks completed the original bundleJPEHRR8WAH01B7Y6VKBZ3PTW3W. Transactionad00c6f43d748ff0cff0161ec2d1eb1cb00b387aa735def1a502e3e0e972ed5eused one original input, paid 1,000 sats with a 141-sat fee, and was not replaced.commitUpsertListing; it was removed before checkout. No readiness or payment-success override remained. The studio cannot author Locks listings. The service currently represents this Locks-backed listing as shipping and asks for an address; ordinary digital-delivery and physical-goods payment routes are not covered by this result.Local-only fallback documentation (bb02e83)
Current master integration (0e00f06)
0.1.0-rc72update while retaining published LDK0.7.0-rc.71. The resolution keeps Max drain selection, original signed hardware fees/receipts and the incoming send-context cancellation checks. The lifecycle-test override matches the combined selection API.testCustomFeeWalletSwitchSurvivesFeeScreenNavigationfails with a repeated preparation and route mismatch. The same single-case failure reproduces on exact master 91fffad with its own dependencies; it is inherited, not a green result. Whether the cause is the fixture or base behavior remains unresolved. Other selected checks completed without failure.Current verification and acceptance
Interrupted unsigned preparation
Authorization rollback
Recovered hardware follow-up
Hardware cancellation before submission
Cancelled original-payment retry
Earlier master integration
0.1.0-rc69from the merged Paykit PR; LDK remains0.7.0-rc.70.Hardware dispatch-state recovery (c742e37)
Hardware authorization recovery (a302ea2)
PaykitPaymentProofServiceTestsandHwFundingSignerTests; the missing-receipt regression failed before correction. Local verification: affected simulator checks and app build completed. Physical hardware, fresh-wallet Shop VSS and funded merchant acceptance remain unrun.Journeys
wallet-restore-unfinished-payment.xml— normal fresh-wallet restore reconciles the original pending proof without another payment (verified with the disposable Debug hook).Temporary wallet restore journey and test hook
In a detached worktree at
bb02e835d5b59af26869b8dab52da6dbe8d6ebf2, save the diff below asrestore.patch, rungit apply --check restore.patchandgit apply restore.patch, then build theBitkitscheme inDebugfor an iOS simulator using the pinnedPackage.resolved. Install that same build on the source and newly created restore simulator. After the test, rungit apply -R restore.patchand verify a clean worktree. Keep recovery words private.Not pushed. Defers proof delivery for one exact request after ordinary checks; observes normal remote backup and restore payload identity.
Manual restore steps
Exact head: bb02e83. Not pushed. The attached patch exposes the existing manual scanner prompt in Debug, defers proof delivery for one exact request after normal on-chain acceptance and identity/request checks, and records ordinary wallet backup upload/restore payload identities. It does not change payment, request, wallet restore, or proof decisions.
Documents/shop05-hold-proofmarker containing the exact request UUID before paying. The Debug hook returns false promptly at the single proof-submission gate for this request, retaining normal pending retry state. Confirm the hook logs the real accepted transaction for the new original request.temporary
ios-locks-merchant-input.xml— reproduce the mixed Locks purchase using normal authorization and one payment.Disposable simulator input and diagnostic hooks; not pushed
Apply the following diff to bb02e83 in a disposable worktree with
git apply --checkfollowed bygit apply; build the normal Debug app with Paykit rc72/native rc71. It exposes the existing manual scanner input and logs only deferred-session error classification. Parser, authorization, payment and proof decisions remain unchanged. Do not commit the patch. After testing, reverse it withgit apply -Rand verify a clean worktree.Use staging-invite identities on the staging homeserver and Bitcoin regtest funding. Retain the original grant privately; never publish an invite, mnemonic, client secret or access credential. The server must retain both the driver and Locks signing keys additively. Record the actual Paykit/Locks revisions and URLs; tunnel names change between starts. The reusable setup is being completed in feat: run the shop's marketplace on staging with our own paykit server bitkit-docker#23; this result does not certify its pending full Shop profile.
If saved-session restoration reports
SharedStateBusy, preserve the original request and session. In this run, stopping the only payer app for 82 seconds allowed the SDK's 60-second lease to expire; normal relaunch then delivered the original request. This is observed recovery, not proof of the prior lock holder or permission to remove remote locks.new
local-paykit-fallback.xml— local-only rc11 pairing/payment checks after a remote staging pairing failure; setup inlocal-paykit-fallback.md. Unrun; standalone payment does not establish Shop order completion.new
onchain-original-payment-retry.xml— Includes backup deferral before signing for ordinary sends and channel funding; that new device step remains unrun. Pending → approve fee and normal payment authentication → retry the same recipient amount and input set; persist both candidates, keep uncertainty guarded and finish only the accepted/observed winner. Funded rc70/rc65 replay at 182b0a0 used actual PIN and withheld backend acknowledgement, then accepted a higher-fee retry of the original mempool payment with unchanged inputs/amount. Live Pending and winner Details refreshed without reopening. A second actual PIN replay at 182b0a0 rejected the successor through a controlled Electrum fixture and restored independent observation of the accepted original; the open Pending sheet enabled original Details without reopening (1,000 sats, actual143-sat fee). This original was unconfirmed, not mined-confirmed. Funded original-Max channel recovery at 2db78a1 preserved the exact inputs and 18,351-sat funding output through normal PIN authorization, backend acceptance with withheld acknowledgement, restart and eventual channel settlement. That run reproduced a stale open Pending result; a subsequent focused simulator regression verifies the durable refresh correction. Funded replay on cd18c1f with published LDK rc70 and Paykit rc65 accepted the original 29,671-sat funding transaction with its acknowledgement withheld. With Pending left open, independent observation restored the original order/transfer, removed Unknown and Retry, and enabled exact original Details without another submission. Final Shop merchant acceptance remains open.new
shop-onchain-proof.xml— linked issuer and funded Bridge hardware wallet: exact original payer, request, transaction, proof and wallet Details, including delayed Sent/contact/tags and reopened Pending after listener consumption, with no repayment while observation is pending. Spec parsed; native hardware journey unrun.new
onchain-accepted-result.xml— accepted fixed-amount and send-all with Manual coin selection finish local activity and expose distinct exact transaction IDs in Details.Manual Tests
Automated Checks
HwFundingSignerTests.swift— recognized backend refusal and invalid transaction errors permit leaving while retaining the original payment; prefixed unknown and connectivity failures remain guarded.OnchainSendAttemptServiceTests.swift— deterministic first admission and an actual signed fee prove competing fixed, Max and transfer sends cannot dispatch.PaykitPaymentStateBackupTests.swift— send-all funding backup round-trips retain the actual signed recipient amount and original order terms; underfunding, amounts above the retained transaction total and fixed-amount mismatch remain rejected.PaykitPaymentStateBackupTests.swiftandTransferServiceActivityTests.swift— restore requires all replacement fee rates, consistent original fees and the original funding amount; original shared fixture bytes remain unchanged.PaykitPaymentProofServiceTests.swift— the hardware restart fixture persists the full signed receipt before dispatch and verifies its fee metadata survives backup restore.PaykitPaymentStateBackupTests.swift— incomplete signed hardware receipts fail restore when fee, fee rate or total spent is missing or null; complete receipts retain all fields.PaykitPaymentStateBackupTests.swift— accepted replacement restore requires its own candidate fee rate; missing and partial maps fail, while valid replacement rates and the original transaction’s fee fallback remain supported.PaykitPaymentProofServiceTests.swift— a proof/attempt capture interrupted by original-attempt cleanup defers the upload instead of backing up an orphaned started proof; a settled empty state can be captured normally. Proof/backup simulator checks and app compilation completed. Funded merchant and fresh-wallet acceptance remain unrun.PaykitPaymentProofServiceTests.swift— overlapping recovery reconciliation writes the original accepted Shop activity and publishes its completion once; a suspended first completion cannot be repeated by another call. Proof/attempt simulator checks and app compilation completed. Funded merchant acceptance remains outstanding.OnchainSendAttemptServiceTests.swiftandTransferServiceActivityTests.swift— unresolved payments with multiple retained candidates ignore unconfirmed received events and choose the confirmed candidate, preserving the original operation, wallet, amount and inputs. Single-candidate received observations and native Accepted results retain their existing completion path. Attempt/transfer simulator checks and app compilation completed; funded replacement and merchant acceptance remain unrun.PaykitPaymentProofServiceTests.swift— after proof delivery and a failed local follow-up, restart reconciliation emits the exact original payment resolution once durable completion succeeds. Failed follow-up emits no new event; repeated reconciliation does not replay completion. The missing-event regression failed before correction. Local verification: proof-service simulator tests and app compilation; funded merchant and fresh-wallet Shop acceptance remain unrun.HwFundingSignerTests.swiftandPaykitPaymentProofServiceTests.swift: failed pre-broadcast candidate persistence permits fresh preparation on retry; software Shop completion publishes its resolution only after durable local follow-up. Accepted proof delivery remains retryable independently. Both reported defects were reproduced before correction; simulator checks and app compilation passed at a4eacf2.HwFundingSignerTests.swift— observed original hardware Shop payment unlocks the signing screen and routes to Success; other payer/request/wallet/transaction resolutions are ignored, including a resolution while dispatch is still running. No second signature or broadcast.TransferServiceActivityTests.swift— actual visible Pending reloads an accepted original channel-funding operation without a native completion callback, saves its one original transfer/activity and avoids success for a different unsent payment. The regression failed on the preceding source; corrected transfer/Pending simulator checks completed.TransferServiceActivityTests.swiftat 73d2b35 — unknown order funding opens its retained exact-operation Pending sheet, retains the order and wallet, keeps the guard and avoids transfer setup completion. The previous route failed this regression; corrected source and existing accepted-funding identity/accounting recovery passed.TransferServiceActivityTests.swiftat 182b0a0 — exact durable resolution before initialization, old/replacement context rejection and original activity without success for a new unsent amount. Simulator build and installed application binary matched the validated source.OnchainSendAttemptServiceTests.swift,PaykitPaymentProofServiceTests.swift,SendConfirmationViewTests.swiftand the native queue deadline test on iOS 27 / Xcode 27: original retry keeps its request deadline through preparation and dispatch; expiry after authentication prevents another broadcast while retaining the original payment guard. Read-only recovery validation preserves started and unstarted proof state.TransferServiceActivityTests.swift,HwFundingSignerTests.swift,PaykitPaymentProofServiceTests.swift— shared-state SDK fixture APIs and captured proof app IDs.OnchainSendAttemptServiceTests.swift— durable admission, outcome persistence, exact observation and callback failure release with zero dispatch; failed release storage remains guarded.PaykitPaymentProofServiceTests.swift,PaykitPaymentStateBackupTests.swift— proof acceptance evidence, exact payer/request/wallet cancellation with failed-save protection, request protection and a suspended Lightning save cannot overwrite the concurrent on-chain start marker.HwFundingSignerTests.swift,HwWalletManagerTests.swift— original hardware account context, durable proof release after denied first authorization with zero dispatch, retained attempted retries, and verified Shop versus Pending navigation.ChannelPurchaseFlow.swift,UtxoSelectionTests.swift— matching outcome API integration.TransferServiceActivityTests.swift— original local follow-up and actual reopened hardware Pending navigation; delayed verification restores Core Sent activity/tags before proof delivery. Actual SDK-failure retry and received-callback regressions preserve deleted/reassigned contacts without another native send. Existing accepted-order tests inject the geo decision while production retains the same geographic check.1955a179fdb6acc5159ead68225dd27029d2f2a74c4b8b13164038eb9f95462f, and downloaded/SwiftPM simulator binaries agree.Funding recovery continuation (3000b91)
Accepted funding follow-up (2db78a1)
Historical validation before the shared-state Paykit integration
Local verification at bfa9646: simulator build and 46 focused tests passed (0 failures/skips), covering Pending initialization after completed Unknown/Rejected observation, original identity across wallet changes, stale/replacement rejection, local follow-up failure details, concurrent transfer creation and hardware contact replay. Four regression methods failed against the prior relevant behavior and passed with the fixes; initial fixture compilation errors are excluded. All 1,435 tracked source hashes match the tested snapshot and resolved rc68 binary. Changed Swift formatting and translation validation passed (0 errors; translation warnings remain). No new funded, hardware or native-fault journey is claimed.
Earlier verification: 64dea0b passed its simulator build and 122 affected tests (0 failures/skips): hardware signing/authorization, proof protection, on-chain attempt reconciliation and actual Core transfer/activity callbacks. Three regression methods failed with the old cancellation/contact-restoration behavior and passed with the fixes; the first-denial red used extracted production callback wiring retaining the old cancellation behavior, rather than an unmodified-head binary. All 1,397 tracked source hashes and the resolved rc68 simulator binary match the tested snapshot. The local hardware follow-up acknowledgement is not acceptance evidence and is omitted from backups so another device restores its own activity. Standard Debug geographic checks remain enabled; local-only
-packageFingerprintPolicy warnhandles the existing VSS fingerprint conflict.Previous master integration verification at a733791: master 62563af was integrated without manual edits after committing the frozen d623a1a integration as 3cf7b91. The exact current source passed its simulator build and 315 affected tests (0 failures/skips): hardware signing, proof protection, attempt reconciliation, transfer/activity callbacks, send confirmation, request authorization and private payment integration. All 1,435 tracked source hashes and the resolved rc68 native binary match the tested snapshot. The earlier frozen merge at 3cf7b91 separately passed 301 affected tests. No new funded/hardware/native-fault journey or Preview run is claimed.
Prior merge validation at c339e9e: master 1eabf70 was integrated and the simulator build plus 263 affected tests passed.
Earlier feedback validation at 2039d39: 99 affected tests passed; three semantic regressions failed on its preceding head and passed with those fixes. This is historical evidence, not a fresh red run for the merge.
Earlier CI at 2039d39 passed unit/integration/build checks; the E2E run was cancelled and its aggregate status failed. The cancellation cause remains unknown. No rerun of that unchanged cancelled run was requested; heavy checks were deferred while the PR was draft; earlier CI does not validate this current head.
Prior rc68 validation: 101 affected tests and funded fixed/Manual Max native journeys passed with independently verified exact backend transactions. Those device runs were not repeated for this batch. The updated hardware journey, physical hardware and controlled native fault scenarios remain unrun; no new hardware Preview is claimed. Missing original metadata after a failed write or crash cannot restore lost tags.
Current integration verification at 402d006: simulator build and 267 affected tests passed, including original-wallet hardware recovery, broadcast attempt guards, proof recovery, transfer accounting and send confirmation. Initial merge/API compile failures were corrected and are not regression evidence. No funded Shop, physical hardware or controlled native-fault journey was run on this integration. That historical integration retained refusal/unknown lockout; the current prepared-send recovery supersedes it.
Startup correction at 994ab3b: accepted ordinary-send recovery routes Pending with the exact fetched attempt ID, original wallet and transaction ID. Completion before Pending initialization reloads the original durable result; an older or replacement attempt cannot satisfy it. The old startup route failed the ordering regression (20 passed/1 failed); corrected source passed simulator build and 21 affected tests (0 failures/skips). No second payment was dispatched. The earlier 267-test integration evidence remains applicable to unchanged areas; that historical batch did not validate prepared-send recovery or final release-pair/native fault scenarios.
Current recovery verification at 498fdeb: merged master f4710eb, resolved published LDK
0.7.0-rc.70at 4ef96f9, built the simulator app and passed 69 focused tests (0 failures/skips). Coverage includes receipt persistence before dispatch, fixed original amount/exact inputs, serialized retries and accepted-original races, fresh payer/request/order authorization, proof successor association and received-event reconciliation. All 1,480 tracked source hashes were checked; the final English-only amount-unit wording change compiled separately. The actual published native entrypoints were exercised with a stopped node and returnedNotRunning; this proves binding/runtime compatibility, not funded broadcasting.Published archive SHA-256:
bd5ddd188adffbd86cc1f9f384b823450dfb865b423b2e7a5c9c7adddde2b8ce; the resolved simulator binary and generated Swift hashes match the validated artifact. Paykit remains0.1.0-rc56and Core remains0.5.18. Owned test resources were stopped and cleaned up.Funded retry/PIN and controlled acknowledgement loss were subsequently exercised on 7632642 as detailed below. Initial native refusal, live Shop/server pairing and physical hardware remain unrun. Draft status was preserved at that validation point pending these release checks and current-head review; previous CI and historical funded media do not certify this head.
Backup and transfer Pending verification at 7632642: simulator build and 77 focused tests passed (0 failures/skips). The published reader failed the fractional-millisecond restore regression; earlier published-source regressions also reproduced the missing backup receipt and transfer resolution. Shared wallet binding vectors, guard-before-proof restore, exact candidate proof association, original transfer resolution and cross-platform timestamp preservation passed. The committed tree matches the tested snapshot. Funded iOS retry, native fault, physical hardware and final chosen Paykit/server Shop checkout remain required; hosted draft checks are not substituted for these tests.
Published 2bccf53: send-all retries are restricted to the original fee before transaction preparation or authentication. Pending explains the constraint and disables fee editing for Max; fixed-amount retries retain fee selection. Recipient amount and exact inputs remain unchanged.
Validation: the previous service failed the Max-fee regression; 33 focused tests passed and the current Pending UI compiled against published rc69.
Funded validation on preceding 7632642: fixed 1,000 sats and Max 18,745 sats exercised real PIN entry and controlled loss of backend acknowledgement. Both original transactions reconciled to their exact Details with original inputs and amounts. A fixed successor met an already-confirmed original; Max higher-fee construction failed safely before dispatch. Successful unconfirmed replacement, initial native refusal, physical hardware and final selected-version Shop checkout remain unproven. These device runs precede the new Max UI constraint. Draft status remains unchanged.
Published 95f7566: successor Details and transfer accounting use the exact winning transaction fee and its own authorized fee rate. Missing successor fee evidence keeps local completion pending. Candidate rates are saved before dispatch and validated in the shared backup format; the original operation and flat candidate IDs remain intact. Max confirmation now has no editable fee field.
Validation: 83 affected tests passed and the simulator application built against published rc69. Prior behavior failed actual fee/rate assertions; exact fee arithmetic, winning-candidate metadata, Core activity updates and shared backup golden passed. Current Max UI was driven: zero fee text fields, original receipt retained and zero native dispatches. Funded winning-Details rerun, true original-in-mempool replacement, physical hardware and final selected-version merchant checkout remain outstanding.
Earlier 2bccf53 device run exercised actual native Rejected through an injected Electrum non-final response, without forwarding the original. One explicit PIN retry was accepted by staging while unconfirmed, with the same input and unchanged 1,000 sats. This proves same-input retry acceptance after controlled refusal, not replacement of an original already in the mempool. The run reproduced the fee Details defect corrected above.
Published b229508: Post-broadcast attempt-store read failures now retain the captured native result and exact Pending context for both first submission and explicit recovery. The durable receipt stays guarded across restart.
Validation: 38 attempt-service tests passed (0 failures/skips). The new post-dispatch read-failure regression failed before the fix and passed for initial and recovery dispatch; the owned simulator was shut down. Funded winning-Details and final selected-version Shop checkout remain outstanding.
Private hardware cleanup (f2f14d0)
Prior private payment boundary (7deedcd)
Unsent private preparation cleanup (cbe12e9)
Retained private payment version (309e5c9)
PaykitPaymentRequestServiceTests.swift— failed signed-proof deletion keeps version 7 consumed, restart repairs the release/deletion gap before retry, successful cancellation restores version 6, and newer consumption remains protected.Accepted payment completion (40e97a2)
OnchainSendAttemptServiceTests.swift— accepted ordinary and Shop payments retain their exact original operation, wallet and transaction on Pending until proof/local follow-up completes; completed payments still reach Sent.Recurring payment backup identity
PaykitPaymentStateBackupTests.swift: exact millisecond and nanosecond billing timestamps survive active-attempt backup and match the retained proof.Ordinary activity completion
OnchainSendAttemptServiceTests.swift: a competing call cannot repeat a suspended activity write, and a failed owner permits a later retry.Prepared funding total correction
Recurring payment recovery
Initial queued expiry
Recovery feedback updates
Hardware refusal navigation after restart
HwFundingSignerTestsandPaykitPaymentProofServiceTestspassed, covering restore, fee refresh, pre-dispatch denial, queued expiry, original signed bytes and backup exclusion.Hardware Shop deadline at dispatch (cbe332e)
Reported Electrum refusal formats (ec99bd0)
Retained hardware request reopen (1067305)
Maximum sends and retained hardware fees (b24a470)