Skip to content

Send and Execute XCM Messages

Introduction

XCM messages are comprised of a series of instructions that are executed by the Cross-Consensus Virtual Machine (XCVM). Combinations of these instructions result in predetermined actions, such as cross-chain token transfers. You can create your own custom XCM messages by combining various XCM instructions.

Pallets such as X-Tokens and XCM Transactor provide functions with a predefined set of XCM instructions to either send XC-20s or remotely execute on other chains via XCM. However, to get a better understanding of the results from combining different XCM instructions, you can build and execute custom XCM messages locally on Moonbeam (only available on Moonbase Alpha). You can also send custom XCM messages to another chain (which will start with the DecendOrigin instruction). Nevertheless, for the XCM message to be successfully executed, the target chain needs to be able to understand the instructions.

To execute or send a custom XCM message, you can either use the Polkadot XCM Pallet directly or through the Ethereum API with the XCM Utilities Precompile. In this guide, you'll learn how to use both methods to execute and send custom-built XCM messages locally on Moonbase Alpha.

This guide assumes that you are familiar with general XCM concepts, such as general XCM terminology and XCM instructions. For more information, you can check out the XCM Overview documentation.

Polkadot XCM Pallet Interface

Extrinsics

The Polkadot XCM Pallet includes the following relevant extrinsics (functions):

execute(message, maxWeight) — supported on Moonbase Alpha only - executes a custom XCM message on the source chain
  • message - the SCALE-encoded versioned XCM message to be executed
  • maxWeight - the maximum weight allowed to be consumed, which is defined by specifying the:
    • refTime - the amount of computational time that can be used for execution
    • proofSize - the amount of storage in bytes that can be used
import { ApiPromise, WsProvider } from '@polkadot/api';

const message = { V4: [INSERT_XCM_INSTRUCTIONS] };
const maxWeight = { refTime: INSERT_REF_TIME, proofSize: INSERT_PROOF_SIZE };

const main = async () => {
  const api = await ApiPromise.create({
    provider: new WsProvider('INSERT_WSS_ENDPOINT'),
  });
  const tx = api.tx.polkadotXcm.execute(message, maxWeight);
  const txHash = await tx.signAndSend('INSERT_ACCOUNT_OR_KEYRING');
};

main();
send(dest, message) — supported on Moonbase Alpha only - sends a custom XCM message to a destination chain. For the XCM message to be successfully executed, the target chain needs to be able to understand the instructions in the message
  • dest - the XCM versioned multilocation representing a chain in the ecosystem where the XCM message is being sent to (the target chain)
  • message - the SCALE-encoded versioned XCM message to be executed
import { ApiPromise, WsProvider } from '@polkadot/api';

const dest = { V4: { parents: INSERT_PARENTS, interior: INSERT_INTERIOR } };
const message = { V4: [INSERT_XCM_INSTRUCTIONS] };

const main = async () => {
  const api = await ApiPromise.create({
    provider: new WsProvider('INSERT_WSS_ENDPOINT'),
  });
  const tx = api.tx.polkadotXcm.send(dest, message);
  const txHash = await tx.signAndSend('INSERT_ACCOUNT_OR_KEYRING');
};

main();

Storage Methods

The Polkadot XCM Pallet includes the following relevant read-only storage methods:

assetTraps(hash) — returns the existing number of times an asset has been trapped given a hash of the asset

hash - (optional) the Blake2-256 hash of the Asset

The number of times an asset has been trapped. If the hash was omitted, it returns an array of all of the hashes and the number of times each asset has been trapped.

// If using Polkadot.js API and calling toJSON() on the value
// If hash was provided:
10

// If hash was omitted:
[
  [
    0xf7d4341888be30c6a842a77c52617423e8109aa249e88779019cf731ed772fb7
  ],
  10
],
...
import { ApiPromise, WsProvider } from '@polkadot/api';

const main = async () => {
  const api = await ApiPromise.create({
    provider: new WsProvider('INSERT_WSS_ENDPOINT'),
  });
  const trappedAssets = await api.query.polkadotXcm.assetTraps.entries();
  trappedAssets.forEach(
    ([
      {
        args: [hash],
      },
      count
    ]) => {
      console.log(
        `Asset with hash ${hash.toJSON()} has been trapped ${count.toJSON()} times`
      );
    }
  );
};

main();
palletVersion() — returns current pallet version from storage

None

A number representing the current version of the pallet.

// If using Polkadot.js API and calling toJSON() on the unwrapped value
0
import { ApiPromise, WsProvider } from '@polkadot/api';

const main = async () => {
  const api = await ApiPromise.create({
    provider: new WsProvider('INSERT_WSS_ENDPOINT'),
  });
  const palletVersion = await api.query.polkadotXcm.palletVersion();
};

main();

Checking Prerequisites

To follow along with this guide, you will need the following:

  • Your account must be funded with DEV tokens. You can get DEV tokens for testing on Moonbase Alpha once every 24 hours from the Moonbase Alpha Faucet

Execute an XCM Message Locally

This section of the guide covers the process of building a custom XCM message to be executed locally (i.e., in Moonbeam) via two different methods: the execute function of the Polkadot XCM Pallet and the xcmExecute function of the XCM Utilities Precompile. This functionality provides a playground for you to experiment with different XCM instructions and see firsthand the results of these experiments. This also comes in handy to determine the fees associated with a given XCM message on Moonbeam.

In the following example, you'll transfer DEV tokens from one account to another on Moonbase Alpha. To do so, you'll be building an XCM message that contains the following XCM instructions, which are executed locally (in this case, on Moonbase Alpha):

  • WithdrawAsset - removes assets and places them into the holding register
  • DepositAsset - removes the assets from the holding register and deposits the equivalent assets to a beneficiary account

Note

Typically, when you send an XCM message cross-chain to a target chain, the BuyExecution instruction is needed to pay for remote execution. However, for local execution, this instruction is not necessary as you are already getting charged via the extrinsic call.

Execute an XCM Message with the Polkadot.js API

In this example, you'll execute a custom XCM message locally on Moonbase Alpha using the Polkadot.js API to interact directly with the Polkadot XCM Pallet.

The execute function of the Polkadot XCM Pallet accepts two parameters: message and maxWeight. You can start assembling these parameters by taking the following steps:

  1. Build the WithdrawAsset instruction, which will require you to define:

    • The multilocation of the DEV token on Moonbase Alpha
    • The amount of DEV tokens to transfer
    const instr1 = {
      WithdrawAsset: [
        {
          id: { parents: 0, interior: { X1: [{ PalletInstance: 3 }] } },
          fun: { Fungible: 100000000000000000n }, // 0.1 DEV
        },
      ],
    };
    
  2. Build the DepositAsset instruction, which will require you to define:

    • The asset identifier for DEV tokens. You can use the WildAsset format, which allows for wildcard matching, to identify the asset
    • The multilocation of the beneficiary account on Moonbase Alpha
    const instr2 = {
      DepositAsset: {
        assets: {
          Wild: {
            AllCounted: 1,
          },
        },
        beneficiary: {
          parents: 0,
          interior: {
            X1: [
              {
                AccountKey20: {
                  key: '0x3Cd0A705a2DC65e5b1E1205896BaA2be8A07c6e0',
                },
              },
            ],
          },
        },
      },
    };
    
  3. Combine the XCM instructions into a versioned XCM message:

    const message = { V4: [instr1, instr2] };
    
  4. Specify the maxWeight, which includes a value for refTime and proofSize that you will need to define. You can get both of these values by providing the XCM message as a parameter to the queryXcmWeight method of the xcmPaymentApi runtime call.

    const maxWeight = { refTime: 7250000000n, proofSize: 19374n };
    

Now that you have the values for each of the parameters, you can write the script for the execution. You'll take the following steps:

  1. Provide the input data for the call. This includes:
    • The Moonbase Alpha endpoint URL to create the provider
    • The values for each of the parameters of the execute function
  2. Create a Keyring instance that will be used to send the transaction
  3. Create the Polkadot.js API provider
  4. Craft the polkadotXcm.execute extrinsic with the message and maxWeight
  5. Send the transaction using the signAndSend extrinsic and the Keyring instance you created in the second step

Remember

This is for demo purposes only. Never store your private key in a JavaScript file.

import { ApiPromise, WsProvider, Keyring } from '@polkadot/api'; // Version 10.13.1
import { cryptoWaitReady } from '@polkadot/util-crypto';

// 1. Provide input data
const providerWsURL = 'wss://wss.api.moonbase.moonbeam.network';
const privateKey = 'INSERT_PRIVATE_KEY';
const instr1 = {
  WithdrawAsset: [
    {
      id: { parents: 0, interior: { X1: [{ PalletInstance: 3 }] } },
      fun: { Fungible: 100000000000000000n }, // 0.1 DEV
    },
  ],
};
const instr2 = {
  DepositAsset: {
    assets: {
      Wild: {
        AllCounted: 1,
      },
    },
    beneficiary: {
      parents: 0,
      interior: {
        X1: [
          {
            AccountKey20: {
              key: '0x3Cd0A705a2DC65e5b1E1205896BaA2be8A07c6e0',
            },
          },
        ],
      },
    },
  },
};
const message = { V4: [instr1, instr2] };
const maxWeight = { refTime: 7250000000n, proofSize: 19374n };

const executeXcmMessage = async () => {
  // 2. Create Keyring instance
  await cryptoWaitReady();
  const keyring = new Keyring({ type: 'ethereum' });
  const alice = keyring.addFromUri(privateKey);

  // 3. Create Substrate API provider
  const substrateProvider = new WsProvider(providerWsURL);
  const api = await ApiPromise.create({ provider: substrateProvider });

  // 4. Craft the extrinsic
  const tx = api.tx.polkadotXcm.execute(message, maxWeight);

  // 5. Send the transaction
  const txHash = await tx.signAndSend(alice);
  console.log(`Submitted with hash ${txHash}`);

  api.disconnect();
};

executeXcmMessage();

Note

You can view an example of the above script, which sends 1 DEV to Bob's account on Moonbase Alpha, on Polkadot.js Apps using the following encoded calldata: 0x1c030408000400010403001300008a5d784563010d010204000103003cd0a705a2dc65e5b1e1205896baa2be8a07c6e007803822b001ba2e0100.

Once the transaction is processed, the 0.1 DEV tokens should be withdrawn from Alice's account along with the associated XCM fees, and the destination account should have received 0.1 DEV tokens in their account. A polkadotXcm.Attempted event will be emitted with the outcome.

Execute an XCM Message with the XCM Utilities Precompile

In this section, you'll use the xcmExecute function of the XCM Utilities Precompile, which is only supported on Moonbase Alpha, to execute an XCM message locally. The XCM Utilities Precompile is located at the following address:

0x000000000000000000000000000000000000080C

Under the hood, the xcmExecute function of the XCM Utilities Precompile calls the execute function of the Polkadot XCM Pallet, which is a Substrate pallet that is coded in Rust. The benefit of using the XCM Utilities Precompile to call xcmExecute is that you can do so via the Ethereum API and use Ethereum libraries like Ethers.js.

The xcmExecute function accepts two parameters: the SCALE encoded versioned XCM message to be executed and the maximum weight to be consumed.

First, you'll learn how to generate the encoded calldata, and then you'll learn how to use the encoded calldata to interact with the XCM Utilities Precompile.

Generate the Encoded Calldata of an XCM Message

To get the encoded calldata of the XCM message, you can create a script similar to the one you created in the Execute an XCM Message with the Polkadot.js API section. Instead of building the message and sending the transaction, you'll build the message to get the encoded calldata. You'll take the following steps:

  1. Provide the input data for the call. This includes:
  2. Create the Polkadot.js API provider
  3. Craft the polkadotXcm.execute extrinsic with the message and maxWeight
  4. Use the transaction to get the encoded calldata

The entire script is as follows:

import { ApiPromise, WsProvider } from '@polkadot/api'; // Version 10.13.1

// 1. Provide input data
const moonbeamAccount = 'INSERT_ADDRESS';
const providerWsURL = 'wss://wss.api.moonbase.moonbeam.network';
const instr1 = {
  WithdrawAsset: [
    {
      id: { parents: 0, interior: { X1: [{ PalletInstance: 3 }] } },
      fun: { Fungible: 100000000000000000n },
    },
  ],
};
const instr2 = {
  DepositAsset: {
    assets: { Wild: { AllCounted: 1 } },
    beneficiary: {
      parents: 0,
      interior: {
        X1: [
          {
            AccountKey20: {
              key: moonbeamAccount,
            },
          },
        ],
      },
    },
  },
};
const message = { V4: [instr1, instr2] };
const maxWeight = { refTime: 7250000000n, proofSize: 19374n };

const getEncodedXcmMessage = async () => {
  // 2. Create Substrate API provider
  const substrateProvider = new WsProvider(providerWsURL);
  const api = await ApiPromise.create({ provider: substrateProvider });

  // 3. Craft the extrinsic
  const tx = api.tx.polkadotXcm.execute(message, maxWeight);

  // 4. Get the encoded XCM message
  // By using index 0, you'll get just the encoded XCM message.
  // If you wanted to get the maxWeight, you could use index 1
  const encodedXcmMessage = tx.args[0].toHex();
  console.log(`Encoded Calldata for XCM Message: ${encodedXcmMessage}`);

  api.disconnect();
};

getEncodedXcmMessage();

Execute the XCM Message

Now that you have the SCALE encoded XCM message, you can use the following code snippets to programmatically call the xcmExecute function of the XCM Utilities Precompile using your Ethereum library of choice. Generally speaking, you'll take the following steps:

  1. Create a provider and signer
  2. Create an instance of the XCM Utilities Precompile to interact with
  3. Define parameters required for the xcmExecute function, which will be the encoded calldata for the XCM message and the maximum weight to use to execute the message. You can set the maxWeight to be 400000000n, which corresponds to the refTime. The proofSize will automatically be set to the default, which is 64KB
  4. Execute the XCM message

Remember

The following snippets are for demo purposes only. Never store your private keys in a JavaScript or Python file.

import { ethers } from 'ethers'; // Import Ethers library
import abi from './xcmUtilsABI.js'; // Import the XCM Utilities Precompile ABI

const privateKey = 'INSERT_YOUR_PRIVATE_KEY';
const xcmUtilsAddress = '0x000000000000000000000000000000000000080C';

/* Create Ethers provider and signer */
const provider = new ethers.JsonRpcProvider(
  'https://rpc.api.moonbase.moonbeam.network'
);
const signer = new ethers.Wallet(privateKey, provider);

/* Create contract instance of the XCM Utilities Precompile */
const xcmUtils = new ethers.Contract(
  xcmUtilsAddress,
  abi,
  signer
);

const executeXcmMessageLocally = async () => {
  /* Define parameters required for the xcmExecute function */
  const encodedCalldata = 'INSERT_ENCODED_CALLDATA';
  const maxWeight = '400000000';

  /* Execute the custom XCM message */
  const tx = await xcmUtils.xcmExecute(encodedCalldata, maxWeight);
  await tx.wait();
  console.log(`Transaction receipt: ${tx.hash}`);
};

executeXcmMessageLocally();
import { Web3 } from 'web3'; // Import Web3 library
import abi from './xcmUtilsABI.js'; // Import the XCM Utilities Precompile ABI

const privateKey = 'INSERT_PRIVATE_KEY';
const accountFrom = web3.eth.accounts.privateKeyToAccount(privateKey).address;
const xcmUtilsAddress = '0x000000000000000000000000000000000000080C';

/* Create Web3 provider */
const web3 = new Web3('https://rpc.api.moonbase.moonbeam.network'); // Change to network of choice

/* Create contract instance of the XCM Utilities Precompile */
const xcmUtils = new web3.eth.Contract(
  abi,
  xcmUtilsAddress,
  { from: accountFrom } // 'from' is necessary for gas estimation
);

const executeXcmMessageLocally = async () => {
  /* Define parameters required for the xcmExecute function */
  const encodedCalldata = 'INSERT_ENCODED_CALLDATA';
  const maxWeight = '400000000';

  /* Send the custom XCM message */
  // Craft the extrinsic
  const tx = await xcmUtils.methods.xcmExecute(encodedCalldata, maxWeight);
  // Sign transaction
  const signedTx = await web3.eth.accounts.signTransaction(
    {
      to: xcmUtilsAddress,
      data: tx.encodeABI(),
      gas: await tx.estimateGas(),
      gasPrice: await web3.eth.getGasPrice(),
      nonce: await web3.eth.getTransactionCount(accountFrom),
    },
    privateKey
  );
  // Send the signed transaction
  const sendTx = await web3.eth.sendSignedTransaction(signedTx.rawTransaction);
  console.log(`Transaction receipt: ${sendTx.transactionHash}`);
};

executeXcmMessageLocally();
from web3 import Web3

abi = "INSERT_XCM_UTILS_ABI"  # Paste or import the XCM Utils ABI
# This is for demo purposes, never store your private key in plain text
private_key = "INSERT_PRIVATE_KEY"
# The wallet address that corresponds to your private key
address = "INSERT_ADDRESS"
xcm_utils_address = "0x000000000000000000000000000000000000080C"

## Create Web3 provider ##
web3 = Web3(Web3.HTTPProvider("https://rpc.api.moonbase.moonbeam.network"))

## Create contract instance of the XCM Utilities Precompile ##
xcm_utils = web3.eth.contract(
    # XCM Utilities Precompile address
    address=xcm_utils_address,
    abi=abi,
)


def execute_xcm_message_locally():
    ## Define parameters required for the xcmExecute function ##
    encoded_calldata = "INSERT_ENCODED_CALLDATA"
    max_weight = 400000000

    ## Execute the custom XCM message ##
    # Craft the extrinsic
    tx = xcm_utils.functions.xcmExecute(encoded_calldata, max_weight).build_transaction(
        {
            "from": address,
            "nonce": web3.eth.get_transaction_count(address),
        }
    )
    # Sign transaction
    signedTx = web3.eth.account.sign_transaction(tx, private_key)
    # Send tx
    hash = web3.eth.send_raw_transaction(signedTx.rawTransaction)
    receipt = web3.eth.wait_for_transaction_receipt(hash)
    print(f"Transaction receipt: { receipt.transactionHash.hex() }")


execute_xcm_message_locally()

And that's it! You've successfully used the Polkadot XCM Pallet and the XCM Utilities Precompile to execute a custom XCM message locally on Moonbase Alpha!

Send an XCM Message Cross-Chain

This section of the guide covers the process of sending a custom XCM message cross-chain (i.e., from Moonbeam to a target chain, such as the relay chain) via two different methods: the send function of the Polkadot XCM Pallet and the xcmSend function of the XCM Utilities Precompile.

For the XCM message to be successfully executed, the target chain needs to be able to understand the instructions in the message. If it doesn't, you'll see a Barrier filter on the destination chain. For security reasons, the XCM message is prepended with the DecendOrigin instruction to prevent XCM execution on behalf of the origin chain Sovereign account. The example in this section will not work for the reasons mentioned above, it is purely for demonstration purposes.

In the following example, you'll be building an XCM message that contains the following XCM instructions, which will be executed in the Alphanet relay chain:

  • WithdrawAsset - removes assets and places them into the holding register
  • BuyExecution - takes the assets from holding to pay for execution fees. The fees to pay are determined by the target chain
  • DepositAsset- removes the assets from the holding register and deposits the equivalent assets to a beneficiary account

Together, the intention of these instructions is to transfer the native asset of the relay chain, which is UNIT for the Alphanet relay chain, from Moonbase Alpha to an account on the relay chain. This example is for demonstration purposes only to show you how a custom XCM message could be sent cross-chain. Please keep in mind that the target chain needs to be able to understand the instructions in the message to execute them.

Send an XCM Message with the Polkadot.js API

In this example, you'll send a custom XCM message from your account on Moonbase Alpha to the relay chain using the Polkadot.js API to interact directly with the Polkadot XCM Pallet.

The send function of the Polkadot XCM Pallet accepts two parameters: dest and message. You can start assembling these parameters by taking the following steps:

  1. Build the multilocation of the relay chain token, UNIT, for the dest:

    const dest = { V4: { parents: 1, interior: null } };
    
  2. Build the WithdrawAsset instruction, which will require you to define:

    • The multilocation of the UNIT token on the relay chain
    • The amount of UNIT tokens to withdraw
    const instr1 = {
      WithdrawAsset: [
        {
          id: { parents: 1, interior: null },
          fun: { Fungible: 1000000000000n }, // 1 UNIT
        },
      ],
    };
    
  3. Build the BuyExecution instruction, which will require you to define:

    • The multilocation of the UNIT token on the relay chain
    • The amount of UNIT tokens to buy for execution
    • The weight limit
    const instr2 = {
      BuyExecution: [
        {
          id: { parents: 1, interior: null },
          fun: { Fungible: 1000000000000n }, // 1 UNIT
        },
        { Unlimited: null },
      ],
    };
    
  4. Build the DepositAsset instruction, which will require you to define:

    • The asset identifier for UNIT tokens. You can use the WildAsset format, which allows for wildcard matching, to identify the asset
    • The multilocation of the beneficiary account on the relay chain
    const instr3 = {
      DepositAsset: {
        assets: { Wild: 'All' },
        beneficiary: {
          parents: 1,
          interior: {
            X1: [
              {
                AccountId32: {
                  id: relayAccount,
                },
              },
            ],
          },
        },
      },
    };
    
  5. Combine the XCM instructions into a versioned XCM message:

    const message = { V4: [instr1, instr2, instr3] };
    

Now that you have the values for each of the parameters, you can write the script to send the XCM message. You'll take the following steps:

  1. Provide the input data for the call. This includes:
    • The Moonbase Alpha endpoint URL to create the provider
    • The values for each of the parameters of the send function
  2. Create a Keyring instance that will be used to send the transaction
  3. Create the Polkadot.js API provider
  4. Craft the polkadotXcm.send extrinsic with the dest and message
  5. Send the transaction using the signAndSend extrinsic and the Keyring instance you created in the second step

Remember

This is for demo purposes only. Never store your private key in a JavaScript file.

import { ApiPromise, WsProvider, Keyring } from '@polkadot/api'; // Version 10.13.1
import { cryptoWaitReady, decodeAddress } from '@polkadot/util-crypto';

// 1. Input data
const providerWsURL = 'wss://wss.api.moonbase.moonbeam.network';
// You can use the decodeAddress function to ensure that your address is properly
// decoded. If it isn't decoded, it will decode it and if it is, it will ignore it
const privateKey = 'INSERT_PRIVATE_KEY';
const relayAccount = decodeAddress('INSERT_ADDRESS');
const dest = { V4: { parents: 1, interior: null } };
const instr1 = {
  WithdrawAsset: [
    {
      id: { parents: 1, interior: null },
      fun: { Fungible: 1000000000000n }, // 1 UNIT
    },
  ],
};
const instr2 = {
  BuyExecution: [
    {
      id: { parents: 1, interior: null },
      fun: { Fungible: 1000000000000n }, // 1 UNIT
    },
    { Unlimited: null },
  ],
};
const instr3 = {
  DepositAsset: {
    assets: { Wild: 'All' },
    beneficiary: {
      parents: 1,
      interior: {
        X1: [
          {
            AccountId32: {
              id: relayAccount,
            },
          },
        ],
      },
    },
  },
};
const message = { V4: [instr1, instr2, instr3] };

const sendXcmMessage = async () => {
  // 2. Create Keyring instance
  await cryptoWaitReady();
  const keyring = new Keyring({ type: 'ethereum' });
  const alice = keyring.addFromUri(privateKey);

  // 3. Create Substrate API Provider
  const substrateProvider = new WsProvider(providerWsURL);
  const api = await ApiPromise.create({ provider: substrateProvider });

  // 4. Create the extrinsic
  const tx = api.tx.polkadotXcm.send(dest, message);

  // 5. Send the transaction
  const txHash = await tx.signAndSend(alice);
  console.log(`Submitted with hash ${txHash}`);

  api.disconnect();
};

sendXcmMessage();

Note

You can view an example of the above script, which sends 1 UNIT to Bobs's relay chain account, on Polkadot.js Apps using the following encoded calldata: 0x1c00040100040c0004010000070010a5d4e813010000070010a5d4e8000d0100010101000c36e9ba26fa63c60ec728fe75fe57b86a450d94e7fee7f9f9eddd0d3f400d67.

Once the transaction is processed, a polkadotXcm.sent event is emitted with the details of the sent XCM message.

Send an XCM Message with the XCM Utilities Precompile

In this section, you'll use the xcmSend function of the XCM Utilities Precompile, which is only supported on Moonbase Alpha, to send an XCM message cross-chain. The XCM Utilities Precompile is located at the following address:

0x000000000000000000000000000000000000080C

Under the hood, the xcmSend function of the XCM Utilities Precompile calls the send function of the Polkadot XCM Pallet, which is a Substrate pallet that is coded in Rust. The benefit of using the XCM Utilities Precompile to call xcmSend is that you can do so via the Ethereum API and use Ethereum libraries like Ethers.js. For the XCM message to be successfully executed, the target chain needs to be able to understand the instructions in the message.

The xcmSend function accepts two parameters: the multilocation of the destination and the SCALE encoded versioned XCM message to be sent.

First, you'll learn how to generate the encoded calldata for the XCM message, and then you'll learn how to use the encoded calldata to interact with the XCM Utilities Precompile.

Generate the Encoded Calldata of an XCM Message

To get the encoded calldata of the XCM message, you can create a script similar to the one you created in the Send an XCM Message with the Polkadot.js API section. Instead of building the message and sending the transaction, you'll build the message to get the encoded calldata. You'll take the following steps:

  1. Provide the input data for the call. This includes:
  2. Create the Polkadot.js API provider
  3. Craft the polkadotXcm.execute extrinsic with the message and maxWeight
  4. Use the transaction to get the encoded calldata

The entire script is as follows:

import { ApiPromise, WsProvider } from '@polkadot/api'; // Version 10.13.1
import { decodeAddress } from '@polkadot/util-crypto';

// 1. Input data
const providerWsURL = 'wss://wss.api.moonbase.moonbeam.network';
// You can use the decodeAddress function to ensure that your address is properly
// decoded. If it isn't decoded, it will decode it and if it is, it will ignore it
const relayAccount = decodeAddress('INSERT_ADDRESS');
const dest = { V4: { parents: 1, interior: null } };
const instr1 = {
  WithdrawAsset: [
    {
      id: { parents: 1, interior: null },
      fun: { Fungible: 1000000000000n }, // 1 UNIT
    },
  ],
};
const instr2 = {
  BuyExecution: [
    {
      id: { parents: 1, interior: null },
      fun: { Fungible: 1000000000000n }, // 1 UNIT
    },
    { Unlimited: null },
  ],
};
const instr3 = {
  DepositAsset: {
    assets: { Wild: 'All' },
    beneficiary: {
      parents: 1,
      interior: {
        X1: [
          {
            AccountId32: {
              id: relayAccount,
            },
          },
        ],
      },
    },
  },
};
const message = { V4: [instr1, instr2, instr3] };

const generateEncodedXcmMessage = async () => {
  // 2. Create Substrate API Provider
  const substrateProvider = new WsProvider(providerWsURL);
  const api = await ApiPromise.create({ provider: substrateProvider });

  // 3. Create the extrinsic
  const tx = api.tx.polkadotXcm.send(dest, message);

  // 4. Get the encoded XCM message
  // By using index 1, you'll get just the encoded XCM message.
  // If you wanted to get the dest, you could use index 0
  const encodedXcmMessage = tx.args[1].toHex();
  console.log(`Encoded Calldata for XCM Message: ${encodedXcmMessage}`);

  api.disconnect();
};

generateEncodedXcmMessage();

Send the XCM Message

Before you can send the XCM message, you'll also need to build the multilocation of the destination. For this example, you'll target the relay chain with Moonbase Alpha as the origin chain:

const dest = [
  1, // Parents: 1 
  [] // Interior: Here
];

Now that you have the SCALE encoded XCM message and the destination multilocation, you can use the following code snippets to programmatically call the xcmSend function of the XCM Utilities Precompile using your Ethereum library of choice. Generally speaking, you'll take the following steps:

  1. Create a provider and signer
  2. Create an instance of the XCM Utilities Precompile to interact with
  3. Define parameters required for the xcmSend function, which will be the destination and the encoded calldata for the XCM message
  4. Send the XCM message

Remember

The following snippets are for demo purposes only. Never store your private keys in a JavaScript or Python file.

import { ethers } from 'ethers'; // Import Ethers library
import abi from './xcmUtilsABI.js'; // Import the XCM Utilities Precompile ABI

const privateKey = 'INSERT_PRIVATE_KEY';
const xcmUtilsAddress = '0x000000000000000000000000000000000000080C';

/* Create Ethers provider and signer */
const provider = new ethers.JsonRpcProvider(
  'https://rpc.api.moonbase.moonbeam.network'
);
const signer = new ethers.Wallet(privateKey, provider);

/* Create contract instance of the XCM Utilities Precompile */
const xcmUtils = new ethers.Contract(
  xcmUtilsAddress,
  abi,
  signer
);

const sendXcm = async () => {
  /* Define parameters required for the xcmSend function */
  const encodedCalldata = 'INSERT_ENCODED_CALLDATA';
  const dest = [
    1, // Parents: 1 
    [] // Interior: Here
  ];

  /* Send the custom XCM message */
  const tx = await xcmUtils.xcmSend(dest, encodedCalldata);
  await tx.wait();
  console.log(`Transaction receipt: ${tx.hash}`);
};

sendXcm();
import { Web3 } from 'web3'; // Import Web3 library
import abi from './xcmUtilsABI.js'; // Import the XCM Utilities Precompile ABI

const privateKey = 'INSERT_PRIVATE_KEY';
const accountFrom = web3.eth.accounts.privateKeyToAccount(privateKey).address;
const xcmUtilsAddress = '0x000000000000000000000000000000000000080C';

/* Create Web3 provider */
const web3 = new Web3('https://rpc.api.moonbase.moonbeam.network'); // Change to network of choice

/* Create contract instance of the XCM Utilities Precompile */
const xcmUtils = new web3.eth.Contract(
  abi,
  xcmUtilsAddress,
  { from: accountFrom } // 'from' is necessary for gas estimation
);

const sendXcm = async () => {
  /* Define parameters required for the xcmSend function */
  const encodedCalldata = 'INSERT_ENCODED_CALLDATA';
  const dest = [
    1, // Parents: 1
    [], // Interior: Here
  ];

  /* Send the custom XCM message */
  // Craft the extrinsic
  const tx = await xcmUtils.methods.xcmSend(dest, encodedCalldata);
  // Sign transaction
  const signedTx = await web3.eth.accounts.signTransaction(
    {
      to: xcmUtilsAddress,
      data: tx.encodeABI(),
      gas: await tx.estimateGas(),
      gasPrice: await web3.eth.getGasPrice(),
      nonce: await web3.eth.getTransactionCount(accountFrom),
    },
    privateKey
  );
  // Send the signed transaction
  const sendTx = await web3.eth.sendSignedTransaction(signedTx.rawTransaction);
  console.log(`Transaction receipt: ${sendTx.transactionHash}`);
};

sendXcm();
from web3 import Web3

abi = "INSERT_XCM_UTILS_ABI"  # Paste or import the XCM Utils ABI
# This is for demo purposes, never store your private key in plain text
private_key = "INSERT_PRIVATE_KEY"
# The wallet address that corresponds to your private key
address = "INSERT_ADDRESS"
xcm_utils_address = "0x000000000000000000000000000000000000080C"

## Create Web3 provider ##
web3 = Web3(Web3.HTTPProvider("https://rpc.api.moonbase.moonbeam.network"))

## Create contract instance of the XCM Utilities Precompile ##
xcm_utils = web3.eth.contract(
    # XCM Utilities Precompile address
    address=xcm_utils_address,
    abi=abi,
)


def send_xcm():
    ## Define parameters required for the xcmSend function ##
    encoded_calldata = "INSERT_ENCODED_CALLDATA"
    xcm_dest = [1, []]  # Parents: 1  # Interior: Here

    ## Send the custom XCM message ##
    # Craft the extrinsic
    tx = xcm_utils.functions.xcmSend(xcm_dest, encoded_calldata).build_transaction(
        {
            "from": address,
            "nonce": web3.eth.get_transaction_count(address),
        }
    )
    # Sign transaction
    signed_tx = web3.eth.account.sign_transaction(tx, private_key)
    # Send tx
    hash = web3.eth.send_raw_transaction(signed_tx.rawTransaction)
    receipt = web3.eth.wait_for_transaction_receipt(hash)
    print(f"Transaction receipt: { receipt.transactionHash.hex() }")


send_xcm()

And that's it! You've successfully used the Polkadot XCM Pallet and the XCM Utilities Precompile to send a message from Moonbase Alpha to another chain!

Last update: October 18, 2024
| Created: March 21, 2023