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 executedmaxWeight
- the maximum weight allowed to be consumed, which is defined by specifying the:refTime
- the amount of computational time that can be used for executionproofSize
- 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 registerDepositAsset
- 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:
-
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 }, ], };
-
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', }, }, ], }, }, }, };
- The asset identifier for DEV tokens. You can use the
-
Combine the XCM instructions into a versioned XCM message:
const message = { V4: [instr1, instr2] };
-
Specify the
maxWeight
, which includes a value forrefTime
andproofSize
that you will need to define. You can get both of these values by providing the XCM message as a parameter to thequeryXcmWeight
method of thexcmPaymentApi
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:
- 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
- Create a Keyring instance that will be used to send the transaction
- Create the Polkadot.js API provider
- Craft the
polkadotXcm.execute
extrinsic with themessage
andmaxWeight
- 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:
- 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 as defined in the Execute an XCM Message with the Polkadot.js API section
- Create the Polkadot.js API provider
- Craft the
polkadotXcm.execute
extrinsic with themessage
andmaxWeight
- 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:
- Create a provider and signer
- Create an instance of the XCM Utilities Precompile to interact with
- 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 themaxWeight
to be400000000n
, which corresponds to therefTime
. TheproofSize
will automatically be set to the default, which is 64KB - 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 registerBuyExecution
- takes the assets from holding to pay for execution fees. The fees to pay are determined by the target chainDepositAsset
- 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:
-
Build the multilocation of the relay chain token, UNIT, for the
dest
:const dest = { V4: { parents: 1, interior: null } };
-
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 }, ], };
-
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 }, ], };
-
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, }, }, ], }, }, }, };
- The asset identifier for UNIT tokens. You can use the
-
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:
- 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
- Create a Keyring instance that will be used to send the transaction
- Create the Polkadot.js API provider
- Craft the
polkadotXcm.send
extrinsic with thedest
andmessage
- 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:
- 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 as defined in the Send an XCM Message with the Polkadot.js API section
- Create the Polkadot.js API provider
- Craft the
polkadotXcm.execute
extrinsic with themessage
andmaxWeight
- 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:
- Create a provider and signer
- Create an instance of the XCM Utilities Precompile to interact with
- Define parameters required for the
xcmSend
function, which will be the destination and the encoded calldata for the XCM message - 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!
| Created: March 21, 2023