Skip to content

XLS-82d MPT-DEX support#704

Open
cybele-ripple wants to merge 49 commits intomainfrom
MPT-XLS-82d-integration-DEX
Open

XLS-82d MPT-DEX support#704
cybele-ripple wants to merge 49 commits intomainfrom
MPT-XLS-82d-integration-DEX

Conversation

@cybele-ripple
Copy link
Copy Markdown
Collaborator

Added support for XLS-82d

@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 24, 2026

Codecov Report

❌ Patch coverage is 98.34711% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.51%. Comparing base (487216c) to head (6dc1b9d).

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     92.74%   93.51%   +0.76%     
- Complexity     2276     2327      +51     
============================================
  Files           460      463       +3     
  Lines          5942     6045     +103     
  Branches        480      493      +13     
============================================
+ Hits           5511     5653     +142     
+ Misses          279      237      -42     
- Partials        152      155       +3     

☔ View full report in Codecov by Sentry.
📢 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.

() -> 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().

Copy link
Copy Markdown
Collaborator

@Patel-Raj11 Patel-Raj11 Mar 11, 2026

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?


// 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.

@cybele-ripple
Copy link
Copy Markdown
Collaborator Author

Looks like the build is broken here (and the branch is out of date). Ping once those are fixed and I can take another pass at reviewing.

The build is failing because the integration tests that reply on the MPT amendment are not in the develop rippled image. I can add "disabled" to tests that would be effected by the MPT ammendment for the time being then create an additional PR that removes these changes. Let me know your thoughts: @sappenin

@sappenin
Copy link
Copy Markdown
Collaborator

sappenin commented Mar 24, 2026

The build is failing because the integration tests that reply on the MPT amendment are not in the develop rippled image.

We should get a new rippled image with the new changes.

For now it seems fine to leave the build broken (no need to do extra work) but we'll need a clean local build before merging.

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 asset type confusion in this financial library. On XRPL, "xrp" (lowercase, hex 787270) is a valid IOU currency distinct from native "XRP" (585250). This silently deserializes such IOUs as native XRP, dropping the issuer field—potentially confusing worthless IOUs with real XRP.
Helpful? Add 👍 / 👎

💡 Fix Suggestion

Suggestion: Change the case-insensitive comparison "XRP".equalsIgnoreCase(currency) to a case-sensitive comparison "XRP".equals(currency) on line 63. On the XRP Ledger, currency codes are case-sensitive: "XRP" (hex 585250) is the reserved native asset, while "xrp" (hex 787270) is a valid IOU currency code. Using equalsIgnoreCase causes IOU tokens with lowercase variants like "xrp" to be silently deserialized as native XRP, dropping the issuer field. This is also inconsistent with the existing PathCurrencyDeserializer which correctly uses case-sensitive "XRP".equals(currency) at line 68. 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)) {

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)) {

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) {

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)) {

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 checks use independent if statements, but fromParser() uses else if (mutual exclusion). If both flags are set in the type byte, toJson() will attempt to read 24 additional bytes for MPT after already reading 20 bytes for currency, reading past the stored data and causing incorrect deserialization or a crash when processing malformed path data from an untrusted node.
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(), consistent with fromParser() (line 69) and fromJson() (line 103). This prevents toJson() from trying to read MPT bytes when currency bytes were already consumed, which would cause an IndexOutOfBoundsException on malformed data with both flags set.

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

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

- 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)) {

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) {

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.");
}
}

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)) {

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) {

…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) {

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)) {

…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%.
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