Multi-Purpose Tokens
Learn how to create and manage Multi-Purpose Tokens (MPT) on the XRP Ledger.
Multi-Purpose Tokens (MPT) are a recent amendment to the XRP Ledger, enhancing the token system by allowing tokens to serve multiple functions beyond just being a medium of exchange. This amendment introduces new capabilities for token issuers and holders.
Use Cases
Stablecoins: Create stablecoins with features like freezing and clawback.
Utility Tokens: Design tokens with specific utility functions.
Security Tokens: Implement security tokens with transfer restrictions.
Community Credit: Track debts and credits between known parties.
MPToken Flags
Each flag is a power of 2, allowing them to be combined using bitwise operations. The total value is the sum of all enabled flags.
canLock
2
Lock tokens globally or per account
requireAuth
4
Require issuer approval for new holders
canEscrow
8
Enable time-locked escrow (Not implemented)
canTrade
16
Enable DEX trading (Not implemented)
canTransfer
32
Enable basic token transfers
canClawback
64
Allow issuer to recover tokens
Common Flag Combinations:
Basic: canTransfer (32)
Secure: canLock + canClawback + canTransfer (98)
Regulated: canLock + canClawback + canTransfer + requireAuth (102)
Getting Started: Creating the Main Structure of Your Script
Create a new file or edit index.ts:
import { Client } from 'xrpl';
const client = new Client("wss://s.devnet.rippletest.net:51233");
const main = async () => {
try {
console.log("Let's create a Multi-Purpose Token...");
await client.connect();
// Create issuer and holder wallets
const { wallet: issuerWallet } = await client.fundWallet();
const { wallet: holderWallet } = await client.fundWallet();
console.log('Issuer Wallet:', issuerWallet.address);
console.log('Holder Wallet:', holderWallet.address);
// Define token metadata
const tokenMetadata = {
name: "MyMPToken",
symbol: "MPT",
description: "A sample Multi-Purpose Token",
};
// Convert metadata to hex string
const metadataHex = Buffer.from(JSON.stringify(tokenMetadata)).toString('hex');
// Set flags for a regulated token
const totalFlagsValue = 102; // canLock + canClawback + canTransfer + requireAuth
// Create MPToken issuance transaction
const transactionBlob = {
TransactionType: "MPTokenIssuanceCreate",
Account: issuerWallet.address,
Flags: totalFlagsValue,
MPTokenMetadata: metadataHex
// MaximumAmount: "1000000000000000000", (Optional) The maximum asset amount of this token that can ever be issued
// TransferFee: 5000, (Optional) between 0 and 50000 for 50.000% fees charged by the issuer for secondary sales of Token
// AssetScale: 2, (Optional) 10^(-scale) of a corresponding fractional unit. For example, a US Dollar Stablecoin will likely have an asset scale of 2, representing 2 decimal places.
};
// Submit token issuance
const mptokenCreationResult = await client.submitAndWait(transactionBlob, {
autofill: true,
wallet: issuerWallet
});
console.log('MPToken Creation Result:', mptokenCreationResult.result.meta?.TransactionResult);
console.log('MPToken Issuance ID:', mptokenCreationResult.result.meta?.mpt_issuance_id);
await client.disconnect();
console.log("All done!");
} catch (error) {
console.error("Error creating MPToken:", error);
}
};
main();Understanding MPTokenIssuanceID
The mpt_issuance_id is a unique identifier generated when an MPToken is created via MPTokenIssuanceCreate. This ID is:
Automatically generated by the XRPL network upon successful token creation
Globally unique across all MPTokens on the ledger
Required for all subsequent operations (payments, clawbacks, authorization)
To obtain the MPTokenIssuanceID:
Submit an
MPTokenIssuanceCreatetransactionRetrieve the ID from the transaction result:
result.meta?.mpt_issuance_idStore this ID for future operations with your token
Alternative ways to find the MPTokenIssuanceID:
From Explorer: You can find the ID on the XRPL explorer (https://devnet.xrpl.org/) by searching with the transaction hash or the creator's address
Query by Issuer: Use the
account_linesAPI method with the issuer's address to find all tokens issued by that account// Query the ledger for all MPTokens issued by a specific account const response = await client.request({ command: "account_objects", account: issuerAddress, ledger_index: "validated", type: "mpt_issuance" }); // Find the specific token by currency code in the metadata const mpTokens = response.result.account_objects; const targetToken = mpTokens.find(token => { // Parse metadata from hex to JSON const metadata = JSON.parse(Buffer.from(token.MPTokenMetadata, 'hex').toString()); return metadata.symbol === "TECH"; // Replace with your token symbol }); const mpTokenId = targetToken?.mpt_issuance_id; console.log("mpTokenId: ", mpTokenId);
The relationship between issuer, token properties, and ID:
Issuer: The account that created the token (unchangeable)
Token Properties: Metadata, flags, and rules defined at creation
MPTokenIssuanceID: The unique identifier linking all operations to this specific token issuance
Technical Specifications
Uses decimal (base-10) math with 15 digits of precision
Can express values from 1.0 × 10^-81 to 9999999999999999 × 10^80
Supports transfer fees that are automatically deducted
Allows issuers to define tick sizes for exchange rates
Destroying MPTokens
The MPTokenIssuanceDestroy transaction allows an issuer to permanently destroy an MPToken issuance. This is useful for:
Retiring deprecated or unused tokens
Removing tokens that were created in error
Regulatory compliance and token lifecycle management
Requirements
Only the original issuer can destroy the MPToken issuance
All tokens must be owned by the issuer (transferred back) before destruction
The MPTokenIssuanceID must be valid and reference an existing issuance
Example Transaction
const transactionBlob = {
TransactionType: "MPTokenIssuanceDestroy",
Account: wallet.address,
MPTokenIssuanceID: mpTokenId
};Freezing MPToken Issuance
The MPTokenIssuanceSet transaction allows an issuer to modify the state of an MPToken issuance, including freezing/unfreezing transfers. This is useful for:
Temporarily halting transfers during maintenance or upgrades
Responding to security incidents
Complying with regulatory requirements
Managing token lifecycle events
Requirements
Only the original issuer can modify the issuance settings
The MPTokenIssuanceID must be valid and reference an existing issuance
Appropriate flags must be set during issuance to enable freezing
Example Transaction
const transactionBlob = {
TransactionType: "MPTokenIssuanceSet",
Account: wallet.address,
MPTokenIssuanceID: mpTokenId,
Flags: 1, // 1: Lock, 2: Unlock
// Holder: r3d4... // Specify holder to freeze; if not specified, it will freeze globally
};Authorizing MPToken Holders
The MPTokenAuthorize transaction enables fine-grained control over who can hold and transfer Multi-Purpose Tokens (MPTs) on the XRPL. This mechanism supports compliance, prevents unsolicited token spam, and allows issuers to manage token distribution effectively.
Features
Authorize a Holder: Permit a specific account to receive and hold a designated MPT
Revoke Authorization: Remove a holder's permission, effectively locking their MPT balance
Self-Unauthorize: Allow a holder to voluntarily relinquish their MPT holdings (requires zero balance)
Global Locking: Restrict all transfers of a particular MPT issuance
Requirements
Only the original issuer can authorize/revoke holders
The MPTokenIssuanceID must be valid and reference an existing issuance
Authorization flags must be enabled during issuance
Holder accounts must be valid XRPL addresses
Example Transaction
const transactionBlob = {
TransactionType: "MPTokenAuthorize",
Account: wallet.address,
MPTokenIssuanceID: mptokenID
Flags: 0 // If set to 1, and transaction is submitted by a holder, it indicates that the holder no longer wants to hold the MPToken
// Holder: r3d4... (Optional) Specify address to authorize; if not specified, it will authorize globally
};Transaction Type modifications
The introduction of Multi-Purpose Tokens (MPT) on the XRP Ledger has brought significant changes to existing transaction types, especially Payment and Clawback. These transactions now support MPTokens, allowing you to transfer or recover tokens issued under the new amendment.
Changes introduced:
Payment: The Payment transaction can now transfer MPTokens by using the
Amountfield as an object containing the issuance identifier (mpt_issuance_id) and the amount to transfer. Example:const transactionBlob = { TransactionType: "Payment", Account: wallet.address, Amount: { "mpt_issuance_id": mpTokenId, "value": "10000" }, Destination: destinationAddress.value, };Clawback: The Clawback transaction allows the issuer to recover MPTokens from a specific account, also using the adapted
Amountfield for MPTokens. Example:const transactionBlob = { TransactionType: "Clawback", Account: wallet.address, Amount: { "mpt_issuance_id": mpTokenId, "value": "10000" }, Holder: holderAddress.value, };
Putting It All Together
Let's create a complete example that demonstrates all the MPToken features by simulating a real-world scenario: issuing company shares to investors. In this example, we'll:
Create a company share token with regulatory controls
Authorize investors to receive shares
Distribute shares to investors
Demonstrate compliance controls (locking and clawback)
You can check every transaction hash on : https://devnet.xrpl.org/
import { Client } from 'xrpl';
const client = new Client("wss://s.devnet.rippletest.net:51233");
const main = async () => {
try {
console.log("Creating company shares using Multi-Purpose Tokens...");
await client.connect();
console.log("Setting up company and investor wallets...");
// Create company (issuer) and investor wallets
const { wallet: companyWallet } = await client.fundWallet();
const { wallet: investor1Wallet } = await client.fundWallet();
const { wallet: investor2Wallet } = await client.fundWallet();
console.log('Company Wallet:', companyWallet.address);
console.log('Investor 1 Wallet:', investor1Wallet.address);
console.log('Investor 2 Wallet:', investor2Wallet.address);
console.log("");
// Define company share token metadata
const tokenMetadata = {
name: "TechCorp Shares",
symbol: "TECH",
description: "Equity shares in TechCorp with regulatory compliance features",
};
// Convert metadata to hex string
const metadataHex = Buffer.from(JSON.stringify(tokenMetadata)).toString('hex');
// Set flags for a regulated security token
const totalFlagsValue = 102; // canLock + canClawback + canTransfer + requireAuth
// Create company share token issuance
let transactionBlob = {
TransactionType: "MPTokenIssuanceCreate",
Account: companyWallet.address,
Flags: totalFlagsValue,
MPTokenMetadata: metadataHex
};
console.log("Issuing company share tokens...");
// Submit token issuance
const createTx = await client.submitAndWait(transactionBlob, { wallet: companyWallet });
// Get the MPTokenID for our company shares
const MPTokenID = createTx.result.meta?.mpt_issuance_id;
console.log('Share token creation transaction hash:', createTx.result.hash);
console.log('Company Share Token ID:', MPTokenID);
console.log("");
// First, investors need to self-authorize to receive the tokens
// Investor 1 self-authorization
transactionBlob = {
TransactionType: "MPTokenAuthorize",
Account: investor1Wallet.address,
MPTokenIssuanceID: MPTokenID,
};
console.log("Investor 1 authorizing to receive shares...");
const investor1SelfAuthTx = await client.submitAndWait(transactionBlob, {wallet: investor1Wallet });
// Investor 2 self-authorization
transactionBlob = {
TransactionType: "MPTokenAuthorize",
Account: investor2Wallet.address,
MPTokenIssuanceID: MPTokenID,
};
console.log("Investor 2 authorizing to receive shares...");
const investor2SelfAuthTx = await client.submitAndWait(transactionBlob, {wallet: investor2Wallet });
console.log("Investor 1 self-authorization transaction hash:", investor1SelfAuthTx.result.hash);
console.log("Investor 2 self-authorization transaction hash:", investor2SelfAuthTx.result.hash);
console.log("");
// With requireAuth flag, the company (issuer) must authorize investors
// Authorize investor 1
transactionBlob = {
TransactionType: "MPTokenAuthorize",
Account: companyWallet.address,
MPTokenIssuanceID: MPTokenID,
Holder: investor1Wallet.address
};
console.log("Company authorizing investor 1 to receive shares...");
const investor1AuthTx = await client.submitAndWait(transactionBlob, {wallet: companyWallet });
// Authorize investor 2
transactionBlob = {
TransactionType: "MPTokenAuthorize",
Account: companyWallet.address,
MPTokenIssuanceID: MPTokenID,
Holder: investor2Wallet.address
};
console.log("Company authorizing investor 2 to receive shares...");
const investor2AuthTx = await client.submitAndWait(transactionBlob, {wallet: companyWallet });
console.log("Investor 1 issuer authorization transaction hash:", investor1AuthTx.result.hash);
console.log("Investor 2 issuer authorization transaction hash:", investor2AuthTx.result.hash);
console.log("");
// Distribute shares to investor 1 (10,000 shares)
transactionBlob = {
TransactionType: "Payment",
Account: companyWallet.address,
Amount: {
"mpt_issuance_id": MPTokenID, // Company share token ID
"value": "10000" // 10,000 shares
},
Destination: investor1Wallet.address,
};
console.log("Distributing 10,000 shares to investor 1...");
const paymentTx = await client.submitAndWait(transactionBlob, {wallet: companyWallet });
console.log("Share distribution transaction hash: ", paymentTx.result.hash);
console.log("");
// Demonstrate compliance: Lock investor 1's shares (e.g., during regulatory investigation)
transactionBlob = {
TransactionType: "MPTokenIssuanceSet",
Account: companyWallet.address,
MPTokenIssuanceID: MPTokenID,
Holder: investor1Wallet.address,
Flags: 1, // Lock the shares
};
console.log("Locking investor 1's shares for compliance review...");
const lockTx = await client.submitAndWait(transactionBlob, {wallet: companyWallet });
console.log("Lock transaction hash: ", lockTx.result.hash);
console.log("TransactionResult: ", lockTx.result.meta.TransactionResult);
console.log("Investor 1 can no longer transfer their shares");
console.log("");
// Attempt transfer while locked (this will fail)
transactionBlob = {
TransactionType: "Payment",
Account: investor1Wallet.address,
Amount: {
"mpt_issuance_id": MPTokenID,
"value": "5000"
},
Destination: investor2Wallet.address,
};
console.log("Attempting to transfer locked shares to investor 2 (this will fail)...");
const transferTx = await client.submitAndWait(transactionBlob, {wallet: investor1Wallet });
console.log("Transfer transaction hash: ", transferTx.result.hash);
console.log("TransactionResult: ", transferTx.result.meta.TransactionResult);
console.log("Transfer failed as expected - shares are locked");
console.log("");
// Company exercises clawback rights (e.g., for regulatory compliance)
transactionBlob = {
TransactionType: "Clawback",
Account: companyWallet.address,
Amount: {
"mpt_issuance_id": MPTokenID,
"value": "10000"
},
Holder: investor1Wallet.address,
};
console.log("Company exercising clawback rights on investor 1's shares...");
const clawbackTx = await client.submitAndWait(transactionBlob, {wallet: companyWallet });
console.log("Clawback transaction hash: ", clawbackTx.result.hash);
console.log("All 10,000 shares have been returned to the company");
console.log("");
await client.disconnect();
console.log("Company share token demonstration complete!");
} catch (error) {
console.error("Error in share token operations:", error);
}
};
main();Resources
Last updated

