コンテンツへスキップ

Send a Multi-Purpose Token (MPT)

This tutorial shows you how to send a direct Multi-Purpose Token (MPT) payment on the XRP Ledger.

Each account must authorize the MPT before it can hold the token. This is to prevent malicious users from spamming accounts with unwanted tokens that could negatively impact storage and XRP reserves. Holders can send the MPT to each other only if the issuance has the Can Transfer flag enabled, and both parties are authorized.

MPTokensV1 amendmentが必要です。 Loading...

Goals

By the end of this tutorial, you will be able to:

  • Authorize an account to hold a specific MPT.
  • Send a payment of an MPT between two accounts.
  • Verify the payment was successful.

Prerequisites

To complete this tutorial, you should:

Source Code

You can find the complete source code for this tutorial's examples in the code samples section of this website's repository.

Steps

1. Install dependencies

From the js/ folder, use npm to install dependencies.

npm install

2. Set up client and accounts

To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports:

  • xrpl: Used for XRPL client connection, transaction submission, and wallet handling.
  • fs: Used to check for and load the tutorial setup data.
  • ./sendMPTSetup.js: The tutorial set up script, imported and called directly.
import xrpl from 'xrpl'
import fs from 'fs'
import { setup } from './sendMPTSetup.js'

// Connect to the network ----------------------
const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233')
await client.connect()

Next, provide the sender and receiver wallets, and the MPT issuance ID.

// Load setup data ----------------------
// This step checks for the necessary setup data to run the tutorial.
// If missing, sendMPTSetup.js will generate it.
if (!fs.existsSync('sendMPTSetup.json')) {
  console.log(`\n=== Setup data doesn't exist. Running setup script... ===\n`)
  await setup()
}

// Load preconfigured sender wallet and MPT issuance ID.
const setupData = JSON.parse(fs.readFileSync('sendMPTSetup.json', 'utf8'))
const sender = xrpl.Wallet.fromSeed(setupData.sender.seed)
const mptIssuanceID = setupData.mptIssuanceID

console.log(`Sender address:   ${sender.address}`)
console.log(`MPT issuance ID:  ${mptIssuanceID}`)

// Fund a fresh receiver wallet from the faucet.
console.log(`\nCreating and funding receiver wallet...`)
const { wallet: receiver } = await client.fundWallet()
console.log(`Receiver address: ${receiver.address}`)

This example uses a preconfigured sender and MPT issuance from the sendMPTSetup.js script, but you can replace sender and mptIssuanceID with your own values.

3. Authorize the receiving account

Any account that wants to hold an MPT, whether sending or receiving, must opt in first. Submit an MPTokenAuthorize transaction, signed by the holder, to create an MPToken ledger entry on their account. In this tutorial, the setup script authorizes the sender before transferring it any tokens, so this step only runs for the receiver.

// Authorize receiver to hold the MPT ----------------------
console.log(`\n=== Authorizing receiver to hold the MPT... ===\n`)
const authorizeTx = {
  TransactionType: 'MPTokenAuthorize',
  Account: receiver.address,
  MPTokenIssuanceID: mptIssuanceID
}
xrpl.validate(authorizeTx)
console.log(JSON.stringify(authorizeTx, null, 2))

const authorizeResponse = await client.submitAndWait(authorizeTx, {
  wallet: receiver,
  autofill: true
})
if (authorizeResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
  const code = authorizeResponse.result.meta.TransactionResult
  console.error('Error: MPTokenAuthorize failed:', code)
  await client.disconnect()
  process.exit(1)
}
console.log('Receiver authorized to hold the MPT!')
console.log(`Explorer link: https://testnet.xrpl.org/transactions/${authorizeResponse.result.hash}`)

A tesSUCCESS result confirms the receiver is now authorized to hold the MPT, and should have an MPToken ledger entry with a balance of zero.

Note

If the issuance uses allow-listing (the Require Auth flag), this step isn't enough on its own. After the holder opts in, the issuer typically must also approve the holder—usually by submitting its own MPTokenAuthorize transaction with the Holder field set to the holder's address. The token used in this tutorial doesn't use allow-listing, so no issuer approval is needed.

4. Check initial balances

Before sending the payment, check each account's MPT holdings using the ledger_entry method. Pass the holder's account and the mpt_issuance_id in the mptoken parameter to look up the MPToken entry directly. This avoids paginating through the account's owner directory and works reliably for accounts that own many ledger entries.

// Check initial balances ----------------------
/**
 * Return the MPTAmount for the given MPT issuance held by an account.
 *
 * Looks up the holder's MPToken ledger entry directly via ledger_entry. 
 * Returns "0" if the entry doesn't exist or has no
 * MPTAmount.
 *
 * @param {string} address - Classic address of the account to query.
 * @param {string} mptIssuanceID - MPT issuance ID to look up.
 * @returns {Promise<string>} The MPT amount as a string, or "0".
 */
async function getMPTBalance(address, mptIssuanceID) {
  try {
    const response = await client.request({
      command: 'ledger_entry',
      ledger_index: 'validated',
      mptoken: {
        mpt_issuance_id: mptIssuanceID,
        account: address
      }
    })
    return response.result.node?.MPTAmount ?? '0'
  } catch (e) {
    if (e.data?.error === 'entryNotFound') {
      return '0'
    }
    throw e
  }
}

console.log(`\n=== Checking initial MPT balances for issuance ${mptIssuanceID}... ===\n`)
const senderBalanceBefore = await getMPTBalance(sender.address, mptIssuanceID)
const receiverBalanceBefore = await getMPTBalance(receiver.address, mptIssuanceID)
console.log(`Sender balance:   ${senderBalanceBefore}`)
console.log(`Receiver balance: ${receiverBalanceBefore}`)

5. Send the token payment

Specify an MPT amount to send to the receiver, then submit the Payment transaction.

// Send MPT from sender to receiver ----------------------
console.log(`\n=== Sending MPT payment... ===\n`)
const paymentTx = {
  TransactionType: 'Payment',
  Account: sender.address,
  Destination: receiver.address,
  Amount: {
    mpt_issuance_id: mptIssuanceID,
    value: '100'
  }
}
xrpl.validate(paymentTx)
console.log(JSON.stringify(paymentTx, null, 2))

const paymentResponse = await client.submitAndWait(paymentTx, {
  wallet: sender,
  autofill: true
})
if (paymentResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
  const code = paymentResponse.result.meta.TransactionResult
  console.error('Error: Payment failed:', code)
  await client.disconnect()
  process.exit(1)
}
console.log('Payment successful!')
console.log(`Explorer link: https://testnet.xrpl.org/transactions/${paymentResponse.result.hash}`)

The MPT issuance in this example uses an asset scale of 2, so applications shift the displayed amount two decimal places. A value of "100" therefore shows as 1.00 units of the token.

The example MPT has a TransferFee of 0, which means the sender's debit matches the payment value exactly. With a non-zero TransferFee, the sender would have to pay extra so the receiver gets the full value.

Caution

If the payment fails, it could be for one of the following reasons:

  • tecNO_AUTH: the issuance does not have Can Transfer enabled, or (with allow-listing) the issuer hasn't approved the holder.
  • tecPATH_PARTIAL: the sender doesn't have enough of the MPT to cover the payment. This can also occur if the issuance has a non-zero TransferFee and the sender doesn't have enough to cover it.
  • tecLOCKED: the issuance, the sender, or the receiver is locked by the issuer (only possible if the issuance has the Can Lock flag enabled).

6. Verify updated balances

Check both accounts' MPT holdings again to confirm the transfer.

// Verify balances ----------------------
console.log(`\n=== Checking final MPT balances for issuance ${mptIssuanceID}... ===\n`)
const senderBalanceAfter = await getMPTBalance(sender.address, mptIssuanceID)
const receiverBalanceAfter = await getMPTBalance(receiver.address, mptIssuanceID)
console.log(`Sender balance:   ${senderBalanceAfter}`)
console.log(`Receiver balance: ${receiverBalanceAfter}`)

await client.disconnect()

The sender's MPTAmount should have decreased by the value you sent, and the receiver's should match it.

See Also

Concepts:

Tutorials:

References: