Skip to content

feat(ripple): show XRP trust-line token balances in the asset list - #5387

Merged
johnnyluo merged 8 commits into
mainfrom
aminsato/5210_xrp_token_balances
Jul 26, 2026
Merged

johnnyluo merged 8 commits into
mainfrom
aminsato/5210_xrp_token_balances

Conversation

@aminsato

@aminsato aminsato commented Jul 23, 2026 •

Copy link
Copy Markdown
Collaborator

What

XRP issued-currency (trust-line) tokens now appear in the asset list with their balances, discovered from account_lines.

Discovery routes through the existing TokenRepository.getTokensWithBalance seam that the EVM and Cosmos-bank finders already use, so the chain-detail screen and the background refresh worker pick these up with no bespoke UI states.

Closes #5210 · SDK counterpart: vultisig/vultisig-sdk#997

Decisions worth reviewing

Android is the first platform to implement this — iOS and the extension have nothing here yet — so the token model was defined in this PR rather than matched to an existing one.

Identity is "<currency>.<issuer>" in contractAddress. An XRPL currency code is only unique per issuer, and the raw on-chain code is stored (3-char ASCII or 40-char hex) so it round-trips into account_lines comparisons unchanged; the display ticker is derived separately.

Coin.id is contract-qualified for these. Several independent issuers each mint their own USD. On the plain ticker-chainId id they would collide, and Room's REPLACE-on-conflict insert means enabling the second would silently overwrite the first's persisted row — the same hazard THORChain secured assets are already qualified for. Native XRP is untouched.

Decimals are a wallet-side choice. Unlike ERC-20/SPL, an XRPL issued currency has no on-chain decimals field: amounts travel as decimal strings with 16 significant digits. Pinned to 15, truncating down, so a displayed balance never rounds up past what the ledger holds.

Zero and negative lines are dropped. A zero-balance line is an opt-in with nothing in it (holding one is how an account subscribes to a currency before ever receiving it); a negative balance means the account issues the currency rather than holds it.

No separate owner-reserve work was needed for the SDK issue's second acceptance item: XRPL's OwnerCount already counts trust lines, so the existing native-XRP reserve subtraction in RippleApi.getBalance already folds them into spendable XRP.

account_lines reads are coalesced. One response carries every line for an account, but the balance layer asks per token — N held currencies would otherwise fire N identical paginated request chains at the public cluster. RippleAccountLinesCache uses a per-address mutex + 10s TTL, mirroring the existing CosmosBalanceCache.

Slightly beyond the ticket: these assets are read-only

Ripple.OperationPayment carries a drops-denominated amount, so the payment builder can only move native XRP. Now that these tokens are reachable in the asset list, routing one into send would have signed away toAmount drops of XRP instead of the token the user picked — a silent loss of unrelated funds.

Moving an issued currency needs a Payment carrying a CurrencyAmount (currency + issuer + value), which is separate work. Until then they are gated: the token-detail send/swap buttons and both asset pickers are closed via Coin.isReadOnlyAsset, and RippleHelper refuses outright so no future entry point can reach the drops path unnoticed. That guard sits after the dApp raw-JSON branch, which signs verbatim and stays free to submit TrustSet and issued-currency payments.

Shipping the display without this would have been unsafe, so it is included rather than deferred.

Test plan

  • :data:testDebugUnitTest and :app:testDebugUnitTest — full suites green
  • :app:lintDebug green; ktfmt applied
  • 36 new tests: RippleAccountLinesTest (11 — parsing, marker pagination, repeated-marker bail-out, per-token lookup, issuer disambiguation, read coalescing), RippleTokenTest (17 — identity round-trip, id collisions, hex currency decoding in both shapes, fixed-point scaling and truncation), RippleTokenFinderTest (8 — zero/negative/XRP filtering, two issuers of one currency, failure tolerance), plus a Ripple-delegation case in TokenRepositoryImplTest
  • Each of the three commits compiles independently (:data + :app)

Not verified on device or mainnet. Reaching this state needs a vault whose XRP account holds a trust line with a positive balance, which requires a TrustSet the app has no screen for — it has to come through the extension's XRPL dApp path. Reviewers with such an account, the two things to eyeball are the balance precision on a fractional holding and two issuers of the same currency code rendering as separate rows.

Summary by CodeRabbit

  • New Features

    • Added XRPL issued-token (trust-line) discovery and balance conversion, including RLUSD.
    • Added support for detecting issued tokens as read-only assets.
  • Bug Fixes

    • Excluded read-only assets from token selection and fast token pickers.
    • Hid the Send action for read-only assets and tightened send capability gating.
    • Improved native XRP vs issued-token balance handling, with fail-closed protection for unsupported issued-currency transfers.
  • Tests

    • Added/expanded coverage for trust-line parsing, pagination/caching, token discovery, balance conversion, and read-only behavior.

aminsato added 3 commits July 23, 2026 05:15
XRPL trust-line tokens have no on-chain decimals field and no contract
address, so both had to be defined before any of them could be read.

Amounts travel as decimal strings with 16 significant digits, so the
fixed-point scale is a wallet-side choice: pin it at 15 and truncate
down, so a displayed balance never rounds up past what the ledger holds.
Identity is the (currency, issuer) pair stored as "<currency>.<issuer>" —
the raw on-chain currency code, so it round-trips into account_lines
comparisons unchanged, with the display ticker derived separately.

Coin.id has to carry that pair. A currency code is only unique per
issuer, and several independent issuers each mint their own USD; on the
plain ticker-chainId id they would collide and REPLACE-overwrite each
other's persisted row, exactly the case THORChain secured assets are
already qualified for.

account_lines returns every line for an account in one response while
the balance layer asks per token, so N held currencies would fire N
identical paginated request chains at the public cluster. Coalesce them
behind a per-address mutex and a short TTL, mirroring CosmosBalanceCache.

Co-Authored-By: aminsato <Amin.saradar@yahoo.com>
Route Chain.Ripple through the same token-discovery seam the EVM and
Cosmos-bank finders already use, so the chain-detail screen and the
background refresh worker pick these up with no bespoke states.

Only a positive balance is a holding. A zero-balance line is an opt-in
with nothing in it — holding one is how an account subscribes to a
currency before ever receiving it — and a negative balance means the
account issues the currency rather than holds it, so neither belongs in
the asset list. Both still count toward OwnerCount, which the existing
native-XRP reserve subtraction already folds into spendable XRP; no
separate trust-line reserve accounting is needed.

A transient account_lines failure yields no tokens rather than
propagating, matching the sibling finders: a network blip must not wipe
tokens the vault already holds, and the next refresh retries.

Closes #5210

Co-Authored-By: aminsato <Amin.saradar@yahoo.com>
Ripple.OperationPayment carries a drops-denominated amount, so the
payment builder can only move native XRP. An issued-currency coin
reaching it would sign away toAmount *drops of XRP* instead of the token
the user picked — a silent loss of unrelated funds, and newly reachable
now that these tokens appear in the asset list.

Moving one needs a Payment carrying a CurrencyAmount (currency, issuer,
value), which is separate work. Until then they are read-only: gate the
token-detail send and swap buttons and both asset pickers so the flows
cannot be entered, and refuse in the helper itself so no future entry
point can reach the drops path unnoticed. The guard sits after the dApp
raw-JSON branch, which signs verbatim and stays free to submit TrustSet
and issued-currency payments.

Co-Authored-By: aminsato <Amin.saradar@yahoo.com>
@coderabbitai

coderabbitai Bot commented Jul 23, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds XRPL issued-token identity, trust-line balance retrieval, token discovery, curated RLUSD metadata, issuer-qualified coin IDs, and read-only asset handling across data, signing, and wallet UI flows.

Changes

XRPL issued assets

Layer / File(s) Summary
Ripple token identity and units
data/src/main/kotlin/com/vultisig/wallet/data/models/RippleToken.kt, data/src/main/kotlin/com/vultisig/wallet/data/models/Coin.kt, data/src/main/kotlin/com/vultisig/wallet/data/db/models/TokenValueEntity.kt, data/src/test/kotlin/com/vultisig/wallet/data/models/*, data/src/test/kotlin/com/vultisig/wallet/data/db/models/TokenValueEntityTest.kt
Issued tokens use currency/issuer identities, fixed-point balances, issuer-qualified IDs, and read-only classification.
Account-lines API and cached balances
data/src/main/kotlin/com/vultisig/wallet/data/api/RippleApi.kt, data/src/test/kotlin/com/vultisig/wallet/data/api/RippleAccountLinesTest.kt, app/src/test/java/com/vultisig/wallet/ui/models/send/ChainValidationServiceTest.kt
RippleApi fetches paginated trust lines, caches account reads, matches currency and issuer, and converts balances.
Token discovery and repository integration
data/src/main/kotlin/com/vultisig/wallet/data/usecases/RippleTokenFinder.kt, data/src/main/kotlin/com/vultisig/wallet/data/repositories/*, data/src/main/kotlin/com/vultisig/wallet/data/usecases/DataUsecasesModule.kt, data/src/main/kotlin/com/vultisig/wallet/data/chains/helpers/RippleHelpter.kt, data/src/test/kotlin/com/vultisig/wallet/data/usecases/RippleTokenFinderTest.kt, data/src/test/kotlin/com/vultisig/wallet/data/repositories/TokenRepositoryImplTest.kt
Ripple token discovery filters trust lines, integrates with repositories, selects token balances, and rejects issued-currency signing.
Curated Ripple asset metadata
data/src/main/kotlin/com/vultisig/wallet/data/models/Coins.kt, app/src/main/java/com/vultisig/wallet/data/models/CoinLogo.kt, app/src/main/res/drawable/rlusd.webp
Adds curated RLUSD metadata and its drawable mapping.
Read-only asset UI filtering
app/src/main/java/com/vultisig/wallet/ui/components/v2/fastselection/FastSelectionPopupSharedViewModel.kt, app/src/main/java/com/vultisig/wallet/ui/models/TokenDetailViewModel.kt, app/src/main/java/com/vultisig/wallet/ui/screens/TokenDetailScreen.kt, app/src/main/java/com/vultisig/wallet/ui/screens/select/SelectAssetViewModel.kt, app/src/main/java/com/vultisig/wallet/ui/models/send/TokenPreselectionService.kt
Read-only assets are removed from selection and preselection flows, excluded from swap and send capabilities, and displayed without the SEND action.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant WalletUI
  participant TokenRepository
  participant RippleTokenFinder
  participant RippleApi
  participant XRPL
  WalletUI->>TokenRepository: request Ripple tokens
  TokenRepository->>RippleTokenFinder: find(address)
  RippleTokenFinder->>RippleApi: fetchAccountLines(address)
  RippleApi->>XRPL: request paginated account_lines
  XRPL-->>RippleApi: return trust lines and markers
  RippleApi-->>RippleTokenFinder: return curated Ripple coins
  RippleTokenFinder-->>TokenRepository: return issuer-qualified read-only coins
  TokenRepository-->>WalletUI: return token list and balances
Loading

Possibly related issues

Possibly related PRs

Suggested reviewers: johnnyluo

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.93% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: displaying Ripple trust-line token balances in the asset list.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch aminsato/5210_xrp_token_balances

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 5

🧹 Nitpick comments (1)
data/src/test/kotlin/com/vultisig/wallet/data/api/RippleAccountLinesTest.kt (1)

41-178: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use runTest instead of runBlocking in these tests.

Replace the test wrappers with kotlinx.coroutines.test.runTest to follow the coroutine testing guideline.

Proposed change
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest

- fun `fetchAccountLines parses currency issuer and balance`() = runBlocking {
+ fun `fetchAccountLines parses currency issuer and balance`() = runTest {

As per coding guidelines, “Avoid runBlocking — use suspend functions and proper coroutines with viewModelScope.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@data/src/test/kotlin/com/vultisig/wallet/data/api/RippleAccountLinesTest.kt`
around lines 41 - 178, Replace the runBlocking wrappers in the
RippleAccountLinesTest test methods with kotlinx.coroutines.test.runTest, adding
the appropriate import. Keep each test’s existing coroutine body and assertions
unchanged while using the coroutine test framework consistently.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/main/java/com/vultisig/wallet/ui/models/TokenDetailViewModel.kt`:
- Line 42: Initialize canSend to false in TokenDetailViewModel so SEND remains
unavailable until a successful matching account load confirms the token is
writable; preserve the existing logic that enables it only after the read-only
check completes.

In `@data/src/main/kotlin/com/vultisig/wallet/data/api/RippleApi.kt`:
- Around line 213-218: Update the account-lines response handling in RippleApi
so `result.error` is propagated instead of returning and caching an empty list,
while preserving `actNotFound` as the legitimate empty-account case. Add a
regression test covering a successful HTTP response with an RPC error and no
lines, asserting that the error is thrown rather than cached.

In `@data/src/main/kotlin/com/vultisig/wallet/data/models/RippleToken.kt`:
- Around line 58-64: Update parseRippleTokenIdentity to reject contractAddress
values containing more than one RIPPLE_TOKEN_SEPARATOR, while preserving the
existing checks for missing currency or issuer; only addresses with exactly one
separator should produce RippleTokenIdentity, and add a parser test covering a
multiple-separator value such as USD.rIssuer.extra.

In `@data/src/main/kotlin/com/vultisig/wallet/data/usecases/RippleTokenFinder.kt`:
- Around line 58-60: Remove the broad Exception catch in the RippleTokenFinder
account_lines flow so unexpected failures from malformed balances,
deserialization, or programming errors propagate instead of returning
emptyList(). Preserve only the existing explicit timeout and NetworkException
handling branches, including their intended tolerant behavior.
- Around line 67-87: Update resolveCoin and its callers to accept the vault
address, set that address on generated Ripple token Coin instances, and copy it
onto curated coins returned by preferCurated. Preserve the existing token
identity and metadata while ensuring every discovered coin carries the vault
address used by RippleApi.getTokenBalance().

---

Nitpick comments:
In `@data/src/test/kotlin/com/vultisig/wallet/data/api/RippleAccountLinesTest.kt`:
- Around line 41-178: Replace the runBlocking wrappers in the
RippleAccountLinesTest test methods with kotlinx.coroutines.test.runTest, adding
the appropriate import. Keep each test’s existing coroutine body and assertions
unchanged while using the coroutine test framework consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 172e7fd9-7be1-4661-bccd-9fda82361ec4

📥 Commits

Reviewing files that changed from the base of the PR and between ee031b1 and f0ab9da.

📒 Files selected for processing (17)
  • app/src/main/java/com/vultisig/wallet/ui/components/v2/fastselection/FastSelectionPopupSharedViewModel.kt
  • app/src/main/java/com/vultisig/wallet/ui/models/TokenDetailViewModel.kt
  • app/src/main/java/com/vultisig/wallet/ui/screens/TokenDetailScreen.kt
  • app/src/main/java/com/vultisig/wallet/ui/screens/select/SelectAssetViewModel.kt
  • app/src/test/java/com/vultisig/wallet/ui/models/send/ChainValidationServiceTest.kt
  • data/src/main/kotlin/com/vultisig/wallet/data/api/RippleApi.kt
  • data/src/main/kotlin/com/vultisig/wallet/data/chains/helpers/RippleHelpter.kt
  • data/src/main/kotlin/com/vultisig/wallet/data/models/Coin.kt
  • data/src/main/kotlin/com/vultisig/wallet/data/models/RippleToken.kt
  • data/src/main/kotlin/com/vultisig/wallet/data/repositories/BalanceRepository.kt
  • data/src/main/kotlin/com/vultisig/wallet/data/repositories/TokenRepository.kt
  • data/src/main/kotlin/com/vultisig/wallet/data/usecases/DataUsecasesModule.kt
  • data/src/main/kotlin/com/vultisig/wallet/data/usecases/RippleTokenFinder.kt
  • data/src/test/kotlin/com/vultisig/wallet/data/api/RippleAccountLinesTest.kt
  • data/src/test/kotlin/com/vultisig/wallet/data/models/RippleTokenTest.kt
  • data/src/test/kotlin/com/vultisig/wallet/data/repositories/TokenRepositoryImplTest.kt
  • data/src/test/kotlin/com/vultisig/wallet/data/usecases/RippleTokenFinderTest.kt

Comment thread app/src/main/java/com/vultisig/wallet/ui/models/TokenDetailViewModel.kt Outdated
Comment thread data/src/main/kotlin/com/vultisig/wallet/data/api/RippleApi.kt
Comment thread data/src/main/kotlin/com/vultisig/wallet/data/models/RippleToken.kt
Comment thread data/src/main/kotlin/com/vultisig/wallet/data/usecases/RippleTokenFinder.kt Outdated
@aminsato
aminsato marked this pull request as draft July 23, 2026 09:51
aminsato added 2 commits July 23, 2026 06:16
A curated entry lets the token be enabled before any balance exists and
gives the discovered trust line an icon and a fiat price: account_lines
discovery prefers the catalog match over the generic coin it would
otherwise synthesize, which carries no logo and no priceProviderID.

The currency code is the 40-hex form XRPL uses for names longer than
three characters, paired with the issuing account in the same
"<currency>.<issuer>" notation the balance lookup matches on, and scaled
to RIPPLE_TOKEN_DECIMALS so its balance converts like any other trust
line.

Co-Authored-By: aminsato <Amin.saradar@yahoo.com>
An XRPL node answers a rate-limited or unsynced read with HTTP 200 and a
result.error, which bodyOrThrow cannot see. Reading that as "no trust
lines" dropped every issued currency from the asset list and cached the
emptiness for the coalescing TTL, so throw on any error other than the
actNotFound an unfunded account legitimately returns.

Reject contract addresses carrying more than one separator: neither a
currency code nor a base58 issuer can contain it, so splitting on the
first yielded an issuer that no trust line could ever match.

Default the token-detail SEND flag to closed like every other action
flag there, since the read-only check only runs once a matching account
resolves and the button has no disabled state.

Co-Authored-By: aminsato <Amin.saradar@yahoo.com>
@aminsato
aminsato marked this pull request as ready for review July 23, 2026 10:27

@rkokhatskyi rkokhatskyi 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.

I think we should do the full support for tokens, not just discovery. This is incomplete feature and needs more work. Few things in mind:

  1. Sending XRPL tokens doesn't work
  2. We need to control the list of tokens as anyone can create a trustline, call it USDT and you don't know where's correct one. XRP doesn't have many tokens, we could add them all
  3. No pricing for XRPL tokens
  4. We cache balances keys on (chain, address, ticker), not the full issuer-qualified id, so two same-ticker issuers can collide (REPLACE-on-conflict)

…cache key

Gate XRPL trust-line discovery to the curated Coins catalog: anyone can open a
trust line under any currency label, so an arbitrary held line is not evidence
of a genuine asset. The finder now surfaces a line only when the catalog lists
its currency/issuer pair and drops the rest; new tokens are onboarded by adding
them to Coins.Ripple.all.

Fix the balance cache-key collision: TokenValueEntity.tokenId only qualified
THORChain secured assets, so for Ripple it collapsed to the plain ticker-chain
id and no longer mirrored the issuer-qualified Coin.id. Two issuers of the same
currency then resolved to one row and BalanceRepository lookups by id missed
(decimals -> 0). tokenId now mirrors Coin.id for issued currencies.

Co-Authored-By: aminsato <Amin.saradar@yahoo.com>
@aminsato

Copy link
Copy Markdown
Collaborator Author

Thanks Roman — agree the full token experience is the goal. I've split it so this PR lands correct, safe discovery now, with sending and pricing as the next steps.

Addressed in this PR (749eb14):

2. Controlled token list. Discovery is now gated to the curated Coins catalog. The finder surfaces a trust line only when its currency/issuer pair matches a catalog entry and drops everything else, so a stranger's line labelled USD/USDT — or the right currency code from the wrong issuer — never shows. New tokens are onboarded by adding them to Coins.Ripple.all (with logo + priceProviderID); RLUSD is in there today and I'll expand the list. Covered by RippleTokenFinderTest.

4. Cache-key collision. Root cause was TokenValueEntity.tokenId: it only issuer-qualified THORChain secured assets, so for Ripple it collapsed to the plain ticker-chain id and no longer mirrored the already-issuer-qualified Coin.id. Two issuers of the same currency then resolved to one balance row and BalanceRepository.getCachedTokenBalances' coins.find { it.id == tokenEntity.tokenId } missed (decimals → 0). tokenId now mirrors Coin.id for issued currencies, so each issuer keeps its own row. Covered by TokenValueEntityTest (two issuers → distinct ids; native XRP stays plain).

Follow-ups (separate PRs under the XRP token support set):

1. Sending XRPL tokens. Needs a Payment carrying a CurrencyAmount (currency + issuer + value) rather than the drops-only path RippleHelper builds today. That's why issued currencies are gated read-only for now — routing one into send/swap would sign an XRP transfer of the numeric balance. I'll build the issued-currency signing path as its own PR and lift the read-only gate there.

3. Pricing. I'll wire XRPL issued currencies into the price provider (keyed per issuer via each catalog entry's priceProviderID) so discovered tokens show a fiat value, as a follow-up.

Keeping this PR to correct, curated, read-only discovery keeps it reviewable; happy to track 1 and 3 as linked issues if you'd like.

Comment thread data/src/main/kotlin/com/vultisig/wallet/data/models/Coin.kt
Comment thread data/src/main/kotlin/com/vultisig/wallet/data/api/RippleApi.kt Outdated
Comment thread data/src/main/kotlin/com/vultisig/wallet/data/api/RippleApi.kt
Comment thread data/src/main/kotlin/com/vultisig/wallet/data/api/RippleApi.kt Outdated
Comment thread data/src/main/kotlin/com/vultisig/wallet/data/models/RippleToken.kt Outdated
Comment thread data/src/main/kotlin/com/vultisig/wallet/data/models/RippleToken.kt Outdated
Address review on #5387:

- Skip read-only assets (XRPL issued currencies) in send preselection so a
  crafted vultisig://send deep link can't pin one as the send source until
  keysign throws.
- Pin the first account_lines page's resolved ledger index on every later page
  instead of re-sending the "validated" shorthand, which could drop a line
  straddling a page boundary and read it as zero.
- Add ignore_default:true so default zero-balance trust lines are filtered
  server-side, bounding the paged walk against trust-line spam.
- Scale getTokenBalance by the coin's own decimal rather than a fixed constant,
  matching the scale the balance is rendered at.
- Remove the dead rippleCurrencyTicker helper (and its hex constants /
  isHexDigit) that had no production caller and carried an inverted 0x00 doc.

Co-Authored-By: aminsato <Amin.saradar@yahoo.com>

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
data/src/main/kotlin/com/vultisig/wallet/data/models/RippleToken.kt (1)

54-65: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject native XRP in the identity parser.

Coin.isRippleIssuedToken delegates to parseRippleTokenIdentity, but the parser’s structural checks allow XRP.<issuer>. A constructible non-native Coin can therefore be classified as an issued/read-only token even though XRP cannot be a trust-line currency. Reject native currency codes here and add a regression test for parseRippleTokenIdentity("XRP.$ISSUER").

Proposed fix
+    if (isRippleNativeCurrency(currency)) return null
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@data/src/main/kotlin/com/vultisig/wallet/data/models/RippleToken.kt` around
lines 54 - 65, Update parseRippleTokenIdentity to return null when the parsed
currency is the native XRP code, while preserving existing separator and issuer
validation. Add a regression test covering
parseRippleTokenIdentity("XRP.$ISSUER") and assert it is rejected.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@data/src/main/kotlin/com/vultisig/wallet/data/models/RippleToken.kt`:
- Around line 54-65: Update parseRippleTokenIdentity to return null when the
parsed currency is the native XRP code, while preserving existing separator and
issuer validation. Add a regression test covering
parseRippleTokenIdentity("XRP.$ISSUER") and assert it is rejected.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9caae5fc-8d5b-491e-b174-e3427334bbb9

📥 Commits

Reviewing files that changed from the base of the PR and between 749eb14 and d497b46.

📒 Files selected for processing (5)
  • app/src/main/java/com/vultisig/wallet/ui/models/send/TokenPreselectionService.kt
  • data/src/main/kotlin/com/vultisig/wallet/data/api/RippleApi.kt
  • data/src/main/kotlin/com/vultisig/wallet/data/models/RippleToken.kt
  • data/src/test/kotlin/com/vultisig/wallet/data/api/RippleAccountLinesTest.kt
  • data/src/test/kotlin/com/vultisig/wallet/data/models/RippleTokenTest.kt
🚧 Files skipped from review as they are similar to previous changes (2)
  • data/src/test/kotlin/com/vultisig/wallet/data/api/RippleAccountLinesTest.kt
  • data/src/main/kotlin/com/vultisig/wallet/data/api/RippleApi.kt

Comment thread data/src/main/kotlin/com/vultisig/wallet/data/api/RippleApi.kt Outdated
@aminsato

Copy link
Copy Markdown
Collaborator Author

@Vaulty-bot pick-up

@Vaulty-bot

Copy link
Copy Markdown
Collaborator

I'm currently working on another task in this repo. I'll pick this up in the next cycle (~5 min).

@aminsato

Copy link
Copy Markdown
Collaborator Author

@Vaulty-bot pick-up

@Vaulty-bot

Copy link
Copy Markdown
Collaborator

Picked up. Reading feedback and working on it.

… response (#5387)

rippled counts every ledger object toward `limit` and pins the marker
before the `ignore_default` filter runs, so the flag reduces each
response body but leaves the page walk unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Vaulty-bot

Copy link
Copy Markdown
Collaborator

Round 3 — one comment, one commit

Commit: `db65fbb49` — fix(ripple): correct the ignore_default comment to say it shrinks the response (#5387)

Fixed

RippleApi.kt:217 — ignore_default wording overstated what the flag does. Correct, and the trace is right: rippled increments the page counter and pins the marker per ledger object before the default-line filter runs (AccountLines.cpp:207 ahead of :229), so the marker sequence is byte-identical with and without the flag. The old comment claimed it "keeps the walk short," which is wrong. Reworded to state that it shrinks each response body, plus an explicit note that it does not shorten the walk and why — so nobody later relies on it to bound MAX_ACCOUNT_LINES_PAGES.

Kept the flag itself: smaller bodies still mean less transfer and less parsing per page. Left RippleAccountLinesTest alone — its assertion only claims server-side filtering, which is accurate.

Comment-only change, 4 insertions / 3 deletions, no behaviour touched.

Declined

None this round.

Verification

  • ./gradlew :data:ktfmtCheckMain --rerun-tasks — passes.
  • Unit tests could not run in this environment: :data:debugCompileClasspath fails to resolve com.trustwallet:wallet-core:4.7.0 (HTTP 401 from maven.pkg.github.com, no GitHub package credentials available here). Flagging that rather than implying a green test run — the change is a source comment, so no test outcome can differ from d497b467a, where the Ripple suites last passed. CI will confirm.

@rkokhatskyi rkokhatskyi 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.

LGTM

@johnnyluo
johnnyluo added this pull request to the merge queue Jul 26, 2026
Merged via the queue into main with commit 8790619 Jul 26, 2026
4 checks passed
@johnnyluo
johnnyluo deleted the aminsato/5210_xrp_token_balances branch July 26, 2026 05:47
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.

feat(ripple): show XRP token balances in the asset list

4 participants