Skip to content

fix: separate usdt recovery backups from paykit - #1118

Draft
ben-kaufman wants to merge 6 commits into
fix/usdt-backup-failure-feedbackfrom
fix/usdt-independent-recovery
Draft

ben-kaufman wants to merge 6 commits into
fix/usdt-backup-failure-feedbackfrom
fix/usdt-independent-recovery

Conversation

@ben-kaufman

@ben-kaufman ben-kaufman commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Twin: synonymdev/bitkit-android#1681
Dependency: #1115

This PR separates acknowledged USDT recovery backups from the full wallet backup, so an ordinary transfer does not export Paykit state or acquire a Pubky lock before broadcast.

Description

  • Adopts published Core 0.8.0-rc11, including the restore-order fix from fix: preserve USDT application identity across backup merges bitkit-core#179 and its regenerated bindings.
  • Writes encrypted, immutable, content-addressed USDT recovery records and acknowledges the required backup before broadcasting. Failed uploads retain the original signed transfer.
  • Keeps complete wallet backups separate, retains pending work through temporary failures and retries capture with exponential delays capped at five minutes.
  • Gates network recovery during restore, retains the selected legacy payload before journal import, validates every selected record and resumes interrupted migration. Concurrent writers cannot replace one another's acknowledged records or overwrite a complete wallet backup with partial data.
  • Advances recovery feedback only after an acknowledged backup callback, retaining Retry after a successful no-op refresh.
  • Reconciles automatic recovery and manual Retry against the retained transfer ID. Retry never creates another quote/payment, and feedback explains that closing the screen does not cancel the operation.
  • Preserves genuine Paykit authorization, request/deadline checks and started-payment guards. Imported proof evidence must be revalidated before it can satisfy a request.

Out of Scope

Native Pubky authentication recovery is tracked in pubky/pubky-homeserver#741. This PR does not release SDK artifacts, replay uncertain contact mutations or establish the historical contact-error cause.

Design

N/A — no new design supplied; Retry uses the existing send-screen controls.

Preview

Unavailable for the backup-failure screen: the controlled funded device fixture is not available. Unit tests are not presented as visual verification.

QA Notes

Setup

Use the committed app dependency pins for automated tests. The complete device journey requires an owned disposable test wallet and recipient, controlled USDT RPC/bundler, encrypted VSS endpoint and Pubky homeserver with fault injection. Standard staging does not provide this combined fixture. Do not use production funds or reset an existing wallet. Record operation IDs, signed bytes, acknowledgement and broadcast counts; see the committed USDT recovery backup document and journey prerequisites.

For the native protocol subset, follow the pinned loopback fixture recipe in synonymdev/bitkit-core#180. It uses a public test mnemonic without funds, real native signing and encrypted VSS, and separate prepare/resume/restore processes. The earlier combined run below used local Core c393f8e0, Paykit e79b7bbb and Pubky 65a331e0 bindings; these overrides are not the committed release pins.

For the deployed-contract UI subset, use the fork provider at Core c393f8e0 with Anvil v1.8.6 on loopback port18545, chain42161, Prague hardfork and Arbitrum block517943110; run its Node provider on18546 using the public fixture mnemonic. Run UsdtWalletUITests/testPaymentUsesUsdtOnLocalArbitrumFork on a newly created dedicated simulator. All funds and transactions stay in the local fork. This subset uses the normal VSS endpoint and does not inject backup or Pubky faults.

Journeys

  • J1: new usdt-independent-recovery.xml — Pubky outage with working VSS, actual backup failure before broadcast, lost acknowledgements, repeated/automatic retry, closing/restarting, interrupted restore/migration and concurrent writers; uses the same actions on both platforms.

Manual Tests

N/A — the controlled device cases are specified in J1; backend fault injection is an explicit prerequisite.

Automated Checks

  • added UsdtNativeRecoveryIntegrationTests.swift — real encrypted backup rejection/lost acknowledgements, separate-process restart, fresh-database restore and the original signed operation before and after retry.
  • added UsdtRecoveryBackupTests.swift — independent uploads, immutable concurrent writers, lost acknowledgements, byte-order/hash compatibility and fail-closed restore.
  • updated UsdtWalletUITests.swift — targets the current address and swipe controls while preserving the local-fork payment, cancellation and offline restart assertions.
  • updated BackupServiceTests.swift, PaykitPaymentStateBackupTests.swift, UsdtWalletManagerTests.swift and UsdtSendFailureTests.swift — migration checkpoints, retained work, proof revalidation and same-transfer recovery feedback.

Evidence

At aeeaa020, Xcode 26.1 / iPhone 17 / iOS 26.5 ran the CI-equivalent selection: 2,889 unit tests passed; UI results were 38 passed, four fixture-dependent skips and one failure. The failing comma-region manual-entry case waited on the Bitcoin node's startup screen, with both recovery gates absent. It reproduced on the unchanged signed parent 008b2e5e; a diagnostic 90-second wait also failed and was discarded. The underlying node-startup delay remains unknown; that historical run did not pass the full UI target. The later complete current-head run below passed with explicit fixture skips.

SwiftFormat 0.64 full-tree lint has an empty report (0/846 files, 77 excluded). Translation validation has zero errors; all 15 warnings were verified against the parent after line-number normalization. Journey XML parses. Independent review and bounded rechecks found no remaining actionable issues.

At test commit 9a763eb4, the combined local SDK build passed 2,889 unit tests and changed-file SwiftFormat lint. Native rejection and lost-acknowledgement sequences passed prepare, resume and restore; bidirectional iOS/Android encrypted snapshot restore also passed. iOS calls the actual BackupService recovery handler, checks zero unrelated backup captures and counts every attempted broadcast. The initial interrupted rejection run is not counted; a fresh complete sequence passed.

Each native failure phase verifies zero broadcast attempts before its own recovery acknowledgement, unchanged signed operation bytes on retry and one unique payment. Full-wallet sentinel bytes remain unchanged. This verifies the native protocol subset, not the complete J1 journey.

At adoption commit 646670df, the normal published Core RC11 package resolves to 22e0acc3; its downloaded archive matches the manifest SHA256 f7ef2ffb625498c651111cb0f5a177893c5abd91c89b077e11abf0ee289cd544. With no local SDK overrides, all 2,889 unit tests passed and full SwiftFormat 0.64 lint reported zero violations across 847 files. Native rejected-backup and lost-acknowledgement cases each passed prepare, resume and restore on the owned iOS simulator. The final documentation-only wording correction was independently reviewed and checked; production and package inputs were unchanged after the unit run. All four PR commits are signed and GitHub reports verified=true, reason=valid.

With Android 258bf9e5b, fresh encrypted restore passed in both directions using each app's normal published Core RC11 dependency. Across both platforms, all 16 native failure/recovery/restore phases preserved the same signed operation and acknowledgement-before-submission ordering. This remains protocol-subset evidence, not completion of J1.

At acknowledgement fix 5851185c, a successful refresh with no backup callback no longer clears recovery feedback. Progress advances only after the native backup callback is acknowledged; failed persistence retains the error and Retry action. The focused regression fails on unchanged 646670df and passes with the repair. Final CI-equivalent unit selection passed 2,890 tests, zero failures/skips; full SwiftFormat 0.64 reported zero violations across 847 files. Complete diagnostics were inspected: compiler metadata warnings and negative-fixture runtime diagnostics are inherited. Independent review found no remaining issue. All five commits against the live parent are signed and GitHub reports verified=true, reason=valid. This follow-up has component/adapter coverage; the earlier native protocol evidence remains tied to the commits above.

A later diagnostic launch at 5851185c completed LDK construction in 1.08 seconds and subsequently synchronized and uploaded recovery data. The unchanged comma-region manual-entry UI test then passed in 35 seconds on the owned simulator. This does not establish the earlier delay's cause. A subsequent complete current UI target finished with exit code zero; the finalized XCTest bundle confirms 39 passing executions (ten test methods, including 30 dynamic runs), four opt-in fixture skips and zero failures. The skips are three Trezor emulator cases and the local Arbitrum fork payment case. Compiler metadata and simulator accessibility warnings match the prior run. Hosted SwiftFormat for this head also passed with zero violations across 847 files.

At signed test repair 6ead1895, the previously skipped local Arbitrum fork payment test ran on a new dedicated iPhone 17 / iOS 26.5 simulator and passed with zero skips. The test now targets the actual address button and draggable confirmation knob, retaining the exact recipient assertion and cancel-then-confirm checks. It sent one synthetic USDT, confirmed, retained zero native ETH and displayed saved activity after an offline restart. The local Anvil receipt contains one successful user operation and exactly one recipient token transfer. This executes deployed contract code in a fork; it does not validate production paymaster pricing or the complete Paykit/fault-injection J1 journey. The final unit selection passed 2,890 tests with no failures/skips; full SwiftFormat reported zero violations across 847 files. Complete diagnostics and independent review were clean apart from unchanged AppIntents metadata warnings. All six actual-base PR commits are signed and GitHub reports verified=true, reason=valid.

Gaps

J1 has not run as a complete UI/Paykit/deployed-contract journey. The protocol fixture does not execute deployed contracts or exercise a real Pubky outage. Full wallet migration and concurrent state changes remain covered by component tests rather than this native fixture; Android's native store test does not exercise all BackupRepo orchestration.

This remains draft pending the remaining integrated validation and published Paykit/Pubky adoption. Core #179 and RC11 preparation #181 are merged, and the published Core RC11 dependency is adopted and validated here. The Android twin adopted the published Maven package in 258bf9e5b and now includes the acknowledgement repair at dc5a47d90; its native libraries match the published debug symbols. Local combination builds and native tests passed with Paykit lease-expiry safety from pubky/paykit-rs#211 and authentication recovery from pubky/pubky-homeserver#741; compatible published Paykit/Pubky artifacts are still unavailable. No app release or deployment is part of this PR.

The later device-sleep lease repair at Paykit 461864a4 passed 1,557 Rust tests, both native binding builds and full generated Android checks, but has not been adopted in this app. The combined app/native evidence above used Paykit e79b7bbb. Core RC11 does not contain the Pubky authentication or Paykit lease fixes.

The legacy complete wallet format remains supported. Older apps cannot read journal-only operations; downgrade recovery is limited to the last complete wallet backup. Records are not pruned, and immutable writes do not make multi-page listing atomic or coordinate simultaneous spending by multiple devices.

Models used

  • Planning/scoping: gpt-6-astra (reasoning: xhigh)
  • Implementation: gpt-6-astra (reasoning: xhigh)
  • Review: gpt-6-astra (reasoning: xhigh)

@ovitrif ovitrif added this to the 3.1.0 milestone Oct 24, 2026
@ovitrif

ovitrif commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator

Triage: 3.1.0. It fixes shipped USDT recovery backups, and 3.1.0 is due 28 Oct.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants