Repository navigation
fix: separate usdt recovery backups from paykit - #1681
Draft
ben-kaufman wants to merge 5 commits into
Draft
ben-kaufman wants to merge 5 commits into
ben-kaufman wants to merge 5 commits into
Conversation
1 task
Collaborator
|
Triage: 3.1.0. It fixes shipped USDT recovery backups, and 3.1.0 is due 28 Oct. |
4 tasks done
This was referenced Oct 24, 2026
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Twin: synonymdev/bitkit-ios#1118
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
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.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, Paykite79b7bbband Pubky65a331e0bindings; these overrides are not the committed release pins.For the deployed-contract UI subset, use the Core fork provider at
c393f8e0with Anvil v1.8.6, chain42161, Prague hardfork and fork block517943110. Build withUSDT_RPC_URL=http://127.0.0.1:18545 USDT_BUNDLER_URL=http://127.0.0.1:18546; install on a new dedicated emulator and reverse only its ports18545/18546. Create a disposable wallet, use the provider'stest_fundWalleton its receive address, then send one synthetic USDT to0x1111111111111111111111111111111111111111. Cancel once, review again, confirm and inspect the receipt. Remove only that emulator's18545 reverse mapping before restarting to check saved activity offline. All funds and mutations remain in the local fork; normal VSS is used and no backup/Pubky faults are injected in this subset.Journeys
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
UsdtNativeRecoveryIntegrationTest.kt— real encrypted backup rejection/lost acknowledgements, separate-process restart, fresh-database restore and the original signed operation before and after retry.UsdtRecoveryBackupStoreTest.kt— independent uploads, immutable concurrent writers, lost acknowledgements, byte-order/hash compatibility and fail-closed restore.BackupRepoTest.kt,PaykitUsdtPaymentRepoRecoveryTest.kt,UsdtRepoTest.kt,WalletViewModelTest.ktandUsdtViewModelTest.kt— restart/migration gates, retained payment progress, proof revalidation and same-transfer Retry/automatic recovery.Evidence
At
cc14496ed, requiredjust compile,just testandjust lintpassed on macOS ARM64 with the committed library coordinates. Complete JUnit reports contain 4,602 passing tests, zero failures or skips. Detekt SARIF and Android Lint XML contain zero findings. The final test/lint run had unchanged source content.Validation caught and repaired a restore-gate early-return type error and a stale test expectation that trusted imported proof. The repaired tests preserve payment-started guards while requiring proof revalidation. Journey XML parses. Independent review and bounded repair rechecks found no remaining actionable issues.
At test commit
2d8f8747, requiredjust compile,just testandjust lintpassed again with committed dependency coordinates: 4,602 tests, zero failures/skips, Detekt SARIF zero findings and Android Lint XML zero findings. With the combined local SDK build, native rejection and lost-acknowledgement sequences passed prepare, resume and restore; bidirectional iOS/Android encrypted snapshot restore also passed. The instrumented native case calls the recovery store directly; BackupRepo orchestration is covered separately by unit tests.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
258bf9e5b, normal Gradle resolution uses published Core RC11. The downloaded AAR SHA256 is9d5d54f5761b7773d2cfaaf0be77eeb914348d52e31ebf81684d7e761ce68b5b, matching Gradle's resolved artifact and the installed app's native library. All four ABI build IDs match the published debug-symbol archive. Required compile/lint checks passed with zero Detekt SARIF or Android Lint XML findings. The initial unit invocation reused cached output, so an explicit fresh execution was run: 4,602 passed, zero failures/errors/skips across 253 suites. Existing Gradle plugin deprecation and JVM CDS diagnostics are recorded separately.The normal published-dependency app and instrumentation APKs were installed on the owned Android emulator and their installed bytes verified. Native rejected-backup and lost-acknowledgement cases each passed prepare, resume and restore. Fresh iOS-to-Android and Android-to-iOS encrypted restores passed with iOS
646670df; all 16 released-dependency native phases preserved the original operation and acknowledgement-before-submission ordering. All four PR commits are signed and GitHub reportsverified=true,reason=valid. Current-head hosted Detekt and Android Lint also passed; their uploaded SARIF/XML reports contain zero findings. Draft-only build/E2E jobs were skipped and are not counted as executed tests.At acknowledgement fix
dc5a47d90, a successful refresh with no backup callback no longer clears recovery feedback. The UI reads live acknowledgements from the native backup adapter, including when a prior payment was already acknowledged. Failed persistence and no-op automatic/manual retry retain the error and Retry action. Regressions fail on unchanged258bf9e5band pass with the repair. Final requiredjust compile,just testandjust lintpassed: 4,604 tests across 254 suites, zero failures/errors/skips; complete Detekt SARIF and Android Lint XML/SARIF reports contain zero findings. Source inputs were unchanged through validation and independent re-review is clean. All five commits against the live base are signed and GitHub reportsverified=true,reason=valid. This follow-up has component/adapter coverage; the earlier native protocol evidence remains tied to the commits above.Hosted lint for
dc5a47d90passed in run40123893660. The downloaded artifact checksum matches GitHub metadata; complete Detekt SARIF and Android Lint XML/SARIF contain zero findings. Build and E2E jobs remain skipped by the draft guards and are not counted as passes.A fresh normal-dependency app and instrumentation build at
dc5a47d90also passed. Both installed APK hashes were verified on the owned Android emulator, without clearing wallet data. All tenUsdtPaymentContentTestdevice cases passed, including submitted/confirmed transitions, closing the payment screen and comma-region input. This is screen-component coverage; it does not execute a full payment against the backend.At
dc5a47d90, a separate full-app run on a newly created Pixel 8 / API 36 ARM emulator used published dependencies with only USDT RPC/bundler URLs pointed to the owned loopback Anvil fork. The exact installed APK SHA256 was02606962f5493af2e32c05219df6888e42f0c10e7ea9d2789aebafb490902cc7. After synthetic funding, canceling the first fee warning submitted no operation. Repeating review and explicitly confirming sent one USDT; the UI showed Confirmed. The local deployed-contract receipt contains one successful user operation and exactly one recipient token transfer, with zero native ETH in the sender. Restarting with the chain RPC unavailable preserved the saved activity. The full build log had no compiler diagnostics; the sole structured Gradle deprecation exactly matched the prior normal-dependency build. This exercises the real app and BackupRepo success path, but does not inject VSS/Pubky faults or complete J1. Android returns to Amount after canceling the fee warning; iOS retains Review. Both preserve the amount and submit nothing before approval.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; published Core RC11 is adopted and validated in both native recovery PRs. 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
461864a4passed 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 Paykite79b7bbb. 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
gpt-6-astra(reasoning:xhigh)gpt-6-astra(reasoning:xhigh)gpt-6-astra(reasoning:xhigh)