XCM Precompile¶
Introduction¶
As a Polkadot parachain, Moonbeam has the inherent ability to communicate and exchange data with other connected parachains. This native cross-chain communication allows safe and fast token transfers leveraging the Cross-Consensus Message format (XCM for short), facilitating communication between different consensus systems.
The communication protocol enabling token transfers is built on Substrate and runs on a lower level than the EVM, making it harder for EVM developers to access.
Nevertheless, Moonbeam networks have an XCM Precompile that fills the gap between execution layers. This precompile exposes a smart contract interface that abstracts away the underlying complexities, making the execution of cross-chain token transfers as easy as any other smart contract call.
This guide will show you how to interact with the XCM Interface precompile to execute cross-chain token transfers through the Ethereum API.
The XCM Precompile is located at the following address:
0x000000000000000000000000000000000000081A
0x000000000000000000000000000000000000081A
0x000000000000000000000000000000000000081A
Note
There can be some unintended consequences when using the precompiled contracts on Moonbeam. Please refer to the Security Considerations page for more information.
The XCM Solidity Interface¶
The XCMInterface.sol
is a Solidity interface that allows developers to interact with the methods of pallet-xcm
.
XCMInterface.sol
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.3;
/// @dev The XCM contract's address.
address constant XCM_CONTRACT_ADDRESS = 0x000000000000000000000000000000000000081A;
/// @dev The XCM contract's instance.
XCM constant XCM_CONTRACT = XCM(XCM_CONTRACT_ADDRESS);
/// @author The Moonbeam Team
/// @title XCM precompile Interface
/// @dev The interface that Solidity contracts use to interact with the substrate pallet-xcm.
interface XCM {
// A location is defined by its number of parents and the encoded junctions (interior)
struct Location {
uint8 parents;
bytes[] interior;
}
// Support for Weights V2
struct Weight {
uint64 refTime;
uint64 proofSize;
}
// A way to represent fungible assets in XCM using Location format
struct AssetLocationInfo {
Location location;
uint256 amount;
}
// A way to represent fungible assets in XCM using address format
struct AssetAddressInfo {
address asset;
uint256 amount;
}
// The values start at `0` and are represented as `uint8`
enum TransferType {
Teleport,
LocalReserve,
DestinationReserve
}
/// @dev Function to send assets via XCM using transfer_assets() pallet-xcm extrinsic.
/// @custom:selector 9ea8ada7
/// @param dest The destination chain.
/// @param beneficiary The actual account that will receive the tokens on dest.
/// @param assets The combination (array) of assets to send in Location format.
/// @param feeAssetItem The index of the asset that will be used to pay for fees.
function transferAssetsLocation(
Location memory dest,
Location memory beneficiary,
AssetLocationInfo[] memory assets,
uint32 feeAssetItem
) external;
/// @dev Function to send assets via XCM to a 20 byte-like parachain
/// using transfer_assets() pallet-xcm extrinsic.
/// @custom:selector a0aeb5fe
/// @param paraId The para-id of the destination chain.
/// @param beneficiary The actual account that will receive the tokens on paraId destination.
/// @param assets The combination (array) of assets to send in Address format.
/// @param feeAssetItem The index of the asset that will be used to pay for fees.
function transferAssetsToPara20(
uint32 paraId,
address beneficiary,
AssetAddressInfo[] memory assets,
uint32 feeAssetItem
) external;
/// @dev Function to send assets via XCM to a 32 byte-like parachain
/// using transfer_assets() pallet-xcm extrinsic.
/// @custom:selector f23032c3
/// @param paraId The para-id of the destination chain.
/// @param beneficiary The actual account that will receive the tokens on paraId destination.
/// @param assets The combination (array) of assets to send in Address format.
/// @param feeAssetItem The index of the asset that will be used to pay for fees.
function transferAssetsToPara32(
uint32 paraId,
bytes32 beneficiary,
AssetAddressInfo[] memory assets,
uint32 feeAssetItem
) external;
/// @dev Function to send assets via XCM to the relay chain
/// using transfer_assets() pallet-xcm extrinsic.
/// @custom:selector 6521cc2c
/// @param beneficiary The actual account that will receive the tokens on the relay chain.
/// @param assets The combination (array) of assets to send in Address format.
/// @param feeAssetItem The index of the asset that will be used to pay for fees.
function transferAssetsToRelay(
bytes32 beneficiary,
AssetAddressInfo[] memory assets,
uint32 feeAssetItem
) external;
/// @dev Function to send assets through transfer_assets_using_type_and_then() pallet-xcm
/// extrinsic.
/// Important: in this selector RemoteReserve type (for either assets or fees) is not allowed.
/// If users want to send assets and fees (in Location format) with a remote reserve,
/// they must use the selector fc19376c.
/// @custom:selector 8425d893
/// @param dest The destination chain.
/// @param assets The combination (array) of assets to send in Location format.
/// @param assetsTransferType The TransferType corresponding to assets being sent.
/// @param remoteFeesIdIndex The index of the asset (inside assets array) to use as fees.
/// @param feesTransferType The TransferType corresponding to the asset used as fees.
/// @param customXcmOnDest The XCM message to execute on destination chain.
function transferAssetsUsingTypeAndThenLocation(
Location memory dest,
AssetLocationInfo[] memory assets,
TransferType assetsTransferType,
uint8 remoteFeesIdIndex,
TransferType feesTransferType,
bytes memory customXcmOnDest
) external;
/// @dev Function to send assets through transfer_assets_using_type_and_then() pallet-xcm
/// extrinsic.
/// @custom:selector fc19376c
/// @param dest The destination chain.
/// @param assets The combination (array) of assets to send in Location format.
/// @param remoteFeesIdIndex The index of the asset (inside assets array) to use as fees.
/// @param customXcmOnDest The XCM message to execute on destination chain.
/// @param remoteReserve The remote reserve corresponding for assets and fees. They MUST
/// share the same reserve.
function transferAssetsUsingTypeAndThenLocation(
Location memory dest,
AssetLocationInfo[] memory assets,
uint8 remoteFeesIdIndex,
bytes memory customXcmOnDest,
Location memory remoteReserve
) external;
/// @dev Function to send assets through transfer_assets_using_type_and_then() pallet-xcm
/// extrinsic.
/// Important: in this selector RemoteReserve type (for either assets or fees) is not allowed.
/// If users want to send assets and fees (in Address format) with a remote reserve,
/// they must use the selector aaecfc62.
/// @custom:selector 998093ee
/// @param dest The destination chain.
/// @param assets The combination (array) of assets to send in Address format.
/// @param assetsTransferType The TransferType corresponding to assets being sent.
/// @param remoteFeesIdIndex The index of the asset (inside assets array) to use as fees.
/// @param feesTransferType The TransferType corresponding to the asset used as fees.
/// @param customXcmOnDest The XCM message to execute on destination chain.
function transferAssetsUsingTypeAndThenAddress(
Location memory dest,
AssetAddressInfo[] memory assets,
TransferType assetsTransferType,
uint8 remoteFeesIdIndex,
TransferType feesTransferType,
bytes memory customXcmOnDest
) external;
/// @dev Function to send assets through transfer_assets_using_type_and_then() pallet-xcm
/// extrinsic.
/// @custom:selector aaecfc62
/// @param dest The destination chain.
/// @param assets The combination (array) of assets to send in Address format.
/// @param remoteFeesIdIndex The index of the asset (inside assets array) to use as fees.
/// @param customXcmOnDest The XCM message to execute on destination chain.
/// @param remoteReserve The remote reserve corresponding for assets and fees. They MUST
/// share the same reserve.
function transferAssetsUsingTypeAndThenAddress(
Location memory dest,
AssetAddressInfo[] memory assets,
uint8 remoteFeesIdIndex,
bytes memory customXcmOnDest,
Location memory remoteReserve
) external;
}
The interface includes the necessary data structures along with the following functions:
transferAssetsToPara20(paraId, beneficiary, assets, feeAssetItem) — sends assets via XCM to a 20 byte-like parachain using the underlying transfer_assets()
transaction included in the XCM pallet module
paraId
uint32 - the para-id of the destination chainbeneficiary
address - the ECDSA-type account in the destination chain that will receive the tokensassets
AssetAddressInfo[] memory - an array of assets to send in Address formatfeeAssetItem
uint32 - the index of the asset that will be used to pay fees
paraId
- 888beneficiary
- 0x3f0Aef9Bd799F1291b80376aD57530D353ab0217assets
- [["0x0000000000000000000000000000000000000802", 1000000000000000000]]feeAssetItem
- 0
transferAssetsToPara32(paraId, beneficiary, assets, feeAssetItem) — sends assets via XCM to a 32 byte-like parachain using the underlying transfer_assets()
transaction included in the XCM pallet module
paraId
uint32 - the para-id of the destination chainbeneficiary
bytes32 - the actual account that will receive the tokens on paraId destinationassets
AssetAddressInfo[] memory - an array of assets to send in Address formatfeeAssetItem
uint32 - the index of the asset that will be used to pay fees
paraId
- 888beneficiary
- 0xf831d83025f527daeed39a644d64d335a4e627b5f4becc78fb67f05976889a06assets
- [["0x0000000000000000000000000000000000000802", 1000000000000000000]]feeAssetItem
- 0
transferAssetsToRelay(beneficiary, assets, feeAssetItem) — sends assets via XCM to the relay chain using the underlying transfer_assets()
transaction included in the XCM pallet module
beneficiary
bytes32 - the actual account that will receive the tokens on the relay chainassets
AssetAddressInfo[] memory - an array of assets to send in Address formatfeeAssetItem
uint32 - the index of the asset that will be used to pay fees
beneficiary
- 0xf831d83025f527daeed39a644d64d335a4e627b5f4becc78fb67f05976889a06assets
- [["0x0000000000000000000000000000000000000802", 1000000000000000000]]feeAssetItem
- 0
transferAssetsLocation(dest, beneficiary, assets, feeAssetItem) — sends assets using the underlying transfer_assets()
transaction included in the XCM pallet module
dest
Location memory - the destination chainbeneficiary
Location memory - the account in the destination chain that will receive the tokensassets
AssetLocationInfo[] memory - an array of assets to sendfeeAssetItem
uint32 - the index of the asset that will be used to pay fees
dest
- ["1",[]]beneficiary
- [0, ["0x01f831d83025f527daeed39a644d64d335a4e627b5f4becc78fb67f05976889a0600"]]assets
- [[[1, ["0x010000000000000000000000000000000000000800"]], 1000000000000000000]]feeAssetItem
- 0
transferAssetsUsingTypeAndThenLocation(dest, assets, assetsTransferType, remoteFeesIdIndex, feesTransferType, customXcmOnDest) — sends assets through transfer_assets_using_type_and_then()
pallet-xcm extrinsic. Important: RemoteReserve type (for either assets or fees) is prohibited. For sending assets and fees (in Location format) with a remote reserve, use the subsequent transferAssetsUsingTypeAndThenLocation
which shares the same function name as this but takes a different set of parameters
dest
Location memory - the destination chainassets
AssetLocationInfo[] memory - an array of assets to send in Location formatassetsTransferType
TransferType - the TransferType corresponding to assets being sent (Teleport = 0, LocalReserve = 1, DestinationReserve = 2)remoteFeesIdIndex
uint8 - the index of the asset (inside assets array) to use as feesfeesTransferType
TransferType - the TransferType corresponding to the asset used as fees (Teleport = 0, LocalReserve = 1, DestinationReserve = 2)customXcmOnDest
bytes memory - the XCM message to execute on destination chain
dest
- ["1",[]]assets
- [[[1, ["0x010000000000000000000000000000000000000802"]], 1000000000000000000]]assetsTransferType
- 0remoteFeesIdIndex
- 0feesTransferType
- 1customXcmOnDest
- 0x0408000400010403001300008a5d784563010d01020400010300f8234bedd9553e7668c4e0d60aced12e22bd2d45
transferAssetsUsingTypeAndThenLocation(dest, assets, remoteFeesIdIndex, customXcmOnDest, remoteReserve) — sends assets through transfer_assets_using_type_and_then()
pallet-xcm extrinsic. Important: The remote reserve must be shared between assets and fees
dest
Location memory - the destination chainassets
AssetLocationInfo[] memory - an array of assets to send in Location formatremoteFeesIdIndex
uint8 - the index of the asset (inside assets array) to use as feescustomXcmOnDest
bytes memory - the XCM message to execute on destination chainremoteReserve
Location memory - the remote reserve corresponding for assets and fees (must be shared)
dest
- ["1",[]]assets
- [[[1, ["0x010000000000000000000000000000000000000800"]], 1000000000000000000]]remoteFeesIdIndex
- 0customXcmOnDest
- 0x0408000400010403001300008a5d784563010d01020400010300f8234bedd9553e7668c4e0d60aced12e22bd2d45remoteReserve
- [1,[]]
transferAssetsUsingTypeAndThenAddress(dest, assets, assetsTransferType, remoteFeesIdIndex, feesTransferType, customXcmOnDest) — sends assets through transfer_assets_using_type_and_then()
pallet-xcm extrinsic. Important: RemoteReserve type (for either assets or fees) is not allowed. For sending assets and fees (in Address format) with a remote reserve, use the subsequent transferAssetsUsingTypeAndThenAddress
, which shares the same name as this function but takes a different set of parameters
dest
Location memory - the destination chainassets
AssetAddressInfo[] memory - an array of assets to send in Address formatassetsTransferType
TransferType - the TransferType corresponding to assets being sent (Teleport = 0, LocalReserve = 1, DestinationReserve = 2)remoteFeesIdIndex
uint8 - the index of the asset (inside assets array) to use as feesfeesTransferType
TransferType - the TransferType corresponding to the asset used as fees (Teleport = 0, LocalReserve = 1, DestinationReserve = 2)customXcmOnDest
bytes memory - the XCM message to execute on destination chain
dest
- ["1",[]]assets
- [["0x0000000000000000000000000000000000000802", 1000000000000000000]]assetsTransferType
- 0remoteFeesIdIndex
- 0feesTransferType
- 1customXcmOnDest
- 0x0408000400010403001300008a5d784563010d01020400010300f8234bedd9553e7668c4e0d60aced12e22bd2d45
transferAssetsUsingTypeAndThenAddress(dest, assets, remoteFeesIdIndex, customXcmOnDest, remoteReserve) — sends assets through transfer_assets_using_type_and_then()
pallet-xcm extrinsic. Important: The remote reserve must be shared between assets and fees
dest
Location memory - the destination chainassets
AssetAddressInfo[] memory - an array of assets to send in Address formatremoteFeesIdIndex
uint8 - the index of the asset (inside assets array) to use as feescustomXcmOnDest
bytes memory - the XCM message to execute on destination chainremoteReserve
Location memory - the remote reserve corresponding for assets and fees (must be shared)
dest
- ["1",[]]assets
- [["0x0000000000000000000000000000000000000802", 1000000000000000000]]remoteFeesIdIndex
- 0customXcmOnDest
- 0x0408000400010403001300008a5d784563010d01020400010300f8234bedd9553e7668c4e0d60aced12e22bd2d45remoteReserve
- [1,[]]
Interact with the Solidity Interface¶
Checking Prerequisites¶
To follow this tutorial, you must have your preferred EVM wallet configured and an account funded with native tokens. You can add Moonbeam to MetaMask wallet following this guide: Interacting with Moonbeam Using MetaMask.
Remix Set Up¶
You can interact with the XCM Precompile using Remix. To add the precompile to Remix, you will need to:
- Get a copy of
XCMInterface.sol
- Paste the file contents into a Remix file named
XCMInterface.sol
Compile the Contract¶
Next, you will need to compile the interface in Remix:
- Click on the Compile tab, second from top
- Compile the interface by clicking on Compile XcmInterface.sol
When the compilation is completed, you will see a green checkmark next to the Compile tab.
Access the Contract¶
Instead of deploying the precompile, you will access the interface given the address of the precompiled contract:
- Click on the Deploy and Run tab directly below the Compile tab in Remix. Please note that the precompiled contracts are already accessible at their respective addresses. Therefore, there is no deployment step
- Make sure Injected Provider - Metamask is selected in the ENVIRONMENT dropdown. Once you select Injected Provider - Metamask, you may be prompted by MetaMask to connect your account to Remix if it's not already connected
- Make sure the correct account is displayed under ACCOUNT
- Ensure XCM - XcmInterface.sol is selected in the CONTRACT dropdown. Given that it is a precompiled contract, there is no deployment step. Instead, you are going to provide the address of the precompile in the At Address field
- Provide the address of the precompile:
0x000000000000000000000000000000000000081A
and click At Address
The XCM Interface precompile will appear in the list of Deployed Contracts.
Send Tokens Over to Another EVM-Compatible Appchain¶
To send tokens over to an account in another EVM-compatible appchain, please follow these steps:
- Expand the transferAssetsToPara20 function
- Enter the appchain ID (paraId)
- Enter the 20-byte (Ethereum-like) destination account (beneficiary)
- Specify the tokens to be transferred. Note that this parameter is an array that contains at least one asset. Each asset is specified by its address and the total amount to transfer
- Enter the index of the asset that will be used to pay the fees. This index is zero-based, so the first element is
0
, the second is1
, and so on - Click transact
- MetaMask will pop up, and you will be prompted to review the transaction details. Click Confirm to send the transaction
After the transaction is confirmed, wait a few blocks for the transfer to reach the destination chain and reflect the new balance.
Send Tokens Over to a Substrate Appchain¶
To send tokens over to an account in a Substrate appchain, please follow these steps:
- Expand the transferAssetsToPara32 function
- Enter the appchain ID (
paraId
) - Enter the sr25519-type destination account (beneficiary)
-
Specify the tokens to be transferred. Note that this parameter is an array that contains at least one asset. Each asset is specified by its address and the total amount to transfer
-
Enter the index of the asset that will be used to pay the fees. This index is zero-based, so the first element is
0
, the second is1
, and so on - Click transact
- MetaMask will pop up, and you will be prompted to review the transaction details. Click Confirm to send the transaction
After the transaction is confirmed, wait a few blocks for the transfer to reach the destination chain and reflect the new balance.
Send Tokens Over to the Relay Chain¶
To send tokens over to an account in the relay chain, please follow these steps:
- Expand the transferAssetsToRelay function
- Enter the sr25519-type destination account (beneficiary)
-
Specify the tokens to be transferred. Note that this parameter is an array that contains at least one asset. Each asset is specified by its address and the total amount to transfer
-
Enter the index of the asset that will be used to pay the fees. This index is zero-based, so the first element is
0
, the second is1
, and so on - Click transact
- MetaMask will pop up, and you will be prompted to review the transaction details. Click Confirm to send the transaction
After the transaction is confirmed, wait a few blocks for the transfer to reach the destination chain and reflect the new balance.
Send Tokens Over Specific Locations¶
There are two methods that share names with closely related methods, transferAssetsUsingTypeAndThenLocation
and transferAssetsUsingTypeAndThenAddress
. However, these are not duplicates. For each function, there is one that accepts five parameters and another that accepts six. The function with five parameters can only be used when the remote reserve is shared between assets and fees. If the remote reserve is not shared between assets and fees, you can use the six parameter version of the method to specify the information needed.
The following example will demonstrate transferAssetsUsingTypeAndThenAddress
when the remote reverse is shared between assets and fees. To follow along with the tutorial, take the following steps:
- Expand the transferAssetsUsingTypeAndThenAddress function
- Enter the multilocation that specifies the destination chain. Note that any chain can be specified, regardless of its configuration or type
- Enter the combination array of assets to send in Address format
- Enter the index of the asset that will be used to pay the fees. This index is zero-based, so the first element is
0
, the second is1
, and so on - Enter the XCM message to be executed on destination chain. For more information about creating XCM call data see Send and Execute XCM Messages
- Enter the remote reserve, e.g.
[1,[]]
- Click transact
- MetaMask will pop up, and you will be prompted to review the transaction details. Click Confirm to send the transaction
After the transaction is confirmed, wait a few blocks for the transfer to reach the destination chain and reflect the new balance.
| Created: July 31, 2024