- 1. Introduction
- 2. Ledger Entries
- 3. Transactions
- 4. MPT Payment Execution
Multi-Purpose Tokens (MPTs) are a native asset type on the XRP Ledger designed to provide an alternative to traditional trust line-based tokens. An MPT consists of an MPTokenIssuance that defines the MPT's properties and configuration, and individual MPToken entries that track each holder's balance and holder-specific settings.
The lifecycle of an MPT begins when an issuer creates an MPT issuance using the MPTokenIssuanceCreate transaction. This transaction defines the MPT's capabilities through capability flags (such as lsfMPTRequireAuth, lsfMPTCanTransfer, and lsfMPTCanClawback) and optional properties like TransferFee and MaximumAmount. Capability flags are immutable by default, but the issuer can grant mutability permissions at creation time to allow changing them later via MPTokenIssuanceSet. See MPTokenIssuance Fields for complete details on mutable and immutable properties.
Once an issuance exists, holders acquire the ability to hold the MPT by sending an MPTokenAuthorize transaction, which creates their MPToken entry. If the issuance has the lsfMPTRequireAuth flag set (on the MPTokenIssuance), the holder's MPToken entry is created but not yet authorized. The issuer must then send their own MPTokenAuthorize transaction specifying the holder's account to set the lsfMPTAuthorized flag (on the holder's MPToken), allowing the holder to receive MPTs. If lsfMPTRequireAuth is not set, holders can immediately receive MPTs after creating their MPToken entry. See MPToken Fields for details on holder-specific settings and Reserves for information about reserve requirements.
After authorization (if required), MPTs can be transferred between holders via the standard Payment transaction. During transfers, the issuer's TransferFee is applied if configured. See MPT Payment Execution for details on how transfer fees are calculated and applied during payments.
The issuer maintains control over the MPT through the MPTokenIssuanceSet transaction, which can toggle global locking (the lsfMPTLocked flag on the MPTokenIssuance) to freeze all operations, or toggle individual holder locking (the lsfMPTLocked flag on a holder's MPToken, requires the lsfMPTCanLock capability flag) to prevent a specific holder from sending or receiving MPTs. See Flags for all lock-related capability flags and Payment validation rules for how locks affect payment processing.
The MPTokenIssuance ledger entry has an optional DomainID field that controls MPT authorization - determining who can hold and receive the MPT. When DomainID is set on an MPTokenIssuance:
- Accounts must have valid, non-expired credentials for the specified PermissionedDomain to be authorized as MPT holders
- Authorization is verified during Payment transactions when the MPT is being received
- Requires both
lsfMPTRequireAuthflag (see Flags) and thefeaturePermissionedDomainsandfeatureSingleAssetVaultamendments
This MPT authorization mechanism is separate and independent from PermissionedDEX domain restrictions on offers. The MPTokenIssuance.DomainID controls who can hold and receive the MPT (verified during Payment transactions), while Offer.DomainID controls which order book an offer is placed in and restricts liquidity consumption to that domain's order book during payments and offer crossing (see Domain Payments and AMM Exclusion). MPTs can be traded in domain offers, open offers, and hybrid offers regardless of whether the MPTokenIssuance has a DomainID set - creating an offer with Offer.DomainID requires the offer creator to have domain access, but does not require the MPT itself to have a DomainID.
MPTs use two ledger entry types: MPTokenIssuance for the MPT definition and MPToken for individual holder balances.
classDiagram
class MPTokenIssuance {
+AccountID Issuer
+uint32 Sequence
+uint16 TransferFee
+uint64 OwnerNode
+uint8 AssetScale
+uint64 MaximumAmount
+uint64 OutstandingAmount
+uint64 LockedAmount
+Blob MPTokenMetadata
+uint32 Flags
+uint256 DomainID
}
class MPToken {
+AccountID Account
+uint192 MPTokenIssuanceID
+uint64 MPTAmount
+uint64 LockedAmount
+uint64 OwnerNode
+uint32 Flags
}
class IssuerAccount {
+AccountID Account
+XRPAmount Balance
+uint32 OwnerCount
}
class HolderAccount {
+AccountID Account
+XRPAmount Balance
+uint32 OwnerCount
}
class DirectoryNode {
}
MPTokenIssuance --> IssuerAccount : issued by
MPToken --> HolderAccount : held by
MPToken --> MPTokenIssuance : references
IssuerAccount --> DirectoryNode : owner directory
HolderAccount --> DirectoryNode : owner directory
DirectoryNode --> MPTokenIssuance : contains
DirectoryNode --> MPToken : contains
The MPTokenIssuance ledger entry (type ltMPTOKEN_ISSUANCE = 0x007e) represents the definition and global state of a specific MPT. An "issuance" is a single MPT type created by an issuer - it defines the token's properties (transfer fee, maximum supply, decimal places, capability flags) and tracks the token's global state (total outstanding amount, lock status). Each issuance is uniquely identified by its MPTID.
An issuer can create multiple separate MPT issuances, each with its own unique MPTID, independent configuration, and distinct set of holders. Each issuance tracks its own OutstandingAmount and has its own capability flags.
The key of the MPTokenIssuance object is the result
of SHA512-Half of the following values
concatenated in order:
- The
MPTokenIssuancespace key0x007E - The MPTID (192-bit identifier)
The MPTID is calculated as:
MPTID = sequence (32 bits, big-endian) || issuer AccountID (160 bits)
Where sequence is the issuer's sequence number at creation time and issuer is the 160-bit AccountID
| Field | Type | Required | Description |
|---|---|---|---|
Issuer |
AccountID | Yes | The account that created this MPT issuance |
Sequence |
UInt32 | Yes | The issuer's sequence number at creation (used in MPTID calculation) |
TransferFee |
UInt16 | Default | Transfer fee in 1/10 of basis points (0-50000, representing 0%-50%) |
OwnerNode |
UInt64 | Yes | Index of the owner directory page for this issuance |
AssetScale |
UInt8 | Default | Number of decimal places for display (0-19). Display hint only; does not affect on-ledger integer arithmetic. |
MaximumAmount |
UInt64 | Optional | Maximum supply cap (must be > 0, cannot exceed 0x7FFFFFFFFFFFFFFF) |
OutstandingAmount |
UInt64 | Yes | Current total amount held across all holders |
LockedAmount |
UInt64 | Optional | Amount currently locked in escrows |
MPTokenMetadata |
Blob | Optional | Arbitrary metadata (1-1024 bytes) |
MutableFlags |
UInt32 | Default | Mutability permissions for capability flags (see Flags) |
DomainID |
UInt256 | Optional | Permissioned domain identifier for MPT authorization (see DomainID and Authorization) |
PreviousTxnID |
UInt256 | Yes | Transaction hash that most recently modified this entry |
PreviousTxnLgrSeq |
UInt32 | Yes | Ledger sequence of the transaction that most recently modified this entry |
Field constraints:
TransferFee: If non-zero, requireslsfMPTCanTransferflagMaximumAmount: If set,OutstandingAmountcannot exceed itMPTokenMetadata: Maximum 1024 bytesDomainID: If set, requireslsfMPTRequireAuthand bothfeaturePermissionedDomainsandfeatureSingleAssetVaultamendments
| Flag Name | Hex Value | Description |
|---|---|---|
lsfMPTLocked |
0x00000001 |
Global lock (freezes all MPT operations for all holders) |
lsfMPTCanLock |
0x00000002 |
Issuer can lock individual MPToken entries |
lsfMPTRequireAuth |
0x00000004 |
Holders must be authorized by issuer before transacting |
lsfMPTCanEscrow |
0x00000008 |
MPT can be held in escrow |
lsfMPTCanTrade |
0x00000010 |
MPT can be traded on the decentralized exchange |
lsfMPTCanTransfer |
0x00000020 |
MPT can be transferred between accounts |
lsfMPTCanClawback |
0x00000040 |
Issuer can claw back MPTs from holders |
Flag Mutability:
-
The
lsfMPTLockedflag is always mutable and can be set/cleared by the issuer via theMPTokenIssuanceSettransaction. -
All capability flags (
lsfMPTCanLock,lsfMPTRequireAuth,lsfMPTCanEscrow,lsfMPTCanTrade,lsfMPTCanTransfer,lsfMPTCanClawback) are immutable by default but can be made mutable at creation time. When creating an issuance viaMPTokenIssuanceCreate, the issuer can set mutability flags (tmfMPTCanMutateCanLock,tmfMPTCanMutateRequireAuth,tmfMPTCanMutateCanEscrow,tmfMPTCanMutateCanTrade,tmfMPTCanMutateCanTransfer,tmfMPTCanMutateCanClawback) that allow the corresponding capability flag to be set or cleared later viaMPTokenIssuanceSet. These mutability permissions are stored in theMutableFlagsfield on theMPTokenIssuanceledger entry and cannot be changed after creation.
lsfMPTCanTrade and lsfMPTCanTransfer distinction:
These flags control different, independent aspects of MPT movement:
-
lsfMPTCanTrade: Required for all DEX operations. When set, the MPT can be listed in offers via OfferCreate (MPT/XRP, MPT/IOU, MPT/MPT pairs), deposited into AMM pools, and used in cross-currency payments through order books and AMMs. Without this flag, all DEX operations fail withtecNO_PERMISSION, regardless of whetherlsfMPTCanTransferis set. DEX operations validate this flag throughcheckMPTDEXAllowed(), which also checks lock status and holder authorization. See Section 3.6 for complete DEX integration details. -
lsfMPTCanTransfer: Required for direct holder-to-holder transfers without DEX involvement. When set, holders can send MPTs directly to other holders via Payment transactions using direct balance update logic (no pathfinding, no order books). This flag is only required when neither the sender nor the receiver is the issuer. Transfers involving the issuer (minting from issuer to holder, or burning from holder to issuer) do not require this flag. See MPT Payment Execution for transfer mechanics.
lsfMPTCanTrade |
lsfMPTCanTransfer |
Allowed Operations |
|---|---|---|
| ✅ Set | ✅ Set | All operations: DEX operations (offers, AMMs, cross-currency payments) AND direct holder-to-holder transfers |
| ✅ Set | ❌ Not set | DEX operations only when destination is the issuer (burning); issuer minting to holders; no holder-to-holder transfers or DEX operations that deliver to non-issuer holders |
| ❌ Not set | ✅ Set | Direct holder-to-holder transfers only; issuer-involved transfers (minting/burning); all DEX operations fail |
| ❌ Not set | ❌ Not set | Only issuer-involved transfers (minting/burning); no holder-to-holder transfers, no DEX operations |
MPTokenIssuance entries do not require pseudo-accounts. The issuer is always a regular account on the ledger.
The MPTokenIssuance entry is owned by the issuer account and linked to the issuer's owner directory. The OwnerNode field stores the directory page index where this issuance appears.
The owner directory is calculated as:
OwnerDirectory Key = SHA512-Half(0x004F, Issuer AccountID)
Where 0x004F is the OwnerDirectory space key (uppercase 'O').
Creating an MPTokenIssuance always requires one owner reserve. The issuer's OwnerCount is incremented when the issuance is created and decremented when it is destroyed.
The MPToken ledger entry (type ltMPTOKEN = 0x007f) tracks an individual holder's balance and settings for a specific MPT issuance.
The key of the MPToken object is the result
of SHA512-Half of the following values
concatenated in order:
- The
MPTokenspace key0x0074 - The
MPTokenIssuancekey - The holder's
AccountID
The MPTokenIssuance key is calculated as SHA512-Half(0x007E, MPTID) where 0x007E is the MPTokenIssuance space key and MPTID is the 192-bit issuance identifier.
| Field | Type | Required | Description |
|---|---|---|---|
Account |
AccountID | Yes | The holder's account |
MPTokenIssuanceID |
UInt192 | Yes | Reference to the MPT issuance (MPTID) |
MPTAmount |
UInt64 | Default | Amount held by this account (max 0x7FFFFFFFFFFFFFFF) |
LockedAmount |
UInt64 | Optional | Amount locked in escrows |
OwnerNode |
UInt64 | Yes | Index of the owner directory page for this holder |
PreviousTxnID |
UInt256 | Yes | Transaction hash that most recently modified this entry |
PreviousTxnLgrSeq |
UInt32 | Yes | Ledger sequence of the transaction that most recently modified this entry |
Field constraints:
MPTAmount: Must not exceedMaximumAmounton the issuance (if set)LockedAmount: If present, requireslsfMPTCanEscrowon the issuance
| Flag Name | Hex Value | Description |
|---|---|---|
lsfMPTLocked |
0x00000001 |
Individual lock (holder cannot send or receive) |
lsfMPTAuthorized |
0x00000002 |
Authorized by issuer (for lsfMPTRequireAuth issuances) |
lsfMPTAMM |
0x00000004 |
MPToken is held by an AMM pseudo-account |
The lsfMPTLocked flag can only be set if the issuance has lsfMPTCanLock flag set.
The lsfMPTAuthorized flag is only relevant when the issuance has lsfMPTRequireAuth flag set.
The lsfMPTAMM flag is automatically set when an AMM creates an MPToken entry for an MPT asset in its pool. See AMM documentation for details on AMM pseudo-account behavior.
MPToken entries do not require pseudo-accounts.
The MPToken entry is owned by the holder account and linked to the holder's owner directory. The OwnerNode field stores the directory page index where this entry appears.
MPToken reserves are determined by the account's total OwnerCount:
- OwnerCount < 2: No reserve required for creating
MPToken - OwnerCount >= 2: One owner reserve per
MPToken
The holder's OwnerCount is always incremented when an MPToken is created and decremented when deleted. This differs from trust lines, which only increment OwnerCount when in non-default state.
The MPTokenIssuanceCreate transaction creates a new MPT issuance with specified properties and capabilities.
| Field Name | Required? | Modifiable? | JSON Type | Internal Type | Default Value | Description |
|---|---|---|---|---|---|---|
TransactionType |
✔️ | No |
String |
UInt16 |
Must be "MPTokenIssuanceCreate" |
|
Account |
✔️ | No |
String |
AccountID |
Account creating the issuance (becomes the issuer) | |
AssetScale |
No |
Number |
UInt8 |
0 |
Number of decimal places for display (0-19). Specifies how many decimal places the MPT can be subdivided. | |
TransferFee |
No |
Number |
UInt16 |
0 |
Transfer fee in tenths of a basis point (0-50000 inclusive, representing 0%-50%). Must not be present if tfMPTCanTransfer is not set. |
|
MaximumAmount |
No |
String - Number |
UInt64 |
Maximum supply cap. Valid range: 1 to 2^63-1. | ||
MPTokenMetadata |
No |
String - Hexadecimal |
Blob |
Arbitrary metadata (1-1024 bytes). By convention, should decode to JSON describing what the MPT represents. | ||
DomainID |
No |
String - Hexadecimal |
UInt256 |
Permissioned domain identifier (requires amendments) | ||
Flags |
No |
Number |
UInt32 |
0 |
Capability flags | |
MutableFlags |
No |
Number |
UInt32 |
0 |
Mutability flags. Requires DynamicMPT amendment. |
Transaction Flags (Capability Flags):
| Flag Name | Hex Value | Description |
|---|---|---|
tfMPTCanLock |
0x00000002 |
Enable individual holder locking |
tfMPTRequireAuth |
0x00000004 |
Require authorization before holders can transact |
tfMPTCanEscrow |
0x00000008 |
Enable escrow functionality |
tfMPTCanTrade |
0x00000010 |
Enable trading on DEX |
tfMPTCanTransfer |
0x00000020 |
Enable transfers between accounts |
tfMPTCanClawback |
0x00000040 |
Enable issuer clawback |
MutableFlags (Mutability Flags):
These flags control whether the corresponding capability flags can be changed after creation via MPTokenIssuanceSet:
| Flag Name | Hex Value | Description |
|---|---|---|
tmfMPTCanMutateCanLock |
0x00000002 |
Allow changing lsfMPTCanLock flag later |
tmfMPTCanMutateRequireAuth |
0x00000004 |
Allow changing lsfMPTRequireAuth flag later |
tmfMPTCanMutateCanEscrow |
0x00000008 |
Allow changing lsfMPTCanEscrow flag later |
tmfMPTCanMutateCanTrade |
0x00000010 |
Allow changing lsfMPTCanTrade flag later |
tmfMPTCanMutateCanTransfer |
0x00000020 |
Allow changing lsfMPTCanTransfer flag later |
tmfMPTCanMutateCanClawback |
0x00000040 |
Allow changing lsfMPTCanClawback flag later |
tmfMPTCanMutateMetadata |
0x00010000 |
Allow changing MPTokenMetadata field later |
tmfMPTCanMutateTransferFee |
0x00020000 |
Allow changing TransferFee field later |
Static validation1
temDISABLED:- MPTokensV1 amendment is not enabled
DomainIDis specified but amendments not enabled (requires both PermissionedDomains and SingleAssetVault)MutableFlagsis specified but DynamicMPT amendment is not enabled
temINVALID_FLAG: Invalid flags orMutableFlagsspecifiedtemBAD_TRANSFER_FEE:TransferFeeexceeds 50000 (50%)temMALFORMED:TransferFeeis non-zero buttfMPTCanTransferis not setDomainIDis specified but is zero (must omit field if not using domains)DomainIDis specified buttfMPTRequireAuthis not setMPTokenMetadatalength is not between 1 and 1024 bytesMaximumAmountis zero or exceeds0x7FFFFFFFFFFFFFFF
Validation during doApply2
tecINSUFFICIENT_RESERVE: Account has insufficient XRP balance to cover the reserve for creating the issuance (one owner reserve required)tecDIR_FULL: Owner directory is full and cannot accommodate the new issuancetecINTERNAL: Signing account does not exist
-
MPTokenIssuanceobject is created:Issuer: Set to signing accountSequence: Set to account's current sequence (before increment)OutstandingAmount: Set to 0OwnerNode: Set to directory page indexFlags: Set to transaction flags (excluding universal flags)MutableFlags: Set if provided (default 0)AssetScale: Set if provided (default 0)TransferFee: Set if provided (default 0)MaximumAmount: Set if providedMPTokenMetadata: Set if providedDomainID: Set if provided
-
Issuer's
AccountRootis modified:OwnerCount: Incremented by 1
-
DirectoryNodeis created or modified:- Issuance is added to issuer's owner directory
- If issuer has no owner directory, one is created
The MPTokenIssuanceDestroy transaction deletes an MPT issuance. This can only be done when no MPTs are outstanding (all holders have zero balances and no MPTs are locked in escrow).
| Field Name | Required? | Modifiable? | JSON Type | Internal Type | Default Value | Description |
|---|---|---|---|---|---|---|
TransactionType |
✔️ | No |
String |
UInt16 |
Must be "MPTokenIssuanceDestroy" |
|
Account |
✔️ | No |
String |
AccountID |
Account submitting the transaction (must be the issuer) | |
MPTokenIssuanceID |
✔️ | No |
String |
UInt192 |
The MPTID of the issuance to destroy | |
Flags |
No |
Number |
UInt32 |
0 |
Must be 0 (only universal flags allowed) |
Static validation3
temDISABLED: MPTokensV1 amendment is not enabledtemINVALID_FLAG: Any non-universal flags specified
Validation against the ledger view4
tecOBJECT_NOT_FOUND:MPTokenIssuancewith specified MPTID does not existtecNO_PERMISSION: Signing account is not the issuertecHAS_OBLIGATIONS:OutstandingAmountis non-zero (tokens still held by holders)LockedAmountis non-zero (tokens locked in escrow)
Validation during doApply5
tecINTERNAL: Signing account is not the issuertefBAD_LEDGER: Failed to remove issuance from owner directory (indicates ledger corruption)
-
MPTokenIssuanceobject is deleted:- Issuance is removed from the ledger
-
Issuer's
AccountRootis modified:OwnerCount: Decremented by 1
-
DirectoryNodeis modified:- Issuance is removed from issuer's owner directory
- If owner directory becomes empty, it may be deleted
The MPTokenIssuanceSet transaction is sent by the issuer only to modify mutable properties of an MPT issuance or individual MPToken entries. It can:
- Lock/unlock the entire issuance (global lock)
- Lock/unlock individual holders
- Set/clear the
DomainIDfield
| Field Name | Required? | Modifiable? | JSON Type | Internal Type | Default Value | Description |
|---|---|---|---|---|---|---|
TransactionType |
✔️ | No |
String |
UInt16 |
Must be "MPTokenIssuanceSet" |
|
Account |
✔️ | No |
String |
AccountID |
Account submitting the transaction (must be the issuer) | |
MPTokenIssuanceID |
✔️ | No |
String |
UInt192 |
The MPTID of the issuance to modify | |
Holder |
No |
String |
AccountID |
If present, modifies holder's MPToken; otherwise modifies MPTokenIssuance |
||
DomainID |
Yes |
String |
UInt256 |
Set/clear domain (only when Holder not present, set to zero to clear) |
||
MPTokenMetadata |
Yes |
String - Hexadecimal |
Blob |
Change metadata (requires tmfMPTCanMutateMetadata permission) |
||
TransferFee |
Yes |
Number |
UInt16 |
Change transfer fee (requires tmfMPTCanMutateTransferFee permission, requires lsfMPTCanTransfer if non-zero) |
||
MutableFlags |
No |
Number |
UInt32 |
0 |
Set/clear capability flags (see below, requires corresponding mutability permissions) | |
Flags |
No |
Number |
UInt32 |
0 |
Lock/unlock flags (see below) |
Transaction Flags:
| Flag Name | Hex Value | Description |
|---|---|---|
tfMPTLock |
0x00000001 |
Lock the issuance or holder |
tfMPTUnlock |
0x00000002 |
Unlock the issuance or holder |
MutableFlags (Set/Clear Capability Flags):
These flags are used in the MutableFlags field to set or clear capability flags. Each capability requires the corresponding mutability permission (set during issuance creation):
| Flag Name | Hex Value | Description |
|---|---|---|
tmfMPTSetCanLock |
0x00000001 |
Set lsfMPTCanLock flag (requires tmfMPTCanMutateCanLock permission) |
tmfMPTClearCanLock |
0x00000002 |
Clear lsfMPTCanLock flag (requires tmfMPTCanMutateCanLock permission) |
tmfMPTSetRequireAuth |
0x00000004 |
Set lsfMPTRequireAuth flag (requires tmfMPTCanMutateRequireAuth permission) |
tmfMPTClearRequireAuth |
0x00000008 |
Clear lsfMPTRequireAuth flag (requires tmfMPTCanMutateRequireAuth permission) |
tmfMPTSetCanEscrow |
0x00000010 |
Set lsfMPTCanEscrow flag (requires tmfMPTCanMutateCanEscrow permission) |
tmfMPTClearCanEscrow |
0x00000020 |
Clear lsfMPTCanEscrow flag (requires tmfMPTCanMutateCanEscrow permission) |
tmfMPTSetCanTrade |
0x00000040 |
Set lsfMPTCanTrade flag (requires tmfMPTCanMutateCanTrade permission) |
tmfMPTClearCanTrade |
0x00000080 |
Clear lsfMPTCanTrade flag (requires tmfMPTCanMutateCanTrade permission) |
tmfMPTSetCanTransfer |
0x00000100 |
Set lsfMPTCanTransfer flag (requires tmfMPTCanMutateCanTransfer permission) |
tmfMPTClearCanTransfer |
0x00000200 |
Clear lsfMPTCanTransfer flag (requires tmfMPTCanMutateCanTransfer permission) |
tmfMPTSetCanClawback |
0x00000400 |
Set lsfMPTCanClawback flag (requires tmfMPTCanMutateCanClawback permission) |
tmfMPTClearCanClawback |
0x00000800 |
Clear lsfMPTCanClawback flag (requires tmfMPTCanMutateCanClawback permission) |
Behavior:
- When
Holderis NOT specified: ModifiesMPTokenIssuance(global lock or DomainID) - When
Holderis specified: Modifies holder'sMPToken(individual lock only)
Lock requirements:
- Individual lock: Requires
lsfMPTCanLockon issuance (after SingleAssetVault)
Static validation6
temDISABLED:- MPTokensV1 amendment is not enabled
DomainIDspecified but amendments not enabled (requires PermissionedDomains and SingleAssetVault)- Mutation fields (
MutableFlags,MPTokenMetadata, orTransferFee) specified but DynamicMPT amendment is not enabled
temMALFORMED:- Both
DomainIDandHolderspecified (mutually exclusive) AccountequalsHolder(cannot lock own MPToken)- With SingleAssetVault or DynamicMPT, must specify at least one of:
tfMPTLock,tfMPTUnlock,DomainID, or mutation fields (transaction must change something) Holderfield present with mutation fields (mutually exclusive)- Transaction flags set with mutation fields (cannot lock/unlock while mutating)
MPTokenMetadataexceeds 1024 bytes- Non-zero
TransferFeewithtmfMPTClearCanTransferin the same transaction
- Both
temINVALID_FLAG:- Both
tfMPTLockandtfMPTUnlockspecified - Invalid flags specified
MutableFlagsis zero or contains invalid flagsMutableFlagssets and clears the same capability flag
- Both
temBAD_TRANSFER_FEE:TransferFeeexceeds 50000 (50%)
Validation against the ledger view7
terNO_ACCOUNT: Signing account does not existtecOBJECT_NOT_FOUND:MPTokenIssuancedoes not existHolderspecified butMPTokendoes not existDomainIDspecified (non-zero) but domain does not exist
tecNO_PERMISSION:- Signing account is not the issuer
- Attempting to lock/unlock without
lsfMPTCanLock(when SingleAssetVault is enabled) DomainIDspecified but issuance does not havelsfMPTRequireAuth- Attempting to change a capability flag (via
MutableFlags) without the corresponding mutability permission set during issuance creation - Attempting to change
MPTokenMetadatawithouttmfMPTCanMutateMetadatapermission - Attempting to change
TransferFeewithouttmfMPTCanMutateTransferFeepermission - Setting non-zero
TransferFeewhenlsfMPTCanTransferflag is not set
tecNO_DST:Holderaccount does not exist
Validation during doApply8
tecINTERNAL:MPTokenIssuancedoes not exist
When Holder is NOT specified (modifying MPTokenIssuance):
MPTokenIssuanceobject is modified:- If
tfMPTLock: SetlsfMPTLockedflag (global lock) - If
tfMPTUnlock: ClearlsfMPTLockedflag (global unlock) - If
DomainIDnon-zero: SetDomainIDfield - If
DomainIDzero: ClearDomainIDfield (remove from entry) - If
MPTokenMetadatapresent: UpdateMPTokenMetadatafield - If
TransferFeepresent: UpdateTransferFeefield - If
MutableFlagspresent with set flags: Set corresponding capability flags (e.g.,tmfMPTSetCanTradesetslsfMPTCanTrade) - If
MutableFlagspresent with clear flags: Clear corresponding capability flags (e.g.,tmfMPTClearCanTradeclearslsfMPTCanTrade). ClearinglsfMPTCanTransferviatmfMPTClearCanTransferalso clears theTransferFeefield.9
- If
When Holder is specified (modifying MPToken):
MPTokenobject is modified:- If
tfMPTLock: SetlsfMPTLockedflag (individual lock) - If
tfMPTUnlock: ClearlsfMPTLockedflag (individual unlock)
- If
The MPTokenAuthorize transaction manages MPToken entries and authorization. It has different behaviors depending on who submits it and which fields are specified:
Holder-initiated (no Holder field):
- Create an
MPTokenentry (opt-in to holding the MPT) - Delete an
MPTokenentry (opt-out, requires zero balance)
Issuer-initiated (Holder field present):
- Authorize a holder by setting
lsfMPTAuthorizedflag - Unauthorize a holder by clearing
lsfMPTAuthorizedflag
| Field Name | Required? | Modifiable? | JSON Type | Internal Type | Default Value | Description |
|---|---|---|---|---|---|---|
TransactionType |
✔️ | No |
String |
UInt16 |
Must be "MPTokenAuthorize" |
|
Account |
✔️ | No |
String |
AccountID |
Account submitting the transaction | |
MPTokenIssuanceID |
✔️ | No |
String |
UInt192 |
The MPTID to authorize for | |
Holder |
No |
String |
AccountID |
If present, issuer is authorizing this holder | ||
Flags |
No |
Number |
UInt32 |
0 |
Unauthorize flag (see below) |
Transaction Flags:
| Flag Name | Hex Value | Description |
|---|---|---|
tfMPTUnauthorize |
0x00000001 |
Delete MPToken entry (holder-initiated only) |
Behavioral Matrix:
| Holder Field | tfMPTUnauthorize |
Who Submits | Action |
|---|---|---|---|
| Not present | Not set | Holder | Create MPToken entry |
| Not present | Set | Holder | Delete MPToken entry (requires zero balance) |
| Present | Not set | Issuer | Set lsfMPTAuthorized flag |
| Present | Set | Issuer | Clear lsfMPTAuthorized flag |
Static validation10
temDISABLED: MPTokensV1 amendment is not enabledtemINVALID_FLAG: Invalid flags specifiedtemMALFORMED:AccountequalsHolder(issuer cannot create/authorize their ownMPToken)
Validation against the ledger view11
When Holder NOT specified (holder-initiated):
- If
tfMPTUnauthorize:tecOBJECT_NOT_FOUND:MPTokendoes not existtefINTERNAL:MPTokenIssuancedoes not exist (andMPTokenhas a positive balance or locked amount)tecHAS_OBLIGATIONS:MPTAmountis non-zero (cannot delete with balance)LockedAmountis non-zero (cannot delete with locked MPTs)
- With SingleAssetVault:
tecNO_PERMISSION:MPTokenis individually locked
- If NOT
tfMPTUnauthorize:tecOBJECT_NOT_FOUND:MPTokenIssuancedoes not existtecDUPLICATE:MPTokenalready existstecNO_PERMISSION: Signing account is the issuer (issuer cannot create ownMPToken)
When Holder specified (issuer-initiated):
tecNO_DST: Holder account does not existtecOBJECT_NOT_FOUND:MPTokenIssuancedoes not existMPTokendoes not exist (must be created by holder first)
tecNO_PERMISSION: Signing account is not the issuertecNO_AUTH: Issuance does not havelsfMPTRequireAuthflag (authorization not required)
Validation during doApply12
tecINTERNAL: Signing account does not exist
When creating MPToken (holder-initiated, no flags):
tecINSUFFICIENT_RESERVE: Account has insufficient XRP balance to cover reserve for creatingMPToken(waived if OwnerCount < 2)tecDIR_FULL: Owner directory is full and cannot accommodate the newMPToken
When deleting MPToken (holder-initiated, tfMPTUnauthorize):
tecINTERNAL:- Failed to remove
MPTokenfrom owner directory (indicates ledger corruption) MPTokendoes not exist or the balance is not 0
- Failed to remove
Holder creating MPToken (no Holder field, no tfMPTUnauthorize):
-
MPTokenobject is created:Account: Set to signing accountMPTokenIssuanceID: Set to specified MPTIDMPTAmount: Set to 0OwnerNode: Set to directory page indexFlags: Set to 0 (not authorized initially)
-
Holder's
AccountRootis modified:OwnerCount: Incremented by 1 (if OwnerCount >= 2)
-
DirectoryNodeis created or modified:MPTokenis added to holder's owner directory
Holder deleting MPToken (no Holder field, tfMPTUnauthorize set):
-
MPTokenobject is deleted:- Entry is removed from the ledger
-
Holder's
AccountRootis modified:OwnerCount: Decremented by 1 (if it was incremented at creation)
-
DirectoryNodeis modified:MPTokenis removed from holder's owner directory
Issuer authorizing holder (Holder field present, no tfMPTUnauthorize):
MPTokenobject is modified:lsfMPTAuthorizedflag: Set to 1 (authorized)
Issuer unauthorizing holder (Holder field present, tfMPTUnauthorize set):
MPTokenobject is modified:lsfMPTAuthorizedflag: Cleared (unauthorized)
The Clawback transaction allows issuers to claw back MPTs from holders when the lsfMPTCanClawback flag is set. This is the same transaction type used for trust line tokens, but with MPT-specific handling.
Fields:
Transaction fields are described in Clawback Fields.
Note: For trust line token clawback, the holder is specified in the Amount.issuer field. For MPT clawback, the Holder field is required because the MPTID identifies the issuance, not the holder.
Static validation:13
temDISABLED: MPTokensV1 amendment is not enabled (for MPT clawback)temINVALID_FLAG: Any non-universal flags specifiedtemMALFORMED:Holderfield is not specified (required for MPT clawback)AccountequalsHolder(cannot claw back from self)
temBAD_AMOUNT:Amountis negative or zero
Validation against the ledger view:14
terNO_ACCOUNT: issuer's or holder's account does not exist.tecAMM_ACCOUNT: If holder's account is an AMM account, such as a SingleAssetVault or AMM account and if amendment SingleAssetVault is not enabled.tecPSEUDO_ACCOUNT: IfSingleAssetVaultis enabled and holder's account is any pseudo-account.tecOBJECT_NOT_FOUND:MPTokenIssuancedoes not exist- Holder's
MPTokendoes not exist
tecNO_PERMISSION:- Signing account is not the issuer
- Issuance does not have
lsfMPTCanClawbackflag
tecINSUFFICIENT_FUNDS: Holder'sMPTAmountis zero (nothing to claw back)
-
Holder's
MPTokenis modified:15MPTAmount: Decreased by clawed back amount (or to zero if amount exceeds balance)- If
MPTAmountbecomes zero and all other fields are default, the entry may be deleted
-
Holder's
MPTokenmay be deleted:- If balance is zero, locked amount is zero, and no non-default flags are set
- Holder's
OwnerCountis decremented
-
MPTokenIssuanceis modified:OutstandingAmount: Decreased by clawed back amount
Notes:
- The clawed back amount is removed from circulation (burned), not transferred to the issuer's balance. If the requested clawback amount exceeds the holder's balance, only the available balance is clawed back (no error).
- Transfer fees are waived during clawback. The full clawed back amount is burned from the holder's balance without deducting any transfer fee, even if a
TransferFeeis set on the issuance.
MPT operations are validated through two public functions that call a common underlying validation logic. These functions are used by various transactions (OfferCreate, Payment, AMMCreate, etc.) to ensure MPTs meet the requirements for their intended use.
Used for DEX operations (offers, cross-currency payments).
Transactions using this validation:
- OfferCreate (CreateOffer::preclaim)
- CheckCash (when cashing checks with MPT amounts)
- Payment (when using paths for cross-currency conversion - validated in BookStep and MPTEndpointStep)
Validation checks performed:
- MPTokenIssuance exists: Verifies the MPTokenIssuance object exists in the ledger
- Issuer account exists: Verifies the issuer's account exists
- Global lock check: Ensures
lsfMPTLockedflag is not set on MPTokenIssuance - DEX permission: Verifies
lsfMPTCanTradeflag is set (required for all DEX operations) - Transfer permission (when neither party is issuer): Verifies
lsfMPTCanTransferflag is set - Individual lock check (if holder has MPToken): Ensures
lsfMPTLockedflag is not set on the holder's MPToken entry
Possible error codes:
tecNO_ISSUER: Issuer account does not existtecOBJECT_NOT_FOUND: MPTokenIssuance does not existtecLOCKED: Global lock (lsfMPTLockedon MPTokenIssuance) OR individual lock (lsfMPTLockedon holder's MPToken)tecNO_PERMISSION:lsfMPTCanTradeflag not set ORlsfMPTCanTransferflag not set (when neither party is issuer)
When lsfMPTCanTransfer is checked:
The lsfMPTCanTransfer check is only performed when the account is not the issuer AND (there is no destination account OR the destination is not the issuer). This means:
- Issuer-involved operations (minting/burning) do not require
lsfMPTCanTransfer - Holder-to-holder operations require both
lsfMPTCanTrade(for DEX) andlsfMPTCanTransfer
Used for transactions involving MPTs that are not strictly DEX operations.
Transactions using this validation:
- CheckCreate (when creating checks with MPT amounts)
- AMMCreate, AMMDeposit, AMMWithdraw (when involving MPT assets)
Validation checks performed:
Same as checkMPTDEXAllowed but the validation logic uses the actual transaction type instead of treating it as a DEX operation. The key difference is:
- For DEX operations: Always treats the operation as requiring DEX permissions
- For other transactions: Validates based on the specific transaction type's requirements
Possible error codes:
Same as checkMPTDEXAllowed:
tecNO_ISSUERtecOBJECT_NOT_FOUNDtecLOCKEDtecNO_PERMISSION
Note: Both functions call the same underlying checkMPTAllowed() function, which contains the core validation logic. The difference is in how they categorize the operation (DEX vs non-DEX) which affects whether lsfMPTCanTrade is required.
MPT payments transfer Multi-Purpose Tokens between accounts. Depending on whether the MPTokensV2 amendment is enabled, these payments execute through one of two paths: a direct transfer mechanism (when MPTokensV2 is not enabled) or the Flow engine (when MPTokensV2 is enabled). Both execution paths use the same underlying transfer logic.
Transfer mechanics: The three transfer scenarios described below apply regardless of which execution path is used, as both ultimately call the same rippleCreditMPT function to modify ledger entries. The transfer behavior depends on whether the issuer is involved in the transaction.
Prerequisites: Both source and destination must have MPToken entries (created via MPTokenAuthorize transaction) unless one party is the issuer. The issuer never has an MPToken entry.
If the MPTokenIssuance has a DomainID field set, the receiving account must have valid, non-expired credentials for that PermissionedDomain to receive the MPT (see Domain Membership for credential verification details). This authorization is checked during payment preclaim:
- If the receiver has an existing
MPTokenentry with thelsfMPTAuthorizedflag set, the payment succeeds (authorization previously granted) - If the receiver has no
MPTokenentry or an unauthorizedMPTokenentry, the ledger checks for valid credentials matching theMPTokenIssuance.DomainID - Valid credentials automatically authorize the receiver, and an
MPTokenentry is created if needed during doApply - The sender does not need domain credentials - only the receiver's authorization is checked
This domain-based authorization is separate from PermissionedDEX domain restrictions on offers. The MPTokenIssuance.DomainID controls who can hold the MPT, while Offer.DomainID controls which order book an offer is placed in. See MPT DomainID and Authorization and PermissionedDomains documentation for detailed explanation of the distinction.
When the issuer sends MPTs to a holder, new tokens are minted into circulation:
sequenceDiagram
participant I as Issuer Account
participant IS as MPTokenIssuance
participant H as Holder's MPToken
Note over I,H: Payment: 100 MPT from Issuer to Holder
I->>IS: OutstandingAmount += 100
I->>H: MPTAmount += 100
Note over IS: OutstandingAmount: 1000 -> 1100
Note over H: MPTAmount: 50 -> 150
Operations:
- Increase
OutstandingAmountonMPTokenIssuanceby sent amount - Increase
MPTAmounton holder'sMPTokenby sent amount
Net effect: Total supply increases by the sent amount.
When a holder sends MPTs back to the issuer, tokens are burned from circulation:
sequenceDiagram
participant H as Holder's MPToken
participant IS as MPTokenIssuance
participant I as Issuer Account
Note over H,I: Payment: 100 MPT from Holder to Issuer
H->>IS: MPTAmount -= 100
H->>IS: OutstandingAmount -= 100
Note over H: MPTAmount: 150 -> 50
Note over IS: OutstandingAmount: 1100 -> 1000
Operations:
- Decrease
MPTAmounton holder'sMPTokenby sent amount - Decrease
OutstandingAmountonMPTokenIssuanceby sent amount
Net effect: Total supply decreases by the sent amount.
When neither party is the issuer, the transfer is executed through a two-step process that applies and burns the transfer fee:
sequenceDiagram
participant S as Sender's MPToken
participant IS as MPTokenIssuance
participant R as Receiver's MPToken
Note over S,R: Payment: 100 MPT from Holder to Holder<br/>TransferFee: 1% (1 MPT fee)
Note over S,R: Step 1: Credit receiver (via issuer)
IS->>R: OutstandingAmount += 100<br/>MPTAmount += 100
Note over S,R: Step 2: Debit sender (via issuer)
S->>IS: MPTAmount -= 101<br/>OutstandingAmount -= 101
Note over S: MPTAmount: 200 -> 99
Note over R: MPTAmount: 50 -> 150
Note over IS: OutstandingAmount: 1000 -> 999<br/>(net: -1 fee burned)
- Calculate transfer fee:
actualAmount = amount * (1 + transferFee%) - Credit step (through issuer to receiver):
- Increase
OutstandingAmountonMPTokenIssuanceby delivery amount - Increase
MPTAmounton receiver'sMPTokenby delivery amount
- Increase
- Debit step (from sender through issuer):
- Decrease
MPTAmounton sender'sMPTokenbyactualAmount(amount + fee) - Decrease
OutstandingAmountonMPTokenIssuancebyactualAmount
- Decrease
Net effect:
- Receiver gets: delivery amount
- Sender pays: delivery amount + fee
- Total supply decreases by: fee amount (burned from circulation)
Footnotes
-
Static validation (preflight):
checkExtraFeatures,getFlagsMask,preflight↩ -
Validation during doApply:
MPTokenIssuanceCreate.cpp↩ -
Static validation (preflight):
getFlagsMask,preflight↩ -
Validation against ledger view (preclaim):
MPTokenIssuanceDestroy.cpp↩ -
Validation during doApply:
MPTokenIssuanceDestroy.cpp↩ -
Static validation (preflight):
checkExtraFeatures,getFlagsMask,preflight↩ -
Validation against ledger view (preclaim):
checkPermission,preclaim↩ -
Validation during doApply:
MPTokenIssuanceSet.cpp↩ -
Clearing
lsfMPTCanTransferclearsTransferFee:MPTokenIssuanceSet.cpp↩ -
Static validation (preflight):
getFlagsMask,preflight↩ -
Validation during doApply:
applyHelper<MPTIssue>↩