Skip to content

fix: unify usdt balance and amount presentation - #1655

Merged
ovitrif merged 5 commits into
masterfrom
fix/usdt-balance-presentation-20261021
Oct 22, 2026
Merged

ovitrif merged 5 commits into
masterfrom
fix/usdt-balance-presentation-20261021

Conversation

@ben-kaufman

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

Copy link
Copy Markdown
Contributor

Fixes #1648
Fixes #1649

Twin: synonymdev/bitkit-ios#1077

Description

USDT balances show — until a successful balance read, then $ 0.00 for a confirmed empty wallet. Balance and activity synchronization stays silent. The wallet overview no longer displays refresh-error or history-sync paragraphs; previously loaded balances and payment-action errors retain their existing behavior.

USD/USDT values and keypads now use the same dot decimal/comma grouping convention as Bitcoin fiat values, and the USDT Home tile includes $. Shared fiat formatting preserves exact token precision, sub-cent balances, rounding and hidden balances. Zero-dollar payment-request drafts start with an empty editable value so the keypad can accept a new amount.

Out of Scope

  • Provider/network recovery and payment execution: this changes presentation, not synchronization or payment safeguards.

Design

Follows Aldert’s clarification recorded in the linked issues. Relevant mapped frames are Savings, Spending, Hardware, USDT Balances › Wallet USDT (empty / funds & activity) and Receive USDT › Specify Amount; exact current node links are unverified.

Preview

The earlier zero-placeholder screenshots are historical. The updated design uses an unknown marker until the balance is read; numeric balances retain the same formatting and layout.

QA Notes

Setup

Use Java 17 and the branch’s pinned Core/Paykit dependencies. In ignored local.properties, set USDT_RPC_URL=http://127.0.0.1:1 and USDT_BUNDLER_URL=http://127.0.0.1:1 for an offline test build. Run GEO=false E2E_BACKEND=network just e2e assembleDevDebug and install the ARM64 APK on a dedicated emulator. Keep English language with Germany/Netherlands region; on the tested image, adb -s <serial> shell cmd locale set-app-locales to.bitkit.dev --locales en-DE selects English/Germany. Use an unfunded wallet, enable USDT in Settings, show balances and select dollar display. Offline endpoints exercise the unknown marker; confirmed-zero checks require a successful read from an owned local test provider and are also covered by regression tests. No payment or wallet reset is required.

Journeys

  • J1: new usdt-number-format.xml — consistent Home/wallet values, silent unknown/confirmed-zero states, masking and exact dot-decimal entry across regions.
  • J2: updated usdt-wallet-navigation.xml — unknown or empty wallet stays usable while synchronization is silent.

Manual Tests

N/A

Automated Checks

  • updated UsdtDisplayTest.kt, PaykitAmountViewTest.kt and UsdtDepositViewModelTest.kt — fixed separators across US/German/Dutch/French regions, exact six-decimal values, sub-cent balances, rounding and maximum-sized amounts.
  • updated UsdtPaymentContentTest.kt and PaymentRequestAmountLocaleTest.kt — silent unknown/zero/loaded/hidden balance rendering and dot-decimal USDT/USD input in comma regions.
  • removed the obsolete localized-input-adapter assertion; canonical input precision coverage remains.

Evidence

At 95dc2fead (including current master 4bb41772), just compile, just test and just lint passed: 4,395 unit tests, zero failures/skips, zero Detekt SARIF results and zero Android Lint XML findings. The E2E app and instrumentation APKs also built successfully. Complete logs and structured reports were inspected; only three JVM class-sharing notices appeared in the unit runner, with no source warning/error diagnostics.

All 11 targeted Compose tests passed on the dedicated ARM64/API 36 emulator, including unknown → confirmed zero → loaded → hidden balance transitions, dot-decimal input and narrow/enlarged-text component checks. With the existing unfunded wallet preserved and English/Germany selected, the final app showed — on Home and $ — in the wallet, without refresh-error or history-sync paragraphs; screenshots and UI hierarchies were inspected (partial J1/J2 evidence).

Independent review and a bounded master-merge recheck found no introduced issues. The merge preserves master’s new amount layout and funding-source dropdown. All five commits in the current-base-to-pushed-head range were cryptographically verified locally, and GitHub’s compare endpoint returned the exact same five commits with verified=true, reason=valid. GitHub’s PR head and exact five-commit list now match the pushed branch. Current-head CI, lint and all seven local E2E shards passed.

Historical 9bb64a38b passed all 4,383 unit tests, full lint with zero Detekt/Android Lint findings and all 11 targeted Compose tests. Earlier author-driven J1/J2 checks covered dot-decimal entry, exact QR amounts, privacy, and compact/enlarged text. Those zero-placeholder screenshots are superseded by the unknown-balance correction.

Gaps

The full regional J1 repeat and broader J2 hardware/activity fixtures were not rerun on this correction. Confirmed-zero and loaded-balance transitions are covered by component tests; offline Home/wallet checks cover the unknown marker. No funded payment or wallet reset was performed. Current-head hosted checks passed; this does not add funded USDT/provider coverage.

Models used

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

@ben-kaufman
ben-kaufman marked this pull request as ready for review October 21, 2026 12:51
@greptile-apps

greptile-apps Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] This PR appears safe to merge; no actionable issues were found.

Summary

This PR gives USD and USDT amounts consistent dot decimals and comma grouping.

  • Dollar and USDT amounts use the same number format across regions.
  • Dollar and USDT amount entry keeps a dot decimal across regions.
  • Empty USDT wallets show zero while syncing stays quiet.

No actionable issues were found. Tests and device checks were not rerun during this review.

ben-kaufman explicitly describes the removed refresh messages and zero-before-refresh display as intentional presentation changes.

Reviews (1) · Last reviewed commit: "fix: allow entering new USD request amou..." · Reviewed by Greptile

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Regtest APK

Built from 95dc2fe (run).

Download bitkit-dev-debug universal APK (expires in 30 days).

@talosmachina talosmachina left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 finding (1 P1). Silences the USDT wallet's error and history-sync copy, starts the balance at $ 0.00, and moves USD/USDT display and keypad entry to the fixed . decimal / , grouping already used for bitcoin fiat values. Reviewed 9bb64a38, full tier.

What I checked, and 4 candidates I ruled out

Read in full: UsdtAmountHeader.kt, PaykitAmountView.kt, UsdtWalletScreen.kt, the changed hunks of CreatePaymentRequestScreen.kt, SendAmountScreen.kt, UsdtReceiveAmountContent.kt, UsdtSendContent.kt, UsdtDepositReceiveScreen.kt, Currency.kt, plus UsdtRepo.kt and HomeScreen.kt for the balance state.
Call sites traced: usdtOverviewAmount, usdtExactAmount, usdDisplayAmount, usdCents, formatCurrency, every NumberPad(type = DECIMAL) caller.
CI: build, detekt, Greptile and the finished e2e shards green; lightning_security still pending at review time. Not built here: Bitkit Android branches are reviewed from code and CI only.

Ruled out

  • Parsing after the keypad change: UsdtRepo.quote and UsdtService.prepareDeposit still replace the locale decimal separator with ., but the keypad now always emits KEY_DECIMAL = "." and NumberPadInputHandler never inserts a comma, so the replace is a no-op, not a mis-parse.
  • formatCurrency default change: the new minimumFractionDigits/roundingMode parameters default to the previous behaviour (decimalPlaces, HALF_EVEN), so existing bitcoin fiat callers are unchanged.
  • checkNotNull on formatCurrency: DecimalFormat.format(BigDecimal) does not throw for any finite value, including ULong.MAX_VALUE cents, which the updated test covers.
  • Zero-USD request draft: an empty dollars renders as 0 via ifEmpty, and the submitted amount for a re-entered value is covered by PaymentRequestAmountLocaleTest.
  • Cold reader: returned no verdict (provider refusal), so the P1 stands on the trace above.

Merge confidence: 3/5, the P1 on the unknown-balance path.

Reviewer model: claude-opus-5-5
Reasoning effort: Not exposed (the review runtime reports the model id only)

Comment thread app/src/main/java/to/bitkit/ui/screens/wallets/usdt/UsdtWalletScreen.kt Outdated

@ovi-reviewer ovi-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: ⛔️ Request changes — a funded wallet shows a false zero after an offline cold start.

Findings:

  • MEDIUM — unavailable balance displayed as zero: app/src/main/java/to/bitkit/ui/screens/wallets/usdt/UsdtWalletScreen.kt:106 and the wallet header at line 345 convert an unloaded balance to zero. I reproduced a wallet holding 1,234.567891 USDT showing $ 0.00 after an offline restart, beside its saved funded activity. It returns to 1,234.57 after RPC connectivity returns; no funds moved. This adds device evidence to the existing finding. The author confirmation leaves the design decision open.

QA for 9bb64a3:

Android 15, English/US, built from this head. USDT transfers below used a local Arbitrum fork; hardware checks used deterministic Trezor fixtures.

Test 1: J1 — US-region number-format repeat ✅ Empty balances, masking, dot entry and exact QR amounts matched.

Home uses dollar symbols and dot decimals; the empty USDT wallet stays silent. Receive preserves 3.50 and 0.000001; I decoded the QR images to uint256=3500000 and uint256=1 and matched their recipient to Details. Manual send entry displays 1.25.

test-1.2x.mp4

US-region presentation, receive precision and manual entry, at 2× speed.

Test 2: J2 — navigation and fresh receive state ✅ Back, copied addresses, validation and receive-state isolation matched.

Details Back returns to the QR; QR Back closes Receive on the USDT wallet. Details Copy, QR Copy and tapping the QR all paste only the address. Invalid Bitcoin text is rejected; a seventh USDT decimal is ignored. A fresh USDT receive sheet clears the previous Bitcoin amount/note when switching to Savings; unfunded Spending shows its receive setup.

test-2.2x.mp4

Navigation, clipboard round trips, decimal limit and fresh receive state, at 2× speed.

Test 3: J2 — odd/even hardware rows ✅ USDT placement, aligned dividers and hardware receiving matched.

With one paired hardware wallet, USDT fills its row’s right cell. With two, USDT starts the next row. Hardware receiving retains a separate Bitcoin QR.

test-3.2x.mp4

One and two paired hardware accounts, then hardware receiving, at 2× speed.

Test 4: J2 — saved activity and search ✅ Sub-cent notation, cent rounding and search matched.

Local-fork receipts display <0.01 and 1,234.57. USDT and usdt find both; u, us and t find neither. A transaction fragment finds one row, a recipient fragment finds both, and clearing restores the list. Search inputs and row identifiers were checked through the device UI hierarchy.

test-4.2x.mp4

Local-fork receipts and activity searches, at 2× speed.

Test 5: funded wallet offline cold start ❌ The unchanged funded balance becomes zero after restarting offline.

The wallet holds 1234567891 atomic units (1,234.567891 USDT). Losing RPC in the running process keeps 1,234.57; force-stop and offline relaunch show $ 0.00 on Home and 0.00 in the wallet while both saved receipts remain. Restoring RPC recovers 1,234.57. The fixture reported the same atomic balance before and after.

test-5.2x.mp4

Online balance, cached offline balance, offline restart and restored balance, at 2× speed.

I relied on the author’s reported German-region walkthrough and final-head automated checks for unchanged regional coverage and zero-USD request initialization; I did not rerun those suites. No planned case remains unrun. Funded payment execution, bridge/deposit screens and provider recovery beyond this display check were not driven.

Full review: ae800ae..9bb64a3, including formatter/input callers, balance state, masking and payment-action error paths.

Twin comparison:

Reviewer model: gpt-6.1-sol
Reasoning effort: high


Reviewed by gpt-6.1-sol-high via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest

@ovi-reviewer ovi-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: ✅ Approve — the unknown-balance finding is fixed.

Findings: None. I verified the previous finding at 95dc2fe, including the funded offline cold start that failed previously.

QA for 95dc2fe:

Android 15, built from this head. English/Germany and English/US; funded checks used a local Arbitrum fork.

Test 1: J1/J2 — unknown and confirmed-zero balances ✅ Unknown, confirmed zero and masked balances remain distinct.

In both regions, an offline cold launch shows — on Home and $ — in the wallet. A successful empty balance read changes both to $ 0.00. Masking hides the Home tile and wallet balance. No refresh-error or history-sync paragraphs appear.

test-1.2x.mp4

Unknown, zero and hidden balance states, at 2× speed.

Test 2: J1 — regional amount entry and merged layout ✅ Dot entry and exact QR amounts match across regions; compact controls remain reachable.

Receive preserves 3.50 and 0.000001 in both regions. I decoded the QR screenshots to uint256=3500000 and uint256=1; the recipient matches receiving Details. A seventh decimal is ignored. Manual USDT send displays 1.25 with a dot key in both regions. At a shorter screen height with 30% larger text, scrolling exposes the keypad and Continue. No quote or payment was submitted.

test-2-complete.2x.mp4

US entry, German entry and compact layout, at 2× speed.

Test 3: J3 — funded wallet offline cold start ✅ Offline relaunch shows unknown beside saved activity; reconnect restores the balance.

The wallet shows 1,234.57 online and retains its loaded value when RPC becomes unavailable. After an offline cold restart, Home shows — and the wallet shows $ — beside its saved 1,234.57 receipt. Reconnecting restores 1,234.57. The fixture reported the same 1234567891 atomic units before and after. A separate failed refresh also retained the loaded balance; I confirmed NetworkUnavailable in the current process's device log.

test-3-complete.2x.mp4

Funded balance, offline restart, recovery and failed-refresh retention, at 2× speed.

No planned case remains unrun. I relied on the earlier passing report for unchanged J2 receive navigation, hardware-row placement and activity search. I relied on the author's current-head automated results for unit and Compose coverage; I did not rerun those suites.

Delta review: 9bb64a3..95dc2fe, including the prior finding and master-merge interactions. The merged Paykit funding selector, fee estimate, settings migration and transaction-ID copying were checked in code, without new device cases. App payment execution and bridge/deposit flows were not driven.

Twin comparison:

Reviewer model: gpt-6.1-sol
Reasoning effort: high


Reviewed by gpt-6.1-sol-high via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest

@ovitrif ovitrif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utAck

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No findings.

Checked and clean:

  • The sent value is the full-precision amount; only the display is formatted. Unknown (— / $ —) and confirmed zero ($ 0.00) stay distinct on Home, the wallet screen and the masked state.
  • The decimal key and display use a dot in comma regions, and entry parses back to the same 6-decimal value the QR and send use.
  • usdt__history_sync is removed with its only users, and no keys are added.
  • The review screen rounds the approved amount and fee to cents. That is already on master and is the accepted behaviour in the USDT QA doc, so it is not raised here.
  • Twin synonymdev/bitkit-ios#1077 matches on every traced point.

Device gate: no run by me. Skipped, already driven this round: usdt-number-format.xml, usdt-wallet-navigation.xml and the offline cold start (Android, by ovi-reviewer at 95dc2fe, passed: #1655 (review)). perf: not relevant; l10n: one key removed, none added; Figma: the mapped Wallet USDT and Receive USDT › Specify Amount rows have no node link, and the — unknown state was settled in the threads.

@ovitrif
ovitrif merged commit 0f44d93 into master Oct 22, 2026
22 checks passed
@ovitrif
ovitrif deleted the fix/usdt-balance-presentation-20261021 branch October 22, 2026 22:41
@ovitrif

ovitrif commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator

Merged: approved and green at 95dc2fe.

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

ovitrif commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator

Put on 3.1.0 with the issues it closes, #1648 and #1649.

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.

bug: usdt amounts use the region's decimals and miss the currency symbol bug: new usdt wallet shows error and sync copy under the balance

4 participants