XLS-82d MPT-DEX support#704
Conversation
…PTs to issues and updated related tests
Codecov Report❌ Patch coverage is
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. 🚀 New features to boost your workflow:
|
| BookOffersRequestParams params = BookOffersRequestParams.builder() | ||
| .taker(Address.of("r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59")) | ||
| .takerGets(Issue.XRP) | ||
| .takerGets(CurrencyIssue.XRP) |
There was a problem hiding this comment.
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.
| .account(Address.of("razqQKzJRdB4UxFPWf5NEpEG3WMkmwgcXA")) | ||
| .build() | ||
| ).isInstanceOf(IllegalArgumentException.class) | ||
| .hasMessage("mpt_issuance_id is mutually exclusive with account, currency, and issuer in a PathStep."); |
There was a problem hiding this comment.
| .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
| BookOffersRequestParams params = BookOffersRequestParams.builder() | ||
| .taker(Address.of("r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59")) | ||
| .takerGets(Issue.XRP) | ||
| .takerGets(XrpIssue.XRP) |
There was a problem hiding this comment.
This would constitute a breaking-change. Going forwards, users will not be able to use Issue.XRP in their Java workflows. Am I correct?
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
can you update this test to deposit the MPTAsset instead of XRP? MPT deposits are the highlight of this work.
There was a problem hiding this comment.
| * Creates an MPT/XRP AMM, then has a trader bid on the auction slot using LP tokens. | ||
| */ | ||
| @Test | ||
| void mptAmmBidOnAuctionSlot() throws JsonRpcClientErrorException, JsonProcessingException { |
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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().
There was a problem hiding this comment.
@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(); |
There was a problem hiding this comment.
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.
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 |
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. |
| // Check if it's XRP (case-insensitive) | ||
| if ("XRP".equalsIgnoreCase(currency)) { |
There was a problem hiding this comment.
🟡 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.
| // Check if it's XRP (case-insensitive) | |
| if ("XRP".equalsIgnoreCase(currency)) { | |
| // Check if it's XRP | |
| if ("XRP".equals(currency)) { |
…PathStep issuer, MPT tests
| // Check if it's XRP (case-insensitive) | ||
| if ("XRP".equalsIgnoreCase(currency)) { |
There was a problem hiding this comment.
🟡 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.
| // 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) { |
There was a problem hiding this comment.
🟡 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.
| 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)) { |
There was a problem hiding this comment.
⚪ 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.
| if ("XRP".equals(currency)) { | |
| if ("XRP".equalsIgnoreCase(currency)) { |
| builder.currency(new CurrencyType().fromParser(parser).toJson()); | ||
| } | ||
|
|
||
| if ((type & TYPE_MPT) > 0) { |
There was a problem hiding this comment.
🟡 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.
| 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
| // Check if it's XRP (case-insensitive) | ||
| if ("XRP".equalsIgnoreCase(currency)) { |
There was a problem hiding this comment.
🟡 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.
| // 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) { |
There was a problem hiding this comment.
🟡 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.
| if ((type & TYPE_MPT) > 0) { | |
| } else if ((type & TYPE_MPT) > 0) { |
| /** | ||
| * Validate that the currency is not "XRP" (case-insensitive). | ||
| */ | ||
| @Value.Check | ||
| default void checkCurrencyNotXrp() { | ||
| if ("XRP".equalsIgnoreCase(currency())) { |
There was a problem hiding this comment.
⚪ 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.
| /** | |
| * 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."); | |
| } | |
| } |
| // Check if it's XRP (case-insensitive) | ||
| if ("XRP".equalsIgnoreCase(currency)) { |
There was a problem hiding this comment.
🟡 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.
| // 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) { |
There was a problem hiding this comment.
🟡 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.
| 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.
| } | ||
|
|
||
| if ((type & TYPE_MPT) > 0) { |
There was a problem hiding this comment.
🟡 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.
| } | |
| if ((type & TYPE_MPT) > 0) { | |
| } else if ((type & TYPE_MPT) > 0) { |
| // Check if it's XRP (case-insensitive) | ||
| if ("XRP".equalsIgnoreCase(currency)) { |
There was a problem hiding this comment.
⚪ 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.
| // 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%.
Added support for XLS-82d