xls: 94 title: Dynamic Multi-Purpose Tokens description: This amendment enables selected fields and flags of MPTokenIssuance to be updated after creation. discussion-from: https://github.com/XRPLF/XRPL-Standards/discussions/289 author: Yinyi Qianstatus: Draft category: Amendment created: 2025-06-09
Dynamic Multi-Purpose Tokens¶
Abstract¶
This proposal introduces a new amendment DynamicMPT as an extension to XLS-33 Multi-Purpose Tokens. Dynamic Multi-Purpose Tokens (Dynamic MPTs) enable specific fields and flags within an MPTokenIssuance to be declared as mutable at the time of creation. By enabling controlled mutability, this feature accommodates evolving token use cases and compliance demands. Modifications to these mutable metadata can be made via the MPTokenIssuanceSet transaction.
1. Overview¶
This proposal introduces:
- Transaction update for
MPTokenIssuanceCreate: -
A new optional field:
sfMutableFlags- indicates specific fields or flags are mutable after issuance.
-
Ledger object update in
MPTokenIssuance: -
A new optional field:
sfMutableFlags- indicates specific fields or flags are mutable after issuance.
-
Transaction update for
MPTokenIssuanceSet: - New optional fields:
sfMPTokenMetadata- updatesMPTokenMetadatasfTransferFee- updatesTransferFeesfMutableFlags- sets/clears flags (e.g.tmfMPTSetCanLock,tmfMPTClearCanLock)
This feature will require an amendment, DynamicMPT.
2. Declaring Mutability via MPTokenIssuanceCreate¶
If issuers want the ability to modify certain fields or flags after issuance, they must explicitly declare those fields or flags as mutable when creating the MPTokenIssuance.
Only a limited set of fields and flags may be declared mutable; all other fields remain permanently immutable.
2.1. Declaring Mutability¶
Issuers can specify mutability during MPTokenIssuanceCreate via the optional MutableFlags field.
Bits in MutableFlags indicate specific fields or flags may be modified after issuance. The fields or flags include:
- Fields:
MPTokenMetadataTransferFee- Flags:
- Refer to XLS-33 Multi-Purpose Tokens: Transaction-specific Fields (under
Flagsfield)
2.2. New Optional Field: MutableFlags¶
| Field Name | Required? | JSON Type | Internal Type | Description |
|---|---|---|---|---|
MutableFlags |
number |
UInt32 |
Indicate specific fields or flags mutable |
Bit Layout of MutableFlags — Declaring Mutability at Creation (MPTokenIssuanceCreate)¶
MutableFlags are prefixed with tmf to clearly distinguish them from standard tf prefix used for Flags.
| Flag Name | Hex Value | Decimal Value | Description |
|---|---|---|---|
| [Reserved] | 0x00000001 |
1 | [Reserved; To align with Flags values, the MutableFlags value starts from 0x00000002.] |
tmfMPTCanMutateCanLock |
️0x00000002 |
2 | Indicates flag lsfMPTCanLock can be changed |
tmfMPTCanMutateRequireAuth |
️0x00000004 |
4 | Indicates flag lsfMPTRequireAuth can be changed |
tmfMPTCanMutateCanEscrow |
0x00000008 |
8 | Indicates flag lsfMPTCanEscrow can be changed |
tmfMPTCanMutateCanTrade |
0x00000010 |
16 | Indicates flag lsfMPTCanTrade can be changed |
tmfMPTCanMutateCanTransfer |
️0x00000020 |
32 | Indicates flag lsfMPTCanTransfer can be changed |
tmfMPTCanMutateCanClawback |
️0x00000040 |
64 | Indicates flag lsfMPTCanClawback can be changed |
tmfMPTCanMutateMetadata |
0x00010000 |
65536 | Allows field MPTokenMetadata to be modified |
tmfMPTCanMutateTransferFee |
0x00020000 |
131072 | Allows field TransferFee to be modified |
Note: Flag value 0x0001 is used by lsfMPTLocked. It is not a valid value for MutableFlags.
2.3. Failure Conditions¶
| Failure Condition | Error Code |
|---|---|
Invalid values in MutableFlags field |
temINVALID_FLAG |
MutableFlags is present but featureDynamicMPT is disabled |
temDISABLED |
3. On-Ledger Data Structure Change¶
This proposal introduces updates to the MPTokenIssuance ledger object, including a new optional field MutableFlags.
3.1. New Optional Field: MutableFlags¶
A new optional field, MutableFlags (SField sfMutableFlags), is added to the MPTokenIssuance ledger object.
| Field Name | Required? | JSON Type | Internal Type |
|---|---|---|---|
MutableFlags |
number |
UINT32 |
Bit Layout of MutableFlags - Recording Mutability On-Ledger (MPTokenIssuance)¶
On-ledger MutableFlags are prefixed with lmf to clearly distinguish them from standard lsf prefix used for Flags.
| Flag Name | Hex Value | Decimal Value | Description |
|---|---|---|---|
| [Reserved] | ️0x00000001 |
1 | [Reserved; To align with Flags values, the MutableFlags value starts from 0x00000002.] |
lsmfMPTCanMutateCanLock |
️0x00000002 |
2 | Indicates flag lsfMPTCanLock can be changed |
lsmfMPTCanMutateRequireAuth |
️0x00000004 |
4 | Indicates flag lsfMPTRequireAuth can be changed |
lsmfMPTCanMutateCanEscrow |
0x00000008 |
8 | Indicates flag lsfMPTCanEscrow can be changed |
lsmfMPTCanMutateCanTrade |
0x00000010 |
16 | Indicates flag lsfMPTCanTrade can be changed |
lsmfMPTCanMutateCanTransfer |
️0x00000020 |
32 | Indicates flag lsfMPTCanTransfer can be changed |
lsmfMPTCanMutateCanClawback |
️0x00000040 |
64 | Indicates flag lsfMPTCanClawback can be changed |
lsmfMPTCanMutateMetadata |
0x00010000 |
65536 | Allows field MPTokenMetadata to be modified |
lsmfMPTCanMutateTransferFee |
0x00020000 |
131072 | Allows field TransferFee to be modified |
Note: Flag value 0x0001 is used by lsfMPTLocked. It is not a valid value for MutableFlags.
4. MPTokenIssuanceSet Transaction Update¶
This proposal extends the functionality of the MPTokenIssuanceSet transaction, allowing the issuer of the MPTokenIssuance to update fields or flags that were explicitly marked as mutable during creation.
For details on the original MPTokenIssuanceSet transaction see: The MPTokenIssuanceSet Transaction
4.1. New Fields¶
| Field Name | Required? | JSON Type | Internal Type |
|---|---|---|---|
MPTokenMetadata |
string |
BLOB |
New metadata to replace the existing value.
The transaction will be rejected if lsmfMPTCanMutateMetadata was not set in MutableFlags.
Setting an empty MPTokenMetadata removes the field.
| Field Name | Required? | JSON Type | Internal Type |
|---|---|---|---|
TransferFee |
number |
UINT16 |
New transfer fee value.
The transaction will be rejected if lsmfMPTCanMutateTransferFee was not set in MutableFlags.
Setting TransferFee to zero removes the field.
| Field Name | Required? | JSON Type | Internal Type |
|---|---|---|---|
MutableFlags |
number |
UINT32 |
Set or clear the flags which were marked as mutable.
The valid MutableFlags values:
The MutableFlags use tmf as prefix.
| Flag Name | Hex Value | Decimal Value | Description |
|---|---|---|---|
tmfMPTSetCanLock |
️0x00000001 |
1 | Sets the lsfMPTCanLock flag. Enables the token to be locked both individually and globally. |
tmfMPTClearCanLock |
️0x00000002 |
2 | Clears the lsfMPTCanLock flag. Disables both individual and global locking of the token. |
tmfMPTSetRequireAuth |
️0x00000004 |
4 | Sets the lsfMPTRequireAuth flag. Requires individual holders to be authorized. |
tmfMPTClearRequireAuth |
️0x00000008 |
8 | Clears the lsfMPTRequireAuth flag. Holders are not required to be authorized. |
tmfMPTSetCanEscrow |
0x00000010 |
16 | Sets the lsfMPTCanEscrow flag. Allows holders to place balances into escrow. |
tmfMPTClearCanEscrow |
0x00000020 |
32 | Clears the lsfMPTCanEscrow flag. Disallows holders from placing balances into escrow. |
tmfMPTSetCanTrade |
0x00000040 |
64 | Sets the lsfMPTCanTrade flag. Allows holders to trade balances on the XRPL DEX. |
tmfMPTClearCanTrade |
0x00000080 |
128 | Clears the lsfMPTCanTrade flag. Disallows holders from trading balances on the XRPL DEX. |
tmfMPTSetCanTransfer |
️0x00000100 |
256 | Sets the lsfMPTCanTransfer flag. Allows tokens to be transferred to non-issuer accounts. |
tmfMPTClearCanTransfer |
️0x00000200 |
512 | Clears the lsfMPTCanTransfer flag. Disallows transfers to non-issuer accounts. |
tmfMPTSetCanClawback |
️0x00000400 |
1024 | Sets the lsfMPTCanClawback flag. Enables the issuer to claw back tokens via Clawback or AMMClawback transactions. |
tmfMPTClearCanClawback |
️0x00000800 |
2048 | Clears the lsfMPTCanClawback flag. The token can not be clawed back. |
Note:
- Setting and clearing the same flag simultaneously will be rejected. For example, you can not provide both
tmfMPTSetCanLockandtmfMPTClearCanLock. - When
lsfMPTCanTransferis cleared, theTransferFeefield is automatically removed.
4.2. Failure Conditions¶
| Failure Condition | Error Code |
|---|---|
MutableFlags contains invalid value (0 is invalid as well) |
temINVALID_FLAG |
MPTokenHolder is provided when MutableFlags, MPTokenMetadata, or TransferFee is present |
temMALFORMED |
Flags (except tfUniversal) is provided when MutableFlags, MPTokenMetadata, or TransferFee is present |
temMALFORMED |
TransferFee exceeds the limit, which is 50000 |
temBAD_TRANSFER_FEE |
MPTokenMetadata length exceeds the limit, which is 1024 |
temMALFORMED |
Setting and clearing the same flag simultaneously, e.g., specifying both tmfMPTSetCanLock and tmfMPTClearCanLock |
temMALFORMED |
Including a non-zero TransferFee when lsfMPTCanTransfer was not set |
temMALFORMED |
Including a non-zero TransferFee and tmfMPTClearCanTransfer in the same transaction |
temMALFORMED |
MutableFlags, MPTokenMetadata, or TransferFee is present but featureDynamicMPT is disabled |
temDISABLED |
MPTokenIssuanceID does not exist |
tecOBJECT_NOT_FOUND |
Account is not the issuer of the target MPTokenIssuance |
tecNO_PERMISSION |
MutableFlags attempts to modify flags not declared as mutable |
tecNO_PERMISSION |
MPTokenMetadata is present but was not marked as mutable |
tecNO_PERMISSION |
TransferFee is present but was not marked as mutable |
tecNO_PERMISSION |
4.3. TransferFee Modification Rules¶
Since TransferFee MUST NOT be present if tfMPTCanTransfer flag is not set during MPTokenIssuanceCreate, there are extra rules we should follow when it comes to mutating the TransferFee field.
The ability to modify TransferFee depends on two flags:
lsfMPTCanTransfer: must already be set to allow any non-zeroTransferFee.lsmfMPTCanMutateTransferFee: must be set at creation (MPTokenIssuanceCreate) to allow any modification of theTransferFeefield.
And lsfMPTCanTransfer can be modified through tmfMPTSetCanTransfer/tmfMPTClearCanTransfer if lsmfMPTCanMutateCanTransfer is set.
Because these flags overlap in function, the rules break down as follows:¶
Case1: lsfMPTCanTransfer not set:
- Setting
TransferFeeto zero: - If
lsmfMPTCanMutateTransferFeeis set: allowed; removes theTransferFeefield. - If
lsmfMPTCanMutateTransferFeeis not set: returnstecNO_PERMISSION. - Setting
TransferFeeto a non-zero value: - Always invalid: returns
temMALFORMED, regardless oflsmfMPTCanMutateTransferFee. - ❗Note: Even including
tmfMPTSetCanTransferin the same transaction returnstemMALFORMED.lsfMPTCanTransfermust already be set before assigning a non-zeroTransferFee.
Case2: lsfMPTCanTransfer set:
- Setting
TransferFeeto a non-zero value: - If
lsmfMPTCanMutateTransferFeeis set: allowed; modifies theTransferFeefield.- ❗Note:
tmfMPTClearCanTransferMUST NOT be included in the same transaction when setting a non-zeroTransferFee: otherwise returnstemMALFORMED.
- ❗Note:
- If
lsmfMPTCanMutateTransferFeeis not set: returnstecNO_PERMISSION. - Setting
TransferFeeto zero: - If
lsmfMPTCanMutateTransferFeeis set: allowed; removes theTransferFeefield.- ❗Note: if
lsmfMPTCanMutateCanTransferis set,tmfMPTClearCanTransferis allowed to be included when setting to a zeroTransferFee: it removesTransferFeefield and clears thelsfMPTCanTransfer.
- ❗Note: if
- If
lsmfMPTCanMutateTransferFeeis not set: returnstecNO_PERMISSION.
5. Examples¶
5.1. Example 1¶
5.1.1. MPTokenIssuanceCreate Transaction¶
{
"TransactionType": "MPTokenIssuanceCreate",
"Account": "rIssuer...",
"AssetScale": "2",
"MaximumAmount": "100000000",
"MutableFlags": 65536, // tmfMPTCanMutateMetadata
"MPTokenMetadata": "464F4F",
"TransferFee": 100
}
tmfMPTCanMutateMetadatais set, indicating theMPTokenMetadatafield can be modified.
5.1.2. MPTokenIssuanceSet Transaction¶
Sample 1(successful):
{
"TransactionType": "MPTokenIssuanceSet",
"Account": "rIssuer...",
"MPTokenMetadata": "575C5C"
}
MPTokenMetadatawas set mutable, so this will update the metadata from464F4Fto575C5C.
Sample 2(rejected):
{
"TransactionType": "MPTokenIssuanceSet",
"Account": "rIssuer...",
"MutableFlags": 1, // tmfMPTSetCanLock (0x0001)
"MPTokenMetadata": "575C5C"
}
- This transaction attempts to set the
lsfMPTCanLockflag by includingtmfMPTSetCanLockin theMutableFlagsfield. However, the mutation will be rejected becauselsfMPTCanLockwas not marked during creation.
5.2. Example 2¶
5.2.1. MPTokenIssuanceCreate Transaction¶
{
"TransactionType": "MPTokenIssuanceCreate",
"Account": "rIssuer...",
"AssetScale": "2",
"MaximumAmount": "100000000",
"Flags": 32, // tfMPTCanTransfer
"MutableFlags": 131082, // tmfMPTCanMutateTransferFee + tmfMPTCanMutateCanLock + tmfMPTCanMutateCanEscrow
"MPTokenMetadata": "464F4F",
"TransferFee": 100
}
tmfMPTCanMutateTransferFeeis set, indicating theTransferFeefield can be modified.tmfMPTCanMutateCanLockandtmfMPTCanMutateCanEscroware set, indicatinglsfMPTCanLockandlsfMPTCanEscrowcan be modified.
5.2.2. MPTokenIssuanceSet Transaction¶
Sample 1(successful):
{
"TransactionType": "MPTokenIssuanceSet",
"Account": "rIssuer...",
"MutableFlags": 33, // tmfMPTSetCanLock(0x0001) + tmfMPTClearCanEscrow (0x0020)
"TransferFee": 200
}
TransferFeewas set mutable. It will be updated to 200.- Set the
lsfMPTCanLockflag and clear thelsfMPTCanEscrowflag, which were marked as mutable.
Sample 2(rejected):
{
"TransactionType": "MPTokenIssuanceSet",
"Account": "rIssuer...",
"MutableFlags": 4, // tmfMPTSetRequireAuth (0x0004)
"TransferFee": 200
}
- This will be rejected. It tries to set
lsfMPTRequireAuth, which is not mutable.
Sample 3(rejected):
{
"TransactionType": "MPTokenIssuanceSet",
"Account": "rIssuer...",
"TransferFee": 200,
"MPTokenMetadata": "575C5C"
}
- This will be rejected. It tries to set
MPTokenMetadata, which is not mutable.
5.3. Example 3¶
5.3.1. MPTokenIssuanceCreate Transaction¶
{
"TransactionType": "MPTokenIssuanceCreate",
"Account": "rIssuer...",
"AssetScale": "2",
"MaximumAmount": "100000000",
"Flags": 2, // tfMPTCanLock
"MutableFlags": 131104, // tmfMPTCanMutateTransferFee + tmfMPTCanMutateCanTransfer
}
tmfMPTCanMutateTransferFeeis set, allowing theTransferFeefield to be modified.tmfMPTCanMutateCanTransferis set, allowing thelsfMPTCanTransferflag to be modified.tfMPTCanLockis set.TransferFeeis not present (and MUST NOT be present) becausetfMPTCanTransferis not set.
5.3.2. MPTokenIssuanceSet Transaction¶
Sample 1(rejected):
{
"TransactionType": "MPTokenIssuanceSet",
"Account": "rIssuer...",
"TransferFee": 200
}
- This will be rejected. Although
lsmfMPTCanMutateTransferFeeis set, a non-zeroTransferFeecannot be specified unlesslsfMPTCanTransferis already enabled.
Sample 2(rejected):
{
"TransactionType": "MPTokenIssuanceSet",
"Account": "rIssuer...",
"MutableFlags": 256, // tmfMPTSetCanTransfer
"TransferFee": 200
}
- This will be rejected. Even if
lsmfMPTCanMutateTransferFeeis set, a non-zeroTransferFeecannot be specified unlesslsfMPTCanTransferis already enabled, even if the transaction also includestmfMPTSetCanTransfer.
Sample 3(successful): The following sequence of transactions illustrates a successful case:
Step1:
{
"TransactionType": "MPTokenIssuanceSet",
"Account": "rIssuer...",
"MutableFlags": 256 // tmfMPTSetCanTransfer
}
- Since
lsmfMPTCanMutateCanTransferis set during creation, this transaction successfully sets thelsfMPTCanTransferflag.
Step2:
{
"TransactionType": "MPTokenIssuanceSet",
"Account": "rIssuer...",
"TransferFee": 200
}
- With both
lsfMPTCanTransferandlsmfMPTCanMutateTransferFeeset, a non-zeroTransferFeecan now be applied.
Step3:
{
"TransactionType": "MPTokenIssuanceSet",
"Account": "rIssuer...",
"MutableFlags": 512 // tmfMPTClearCanTransfer
}
- Since
lsmfMPTCanMutateCanTransferis set, thelsfMPTCanTransferflag can be cleared. - Clearing
lsfMPTCanTransferautomatically removes theTransferFeefrom theMPTokenIssuanceobject.