Skip to content

Add binary codec and transaction models for MPT#131

Merged
e-desouza merged 1 commit into
fix/typed-rpc-submit-and-waitfrom
feat/mpt-binary-codec
Jun 11, 2026
Merged

Add binary codec and transaction models for MPT#131
e-desouza merged 1 commit into
fix/typed-rpc-submit-and-waitfrom
feat/mpt-binary-codec

Conversation

@e-desouza

@e-desouza e-desouza commented Feb 20, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds XLS-0033 Multi-Purpose Token (MPT) support to xrpl-rust, including typed models, binary codec support, request/result handling, and integration coverage against local xrpld/Clio services.

MPT Support

  • Add MPT amount/currency support:
    • MPTAmount
    • MPTCurrency
    • Amount::MPTAmount
    • Currency::MPTCurrency
  • Add MPT transaction models and validation:
    • MPTokenIssuanceCreate
    • MPTokenIssuanceDestroy
    • MPTokenIssuanceSet
    • MPTokenAuthorize
  • Add MPT ledger object models:
    • MPToken
    • MPTokenIssuance
  • Add MPT binary codec support for Hash192 / MPTokenIssuanceID and MPT amounts.
  • Add MPT support in transaction parsing / balance conversion helpers.
  • Add account_objects MPT filters for mpt_issuance and mptoken.

Correctness / Review Follow-ups

  • Align MPT validation with XLS-0033 and xrpld behavior:
    • 63-bit maximum MPT amount handling.
    • strict decimal-string MPT amount validation.
    • strict 48-hex-character MPTokenIssuanceID validation.
    • metadata hex validation and 1024-byte limit.
    • non-zero transfer fee requires tfMPTCanTransfer.
  • Harden Amount and Currency deserialization to avoid ambiguous MPT/issued-currency fallback.
  • Replace fragile RPC string checks with typed XRPLRpcError mapping where touched.
  • Replace hardcoded branch test literals with named fixtures.
  • Preserve no_std compatibility for model/helper paths.

Integration Coverage

Adds end-to-end MPT coverage for:

  • Issuance creation with XLS-89-style metadata.
  • Holder opt-in / issuer authorization / holder unauthorize lifecycle.
  • Issuance destroy success and destroy-with-obligations rejection.
  • MPT payments and outstanding amount updates.
  • Require-auth rejection before issuer authorization.
  • Global lock transfer rejection.
  • Maximum amount enforcement.
  • Clawback success and clawback permission rejection.
  • Holder transfer restriction when tfMPTCanTransfer is absent.
  • MPT payment binary codec round-trip.

Validation

Validated locally against Podman xrpld / Clio services:

  • cargo fmt --check
  • git diff --check
  • cargo test --features integration --lib -- --test-threads=1
  • cargo test --features integration --test integration_test -- --test-threads=1
  • cargo test --features integration --test cli_integration -- --test-threads=1
  • cargo test --no-default-features --features models --lib -- --test-threads=1
  • cargo test --no-default-features --features "models,wallet,helpers,json-rpc,embassy-rt" --lib -- --test-threads=1
  • cargo test --no-default-features --features "models,wallet,helpers,websocket,embassy-rt" --lib -- --test-threads=1
  • cargo check --no-default-features --features "models,wallet,helpers,json-rpc,websocket,embassy-rt"

@e-desouza e-desouza force-pushed the feat/mpt-binary-codec branch from baf2e82 to 841449f Compare February 21, 2026 00:01
@e-desouza e-desouza marked this pull request as draft February 21, 2026 00:11
@e-desouza e-desouza force-pushed the feat/mpt-binary-codec branch from 841449f to 7925f94 Compare February 21, 2026 05:20
@e-desouza e-desouza marked this pull request as ready for review February 22, 2026 18:28
@e-desouza e-desouza force-pushed the feat/mpt-binary-codec branch from 7925f94 to d136241 Compare March 25, 2026 21:56
@e-desouza e-desouza requested review from Patel-Raj11 and pdp2121 and removed request for LimpidCrypto March 25, 2026 22:38
@e-desouza e-desouza force-pushed the feat/mpt-binary-codec branch 5 times, most recently from 2792500 to 84f099a Compare April 1, 2026 21:52
@pdp2121

pdp2121 commented Apr 2, 2026

Copy link
Copy Markdown
Collaborator

/ai-review

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

One correctness gap flagged inline: missing asset_scale range validation.

Review by Claude Opus 4.6 · Prompt: V13

Comment thread src/models/transactions/mptoken_issuance_create.rs
e-desouza

This comment was marked as resolved.

@pdp2121

pdp2121 commented Apr 8, 2026

Copy link
Copy Markdown
Collaborator

@e-desouza can you add integration tests for all transactions?

Comment thread src/models/ledger/objects/mptoken.rs
Comment thread src/models/ledger/objects/mptoken_issuance.rs
Comment thread src/models/ledger/objects/mptoken.rs
@e-desouza

e-desouza commented Apr 10, 2026

Copy link
Copy Markdown
Collaborator Author

Added in 380da30d. There's now an integration test for each of the four MPToken transaction types: create (with a second variant that sets metadata, scale, and fee), authorize (holder opt-in), set (locking via TfMPTLock), and destroy (on an empty issuance).

The authorize/set/destroy tests all need a real issuance to work against, so I added a create_mptoken_issuance() helper in tests/common that creates one and derives the MPTokenIssuanceID from the autofilled sequence + account ID.

@e-desouza e-desouza force-pushed the feat/mpt-binary-codec branch from 380da30 to 44cc257 Compare April 16, 2026 01:43
pdp2121 pushed a commit that referenced this pull request Apr 21, 2026
… #3270) (#291)

## Summary

The `rippled` binary was renamed to `xrpld` upstream, and the
`rippleci/rippled` image stopped receiving updates. Our integration
tests across every open PR started failing because the published
`develop` image exited before becoming healthy (`Connection refused` on
`localhost:5005`, **0 passed / 41 failed**).

This PR mirrors the upstream fix in xrpl.js:
[XRPLF/xrpl.js#3270](XRPLF/xrpl.js#3270).
Switching to `rippleci/xrpld:develop` is the **actual root-cause fix**
rather than pinning an old digest of the deprecated image.

## Changes

`.github/workflows/integration_test.yml`:
- `RIPPLED_DOCKER_IMAGE` -> `XRPLD_DOCKER_IMAGE:
rippleci/xrpld:develop`.
- `docker run` simplified to `${IMAGE} --standalone` (the `xrpld` image
handles `mkdir` + launch internally; no more `bash -c "mkdir -p
/var/lib/rippled/db/ && rippled -a"` wrapper).
- Volume mount changed from `/etc/opt/ripple/` to `/etc/opt/xrpld/`.
- Container name: `rippled-service` -> `xrpld-service`.
- Removed the docker `--health-cmd` (which shelled out to the renamed
`rippled` CLI and always failed) in favour of a direct JSON-RPC poll
against `http://localhost:5005/`.
- Always dump container logs on the stop step for post-mortem
visibility.

`.ci-config/rippled.cfg` -> `.ci-config/xrpld.cfg`:
- `path=/var/lib/rippled/db/nudb` -> `path=/var/lib/xrpld/db/nudb`.
- `[database_path] /var/lib/rippled/db` -> `/var/lib/xrpld/db`.
- `[debug_logfile] /var/log/rippled/debug.log` ->
`/var/log/xrpld/debug.log`.

## Verification

Validated on throwaway PR #292 (now closed): **Integration Test green in
2m53s** on this exact workflow. Unit tests, Build & Lint, Quality Check
also pass.

## Related follow-up

The 7 in-flight PRs (#130, #131, #151, #153, #156, #157, #158) currently
carry a stopgap commit pinning `rippleci/rippled:develop` to a specific
digest. After this PR merges to `main`, those branches should:
1. Rebase on `main` to pick up the xrpld switch, or
2. Cherry-pick this commit and drop the stopgap digest pin.

## Test plan

- [x] Validated end-to-end on PR #292
- [x] Build & Lint, Unit Test, Integration Test, Quality Check all pass
- [ ] Merge and confirm subsequent PRs inherit the fix without manual
cherry-pick

## Credit

Approach lifted from @ckeshava's
[xrpl.js#3270](XRPLF/xrpl.js#3270).
Comment thread src/models/transactions/mptoken_issuance_create.rs
Comment thread src/models/transactions/mptoken_issuance_create.rs Outdated
Comment thread src/models/transactions/mptoken_issuance_create.rs
Comment thread src/models/amount/mod.rs Outdated
Comment thread src/models/amount/mod.rs
Comment thread src/utils/txn_parser/utils/mod.rs
@e-desouza e-desouza force-pushed the feat/mpt-binary-codec branch from bef984f to 091ddde Compare June 3, 2026 19:35
pdp2121
pdp2121 previously approved these changes Jun 4, 2026

@pdp2121 pdp2121 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

@e-desouza

Copy link
Copy Markdown
Collaborator Author

@copilot resolve the merge conflicts in this pull request

Copilot AI commented Jun 4, 2026

Copy link
Copy Markdown

@copilot resolve the merge conflicts in this pull request

Resolved by merging origin/main and fixing the conflict in tests/transactions/mod.rs by keeping both sides. Addressed in commit 678abe6.

Comment thread tests/common/mod.rs Outdated
Comment on lines 272 to 277
@@ -276,3 +276,47 @@ where
);
ledger_accept().await;

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.

Thanks for adding the wait_for_ledger_close_time method. However, this change is not sufficient. The above assert statement is still working on a temporary transaction-status result.

The correct way to validate the status of a transaction would be to invoke the Tx RPC call on the next validated ledger. This will provide the authoritative grouth-truth info from the blockchain-state.

For example, here is the pattern used by xrpl.js integ-tests to verify that the submitted transaction was in-fact processed correctly. Specifically, we need a Rust-equivalent of the verifySubmittedTransaction method."

After deeper inspection, I acknowledge that this is missing in the integration-test harness of the xrpl-py SDK. This is something that I will raise with the team and prioritize for future work of xrpl-py SDK.

Comment thread src/utils/testing.rs Outdated
Comment on lines +29 to +33
// Faucet errors
"Funding request timed out",
"faucet",
// WS result-type mismatch from unexpected push messages (local rippled)
"Unexpected result type",

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.

IMO we should not have this type of file because its a code-smell. The tests should be as stringent as possible.

In the current integ-test harness, we are liberally allowing all of these errors which mask our understanding.

None of the other SDKs have this type of "allow-list" of potential errors for a correctly structured transaction.

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.

Ideally, we should have granular numeric/enum error codes, but it seems we lose fidelity when we map real errors to the existing error codes. Will analyze and see if this is something we can improve in the ecosystem overall.

Will try to fix the above to map to error codes/something more robust in the meantime.

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.

This is addressed by the typed error cleanup in 3a93900 and bd5e337. We no longer rely on a broad string allow-list for branch-touched network/RPC paths: transport failures classify through the centralized XRPLNetworkErrorKind helpers in src/asynch/clients/*/exceptions.rs, and server RPC failures classify through reusable XRPLRpcError in src/models/results/mod.rs.

Comment thread src/asynch/transaction/mod.rs Outdated
Comment on lines +401 to +412
// Skip only on infrastructure failures after connect (timeout, reset, HTTP layer).
// Semantic XRPL errors (actNotFound, malformedTx, etc.) propagate as failures.
if msg.contains("timed out")
|| msg.contains("timeout")
|| msg.contains("Connection reset")
|| msg.contains("Connection refused")
|| msg.contains("reqwest")
|| msg.contains("hyper")
|| msg.contains("EmptyResponse")
{
return Ok(());
}

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.

IMO we should not liberally allow these errors. This test-structure can hide regression errors.

For example, in the future, if the WebSocket connection's port is updated to 51232, then these tests will pass although the rippled server returns "Connection Refused" on port 51233.

I don't see the benefits of allowing these errors to pass through.

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.

This is addressed by the same typed error cleanup in 3a93900 and bd5e337. The broad pass-through behavior was removed for the branch-touched tests; transport errors are now classified via centralized XRPLNetworkErrorKind instead of brittle string matching, and server-side RPC errors use XRPLRpcError (including is_server_network_state) where relevant.

Comment on lines +593 to +605
#[test]
fn test_maximum_amount_zero_is_ok() {
let txn = MPTokenIssuanceCreate {
common_fields: CommonFields {
account: "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B".into(),
transaction_type: TransactionType::MPTokenIssuanceCreate,
..Default::default()
},
maximum_amount: Some("0".into()),
..Default::default()
};
assert!(txn.validate().is_ok());
}

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 unit-test diverges from the rippled behavior. In fact, rippled rejects these types of transactions with temMALFORMED error.

Rippled code ref here

@e-desouza e-desouza Jun 9, 2026

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 6957139. MaximumAmount validation now matches xrpld: zero is rejected, values above i64::MAX are rejected, and the max accepted value is covered by a positive test.

Comment on lines +510 to +525
#[test]
fn test_transfer_fee_zero_without_flag_error() {
// Some(0) without TfMPTCanTransfer should still be rejected — the guard
// fires on is_some() regardless of value, matching rippled behaviour.
let txn = MPTokenIssuanceCreate {
common_fields: CommonFields {
account: "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B".into(),
transaction_type: TransactionType::MPTokenIssuanceCreate,
..Default::default()
},
transfer_fee: Some(0),
..Default::default()
};

assert!(txn.validate().is_err());
}

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, did you mean to demonstrate a unit-test where non-zero transfer fee + no-flag will throw an error? It is not obvious that this case actually throws an error with rippled.

Here is the validation source code of rippled. Only non-zero transfer-fees + no-flag are rejected here.

@e-desouza e-desouza Jun 9, 2026

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 6957139. The validation now rejects non-zero TransferFee without tfMPTCanTransfer, while TransferFee: 0 without the flag is allowed. Tests cover both cases.

realonbebeto pushed a commit to realonbebeto/xrpl-rust that referenced this pull request Jun 5, 2026
… #3270) (XRPLF#291)

## Summary

The `rippled` binary was renamed to `xrpld` upstream, and the
`rippleci/rippled` image stopped receiving updates. Our integration
tests across every open PR started failing because the published
`develop` image exited before becoming healthy (`Connection refused` on
`localhost:5005`, **0 passed / 41 failed**).

This PR mirrors the upstream fix in xrpl.js:
[XRPLF/xrpl.js#3270](XRPLF/xrpl.js#3270).
Switching to `rippleci/xrpld:develop` is the **actual root-cause fix**
rather than pinning an old digest of the deprecated image.

## Changes

`.github/workflows/integration_test.yml`:
- `RIPPLED_DOCKER_IMAGE` -> `XRPLD_DOCKER_IMAGE:
rippleci/xrpld:develop`.
- `docker run` simplified to `${IMAGE} --standalone` (the `xrpld` image
handles `mkdir` + launch internally; no more `bash -c "mkdir -p
/var/lib/rippled/db/ && rippled -a"` wrapper).
- Volume mount changed from `/etc/opt/ripple/` to `/etc/opt/xrpld/`.
- Container name: `rippled-service` -> `xrpld-service`.
- Removed the docker `--health-cmd` (which shelled out to the renamed
`rippled` CLI and always failed) in favour of a direct JSON-RPC poll
against `http://localhost:5005/`.
- Always dump container logs on the stop step for post-mortem
visibility.

`.ci-config/rippled.cfg` -> `.ci-config/xrpld.cfg`:
- `path=/var/lib/rippled/db/nudb` -> `path=/var/lib/xrpld/db/nudb`.
- `[database_path] /var/lib/rippled/db` -> `/var/lib/xrpld/db`.
- `[debug_logfile] /var/log/rippled/debug.log` ->
`/var/log/xrpld/debug.log`.

## Verification

Validated on throwaway PR XRPLF#292 (now closed): **Integration Test green in
2m53s** on this exact workflow. Unit tests, Build & Lint, Quality Check
also pass.

## Related follow-up

The 7 in-flight PRs (XRPLF#130, XRPLF#131, XRPLF#151, XRPLF#153, XRPLF#156, XRPLF#157, XRPLF#158) currently
carry a stopgap commit pinning `rippleci/rippled:develop` to a specific
digest. After this PR merges to `main`, those branches should:
1. Rebase on `main` to pick up the xrpld switch, or
2. Cherry-pick this commit and drop the stopgap digest pin.

## Test plan

- [x] Validated end-to-end on PR XRPLF#292
- [x] Build & Lint, Unit Test, Integration Test, Quality Check all pass
- [ ] Merge and confirm subsequent PRs inherit the fix without manual
cherry-pick

## Credit

Approach lifted from @ckeshava's
[xrpl.js#3270](XRPLF/xrpl.js#3270).
Comment thread src/models/ledger/objects/mptoken_issuance.rs Outdated
Comment thread CHANGELOG.md
@e-desouza e-desouza force-pushed the feat/mpt-binary-codec branch 4 times, most recently from 3807217 to a88d217 Compare June 9, 2026 21:47
Comment thread src/models/transactions/clawback.rs Outdated
@e-desouza e-desouza force-pushed the feat/mpt-binary-codec branch 3 times, most recently from a02b59f to c502be0 Compare June 10, 2026 00:58
Comment thread src/models/transactions/xchain_claim.rs
@e-desouza e-desouza force-pushed the feat/mpt-binary-codec branch from c502be0 to 62965cd Compare June 10, 2026 04:26
@e-desouza e-desouza changed the base branch from main to fix/typed-rpc-submit-and-wait June 10, 2026 04:27
@e-desouza

e-desouza commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator Author

Split the prerequisite plumbing into stacked PRs so this PR can stay focused on MPT:

  1. Stabilize client network error coverage #327: typed async client network errors + integration-covered exception mapping tests + CI stabilization.
  2. Add typed RPC errors for submit-and-wait #328: typed xrpld/Clio RPC errors and submit_and_wait txnNotFound handling, stacked on Stabilize client network error coverage #327.
  3. Add binary codec and transaction models for MPT #131: MPT binary codec/models/transactions, now stacked on Add typed RPC errors for submit-and-wait #328.

I force-pushed #131 with --force-with-lease after validating the stack locally with podman-backed xrpld and clio smoke tests.

@e-desouza e-desouza merged commit 980ed88 into fix/typed-rpc-submit-and-wait Jun 11, 2026
9 checks passed
@e-desouza e-desouza deleted the feat/mpt-binary-codec branch June 11, 2026 19:42
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.

7 participants