Skip to content

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, exposing 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 precompile's functions.

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;
    }

    /// @dev Function to send assets via XCM using transfer_assets() pallet-xcm extrinsic.
    /// @custom:selector 59df8416
    /// @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.
    /// @param feeAssetItem The index of the asset that will be used to pay for fees.
    /// @param weight The weight to be used for the whole XCM operation.
    /// (uint64::MAX in refTime means Unlimited weight) 
    function transferAssetsLocation(
        Location memory dest,
        Location memory beneficiary,
        AssetLocationInfo[] memory assets,
        uint32 feeAssetItem,
        Weight memory weight
    ) external;

    /// @dev Function to send assets via XCM to a 20 byte-like parachain 
    /// using transfer_assets() pallet-xcm extrinsic.
    /// @custom:selector b489262e
    /// @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.
    /// @param feeAssetItem The index of the asset that will be used to pay for fees.
    /// @param weight The weight to be used for the whole XCM operation.
    /// (uint64::MAX in refTime means Unlimited weight)
    function transferAssetsToPara20(
        uint32 paraId,
        address beneficiary,
        AssetAddressInfo[] memory assets,
        uint32 feeAssetItem,
        Weight memory weight
    ) external;

    /// @dev Function to send assets via XCM to a 32 byte-like parachain 
    /// using transfer_assets() pallet-xcm extrinsic.
    /// @custom:selector 4461e6f5
    /// @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.
    /// @param feeAssetItem The index of the asset that will be used to pay for fees.
    /// @param weight The weight to be used for the whole XCM operation.
    /// (uint64::MAX in refTime means Unlimited weight)
    function transferAssetsToPara32(
        uint32 paraId,
        bytes32 beneficiary,
        AssetAddressInfo[] memory assets,
        uint32 feeAssetItem,
        Weight memory weight
    ) external;

    /// @dev Function to send assets via XCM to the relay chain 
    /// using transfer_assets() pallet-xcm extrinsic.
    /// @custom:selector d7c89659
    /// @param beneficiary The actual account that will receive the tokens on the relay chain.
    /// @param assets The combination (array) of assets to send.
    /// @param feeAssetItem The index of the asset that will be used to pay for fees.
    /// @param weight The weight to be used for the whole XCM operation.
    /// (uint64::MAX in refTime means Unlimited weight)
    function transferAssetsToRelay(
        bytes32 beneficiary,
        AssetAddressInfo[] memory assets,
        uint32 feeAssetItem,
        Weight memory weight
    ) external;
}

The interface includes the necessary data structures along with the following functions:

transferAssetsToPara20(paraId, beneficiary, assets, feeAssetItem, weight) — sends assets to another EVM-compatible appchain using the underlying transfer_assets() transaction included in the XCM pallet module
  • paraId uint32 - the destination's appchain ID
  • beneficiary address - the ECDSA-type account in the destination chain that will receive the tokens
  • assets AssetAddressInfo[] memory - an array of assets to send
  • feeAssetItem uint32 - the index of the asset that will be used to pay fees
  • weight Weight memory - the maximum gas to use in the whole operation. Setting uint64::MAX to refTime acts in practice as unlimited weight
  • paraId - 888
  • beneficiary - 0x3f0Aef9Bd799F1291b80376aD57530D353ab0217
  • assets - [["0x0000000000000000000000000000000000000800", 1000000000000000000]]
  • feeAssetItem - 0
  • weight - [9223372036854775807, 9223372036854775807]
transferAssetsToPara32(paraId, beneficiary, assets,feeAssetItem, weight) — sends assets to a Substrate appchain using the underlying transfer_assets() transaction included in the XCM pallet module
  • paraId uint32 - the destination's appchain ID
  • beneficiary bytes32 - the Substrate's SR25519-type account in the destination chain that will receive the tokens
  • assets AssetAddressInfo[] memory - an array of assets to send
  • feeAssetItem uint32 - the index of the asset that will be used to pay fees
  • weight Weight memory - the maximum gas to use in the whole operation. Setting uint64::MAX to refTime acts in practice as unlimited weight
  • paraId - 888
  • beneficiary - 0xf831d83025f527daeed39a644d64d335a4e627b5f4becc78fb67f05976889a06
  • assets - [["0x0000000000000000000000000000000000000800", 1000000000000000000]]
  • feeAssetItem - 0
  • weight - [9223372036854775807, 9223372036854775807]
transferAssetsToRelay(beneficiary, assets, feeAssetItem, weight) — sends assets to the relay chain using the underlying transfer_assets() transaction included in the XCM pallet module
  • beneficiary bytes32 - the Substrate's sr25519-type account in the relay chain that will receive the tokens
  • assets AssetAddressInfo[] memory - an array of assets to send
  • feeAssetItem uint32 - the index of the asset that will be used to pay fees
  • weight Weight memory - the maximum gas to use in the whole operation. Setting uint64::MAX to refTime acts in practice as unlimited weight
  • beneficiary - 0xf831d83025f527daeed39a644d64d335a4e627b5f4becc78fb67f05976889a06
  • assets - [["0x0000000000000000000000000000000000000800", 1000000000000000000]]
  • feeAssetItem - 0
  • weight - [9223372036854775807, 9223372036854775807]
transferAssetsLocation(dest, beneficiary, assets, feeAssetItem, weight) — sends assets using the underlying transfer_assets() transaction included in the XCM pallet module
  • dest Location memory - the destination chain
  • beneficiary Location memory - the account in the destination chain that will receive the tokens
  • assets AssetLocationInfo[] memory - an array of assets to send
  • feeAssetItem uint32 - the index of the asset that will be used to pay fees
  • weight Weight memory - the maximum gas to use in the whole operation. Setting uint64::MAX to refTime acts in practice as unlimited weight
  • dest - ["1",[]]
  • beneficiary - [0, ["0x01f831d83025f527daeed39a644d64d335a4e627b5f4becc78fb67f05976889a0600"]]
  • assets - [[[1, ["0x010000000000000000000000000000000000000800"]], 1000000000000000000]]
  • feeAssetItem - 0
  • weight - [9223372036854775807, 9223372036854775807]

Interact with the Solidity Interface

Checking Prerequisites

To follow along with this tutorial, you will need to 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:

  1. Get a copy of XCMInterface.sol
  2. Paste the file contents into a Remix file named XCMInterface.sol

Compile the Contract

Next, you will need to compile the interface in Remix:

  1. Click on the Compile tab, second from top
  2. Compile the interface by clicking on Compile XcmInterface.sol

Compiling 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:

  1. 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
  2. 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
  3. Make sure the correct account is displayed under ACCOUNT
  4. 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
  5. Provide the address of the precompile: 0x000000000000000000000000000000000000081A and click At Address

Access the 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:

  1. Expand the transferAssetsToPara20 function
  2. Enter the appchain ID (paraId)
  3. Enter the 20-bytes (Ethereum-like) destination account (beneficiary)
  4. 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
  5. 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 is 1, and so on
  6. Enter the maximum gas to pay for the transaction. This gas is derived from two parameters, the processing time (refTime) and the proof size (proofSize). In practice, setting refTime to uint64::MAX is equal to unlimited weight
  7. Click transact
  8. MetaMask will pop up, and you will be prompted to review the transaction details. Click Confirm to send the transaction

Confirm Approve Transaction

After the transaction is confirmed, wait for 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:

  1. Expand the transferAssetsToPara32 function
  2. Enter the appchain ID (paraId)
  3. Enter the sr25519-type destination account (beneficiary)
  4. 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

  5. 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 is 1, and so on

  6. Enter the maximum gas to pay for the transaction. This gas is derived from two parameters, the processing time (refTime) and the proof size (proofSize). In practice, setting refTime to uint64::MAX is equal to unlimited weight
  7. Click transact
  8. MetaMask will pop up, and you will be prompted to review the transaction details. Click Confirm to send the transaction

Confirm Approve Transaction

After the transaction is confirmed, wait for 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:

  1. Expand the transferAssetsToRelay function
  2. Enter the sr25519-type destination account (beneficiary)
  3. 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

  4. 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 is 1, and so on

  5. Enter the maximum gas to pay for the transaction. This gas is derived from two parameters, the processing time (refTime) and the proof size (proofSize). In practice, setting refTime to uint64::MAX is equal to unlimited weight
  6. Click transact
  7. MetaMask will pop up, and you will be prompted to review the transaction details. Click Confirm to send the transaction

Confirm Approve Transaction

After the transaction is confirmed, wait for a few blocks for the transfer to reach the destination chain and reflect the new balance.

Send Tokens Over Specific Locations

This function is more generic than the others, allowing the destination chain, destination account, and assets to be specified using XCM Multilocations. To send tokens to specific locations, please follow these steps:

  1. Expand the transferAssetsLocation function
  2. Enter the multilocation that specifies the destination chain. Note that any chain can be specified, regardless of its configuration or type
  3. Enter the Multilocation that specifies the destination account. Note that any account can be specified, regardless of its type (ECDSA, sr25519, or any other)
  4. Specify the tokens to be transferred. Note that this parameter is an array that contains at least one asset and each asset is specified by its Multilocation and the total amount to transfer

  5. 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 is 1, and so on

  6. Enter the maximum gas to pay for the transaction. This gas is derived from two parameters, the processing time (refTime) and the proof size (proofSize). In practice, setting refTime to uint64::MAX is equal to unlimited weight
  7. Click transact
  8. MetaMask will pop up, and you will be prompted to review the transaction details. Click Confirm to send the transaction

Confirm Approve Transaction

After the transaction is confirmed, wait for a few blocks for the transfer to reach the destination chain and reflect the new balance.

Last update: August 14, 2024
| Created: July 31, 2024