feat: add Dynamic MPT support (XLS-0094)#42
Open
Patel-Raj11 wants to merge 5 commits into
Open
Conversation
* Add DynamicMPT to .ci-config/xrpld.cfg [features] for standalone tests * Refresh packages/ripple-binary-codec/src/enums/definitions.json from rippled (renames lsmf*CanMutate<Cap> -> lsmf*CanEnable<Cap>) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* MPTokenIssuanceCreate: optional MutableFlags (8-bit mask of
tmfMPTCanEnable<Cap> + tmfMPTCanMutate{Metadata,TransferFee}).
* MPTokenIssuanceSet: optional MutableFlags (6-bit mask of
tmfMPTSet<Cap>), MPTokenMetadata, TransferFee. Validation enforces
mutate-mode constraints (no Holder, mutually exclusive with
lock/unlock) and rejects pairing tmfMPTSetCanTransfer with non-zero
TransferFee in the same transaction (tecNO_PERMISSION pre-empt).
* MPTokenIssuance ledger entry gains optional MutableFlags and
MPTokenIssuanceMutableFlags enum.
* utils/flags.ts adds convertTxMutableFlagsToNumber and the
txToMutableFlag map; autofill() and validate() now resolve the
MutableFlags interface form to its numeric mask, mirroring how the
standard Flags field is converted.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds unit tests covering each MutableFlags validation rule on MPTokenIssuanceCreate and MPTokenIssuanceSet (MutableFlags=0, out-of-mask bits, mutate-mode Holder ban, lock/unlock vs. mutate-mode exclusion, MPTokenMetadata / TransferFee bounds, tmfMPTSetCanTransfer + non-zero TransferFee gate). Adds binary-codec round-trip cases for MPTokenIssuanceCreate and MPTokenIssuanceSet exercising the new MutableFlags field and the empty-blob metadata deletion form. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds dynamicMPT.test.ts covering: MPTokenIssuance creation with MutableFlags, immutable default when omitted, mutable metadata lifecycle (replace + delete), dormant-capability enable, staged CanTransfer enable + TransferFee mutation + clear, mutate-permission gating returning tecNO_PERMISSION, and a response-type contract asserting MutableFlags surfaces as a number on the on-ledger MPTokenIssuance. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Patel-Raj11
commented
Jun 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
References
High Level Overview of Change
Adds SDK support for XLS-0094 DynamicMPT: lets MPT issuers declare a subset of an
MPTokenIssuance's capabilities and fields as mutable at creation time, then alter them later viaMPTokenIssuanceSet. Without this PR, MPT issuances remain fully immutable post-creation (XLS-0033 default); with it, an issuer can enable previously-dormant capabilities (e.g.,lsfMPTCanTransfer,lsfMPTCanClawback) or replaceMPTokenMetadata/TransferFeeafter the fact.Scope (by category):
MutableFlagsadded toMPTokenIssuanceCreate;MutableFlags,MPTokenMetadata, andTransferFeeadded toMPTokenIssuanceSet, introducing a "mutate mode" mutually exclusive with the existing lock/unlock mode.MutableFlags(DEFAULT, UInt32) added toMPTokenIssuance.tmfMPTCanEnable*× 6 +tmfMPTCanMutate{Metadata,TransferFee}), set-side (tmfMPTSet*× 6), and on-ledger (lsmfMPTCanEnable*× 6 +lsmfMPTCanMutate{Metadata,TransferFee}) bit vocabularies.MutableFlags = 0and out-of-mask rejection on both transactions; mutate-mode/Holderexclusion; mutate-mode/universal-Flagsexclusion; metadata length cap (1024 bytes); transfer-fee cap (50000); pre-emptive refusal oftmfMPTSetCanTransferpaired with non-zeroTransferFeein the same tx.convertTxMutableFlagsToNumberregistered alongside the existing tx-flags converter and invoked from bothvalidate()andClient.autofill().sfMutableFlags(UInt32, nth=53),MPTokenIssuanceMutableledger flag enum, and the three updated transaction/ledger formats inripple-binary-codec'sdefinitions.json.DynamicMPTadded to.ci-config/xrpld.cfgso integration tests run against an activated amendment.Context of Change
XLS-0094 is a strictly-additive extension of XLS-0033 MPT: no new transaction types, no new ledger entry types, no new RPC methods, and no new TER codes. The new
MutableFlagsvalue flows through the existing MPT RPC surface unchanged, so the work concentrates in models, validation, codec definitions, and tests.The implementation tracks the final rippled state at SHA
997267f845(PR #7439, the activating squash). That state diverges from the XLS README in three places (see the "Notes for reviewers" section below). SDK identifiers, mask values, and JSON parsing tables follow rippled, not the pre-redesign XLS vocabulary.Type of Change
Did you update HISTORY.md?
Test Plan
MutableFlagsfield on all three hosts, every validation rule listed in the Scope section (rejection ofMutableFlags = 0, out-of-mask bits,Holderin mutate mode, universal-Flagsin mutate mode, oversizedMPTokenMetadata, oversizedTransferFee, and thetmfMPTSetCanTransfer+ non-zeroTransferFeecombination), and binary round-trip ofsfMutableFlagsonMPTokenIssuanceCreate,MPTokenIssuanceSet, and theMPTokenIssuanceledger entry.CanTransferenable followed byTransferFeemutation and clearing, and the mutate-permission gating path (a mutatingMPTokenIssuanceSetagainst an issuance that did not declare the corresponding mutability returnstecNO_PERMISSION).Notes for reviewers
CanEnable<Cap>(e.g.,tmfMPTCanEnableCanLock,lsmfMPTCanEnableCanLock) rather than theCanMutate<Cap>names in the XLS README; thetmfMPTClear*half of the XLS §4.1 set/clear pairs is gone; and the remainingtmfMPTSet*flags are renumbered to0x01..0x20. Metadata and TransferFee retainCanMutateon both sides. This SDK matches the rippled final state.lsfMPTCan<Cap>is set on an issuance — at creation via thetfMPTCan<Cap>create flag, or post-creation viatmfMPTSet<Cap>onMPTokenIssuanceSet— there is no protocol mechanism to clear it. Callers composing a mutatingMPTokenIssuanceSetshould treat capability enabling as irreversible; downstream UIs may want to surface this affordance.TransferFeemutation requires a staged enable. Setting a non-zeroTransferFeeonMPTokenIssuanceSetrequireslsfMPTCanTransferto already be set on the issuance; pairingtmfMPTSetCanTransferand a non-zeroTransferFeein the same tx does not satisfy the gate (rippled returnstecNO_PERMISSION). The SDK rejects that combination at validation time to pre-empt obvious-rejection submissions.{@link}cross-references in place of section identifiers from the spec. Specifically:MPTokenIssuance.MutableFlags(uses{@link MPTokenIssuanceMutableFlags}and adds a trailing "Absent or 0 means the issuance is fully immutable" sentence), and theMPTokenMetadata/TransferFeefields onMPTokenIssuanceSet(insert the clarifier "on the issuance"). Left as-is on the theory that they are repo-convention enhancements; trivial to revert if the convention is verbatim copy.resolveMPTokenIssuance{Create,Set}MutableFlagshelpers duplicate the centralisedconvertTxMutableFlagsToNumberlogic. The centralised converter runs first fromvalidate()/autofill(), so the per-tx resolvers' boolean-interface branch is reachable only when callers invoke the per-tx validators directly. Worth a consolidating refactor later if the conventions allow.