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.

Flag
Value
Description

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:

  1. Submit an MPTokenIssuanceCreate transaction

  2. Retrieve the ID from the transaction result: result.meta?.mpt_issuance_id

  3. Store 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_lines API 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 Amount field 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 Amount field 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:

  1. Create a company share token with regulatory controls

  2. Authorize investors to receive shares

  3. Distribute shares to investors

  4. 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