Skip to content

Add Single Asset Vault support - XLS-65#713

Open
Patel-Raj11 wants to merge 21 commits intomainfrom
rp/single-asset-vault
Open

Add Single Asset Vault support - XLS-65#713
Patel-Raj11 wants to merge 21 commits intomainfrom
rp/single-asset-vault

Conversation

@Patel-Raj11
Copy link
Copy Markdown
Collaborator

Single Asset Vault - XLS-65
Single Asset Vault had multiple PRs, so rippled/xrpld release 3.1.0 was considered as source of truth for Transactions, ledger objects, RPC and binary codec changes.

Transactions added/updated:

  1. VaultCreate
  2. VaultSet
  3. VaultDelete
  4. VaultDeposit
  5. VaultWithdraw
  6. VaultClawback
  7. MpTokenIssuanceCreate
  8. MpTokenIssuanceSet

Ledger objects added/updated:

  1. VaultObject
  2. MpTokenIssuanceObject

RPC added/updated:

  1. vault_info

Binary codec added/updated:

  1. IssueType
  2. NumberType

MPT DEX PR and this PR both have IssueType binary codec change and Issue object refactoring change in common. Since, Single Asset Vault is up for voting, this PR would likely get merged first. So, I have kept these suggestions in mind when making changes:

  1. XLS-82d MPT-DEX support #704 (comment)
  2. XLS-82d MPT-DEX support #704 (comment)
  3. XLS-82d MPT-DEX support #704 (comment)
  4. XLS-82d MPT-DEX support #704 (comment)

@Patel-Raj11 Patel-Raj11 requested review from cybele-ripple and removed request for nhartner and nkramer44 March 16, 2026 19:50

UnsignedByteArray byteArray = new CurrencyType().fromJson(issue.currency()).value();
if (issue.mptIssuanceId().isPresent()) {
// MPTokenIssuanceID binary format: issuer (20 bytes) + ACCOUNT_ONE (20 bytes) + sequence (4 bytes) = 44 bytes
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.

Remind me why rippled adds an additional ACCOUNT_ONE into the binary encoding? I feel like there was a good reason, but I can't remember it.

Also, we should update the comment description to properly describe the two different formats. Currently, the only descriptor is binary vs hex, which is confusing (generally binary and hex should be the same, but in this case they're not, which means they're describing two different values)

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.

why rippled adds an additional ACCOUNT_ONE into the binary encoding?

IOU - currency (20 bytes) || issuer (20 bytes)
MPT - issuer (20 bytes) || ACCOUNT_ONE (20 bytes) || sequence (4 bytes)

I think ACCOUNT_ONE acts as a discriminator between IOU and MPT for deserializer. Reading first 20 bytes helps us to distinguish between XRP (all zeros) and IOU/MPT, but it won't be possible to distinguish between IOU and MPT if ACCOUNT_ONE is not present.

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.

Updated the code comment in 5256ce8

// After rounding, mantissa may exceed MAX_INT64 again
if (absMantissa.compareTo(MAX_INT64) > 0) {
if (exponent >= MAX_EXPONENT) {
throw new IllegalArgumentException("Exponent overflow: value too large to represent");
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 line seems uncovered by the unit 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.

Addressed in 5256ce8

lastDigit = absMantissa.mod(BigInteger.TEN);
absMantissa = absMantissa.divide(BigInteger.TEN);
exponent++;
if (lastDigit.compareTo(BigInteger.valueOf(5)) >= 0) {
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 line seems uncovered by the unit 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.

Addressed in 5256ce8

* @return An optionally present {@link JsonNode}.
*/
@JsonProperty("mpt_issuance_id")
Optional<JsonNode> mptIssuanceId();
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 is technically a breaking change (though I doubt any external software is using this object, but you never know).

Can we capture somewhere a list of all breaking changes in this PR? We'll want to use that to draft release notes. We should probably have (as part of this PR) something that outlines info that will eventually make its way into a migration guide, sort of like this one.

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.

I think since mptIssuanceId is Optional, it won't be a breaking change. Any user using Issue object present in binary.types package would still be able to create Issue object the way they have been creating it, ever after the upgrade. Did I understand it incorrectly?

There is a breaking change in Issue interface present in model.ledger package this PR, and I am planning to document it.

transactionWithSignature = PermissionedDomainDelete.builder().from((PermissionedDomainDelete) transaction)
.transactionSignature(signature)
.build();
} else if (VaultCreate.class.isAssignableFrom(transaction.getClass())) {
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.

FYI - take a look at #712 and #715. That change will remove the need to update these types here.

I suggest we merge that before merging this PR, FWIW.

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.

Will rebase and adjust once #712 and #715 are merged.

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 migration guide - 31ba40e


@Override
public VaultData deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
return VaultData.of(jsonParser.getText());
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'm not sure VaultData should be a String, but maybe it should? It's going to be binary data, so if it's a String it will be Hex, right?

(maybe this is fine, but I'm trying to think if VaultData should actually just hold an array (e.g., List of UnsignedByte).

That said, perhaps there's prior art in xrpl4j for this kind of thing?

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.

VaultData is a wrapper type containing String and it has checks to make sure that the content is valid Hex.
I followed the convention that we have in xrpl4j for such metadata (MpTokenMetadata, DidData etc).

We could make this type wrap List<UnsignedByte> or UnsignedByteArray and update the serializer and deserialize to do bytes <-> String (Hex) conversion, but I think doing that won't make much difference as we will anyway provide static convenience methods like fromHex for users to create an instance of VaultData from off-chain binary data that they would have stored in Hex format.

* @return An optionally-present {@link AssetScale}.
*/
@JsonProperty("Scale")
Optional<AssetScale> scale();
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.

In the spec, scale and most of the other values have are required (but default to 0).

Looks like only data and assetsMaximum are actually optional.

See https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0065-single-asset-vault#212-fields

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.

Thanks for highlighting this. I was marking all thesoeDEFAULT fields as optional just to be on safer side. But now have updated them to default to zero value of their type. From ledger_entries.macro only
Data is soeOPTIONAL other fields are either soeREQUIRED or soeDEFAULT.

Addressed in 5256ce8

},
iouIssue -> {
long scaleVal = scaleValue.value().longValue();
if (scaleVal < 0 || scaleVal > 18) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think this line needs some more coverage.

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.

AssetScale wraps UnsignedInteger, so I have updated the check accordingly and now this line has full coverage.

Addressed in 5256ce8

.issuer(Address.of("rPZtDb6ZHtfYzMmzyUGvehoi2vYhrNAPhy"))
.build()
)
.asset2(Issue.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.

This is a breaking change - see my other comment about how we should document breakage?

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 in 95a0530

* ========================LICENSE_START=================================
* xrpl4j :: core
* %%
* Copyright (C) 2020 - 2023 XRPL Foundation and its contributors
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.

Minor nit - If you're adding new files and adding a license, can you make the date correct, like this:

Suggested change
* Copyright (C) 2020 - 2023 XRPL Foundation and its contributors
* Copyright (C) 2020 - 2026 XRPL Foundation and its contributors

(Alternatively, don't add this license and it will get taken care of, eventually, by #669)

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.

Addressed in 5256ce8 as one off. But fixing #669 is the right approach.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 24, 2026

Codecov Report

❌ Patch coverage is 97.59036% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.70%. Comparing base (e48a6a6) to head (5cd4613).

Files with missing lines Patch % Lines
...org/xrpl/xrpl4j/codec/binary/types/NumberType.java 93.97% 0 Missing and 5 partials ⚠️
...l4j/model/client/vault/VaultInfoRequestParams.java 88.23% 1 Missing and 1 partial ⚠️
.../org/xrpl/xrpl4j/model/flags/VaultCreateFlags.java 95.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main     #713      +/-   ##
============================================
+ Coverage     92.38%   92.70%   +0.32%     
- Complexity     2115     2259     +144     
============================================
  Files           437      459      +22     
  Lines          5594     5910     +316     
  Branches        430      479      +49     
============================================
+ Hits           5168     5479     +311     
  Misses          279      279              
- Partials        147      152       +5     

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

@Patel-Raj11 Patel-Raj11 requested a review from sappenin March 24, 2026 12:55
// xrpld implementation: src/libxrpl/basics/Number.cpp -> doNormalize
@Override
public NumberType fromJson(JsonNode node) {
String value = node.isInt() || node.isLong() ? String.valueOf(node.asLong()) : node.asText();
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 add a test case for when node.isInt() returns true and node.isLong() returns true? Looks like just the node.asText() true case is tested

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.

I think instead I should remove these checks as the type would always be string since AssetAmount wraps String type.

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.

Removed these checks in 4d6b600

@Override
public Issue deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
JsonNode node = jsonParser.getCodec().readTree(jsonParser);

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.

Would there be an instance where mpt_issuance_id and currency are present? If so, maybe include something like

  boolean hasMpt = node.has("mpt_issuance_id");
  boolean hasCurrency = node.has("currency");

  // Validate mutual exclusivity
  if (hasMpt && hasCurrency) {
    throw new IOException(
      "Invalid Issue JSON: cannot have both 'mpt_issuance_id' and 'currency' fields"
    );
  }

Copy link
Copy Markdown
Collaborator Author

@Patel-Raj11 Patel-Raj11 Mar 24, 2026

Choose a reason for hiding this comment

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

This codepath would get exercised when parsing the transaction/ledger object from rippled. The object returned would never have both mpt_issuance_id and currency present at the same time.

@Patel-Raj11
Copy link
Copy Markdown
Collaborator Author

/ai-review

1 similar comment
@zach-source
Copy link
Copy Markdown

/ai-review

Copy link
Copy Markdown

@xrplf-ai-reviewer xrplf-ai-reviewer bot left a comment

Choose a reason for hiding this comment

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

ℹ️ Note: This is a large diff (403,683 chars). Complex issues deep in the diff may receive less attention.

Four issues flagged inline: a typo in IssueType.java, an NPE risk in IssueDeserializer, a validation gap in VaultInfoRequestParams.check() that produces misleading errors, and missing codec fixtures for five of the six new vault transaction types.

Review by Claude Opus 4.6 · Prompt: V12

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