Skip to content

XLS-82d MPT-DEX support#704

Open
cybele-ripple wants to merge 54 commits into
mainfrom
MPT-XLS-82d-integration-DEX
Open

XLS-82d MPT-DEX support#704
cybele-ripple wants to merge 54 commits into
mainfrom
MPT-XLS-82d-integration-DEX

Conversation

@cybele-ripple

Copy link
Copy Markdown
Collaborator

Added support for XLS-82d

@codecov

codecov Bot commented Feb 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.34711% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.19%. Comparing base (6234b48) to head (4c090d2).
⚠️ Report is 6 commits behind head on main.

Files with missing lines Patch % Lines
.../org/xrpl/xrpl4j/codec/binary/types/IssueType.java 91.66% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main     #704      +/-   ##
============================================
+ Coverage     93.50%   94.19%   +0.68%     
- Complexity     2606     2657      +51     
============================================
  Files           486      489       +3     
  Lines          6578     6681     +103     
  Branches        565      578      +13     
============================================
+ Hits           6151     6293     +142     
+ Misses          270      228      -42     
- Partials        157      160       +3     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/ledger/Issue.java
BookOffersRequestParams params = BookOffersRequestParams.builder()
.taker(Address.of("r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59"))
.takerGets(Issue.XRP)
.takerGets(CurrencyIssue.XRP)

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.

Hello Cybele, building on Raj's suggestions above:
I feel the following tests in this file could be improved by including an MPT in the input parameters of the following tests: ripplePathFind, bookOffers, ammInfo --> these methods only have XRP/IOU input combinations in the test.

ledgerEntry test could use an amm: <mpt1, mpt2> input, instead of only relying on the 256-byte cryptographic hash of the ledger object.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

These tests include MPTs

.account(Address.of("razqQKzJRdB4UxFPWf5NEpEG3WMkmwgcXA"))
.build()
).isInstanceOf(IllegalArgumentException.class)
.hasMessage("mpt_issuance_id is mutually exclusive with account, currency, and issuer in a PathStep.");

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.

Suggested change
.hasMessage("mpt_issuance_id is mutually exclusive with account, currency, and issuer in a PathStep.");
.hasMessage("mpt_issuance_id is mutually exclusive with account and currency in a PathStep.");

In a PathStep element, mpt_issuance_id can be used in conjunction with an issuer value. However, the issuer value is optional. If provided, the issuer must match the issuer-of-the-mpt_issuance_id

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Added here

BookOffersRequestParams params = BookOffersRequestParams.builder()
.taker(Address.of("r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59"))
.takerGets(Issue.XRP)
.takerGets(XrpIssue.XRP)

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.

This would constitute a breaking-change. Going forwards, users will not be able to use Issue.XRP in their Java workflows. Am I correct?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes this would be a breaking change however this PR already introduces breaking changes related to MPT. These changes are breaking but would add clarity to the code base based on this comment: #704 (comment)

.issuingChainDoor(Address.of("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh"))
.issuingChainIssue(
Issue.builder()
IouIssue.builder()

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.

It is preferable to allow users to use the existing Issue.builder() methods for the IOU and XRP assets. This PR would necessitate all users to migrate from Issue.builder() -> IouIssue.builder(), am I correct?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

That is correct however I agree with @Patel-Raj11 that since we are already introducing breaking changes, we should include these updates for overall code clarity

.account(holderKeyPair.publicKey().deriveAddress())
.asset(MptIssue.of(mptIssuanceId))
.asset2(XrpIssue.XRP)
.amount(depositXrpAmount)

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.

can you update this test to deposit the MPTAsset instead of XRP? MPT deposits are the highlight of this work.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

* Creates an MPT/XRP AMM, then has a trader bid on the auction slot using LP tokens.
*/
@Test
void mptAmmBidOnAuctionSlot() throws JsonRpcClientErrorException, JsonProcessingException {

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 believe these integration are not necessary -- at least as a part of this PR mptAmmVoteOnTradingFee and mptAmmBidOnAuctionSlot. The core functionality of bidding and fee-voting mechanisms have not been modified in this amendment.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Both methods have now been removed from AmmIT.java

() -> this.getValidatedAccountInfo(issuerKeyPair.publicKey().deriveAddress())
);

Payment payment = Payment.builder()

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.

You need to specify the discovered path in the Payment transaction. Without an explicit Path specified, this Payment transaction could take up any of the potential paths from the source to destination.

In this case, there is only one path. However, for the purposes of testing, I feel it is more robust to explicitly specify the pathFindResult in the Payment.builder().

@Patel-Raj11 Patel-Raj11 Mar 11, 2026

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.

@cybele-ripple Was comparing the binary serialization changes with xrpl.js and looks like we are missing changes in HopObject. Can we double check if we need the changes in xrpl4j?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@ckeshava Added to this IT to include a test using pathFindResult() and pathsComputed() for more robust coverage.

@Patel-Raj11 now using else if to match the way MPT hops are handled in xrpl.js


// The path finding may or may not find a path depending on liquidity
// For this test, we're primarily verifying that the RPC call succeeds with MPT as sendMax
assertThat(pathFindResult).isNotNull();

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.

If liquidity is desired, then we can create an AMM or an outstanding offer on the DEX. However, we should test for assertThat(pathFindResult.alternatives().length > 0); (I'm not sure of the exact Java syntax)

Users will need to access this alternatives list to grab the necessary paths. This is the most critical aspect of the response that we should test.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Added a test to ensure the pathFindResult.alternatives() is not empty here

Comment on lines +62 to +63
// Check if it's XRP (case-insensitive)
if ("XRP".equalsIgnoreCase(currency)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Severity: MEDIUM

Case-insensitive XRP check causes type confusion. XRPL protocol states "Currency codes are case-sensitive" and only uppercase "XRP" is disallowed (source). An IOU with currency "xrp" (lowercase) would be silently deserialized as native XRP, dropping the issuer — potentially leading to incorrect financial transaction construction.
Helpful? Add 👍 / 👎

💡 Fix Suggestion

Suggestion: Change equalsIgnoreCase to equals on line 63 to perform a case-sensitive comparison. According to the XRPL protocol, currency codes are case-sensitive and only uppercase "XRP" is the disallowed native asset representation. Using case-insensitive matching could cause an IOU with currency "xrp" (or "Xrp", etc.) to be silently deserialized as native XRP, dropping the issuer field. Also update the comment on line 62 to reflect the change.

⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.

Suggested change
// Check if it's XRP (case-insensitive)
if ("XRP".equalsIgnoreCase(currency)) {
// Check if it's XRP (case-sensitive per XRPL protocol)
if ("XRP".equals(currency)) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The equalsIgnoreCase is intentional and has explicit test coverage

builder.currency(new CurrencyType().fromParser(parser).toJson());
}

if ((type & TYPE_MPT) > 0) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Severity: MEDIUM

In toJson(), TYPE_MPT is checked with an independent if while fromParser() uses else if (mutually exclusive with TYPE_CURRENCY). When both flags are set (0x50) in stored binary data, fromParser stores only 20 currency bytes, but toJson attempts to parse both currency (20B) and MPT (24B) sequentially — causing a buffer overread/exception on crafted path data.
Helpful? Add 👍 / 👎

💡 Fix Suggestion

Suggestion: Change the independent if at line 131 to else if to match the mutual-exclusion logic used in fromParser(). In fromParser(), TYPE_CURRENCY and TYPE_MPT are handled with if/else if, meaning only one branch is taken when both flags are set. The toJson() method must mirror this behavior; otherwise, when both flags are present (e.g., type=0x50), toJson will attempt to read bytes for both currency (20B) and MPT (24B) sequentially from a buffer that only contains one of them, causing a buffer overread/exception.

⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.

Suggested change
if ((type & TYPE_MPT) > 0) {
} else if ((type & TYPE_MPT) > 0) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

String currency = node.get("currency").asText();

// Check if it's XRP
if ("XRP".equals(currency)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Severity: LOW

Inconsistent XRP detection: PathCurrencyDeserializer uses case-sensitive equals here, while IssueDeserializer uses case-insensitive equalsIgnoreCase. The same JSON {"currency":"xrp"} deserialized via Issue yields native XRP, but via PathCurrency it falls through to IOU handling — causing divergent behavior in pathfinding vs. other contexts.
Helpful? Add 👍 / 👎

💡 Fix Suggestion

Suggestion: Change the case-sensitive equals comparison to equalsIgnoreCase on line 68 to match the behavior used in IssueDeserializer. This ensures consistent XRP detection across all deserializers, even for non-canonical casing like "xrp" or "Xrp".

⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.

Suggested change
if ("XRP".equals(currency)) {
if ("XRP".equalsIgnoreCase(currency)) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

equalsIgnoreCase is now used which is consistent with IssueDeserializer

Comment thread xrpl4j-core/src/main/java/org/xrpl/xrpl4j/codec/binary/types/HopType.java Outdated
- Add tfMptCanTrade(true) to MPT issuance in CheckIT tests; required by
  canTrade() check in rippled develop's CheckCreate/CheckCash preclaim
- Add @DisabledIf(shouldNotRunMptChecks) to skip MPT check tests on
  testnet/devnet/clio where MPTokensV2 is not yet enabled
- Add useDevelop system property support to XrplEnvironment to use
  local rippleci/xrpld:develop Docker container for integration tests
- Rename build_devnet_its CI job to build_develop_its and switch from
  -DuseDevnet to -DuseDevelop so MPT tests run against develop image
- Fix pre-existing checkstyle violations in OfferIT, PathFindIT,
  CheckIT, and XrplEnvironment
Comment on lines +62 to +63
// Check if it's XRP (case-insensitive)
if ("XRP".equalsIgnoreCase(currency)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Severity: MEDIUM

XRPL protocol defines currency codes as case-sensitive, and only "XRP" (uppercase) is reserved for the native asset. Using equalsIgnoreCase means a crafted IOU with currency "xrp" (lowercase) — which is a valid IOU currency on-ledger — would be silently misidentified as native XRP, potentially deceiving users about the asset type in a financial transaction.
Helpful? Add 👍 / 👎

💡 Fix Suggestion

Suggestion: Change equalsIgnoreCase to equals on line 63 to perform a case-sensitive comparison. The XRPL protocol treats currency codes as case-sensitive, and only uppercase "XRP" designates the native asset. Using equalsIgnoreCase would cause a crafted IOU with currency "xrp" (lowercase) to be silently misidentified as native XRP. Also update the comment on line 62 to remove the "(case-insensitive)" note. This is consistent with the sibling PathCurrencyDeserializer which already uses case-sensitive equals("XRP").

⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.

Suggested change
// Check if it's XRP (case-insensitive)
if ("XRP".equalsIgnoreCase(currency)) {
// Check if it's XRP
if ("XRP".equals(currency)) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

equalsIgnoreCase is intentionally used and has explicit test coverage

builder.currency(new CurrencyType().fromParser(parser).toJson());
}

if ((type & TYPE_MPT) > 0) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Severity: MEDIUM

toJson() uses independent if blocks for TYPE_CURRENCY and TYPE_MPT, while fromParser() uses else if (mutual exclusion). If a crafted binary hop has both flags set (type 0x50), fromParser stores only currency data, but toJson attempts to read both — causing a buffer over-read on the internal BinaryParser and potential data misinterpretation.
Helpful? Add 👍 / 👎

💡 Fix Suggestion

Suggestion: Change the independent if block for TYPE_MPT on line 131 to else if so that toJson() uses the same mutual exclusion logic as fromParser(). This prevents attempting to read MPT data from the buffer when both TYPE_CURRENCY and TYPE_MPT flags are set, which would cause a buffer over-read since fromParser() only stored currency data in that case.

⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.

Suggested change
if ((type & TYPE_MPT) > 0) {
} else if ((type & TYPE_MPT) > 0) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Comment on lines +63 to +68
/**
* Validate that the currency is not "XRP" (case-insensitive).
*/
@Value.Check
default void checkCurrencyNotXrp() {
if ("XRP".equalsIgnoreCase(currency())) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Severity: LOW

Same case-insensitivity issue as in IssueDeserializer. The XRPL protocol treats "xrp" (lowercase) as a valid IOU currency code distinct from native "XRP". This check prevents constructing an IouIssue for the legitimate IOU currency "xrp", breaking the library's ability to represent valid on-ledger assets.
Helpful? Add 👍 / 👎

💡 Fix Suggestion

Suggestion: Change the case-insensitive comparison "XRP".equalsIgnoreCase(currency()) to a case-sensitive comparison "XRP".equals(currency()). The XRPL protocol treats currency codes case-sensitively: only the exact uppercase string "XRP" is reserved for the native asset. Lowercase variants like "xrp" or "Xrp" are legitimate 3-character IOU currency codes and should not be rejected. Also update the Javadoc comment to reflect the case-sensitive check.

⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.

Suggested change
/**
* Validate that the currency is not "XRP" (case-insensitive).
*/
@Value.Check
default void checkCurrencyNotXrp() {
if ("XRP".equalsIgnoreCase(currency())) {
/**
* Validate that the currency is not "XRP".
*/
@Value.Check
default void checkCurrencyNotXrp() {
if ("XRP".equals(currency())) {
throw new IllegalStateException("IouIssue currency cannot be 'XRP'. Use XrpIssue instead.");
}
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

equalsIgnoreCase is intentionally used and has explicit test coverage

Comment on lines +62 to +63
// Check if it's XRP (case-insensitive)
if ("XRP".equalsIgnoreCase(currency)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Severity: MEDIUM

Changed from case-sensitive equals to equalsIgnoreCase. XRPL protocol treats currency codes case-sensitively; "xrp" (lowercase) is a valid IOU currency code distinct from native "XRP". This silently converts such IOUs to native XRP, dropping the issuer field—potentially causing financial asset misidentification in payment processing.
Helpful? Add 👍 / 👎

💡 Fix Suggestion

Suggestion: Change "XRP".equalsIgnoreCase(currency) back to "XRP".equals(currency) on line 63. The XRPL protocol treats currency codes case-sensitively: only uppercase "XRP" designates native XRP, while lowercase variants like "xrp" are valid IOU currency codes. Using case-insensitive comparison silently converts such IOUs to native XRP, dropping the issuer field and causing asset misidentification. This also aligns with the case-sensitive comparison used in PathCurrencyDeserializer.java (line 68) and the binary codec layer. Also update the comment on line 62 to remove the "(case-insensitive)" annotation.

⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.

Suggested change
// Check if it's XRP (case-insensitive)
if ("XRP".equalsIgnoreCase(currency)) {
// Check if it's XRP
if ("XRP".equals(currency)) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

equalsIgnoreCase is intentionally used and has explicit test coverage

builder.currency(new CurrencyType().fromParser(parser).toJson());
}

if ((type & TYPE_MPT) > 0) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Severity: MEDIUM

toJson() checks TYPE_CURRENCY and TYPE_MPT with independent if blocks (lines 127, 131), but fromParser() (line 67-70) uses else if for mutual exclusion. When both flags are present in the type byte, toJson() reads beyond stored data, causing incorrect parsing of payment path binary data.
Helpful? Add 👍 / 👎

💡 Fix Suggestion

Suggestion: Change the independent if on line 131 to else if so that TYPE_CURRENCY and TYPE_MPT are mutually exclusive in toJson(), matching the else if pattern already used in fromParser() (line 69) and fromJson() (line 103). This ensures that when both flags are unexpectedly set in the type byte, toJson() only reads the currency data (which is all fromParser() stored), preventing out-of-bounds reads or data corruption.

⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.

Suggested change
if ((type & TYPE_MPT) > 0) {
} else if ((type & TYPE_MPT) > 0) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

…kensV2 support

Add @DisabledIf(shouldNotRunMptDex) to mptOfferCreateAndVerifyWithBookOffers,
mptOfferCrossing, mptOfferCreateWithIouAndVerifyWithBookOffers in OfferIT and
ripplePathFindWithMptDestination, ripplePathFindAlternativesNonEmptyWithDexLiquidity
in PathFindIT. Tests still run against the local rippleci/xrpld:develop Docker image
which has MPTokensV2 enabled; they are skipped only when useTestnet, useClioTestnet,
or useDevnet is set. Also adds -DuseDevelop as an explicit alias for the local Docker
environment in XrplEnvironment.
…ronments

AmmIT mptAmm* tests fail with "Amount can not be MPT." on testnet because
MPT AMM support requires MPTokensV2, which is only enabled in the develop
rippled image.

MpTokenIT.mptIssuanceWithPermissionedDomainSuccessAndFailure and
PermissionedDomainIT.testPermissionedDomainCreateUpdateAndDelete fail with
temDISABLED on testnet because the PermissionedDomain feature is not yet
enabled there.

Add @DisabledIf guards following the same pattern used in OfferIT,
PathFindIT, and CheckIT to skip these tests when running against
testnet/clio/devnet environments.
Comment on lines +129 to +131
}

if ((type & TYPE_MPT) > 0) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Severity: MEDIUM

toJson() uses independent if blocks for TYPE_CURRENCY (line 127) and TYPE_MPT (line 131), but fromParser() uses else if (line 67-71), treating them as mutually exclusive. When a binary type byte has both flags set (e.g., 0x50), toJson() will attempt to read MPT bytes (24) that were never stored by fromParser(), causing a buffer over-read on the internal BinaryParser — reading garbage data or throwing an exception in this financial codec.
Helpful? Add 👍 / 👎

💡 Fix Suggestion

Suggestion: Change the independent if block for TYPE_MPT on line 131 to else if to match the mutually exclusive treatment in fromParser() (line 67-71) and fromJson() (line 100-106). This ensures that when both TYPE_CURRENCY and TYPE_MPT flags are set in a malformed type byte, toJson() only reads the currency bytes (which is what fromParser() stored), preventing a buffer over-read on the internal BinaryParser.

⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.

Suggested change
}
if ((type & TYPE_MPT) > 0) {
} else if ((type & TYPE_MPT) > 0) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Comment on lines +62 to +63
// Check if it's XRP (case-insensitive)
if ("XRP".equalsIgnoreCase(currency)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Severity: LOW

Changed from case-sensitive equals to equalsIgnoreCase for XRP detection. XRPL currency codes are case-sensitive; a hypothetical custom IOU with code "xrp" (lowercase) would now be silently misidentified as native XRP. This is inconsistent with PathCurrencyDeserializer which uses case-sensitive equals, meaning the same JSON can be parsed as different asset types depending on code path — a type-confusion risk in a financial library.
Helpful? Add 👍 / 👎

💡 Fix Suggestion

Suggestion: Change equalsIgnoreCase back to case-sensitive equals on line 63 to be consistent with XRPL's case-sensitive currency codes and with PathCurrencyDeserializer (which uses "XRP".equals(currency)). Also update the comment on line 62 to remove the "case-insensitive" note.

⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.

Suggested change
// Check if it's XRP (case-insensitive)
if ("XRP".equalsIgnoreCase(currency)) {
// Check if it's XRP
if ("XRP".equals(currency)) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

equalsIgnoreCase is intentionally used and has explicit test coverage

cybele-ripple and others added 6 commits May 5, 2026 15:34
…t and JsonNode

Implements #773.

In AmountType, the IOU encoding path previously used a package-private
Amount model (currency + value + issuer as Strings) to round-trip through
Jackson. The value field was the only thing actually needed from that
deserialization; currency and issuer were read directly from the JsonNode.

- fromJson() IOU path: read value directly from JsonNode instead of
  deserializing into the codec-private Amount class.
- toJson() IOU path: build IssuedCurrencyAmount (the model-layer
  CurrencyAmount subtype) instead of the codec-private Amount, aligning
  the binary codec output type with the rest of the codebase.
- Delete codec/binary/types/Amount.java (no longer referenced anywhere).

In IssueType, a package-private Issue model with Optional<JsonNode> fields
was used purely to shuttle data between the raw JsonNode and the binary
encoding logic.

- fromJson(): access mpt_issuance_id, currency, and issuer directly on
  the JsonNode via has()/get() — no intermediate model needed.
- toJson(): build ObjectNode responses directly instead of serializing
  through the codec-private Issue model.
- Delete codec/binary/types/Issue.java (no longer referenced anywhere).

Both deleted classes were internal implementation details of the codec
with no public API exposure.
Fix CI: the IOU branch of AmountType.toJson() was rewritten in 2542bef
to serialize via the model class IssuedCurrencyAmount instead of the
codec-private Amount class. IssuedCurrencyAmount declares fields as
{value, currency, issuer} while Amount used {currency, value, issuer},
so JSON output ordering flipped and AmountTypeTest.decodeCurrencyAmount,
AmountTypeTest.decodeNegativeCurrencyAmount, and
XrplBinaryCodecTest.encodeDecodeIssuedCurrency began failing across the
unit-test CI matrix. d5d4bfd already reverted the analogous IssueType
change but missed AmountType. Restore Amount.java and revert the IOU
branches of AmountType.fromJson/toJson to main, mirroring the IssueType
revert.

Also revert changes that are unrelated to XLS-82d or issue #773:

- MpTokenIT: restore class-level @DisabledIf("shouldNotRun") and the
  shouldNotRun() predicate that were removed. Whether existing
  MPTokens v1 tests should now run on testnet/clio is independent of
  XLS-82d. The new mptIssuanceWithPermissionedDomainSuccessAndFailure
  test keeps its own method-level skip.

- PermissionedDomainIT: revert addition of a method-level
  @DisabledIf("shouldNotRunPermissionedDomain") on
  testPermissionedDomainCreateUpdateAndDelete. The PermissionedDomain
  amendment is unrelated to MPT-DEX.

- ServerInfoIT: narrow shouldSkipPublicServerTests() to only check
  useDevelop. Skipping on the local develop Docker job is required
  (the test hits public XRPL servers); the broader CI/useTestnet/
  useClioTestnet/useDevnet conditions were masking behavior on jobs
  that were green on main.

- XrplClient.ledgerEntry, MultiSignedTransaction.builder,
  SingleSignedTransaction.builder, BatchSigner.checkAndNormalize, and
  FeeUtils.computeNetworkFees: revert Javadoc-only tag additions on
  methods that this PR does not otherwise touch. Build succeeds with
  javadoc generation enabled without them.
Earlier carve-out narrowed shouldSkipPublicServerTests() to only check
useDevelop, on the (incorrect) reasoning that the broader predicate was
masking pre-existing flakes on jobs that were green on main.

In fact, the broader predicate handled a real failure mode:
testServerInfoAcrossAllTypes hits public XRPL servers whose TLS
certificate chain fails PKIX validation on some Temurin JDK builds
(notably 16 and 21) due to cacerts trust-store gaps. It was passing on
main as recently as 2026-04-29; this run shows it failing on
build_jdk_temurin_other (16) and (21) with:

  PKIX path building failed:
  unable to find valid certification path to requested target
  executing POST https://s.altnet.rippletest.net:51234/

Restore the original predicate (CI env var + the four use* properties)
and add a comment explaining why this skip is needed.
Add 8 unit tests to bring four PR-touched files to 100% instruction
coverage:

- PathCurrencyTest: assert IllegalArgumentException from deprecated
  PathCurrency.of(String) when the currency is not "XRP", and
  NullPointerException from PathCurrency.of((Issue) null).

- IouIssueTest: assert IllegalStateException from the @Value.Check
  validator when currency() is "XRP" or "xrp" (case-insensitive).

- PathCurrencySerializerTest: cover the deprecated CurrencyIssue
  branch when issuer is present (happy path and IOException catch),
  and the XrpIssue IOException catch.

- HopTypeTest: cover the TYPE_ACCOUNT branch in HopType.toJson() via
  a Hop with only an account.

xrpl4j-core line coverage: 96.36% -> 96.53%.
xrpl4j-core instruction coverage: 95.76% -> 95.95%.
Comment on lines +62 to +63
// Check if it's XRP (case-insensitive)
if ("XRP".equalsIgnoreCase(currency)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Severity: MEDIUM

The case-insensitive comparison (equalsIgnoreCase) treats IOU tokens with currency codes like "xrp" or "Xrp" as native XRP. Per XRPL docs, currency codes are case-sensitive and only uppercase "XRP" is reserved. An attacker could issue an IOU with currency code "xrp" that this library would misidentify as native XRP, enabling financial manipulation.
Helpful? Add 👍 / 👎

💡 Fix Suggestion

Suggestion: Replace equalsIgnoreCase with equals for a case-sensitive comparison of the currency code. Per the XRPL protocol specification, currency codes are case-sensitive and only the exact uppercase string "XRP" is reserved for the native asset. Using equalsIgnoreCase would cause IOU tokens with currency codes like "xrp" or "Xrp" to be misidentified as native XRP. Also update the comment on line 62 to reflect the case-sensitive check. This is consistent with PathCurrencyDeserializer.java:68 which already uses "XRP".equals(currency).

⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.

Suggested change
// Check if it's XRP (case-insensitive)
if ("XRP".equalsIgnoreCase(currency)) {
// Check if it's XRP (case-sensitive, per XRPL currency format spec)
if ("XRP".equals(currency)) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

equalsIgnoreCase is intentionally used and has explicit test coverage

builder.currency(new CurrencyType().fromParser(parser).toJson());
}

if ((type & TYPE_MPT) > 0) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Severity: MEDIUM

In toJson(), TYPE_CURRENCY and TYPE_MPT are checked with independent if blocks, but fromParser() uses else if (line 69). If binary data has both flags set, toJson() reads currency (20 bytes) and MPT (24 bytes) from a buffer that only stored one, causing a buffer overread into adjacent hop/path data and corrupting the deserialized output.
Helpful? Add 👍 / 👎

💡 Fix Suggestion

Suggestion: Change the if on line 131 to else if so that TYPE_CURRENCY and TYPE_MPT are treated as mutually exclusive in toJson(), consistent with fromParser() (line 69) and fromJson(). If both flags are set, only the currency branch should execute (matching the backward-compatibility preference stated in the comments). Replace:

    if ((type & TYPE_MPT) > 0) {

with:

    } else if ((type & TYPE_MPT) > 0) {

This prevents a buffer overread when both TYPE_CURRENCY and TYPE_MPT flags are set simultaneously.

⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.

Suggested change
if ((type & TYPE_MPT) > 0) {
} else if ((type & TYPE_MPT) > 0) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Comment on lines +64 to +68
* Validate that the currency is not "XRP" (case-insensitive).
*/
@Value.Check
default void checkCurrencyNotXrp() {
if ("XRP".equalsIgnoreCase(currency())) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Severity: MEDIUM

The case-insensitive check rejects IOU tokens with valid XRPL currency codes like "xrp" or "Xrp". The XRPL protocol only reserves uppercase "XRP" (source). Combined with the IssueDeserializer change, tokens with these valid currency codes are silently converted to native XRP, misrepresenting the actual asset.
Helpful? Add 👍 / 👎

💡 Fix Suggestion

Suggestion: Change equalsIgnoreCase to equals on line 68 so that only the exact uppercase string "XRP" is rejected. The XRPL protocol only reserves the exact uppercase "XRP" as the native asset identifier; other casings like "xrp" or "Xrp" are valid 3-character IOU currency codes. Also update the Javadoc comment on line 64 to reflect the case-sensitive check. The same fix should be applied in IssueDeserializer.java at line 63, where equalsIgnoreCase should also be changed to equals.

⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.

Suggested change
* Validate that the currency is not "XRP" (case-insensitive).
*/
@Value.Check
default void checkCurrencyNotXrp() {
if ("XRP".equalsIgnoreCase(currency())) {
* Validate that the currency is not "XRP".
*/
@Value.Check
default void checkCurrencyNotXrp() {
if ("XRP".equals(currency())) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

equalsIgnoreCase is intentionally used and has explicit test coverage

…nsitive XRP design

HopType.toJson() used independent if-blocks for TYPE_CURRENCY and TYPE_MPT,
while fromParser/fromJson both used else-if (mutually exclusive). If a binary
hop had both flags set toJson would attempt to read MPT bytes that were never
stored, corrupting output. Changed to else-if to match.

PathCurrencyDeserializer used case-sensitive equals() for XRP detection while
IssueDeserializer uses equalsIgnoreCase(). Same JSON {"currency":"xrp"} produced
XrpIssue via IssueDeserializer but CurrencyIssue("xrp") via PathCurrencyDeserializer.
Changed to equalsIgnoreCase() and added matching test coverage.
The DEX-liquidity test now submits a cross-currency Payment after calling
ripple_path_find, using the first alternative's pathsComputed and sourceAmount
directly.  This proves the discovered path is valid and executable, not just
that alternatives is non-empty.

Also fixes three double-submit bugs in the same test where
xrplClient.submit() was called a second time just to read validatedLedgerIndex.
Comment on lines +62 to +63
// Check if it's XRP (case-insensitive)
if ("XRP".equalsIgnoreCase(currency)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Severity: MEDIUM

Changed from case-sensitive equals to equalsIgnoreCase. Per XRPL protocol docs, "Currency codes are case-sensitive" and only "XRP" (all-uppercase) is reserved. An IOU with currency "xrp" is a valid distinct asset, but this deserializer now silently converts it to native XRP, discarding the issuer — leading to asset type confusion in a financial library.
Helpful? Add 👍 / 👎

💡 Fix Suggestion

Suggestion: Revert equalsIgnoreCase back to equals for the XRP currency check. Per the XRPL protocol specification, currency codes are case-sensitive, and only the exact uppercase string "XRP" is reserved as the native currency identifier. Using case-insensitive comparison would silently convert a valid IOU with a case-variant currency code (e.g., "xrp", "Xrp") to native XRP, discarding the issuer and causing asset type confusion.

⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.

Suggested change
// Check if it's XRP (case-insensitive)
if ("XRP".equalsIgnoreCase(currency)) {
// Check if it's XRP (case-sensitive per XRPL protocol)
if ("XRP".equals(currency)) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

equalsIgnoreCase is intentionally used and has explicit test coverage

AmmIT was missed when PR #787 fixed the same class of flakiness across
nine other integration-test files.  After scanForFinality returns, a Clio
cluster node may not yet have replicated the newly-validated ledger state,
causing actNotFound on ammInfo or stale AMM balances on subsequent reads.

Apply the same guard introduced by #787: after each scanForFinality, poll
getValidatedAccountInfo until the account sequence reflects the expected
post-transaction value.  This confirms the responding Clio node has
indexed the transaction before any AMM-state queries are issued.

Specific changes:
- createAmm: guard before getAmmInfo (fixes actNotFound on all callers)
- depositXrp: guard + getValidatedAccountInfo + CURRENT→VALIDATED for accountLines
- depositAndVoteOnTradingFee: guard after vote + CURRENT→VALIDATED for both accountLines calls
- depositAndWithdraw: guard after withdraw
- depositAndBid: guard after bid
run: mvn clean install -Dmaven.javadoc.skip=true -Pandroid

build_devnet_its:
build_develop_its:

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.

Clarify this change?

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.

4 participants