Repository navigation
feat: add usdt wallet and paykit payments - #1432
ben-kaufman wants to merge 63 commits into
Conversation
190743f to
378b76e
Compare
707d8aa to
2667c7f
Compare
|
talosmachina
left a comment
There was a problem hiding this comment.
No findings. Adds a USDT (Arbitrum) wallet with Paykit USDT payments, ethereum: request scanning, a USDT companion claim in Pubky auth approval, and USDT state in the wallet backup, on bitkit-core 0.8.0-rc2. Reviewed dc540ee, oversized tier (158 files, about 11.7k added lines): I read only the money, signing, auth-claim, backup and scan-routing paths. The UI screens, strings and the rest were not reviewed.
What I checked, and the candidates I ruled out
Read: UsdtService.kt, UsdtRepo.kt, PaykitUsdtPaymentRepo.kt, the USDT paths of AppViewModel.kt (sendPaykitUsdt, authorizeUsdtExecution, handleUsdtPaymentInput, launchScan, openPaykitEndpoints), PubkyAuthClaimCodec.kt, PaykitSdkService.approveAuthWithCompanionClaim, BackupRepo.getWalletBackupDataBytes, and in bitkit-core v0.8.0-rc2: usdt/wallet.rs (send, recover_pending), usdt/backup.rs, usdt/payment_request.rs, usdt/proof.rs.
Ruled out
- Signing a changed request:
authorizeUsdtExecutionruns inside core's authorize callback and re-checks the active context, the reviewed amount, the payment terms and their validity window before anything is signed. - Broadcast without a backup: core persists the backup snapshot before broadcasting, on both the send and the recovery path, so a signed operation never leaves the device unbacked.
- Foreign chain or token in a scanned request:
usdt_parse_payment_requestrejects anything but chain 42161 with the USDT contract, and rejects unknown or repeated query keys. The app routes it only with the flag on, no hardware wallet active, and no USDT send already open. - Companion claim payload swap:
approveAuthWithCompanionClaimre-parses the claim from the URL, requires the same claim type, and validates the payload before the Paykit key is added, and adds it only when the claim asks for Paykit access. - PIN bypass: the USDT send and deposit flows check PIN-for-payments the same way the bitcoin send does.
- Backup during an unfinished restore:
getWalletBackupDataBytesrefuses while a Paykit restore is pending.
Not done: no local build. On this track an unrequested review does not run Gradle on a PR branch, so this is reasoned from the code. CI was still pending at review time.
Merge confidence: 3/5, the oversized-tier ceiling. The reviewed paths are clean, but most of the diff was not read.
|
@ben-kaufman build error |
jvsena42
left a comment
There was a problem hiding this comment.
Requesting changes at 3b6dbdf: 2 MEDIUM and 1 LOW inline, each with a failing test. The device gate has not run; it waits for these.
build is also red at this head. The failure is on the merge with master: tests added there by #1457 (AppViewModelSendFlowTest.kt, around lines 7739–7862 of the merged file) still pass amountSats, which this PR replaces. Merging master and updating those call sites clears it.
The PR is large, so the review was split into three areas (USDT wallet, Paykit payments and backup, authorization and the rest). What held up on reading, with core checked at v0.8.0-rc2:
- Amounts: the keypad caps at 6 decimals, parsing is integer-only with checked arithmetic,
PaykitAmount.quotedTousesBigDecimaland rounds up; noDoubleon a payment path. - Payment links: core pins chain 42161 and the USDT token, rejects unknown or repeated keys and checksums the recipient; review and send use the same quote id;
ethereum:links are refused while a hardware send sheet is open. - Send:
confirm/sendguard on busy, submitted and quote id; coresendis idempotent per quote id and refuses a second pending transfer; a backup failure after signing leaves one pending operation that is rebroadcast. - Paykit: the payable amount is recomputed and must equal the reviewed terms at pay time on the Bitcoin rails (
hasMismatchedIncomingPaymentRequest) and inprepare/authorizeUsdtExecutionfor USDT; deadlines use the earlier of the request deadline and the quote expiry, including on-chain recovery after relaunch; a quoted initial subscription payment cannot skip amount review; onepaymentIdcannot satisfy two requests. - Persisted formats from
v2.5.0:PendingPaykitPaymentProofonly gains a defaulted field and an additive enum value. - Address sharing: the USDT row renders whenever the claim asks for it, unknown or duplicate claim items are rejected, the address is re-read and compared at approval, and
authorizere-parses and compares the request with what was displayed. - Wipe order and the Trezor stored-key mapping.
- The copies of #1460 and #1461 carried here match those PRs' code.
Question, not a finding: from a hardware-wallet Send sheet, Contacts → a contact with a USDT endpoint reaches openPaykitEndpoints (AppViewModel.kt:3792) before the hardware guard (:3930), and prefersUsdt picks USDT when the seed wallet has no on-chain balance, which is the normal state for a hardware-only user. The payment would then come from the seed wallet's USDT. The review is the USDT one, so it is not hidden, but ethereum: links are refused in the same sheet (:3889). Is that path intended?
perf: relevant (a 30 s refresh loop with history from the Home card, 10 s while pending; the repo mutex is held across the refresh; a wallet backup upload before every USDT send). Not measured yet.
usdt-bridge-support.xml and the paid steps of paykit-quoted-usdt.xml need mainnet USDT and provider history, which I do not have.
The iOS side is reviewed at synonymdev/bitkit-ios#884.
3b6dbdf to
6a2f645
Compare
|
@jvsena42 Fixed the three findings and synchronized with current On the Contacts question: that opens the shared Paykit method selector. It can select USDT when the seed wallet has no Bitcoin balance, and the USDT review makes that source explicit before approval. It uses the seed wallet, not hardware signing. The raw The fresh-wallet receive check also passed: Back preserves both an amountless QR and an earlier committed amount. Funded/provider-dependent acceptance cases remain listed separately in the PR. |
jvsena42
left a comment
There was a problem hiding this comment.
Code scan at b6bcdbb: the three findings from my last review are fixed and the delta has no new HIGH or MEDIUM. Device result follows.
- Restart double pay:
hasBindingnow falls back to the stored attempts (PaykitUsdtPaymentRepo.kt:315-317) and fails closed when the store is unreadable; covered bystarted payment remains bound after restart before reconciliation. The stored fallback is not filtered by wallet or identity, which can only over-bind. - iOS backup decode:
requestId,bindingandbillingPeriodhave= nulldefaults (:75-77);wallet backup restores contact attempts with omitted optional fieldsdecodes a wholeWalletBackupV1. Backups still write explicit nulls. - Cancelled send: the reset runs in
NonCancellableand only after the journal read succeeds with no transfer (:226-237); the cancellation is still rethrown.
Read in the delta (6a2f6451c and the rebase onto 97923d8):
- A fixed Lightning invoice amount is locked on Spending from the amount screen and the fee selector, and submit rejects a mismatch (
AppViewModel.kt:5667). Switching Spending → Savings keeps the invoice amount rather than an earlier typed one; what is shown is what is paid. - USDT request payments pass the earlier of the request deadline and the quote expiry to core
0.8.0-rc3, which checks it before signing and again before broadcast. - USDT links are ignored while a contact payment is open; the receive amount is a draft until Continue.
- The dust check on the Lightning-to-on-chain fallback is
master's code kept through the rebase.
The hardware Send sheet question is answered in the author's comment (pays from the seed wallet, the review names the source); nothing further from me.
CI at this head: build-local, lint and detekt pass; build and the e2e shards were still running when I posted this.
perf: relevant (refresh loop from the Home card, a keychain read plus state decode in hasBinding for each unbound request check). Not measured yet.
Device gate (partial): b6bcdbb — not run yet; pending usdt-wallet-navigation.xml, usdt-receive-amount-and-units.xml and the direct-link steps of usdt-payment-links-and-scans.xml on an emulator without USDT funds, plus the idle refresh measurement.
Regtest APKDownload bitkit-dev-debug universal APK (expires in 30 days). |
|
Applied the activity-search fix also raised in the iOS review, in 3f44eec: searching for |
piotr-iohk
left a comment
There was a problem hiding this comment.
QA review
Scope: Follow-up review of 3f44eec since 77545b8, using baseline bc-be8931d7-1a05-445c-b0ee-01ed77242cb9. This pass inspected USDT activity search and the updated opt-in dismissal wording. Inherited baseline coverage remains for USDT opt-in and link gating, send amount and MAX, Paykit USDT execution, request pricing, Pubky receiving-address consent, deposit refund and activity status, hardware-wallet rejection of non-Bitcoin targets, and iOS enable-prompt and activity-title parity at 810858ae.
No new actionable code findings.
Activity search keeps a USDT row when the query is empty, equals USDT in any case, or occurs in the recipient or transaction hash. Queries such as u, us, and t no longer match every row. bitkit-ios#884 at b522b8f1 uses the same predicate. The opt-in journey matches the Android alert: Cancel and Enable, with outside taps and Back ignored. The documented core version remains 0.8.0-rc6.
UsdtActivityFilterTest.kt was inspected and not executed. bitkit-e2e-tests#280 at fd0b8d1e does not assert this search. The send shard on this revision failed during regtest setup before Appium started.
Recommended before device testing: MAX still depends on the gateway deployment named in the pull request, synonymdev/bitkit-usdt-service@1456181. This review did not inspect that service revision.
Device testing: not performed in this review.
jvsena42
left a comment
There was a problem hiding this comment.
Round 6, the delta 1ccb284..3f44eec: c51f3bd (cross-asset review and recovery), b1fbfb8 (USD formatting), f2f766a and d3acbb6 (bridge design), 77545b8 (docs), 3f44eec (exact USDT activity search; read, no findings). The device run used a build of 77545b8; 3f44eec only changes activity search.
The code review of the delta found nothing above LOW and nothing in c51f3bd that changes funds handling. Checked and clean:
- The fixed-price fallback for other currencies is shown and paid as the same sats, and the rate is never applied twice.
canReleaseBindingreleases a request only for FAILED/REPLACED attempts with no proof.- Pending navigation pops the whole graph, so Back can't land on Confirm again.
- USDT stays out of the hardware wallet path.
- The
UsdtViewModelfail-closed guards hold.
b1fbfb8 fixes my USD formatting LOW (thread resolved, verified on device).
Requesting changes for the MEDIUM inline: a Paykit USDT contact payment from this emulator failed twice with an expired quote after a 1.5–3 minute spinner, while the same payment from iOS took 21 s. It predates the latest push but came with this PR's Paykit USDT payments; the round-4 gate drove only plain USDT sends, so my earlier approvals missed it.
Also inline: one functional LOW and three design LOWs. The design LOWs come from re-checking the linked frames against the round-1 baselines; only 49861-363168 changed in a way that affects code. They are non-blocking.
Not raised:
- "5." in a USDT amount: I suspected it skipped normalisation before the quote, but on device it quotes as $5.00, so dropped.
- The Home headline shows "—" when the paykit rate is older than 10 minutes. Both platforms do this on purpose; there is no Figma state for it, worth a design note.
Device gate: 77545b8 — cross-tested against the iOS PR build (gater4 ↔ Gate58, usdt-fixture):
- Incoming USD request presents with
$1.00. - The Received USDT celebration is deferred while Cross-Network Deposits is open and shows after Back.
- "5." quotes correctly.
- Paykit USDT contact payment failed twice (MEDIUM).
Not run:
⚠️ payment-requests/fixed-price-bitcoin.xmlandsubscriptions/create-and-propose.xml— not run yet, still pending.⚠️ usdt-bridge-details.xmlrefund flow — not run yet, still pending.⚠️ A FAILED USDT attempt re-offering Bitcoin — not run; there is no way to fail a transfer on the fixture.
perf: Paykit USDT pay, swipe to failure 112–187 s; iOS swipe to submit 21 s.
Skipped — covered by CI at 77545b8: unit tests, detekt.
jvsena42
left a comment
There was a problem hiding this comment.
Round 7, the delta 3f44eec..7df6209: 4f3ab97 (unblock USDT contact payments), f5dc875 (docs), 00a8e5d (localized refund amounts), 7df6209 (tests).
Approving: nothing above LOW. One new LOW inline, non-blocking; fix it here or in a follow-up issue.
Code review, Fable, of the reconcile rewrite. Checked and clean:
- Snapshots are taken under the mutex, network work runs outside it, and merges re-read under the lock, keyed by binding state.
- No lost update, resurrected binding, dropped proof or double pay against concurrent prepare/send/setPaymentStarted/restoreBackup.
requireValidForSubmissionchecks expiry at three points.isSubmittingPaymentRequestnow covers the whole contact payment, so background backups and SDK sends defer.
Device gate: 7df6209, cross-tested with the iOS PR build (Gate58 → gater4, usdt-fixture):
- Paykit USDT contact payment, two runs. Continue → Confirm 9 s (was 85 s), no quote expiry.
- Run 1: swipe → result screen 83 s;
BackupUnavailableat 22:59:32, then core submitted the persisted transfer at 23:04:46. - Run 2: swipe →
eth_sendUserOperation39 s, "USDT Sent" at 46 s. - One payment per run (details on the MEDIUM thread).
- Run 1: swipe → result screen 83 s;
- Labels: "AVAILABLE $46.00" with USDT selected.
- Deposits: the detail opens on the summary (Status, Receiving network Arbitrum One, Sender network, Provider status), and Details shows "Request refund" / "Hide details".
⚠️ The Android refund-dialog amount was not driven, because the field kept closing (the LOW). It rests onusdtOverviewAmountand the German-locale assertion inUsdtBridgeDetailsTest.
perf: Continue → Confirm 9 s (was 85 s); swipe → submit 39 s (run 2), 6.5 min (run 1, BackupUnavailable recovery). Opening a contact payment (Contact › Pay → Amount) took 106 s here against 24 s on iOS; that predates this delta and is part of the Paykit open path.
Skipped — covered by CI: unit tests, detekt.
64f4d4c to
dcbf816
Compare
piotr-iohk
left a comment
There was a problem hiding this comment.
QA review
Scope: Follow-up review of dcbf816 since 3f44eec, using the previous QA review. This pass inspected quote reuse, the five-second submission buffer, contact-payment submission locking, deposit-refund refresh and localized amounts, activity status icon colors, and the subscription acceptance journey wording. Inherited baseline coverage remains for activity search, opt-in dismissal, opt-in and link gating, MAX and bridge-cost behavior, request pricing, Pubky receiving-address consent, hardware-wallet rejection of non-Bitcoin targets, and iOS enable-prompt and activity-title parity at 810858ae.
No new actionable code findings.
Reused quotes stay bound to the sender, amount, recipient, and network, and only while more than five seconds of life remain. A quote inside that buffer is rejected before core submission, including after authorization. Contact USDT sends hold the payment-submission lock until prepare and send finish, including when there is no incoming request. Background deposit refresh does not mark the screen busy, so an in-progress refund address is kept while the provider status can still update.
Source inspection only. UsdtViewModelTest, UsdtRepoTest, UsdtDepositViewModelTest, PaykitUsdtPaymentRepoTest, and AppViewModelSendFlowTest were read and not executed. Device testing was not performed. Earlier unresolved design notes on the history-sync line, the cross-network deposits entry, subscription rows, and the Earn intro remain on their existing threads.
|
Thanks Ben. The Shop is moving to one "Pay with Paykit" option, so a single request will offer both BTC and USDT, and I checked that this PR already handles it: it parses both endpoints, lets the buyer switch rails with the fixed rate, and sends the USDT proof back to the server that issued the request. Nothing here blocks. One small UX note, not blocking: on a USD-priced request, USDT is preselected whenever the buyer has USDT enabled, even with a zero USDT balance (Android |
|
@BitcoinErrorLog thanks for checking it against the single "Pay with Paykit" option. Acknowledged: with the Shop on Paykit, USDT via Paykit from this PR is in scope for Bitkit 2.6.0 and goes into RC2. |
|
@BitcoinErrorLog fixed in 423b74a. For a dollar request offering Bitcoin and USDT, USDT now starts selected only when its balance covers the exact payable USDT amount; otherwise Bitcoin starts selected. This uses the request’s quoted rail rate and atomic rounding. USDT-only requests, existing bindings, manual switching and final fee checks are preserved. iOS has the matching fix. The new boundary test and all 4,176 unit tests pass. Compile passes and the full Detekt report has no additions against the preceding head. The shared payment-request journey is updated. No funded payments were repeated. @piotr-iohk ready for another look. |
ovitrif
left a comment
There was a problem hiding this comment.
Code review from our side, no device runs: one MEDIUM (display) and some LOWs. No funds-safety issue found: double-pay across rails and restarts, quote reuse, locking order, and backup shape against the iOS twin all hold. Already-raised threads are not repeated.
Other LOWs:
- USDT preference doesn't gate gateway traffic:
UsdtHomeRefreshEffect(HomeScreen.kt:~616) andbackupSnapshot()run onEnv.isUsdtEnabledalone, so every wallet derives its USDT address, createsusdt-<addr>.sqliteand polls the gateway every 30 s, including users who declined USDT (address and IP visible to the gateway). Because every wallet then has that file, clearing the RPC/bundler URLs (the documented switch-off) makesUsdtService.backupSnapshot()throwNotConfiguredand every WALLET backup fail, not only USDT users'. - "No Thanks" on the USDT intro can't complete for a wallet that already holds USDT with the preference undecided:
setUsdtEnabled(false)hitscheckCanDisableand throws, so the sheet stays (UsdtTimedSheet.kt,ContentView.kt:~661). - Receipt and refund amounts are rounded to cents (
usdtOverviewAmount, HALF_UP at 2 dp): a $5.00 request paid with 4.996 USDT reads "Underpaid · 5.00 USDT", and a 1.234567 USDT refund confirms as 1.23. usdCentsinUsdtDepositReceiveScreen.kt:~286joins locale grouping with a hard-coded "." (de_DE shows "$10.000.00"); and the Receive "Contacts" button resets amount and note but keeps the previous draft's accepted endpoints (ReceiveSheet.kt:~193).
| } | ||
|
|
||
| private fun usdtHeadlineSats(balance: ULong?, visible: Boolean, rate: PaykitExchangeRate?): Long? = | ||
| if (visible && balance != 0uL) balance?.let { usdtDisplaySats(usdtFormatAmount(it), rate) } else 0L |
There was a problem hiding this comment.
MEDIUM: the Home total balance shows "—" for a USDT-enabled user whenever the USDT balance is unknown
usdtHeadlineSats returns null when balance is null (only exact 0 maps to 0), and line 686 sets isUnavailable = usdtSats == null, so both headline rows become "—". UsdtRepo.state.balance stays null until wallet.balance() succeeds, so a cold start offline, or a slow or down gateway, blanks the Bitcoin total even with 0 USDT held and the Bitcoin balances known. A USD rate older than 10 minutes does the same for users holding USDT. Suggest treating unknown USDT as 0 in the headline, or showing the Bitcoin total with a small "USDT unavailable" marker.
There was a problem hiding this comment.
I would keep the unavailable combined total here: an unknown USDT balance is not a zero balance, and showing only Bitcoin as the total can understate the wallet. Savings and Spending still show their known balances in the individual cards. The same rule applies on iOS. A labelled partial total would be a reasonable design alternative, but I have kept the current truthful unavailable state and left this discussion open.
|
Addressed the new review in 12dfd8a and checked the matching iOS paths:
The unavailable combined Home total is intentional; I explained that on its thread. Validation: compile and all 4,182 unit tests pass, plus a final 544-test affected rerun. The full Detekt report has the same 1,399 existing diagnostics and no additions. Independent recovery/quality review is clean. Journey specs match iOS; no funded payment or native journey run was repeated for this batch. |
piotr-iohk
left a comment
There was a problem hiding this comment.
QA review
Scope: Follow-up review of 12dfd8a since 423b74a, baseline bc-836efa4d-0d5c-407e-a13e-0426a838e9e2. The comparison base and merge base are still bcc3185. This pass covered the recovery and payment-detail delta; the quoted-request, quote-reuse, deposit, subscription, search, opt-in, MAX, request, Pubky, and hardware coverage from that baseline is unchanged and inherited.
No new actionable code findings.
No new actionable code findings in the follow-up. A missing local transfer now clears paymentStarted and can release the Paykit binding, including after a failed send whose history read is empty. That matches the updated recovery tests and the iOS change at 767789a: a pending or confirmed local transfer still keeps the binding, and a failed history read does not release it. Unused wallets skip gateway refresh and do not open USDT storage for backup. Bridge warnings add the deducted bridge amount to the network fee. A recorded send that misses its submission deadline stays on that quote, shows the expired state, and does not send again.
The open Home-total comment on 423b74a still matches this head: a visible USDT balance that is unknown, or a positive balance without a fresh rate, blanks the combined headline. Savings and Spending stay available, and the matching iOS test treats that headline as intentional. This delta does not change it.
Validation: source and unit tests for this delta were read, not executed. Device testing: not performed in this review.
Suggested additional test cases
- Android, funded wallet whose USDT choice is still undecided: tap No Thanks. The intro does not return, the USDT tile stays, and Settings cannot turn USDT off while a balance or pending transfer remains.
- Android, Paykit contact payment: let the submission deadline expire after the wallet has stored the transfer. The sheet stays on that quote, shows the expired message, and a second confirm does not send again.
Closes #1466
This PR adds USDT0 on Arbitrum One to Bitkit, with fees paid in USDT.
Uses Core
0.8.0-rc6and Paykitrc74. Other platform: synonymdev/bitkit-ios#884. MAX requires deployment of the matching gateway update in synonymdev/bitkit-usdt-service@1456181 (MAX gas estimation, bridge status handling and actionable amount errors).Description
#009393) throughout.bitkit.to/v1/usdtby default, with local build overrides. Recognized USDT fee-payment failures explain that a smaller payment may be needed.Out of Scope
Design
Bitkit - Handoff v62. The current USDT designs are in Refactor v63.
USDT introduction, Settings and send network picker.
Home and USDT wallet/activity.
Receive QR, networks, amount, fees and received.
Send amount, review, loading and success, balance menu.
Sent summary, sent technical details, received summary and received technical details.
Authorization, generic authorization, payment request and request details.
Request creation, subscription creation, Earn introduction and authorization completion. Requests group available Bitcoin rails and USDT in outlined chips; new subscriptions select one asset family.
Subscription overview: one monthly total aligned with Active and Created.
Receiving pending, delivering summary, delivering technical details, receiving/refund details, cross-network deposits and refund confirmation.
Incoming Activity shows the verified Arbitrum receipt and sender. Unknown original sender networks are omitted from incoming Activity; bridge-network details remain in Deposit Activity because batched deposits are not correlated to individual receipts. The new bridge activity, provider deposit history and refund layouts reuse the existing amount headers, detail rows, activity cards and buttons. Deposit history remains reachable before an Arbitrum receipt exists. Delayed, failed, refunding, refunded and support-needed states adapt these layouts with truthful provider status and conditional actions. Cross-network payment review and history loading/error variants still use the existing components and disclosures. Exact payment values, explicit networks and truthful provider states take precedence over inconsistent sample data.
Earn retains the existing-account privacy disclosure raised in Figma thread #999: sharing a USDT address exposes that account's balance and history. This text, the USDT-only introduction and the loading/unavailable variants do not yet have dedicated frames. Decorative artwork adapts to keep the disclosure and actions readable; these states are not claimed as exact Figma matches.
QA Notes
Earn uses the same three-line English headline and unavailable-address message as iOS.
Includes master
bcc3185f6, including LDK rc74 and Paykit rc74. Core is pinned to0.8.0-rc6; production release acceptance must include the matching Core release. The integration preserves proposal stream IDs, durable proof delivery, prepared Bitcoin fees and the existing USDT conversion/recovery rules.Release acceptance remains open for the remaining live bridge routes, recovery faults, cross-platform VSS restore and Locks payment/unlock. Shop testing is deferred. MAX needs the gateway deployment noted above. Configuration and funded checks are in
docs/usdt.mdanddocs/usdt-qa.md. Local bridge fixtures simulate delivery and use fixed paymaster prices; they do not establish live provider or fee coverage.Local validation: compile and all 4,182 unit tests pass with rc6; the final affected 544-test rerun also passes. The latest recovery, offline-backup and fee-warning changes received independent source review; these UI journeys have not been rerun for this fix batch. All three committed native bridge UI tests pass on the final tree, covering receiving/delivering details, explorer/history actions, summary-first deposit navigation, comma-locale refund confirmation, cancellation and terminal-state eligibility. Refund-address focus and typed text are preserved across provider-status updates. Unit tests also cover stale responses after Back and refund acknowledgment. Earlier isolated fixture checks also covered activity status icons. All six new bridge frames were checked against Figma using isolated native fixtures; no payment or refund was submitted. Full Detekt diagnostics have no additions against the preceding PR head. USD request and subscription display uses locale-aware grouping and exactly two decimals, including trailing zeros; stored payment values are unchanged. Three native UI tests exercise the production saved-state/navigation helpers for payment progress/result retention across configuration recreation, returning to Wallet after process death during or after submission, unpaid draft restoration, fresh payment-link navigation and clickable secondary-button identifiers. These ran in an isolated app without funded transactions or permission grants. Prior native fixtures cover the supplied request/subscription and Earn designs, both asset families, opt-in/decline and receiving-details completion.
The full Detekt report contains 1,399 findings and no new violations from this update. Comparison with master confirms the same existing rule sites; 11 earlier PR diagnostics have increased metrics in methods that already exceeded their limits on master. Unrelated baseline warnings are preserved. Journey XML validates. Funded payment and remote authorization flows were not repeated for these fixes.
Companion E2E #280 handles the USDT intro before requiring the visible balance. Both mobile workflows select that companion branch for Appium tests.
Journeys
J16 new
usdt-bridge-details.xml— receiving and delivering layouts, provider deposit history, summary/details navigation, deferred incoming celebrations, a single celebration when replacing Receive, status icons, uninterrupted refund-address editing during polling, exact localized refund confirmation and cancellation, terminal states, explorer access and history access before a receipt.J15 new
usdt-payment-restoration.xml— unpaid draft restoration, payment progress across Android recreation, returning to Wallet after process death during or after submission, a fresh empty Send and explicit link reopening; uses local test funds only.J14 new
usdt-opt-in.xml— introduction choice, protected on/disabled Settings, explicit-link enablement, quiet cancellation and no gateway polling for unused opted-out wallets. Other USDT journeys now explicitly enable USDT first.J10 updated
hardware-wallet/send-onchain.xml— open the funding-source menu and explicitly select the paired wallet.J1 new
paykit-quoted-usdt.xml— quoted currencies and methods, localized two-decimal USD amounts across review, sent screens, cards and details, Back navigation, the available USDT balance label and unpaid dismissal.J2 new
share-receiving-details.xml— enabled consent, disabled combined Bitcoin/USDT authorization, USDT-only unavailability and address-loading failures and USDT becoming enabled while authorization is open.J3 new
lightning-fee-review.xml— fixed Lightning amounts and available fee estimates.J4 updated
create-and-propose.xml— subscription methods follow the selected denomination.J5 new
usdt-bridge-support.xml— refund/support details and copied provider references.J6 new
usdt-payment-links-and-scans.xml— enabled/disabled clipboard detection, explicit links/scans, exact amounts, dismissal across navigation and preserving an active send.J7 new
usdt-receive-amount-and-units.xml— dollar entry, BTC equivalent, explicit networks and discarding unconfirmed edits.J8 new
usdt-wallet-navigation.xml— Home tile placement with odd/even hardware-wallet counts, wallet activity and fresh receive sessions.J9 new
usdt-send-review-and-details.xml— quote reuse, changed-payment and expiry checks, deducted bridge-cost warnings, MAX, network changes, payment-source menu, long balance/network labels, bridge details visibility and technical transaction details.J12 new
usdt-bridge-amount-errors.xml— rejected send quotes and receive estimates show the bridge-cost error, block review/QR and recover with a valid quote.J13 updated
subscriptions/review-and-subscribe.xml— USDT-only subscriptions preserve their locked payment and use teal; the overview combines mixed currencies into one wallet-valued monthly total.Manual Tests
Automated Checks
updated
UsdtViewModelTest.kt— quote reuse within a send flow, changed inputs/wallets, expiry, refresh errors, deducted bridge-cost warnings, retained recovery after submission-deadline expiry and invalidation after dismissal or submission.updated
PaykitUsdtPaymentRepoTest.kt,PaykitUsdtPaymentRepoRecoveryTest.kt,UsdtRepoTest.ktandAppViewModelSendFlowTest.kt— payment preparation proceeds during background receipt verification, concurrent attempts and restored state remain protected, expired quotes fail before submission, and contact sends pause background work until completion or failure.added
UsdtBridgeDetailsTest.kt— native receiving/delivery screens, real networks, explorer/history access, refund form focus and text across status updates, localized confirmation, cancellation and terminal-state eligibility.updated
UsdtDepositViewModelTest.kt— current provider status in history, nonblocking background refresh, refund eligibility and stale-result rejection after navigation or refund acknowledgment.added
PaykitAmountViewTest.kt— locale-aware two-decimal USD display, grouping, single-cent and maximum-value boundaries.expanded amount, request, proof, endpoint, review and navigation tests — stable unit changes, fixed external denominations, proof-free retry binding release, malformed-period isolation, optional endpoint failures, hardware-only routing, retained submission state and required Paykit authorization.
added
UsdtPaymentNavigationTest.kt— actual Compose saved-state restoration and fresh same/different payment-link navigation.updated
SubscriptionsScreenTest.kt— combines monthly amounts at the wallet rate and avoids a partial total when the rate is unavailable.updated
PubkyAuthApprovalViewModelTest.kt— enabled/disabled consent, Bitcoin-only and combined payloads, required USDT unavailability, late enablement and preserving optional consent during authorization.updated
AppViewModelSendFlowTest.kt— disabled clipboard filtering, explicit enable/cancel behavior, contact/request cleanup and effective Paykit payment methods. Also checks quoted USDT preselection at zero, insufficient, exactly rounded and surplus balances.updated
UsdtPreferencesRepoTest.kt,UsdtRepoTest.ktandSettingsViewModelTest.kt— protected funds, pending deposits, settlement, reset, intro dismissal and avoiding gateway refresh for unused opted-out wallets.updated Paykit repository tests — receiving methods and published endpoints follow effective USDT availability.
ran native emulator presentation checks with isolated rendering fixtures — authorization, protected Settings, enable modal, network picker, amount layout and confirmation details. Fixtures do not prove live settlement or service authorization.
Matching send, Paykit and hardware-wallet Appium updates: synonymdev/bitkit-e2e-tests#280. Both mobile workflows select that companion branch automatically; the full Appium payment flows remain for CI.