Skip to content

Using the XCM Transactor Pallet for Remote Executions

XCM Transactor Precompile Contracts Banner

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 and, more interestingly, remote cross-chain execution.

Nevertheless, building an XCM message from scratch is somewhat tricky. Moreover, XCM messages are sent to other participants in the ecosystem from the root account (that is, SUDO or through a democratic vote), which is not ideal for projects that want to leverage remote cross-chain calls via a simple transaction.

To overcome these issues, developers can leverage wrapper functions/pallets to use XCM features on Polkadot/Kusama, such as the XCM Transactor Pallet. In that aspect, the XCM Transactor Pallet allows users to perform remote cross-chain calls through different methods that dispatch the call from different derivated accounts so that they can be easily executed with a simple transaction.

The two main extrinsics of the pallet are transacting through a sovereign derivative account or a derivative account calculated from a given multilocation. Each extrinsic is named accordingly.

This guide will show you how to use the XCM Transactor Pallet to send XCM messages from a Moonbeam-based network to other chains in the ecosystem (relay chain/parachains). In addition, you'll also learn how to use the XCM Transactor Precompile to perform the same actions via the Ethereum API.

Note that there are still limitations in what you can remotely execute through XCM messages.

Developers must understand that sending incorrect XCM messages can result in the loss of funds. Consequently, it is essential to test XCM features on a TestNet before moving to a production environment.

XCM Instructions For Remote Execution

The relevant XCM instructions to perform remote execution through XCM are, but not limited to:

  • DescendOrigin - gets executed in the target chain. Mutates the origin that will be used for executing the subsequent XCM instructions
  • WithdrawAsset - gets executed in the target chain. Removes assets and places them into the holding register
  • BuyExecution - gets executed in the target chain. Takes the assets from holding to pay for execution fees. The fees to pay are determined by the target chain
  • Transact - gets executed in the target chain. Dispatches the encoded call data from a given origin

When the XCM message built by the XCM Transactor Pallet is executed, fees must be paid. All the relevant information can be found in the XCM Transactor Fees section of the XCM Fees page.

Relevant XCM Definitions

  • Sovereign account — an account each chain in the ecosystem has, one for the relay chain and the other for other parachains. It is calculated as the blake2 hash of a specific word and parachain ID concatenated (blake2(para+ParachainID) for the Sovereign account in the relay chain, and blake2(sibl+ParachainID) for the Sovereign account in other parachains), truncating the hash to the correct length. The account is owned by root and can only be used through SUDO (if available) or governance (referenda). The Sovereign account typically signs XCM messages in other chains in the ecosystem
  • Multilocation — a way to specify a point in the entire relay chain/parachain ecosystem relative to a given origin. For example, it can be used to specify a specific parachain, asset, account, or even a pallet inside a parachain. In general terms, a multilocation is defined with a parents and an interior:

    • parents - refers to how many "hops" into a parent blockchain you need to take from a given origin
    • interior - refers to how many fields you need to define the target point.

    For example, to target a parachain with ID 1000 from another parachain, the multilocation would be { "parents": 1, "interior": { "X1": [{ "Parachain": 1000 }]}} - Multilocation-derivative account — an account derivated from the new origin set by the Descend Origin XCM instruction and the provided multilocation, which is typically the sovereign account from which the XCM originated. Derivative accounts are keyless (the private key is unknown). Consequently, derivative accounts related to XCM-specific use cases can only be accessed through XCM extrinsics. For Moonbeam-based networks, the derivation method is calculating the blake2 hash of the multilocation, which includes the origin parachain ID, and truncating the hash to the correct length (20 bytes for an Ethereum-styled account). The XCM call origin conversion happens when the Transact instruction gets executed. Consequently, each parachain can convert the origin with its own desired procedure, so the user who initiated the transaction might have a different derivative account per parachain. This derivative account pays for transaction fees, and it is set as the dispatcher of the call - Transact information — relates to extra weight and fee information for the XCM remote execution part of the XCM Transactor extrinsic. This is needed because the XCM transaction fee is paid by the sovereign account. Therefore, XCM Transactor calculates what this fee is and charges the sender of the XCM Transactor extrinsic the estimated amount in the corresponding XC-20 token to repay the sovereign account

XCM Transactor Pallet Interface

Extrinsics

The XCM Transactor Pallet provides the following extrinsics (functions):

  • hrmpManage(action, fee, weightInfo) - manages HRMP operations related to opening, accepting, and closing an HRMP channel. The given action can be any of these four actions: InitOpen, Accept, Close, and Cancel
  • removeFeePerSecond(assetLocation) — remove the fee per second information for a given asset in its reserve chain. The asset is defined as a multilocation
  • removeTransactInfo(location) — remove the transact information for a given chain, defined as a multilocation
  • setFeePerSecond(assetLocation, feePerSecond) — sets the fee per second information for a given asset on its reserve chain. The asset is defined as a multilocation. The feePerSecond is the token units per second of XCM execution that will be charged to the sender of the XCM Transactor extrinsic
  • setTransactInfo(location, transactExtraWeight, maxWeight) — sets the transact information for a given chain, defined as a multilocation. The transact information includes:
    • transactExtraWeight — weight to cover execution fees of the XCM instructions (WithdrawAsset, BuyExecution, and Transact), which is estimated to be at least 10% over what the remote XCM instructions execution uses
    • maxWeight — maximum weight units allowed for the remote XCM execution
    • transactExtraWeightSigned — (optional) weight to cover execution fees of the XCM instructions (DescendOrigin, WithdrawAsset, BuyExecution, and Transact), which is estimated to be at least 10% over what the remote XCM instructions execution uses
  • transactThroughSigned(destination, fee, call, weightInfo) — sends an XCM message with instructions to remotely execute a given call in the given destination. The remote call will be signed and executed by a new account that the destination parachain must derivate. For Moonbeam-based networks, this account is the blake2 hash of the descended multilocation, truncated to the correct length. The XCM Transactor Pallet calculates the fees for the remote execution and charges the sender of the extrinsic the estimated amount in the corresponding XC-20 token given by the asset ID
  • transactThroughSovereign(destination, feePayer, fee, call, originKind, weightInfo) — sends an XCM message with instructions to remotely execute a given call in the given destination. The remote call will be signed by the origin parachain sovereign account (who pays the fees), but the transaction is dispatched from a given origin. The XCM Transactor Pallet calculates the fees for the remote execution and charges the given account the estimated amount in the corresponding XC-20 token given by the asset multilocation

Where the inputs that need to be provided can be defined as:

  • assetLocation — a multilocation representing an asset on its reserve chain. The value is used to set or retrieve the fee per second information
  • location — a multilocation representing a chain in the ecosystem. The value is used to set or retrieve the transact information
  • destination — a multilocation representing a chain in the ecosystem where the XCM message is being sent to
  • fee — an enum that provides developers two options on how to define the XCM execution fee item. Both options rely on the feeAmount, which is the units of the asset per second of XCM execution you provide to execute the XCM message you are sending. The two different ways to set the fee item are:
    • AsCurrencyID — is the ID of the currency being used to pay for the remote call execution. Different runtimes have different ways of defining the IDs. In the case of Moonbeam-based networks, SelfReserve refers to the native token, ForeignAsset refers to the asset ID of an external XC-20 (not to be confused with the XC-20 address), and Erc20 refers to the contract address of a local XC-20
    • AsMultiLocation — is the multilocation that represents the asset to be used for fee payment when executing the XCM
  • innerCall — encoded call data of the call that will be executed in the destination chain. This is wrapped with the asDerivative option if transacting through the sovereign derivative account
  • weightInfo — a structure that contains all the weight related information. If not enough weight is provided, the execution of the XCM will fail, and funds might get locked in either the sovereign account or a special pallet. Consequently, it is essential to correctly set the destination weight to avoid failed XCM executions. The structure contains two fields:
    • transactRequiredWeightAtMost — weight related to the execution of the Transact call itself. For transacts through sovereign-derivative, you have to take into account the weight of the asDerivative extrinsic as well. However, this does not include the cost (in weight) of all the XCM instructions
    • overallWeight — the total weight the XCM Transactor extrinsic can use. This includes all the XCM instructions plus the weight of the call itself (transactRequiredWeightAtMost)
  • call — similar to innerCall, but it is not wrapped with the asDerivative extrinsic
  • feePayer — the address that will pay for the remote XCM execution in the transact through sovereign extrinsic. The fee is charged in the corresponding XC-20 token
  • originKind — dispatcher of the remote call in the destination chain. There are four types of dispatchers available

Storage Methods

The XCM Transactor Pallet includes the following read-only storage method:

  • destinationAssetFeePerSecond() - returns the fee per second for an asset given a multilocation. This enables the conversion from weight to fee. The storage element is read by the pallet extrinsics if feeAmount is set to None
  • palletVersion() — returns current pallet version from storage
  • transactInfoWithWeightLimit(location) — returns the transact information for a given multilocation. The storage element is read by the pallet extrinsics if feeAmount is set to None

Pallet Constants

The XCM Transactor Pallet includes the following read-only functions to obtain pallet constants:

  • baseXcmWeight() - returns the base XCM weight required for execution, per XCM instruction
  • selfLocation() - returns the multilocation of the chain

XCM Transactor Transact Through Signed

This section covers building an XCM message for remote executions using the XCM Transactor Pallet, specifically with the transactThroughSigned function. However, you'll not be able to follow along as the destination parachain is not publicly available.

Note

You need to ensure that the call you are going to execute remotely is allowed in the destination chain!

Checking Prerequisites

To be able to send the extrinsics in this section, you need to have:

For this example, the following accounts will be used:

  • Alice's account in the origin parachain with address 0x44236223aB4291b93EEd10E4B511B37a398DEE55
  • Its multilocation-derivative address in the target parachain is 0x5c27c4bb7047083420eddff9cddac4a0a120b45c

Building the XCM

Since you'll be interacting with the transactThroughSigned function of the XCM Transactor Pallet, you'll need to assemble the dest, fee, call, and weightInfo parameters. To do so, you can take the following steps:

  1. Define the destination multilocation, which will target parachain 888:

    const dest = {
      V3: {
        parents: 1,
        interior: { X1: { Parachain: 888 } },
      },
    };
    
  2. Define the fee information, which will require you to:

    • Define the currency ID and provide the asset details
    • Set the fee amount
    const fee = {
      currency: {
        AsCurrencyId: { ForeignAsset: 35487752324713722007834302681851459189n },
      },
      feeAmount: 50000000000000000n,
    };
    
    const fee = {
      currency: {
        AsCurrencyId: { Erc20: { contractAddress: ERC_20_ADDRESS} },
      },
      feeAmount: 50000000000000000n,
    };
    
  3. Define the call that will be executed in the destination chain. This is the encoded call data of the pallet, method, and input values to be called. It can be constructed in Polkadot.js Apps (must be connected to the destination chain) or using the Polkadot.js API. For this example, the inner call is a simple balance transfer of 1 token of the destination chain to Alice's account there:

    const call =
      '0x030044236223ab4291b93eed10e4b511b37a398dee5513000064a7b3b6e00d';
    
  4. Set the weightInfo, which includes the required transactRequiredWeightAtMost weight and the optional overallWeight parameters. Both weight parameters require you to specify refTime and proofSize, where refTime is the amount of computational time that can be used for execution and proofSize is the amount of storage in bytes that can be used. For each parameter, you can follow these guidelines:

    • For transactRequiredAtMost, the value must include the asDerivative extrinsic as well. However, this does not include the weight of the XCM instructions. For this example, set refTime to 1000000000 weight units and proofSize to 0
    • For overallWeight, the value must be the total of transactRequiredWeightAtMost plus the weight needed to cover the XCM instructions execution costs in the destination chain. If you do not provide this value, the pallet will use the element in storage (if exists), and add it to transactRequiredWeightAtMost. For this example, set refTime to 2000000000 weight units and proofSize to 0
    const weightInfo = {
      transactRequiredWeightAtMost: { refTime: 1000000000n, proofSize: 0 },
      overallWeight: { refTime: 2000000000n, proofSize: 0 },
    };
    

Now that you have the values for each of the parameters, you can write the script for the transaction. 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 transactThroughSigned function
  2. Create a Keyring instance that will be used to send the transaction
  3. Create the Polkadot.js API provider
  4. Craft the xcmTransactor.transactThroughSigned extrinsic with the dest, fee, call and weightInfo values
  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 9.13.6

// 1. Provide input data
const providerWsURL = 'wss://wss.api.moonbase.moonbeam.network';
const privateKey = 'INSERT_PRIVATE_KEY';
const dest = {
  V3: {
    parents: 1,
    interior: { X1: { Parachain: 888 } },
  },
};
const fee = {
  currency: {
    AsCurrencyId: { ForeignAsset: 35487752324713722007834302681851459189n },
  },
  feeAmount: 50000000000000000n,
};
const call = '0x030044236223ab4291b93eed10e4b511b37a398dee5513000064a7b3b6e00d';
const weightInfo = {
  transactRequiredWeightAtMost: { refTime: 1000000000n, proofSize: 0 },
  overallWeight: { refTime: 2000000000n, proofSize: 0 },
};

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

const transactThroughSigned = async () => {
  // 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.xcmTransactor.transactThroughSigned(
    dest,
    fee,
    call,
    weightInfo
  );

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

  api.disconnect();
};

transactThroughSigned();

Note

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

Once the transaction is processed, Alice should've received one token in her address on the destination chain.

XCM Transactor Precompile

The XCM Transactor Precompile contract allows developers to access the XCM Transactor Pallet features through the Ethereum API of Moonbeam-based networks. Similar to other precompile contracts, the XCM Transactor Precompile is located at the following addresses:

0x000000000000000000000000000000000000080d
0x000000000000000000000000000000000000080d
0x000000000000000000000000000000000000080d

The XCM Transactor Legacy Precompile is still available on all Moonbeam-based networks. However, the legacy version will be deprecated in the near future, so all implementations must migrate to the newer interface. The XCM Transactor Legacy Precompile is located at the following addresses:

0x0000000000000000000000000000000000000806
0x0000000000000000000000000000000000000806
0x0000000000000000000000000000000000000806

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 Transactor Solidity Interface

XcmTransactor.sol is an interface through which developers can interact with the XCM Transactor Pallet using the Ethereum API.

Note

The legacy version of the XCM Transactor Precompile will be deprecated in the near future, so all implementations must migrate to the newer interface.

The interface includes the following functions:

  • indexToAccount(uint16 index) — read-only function that returns the registered address authorized to operate using a derivative account of the Moonbeam-based network sovereign account for the given index
  • transactInfoWithSigned(Multilocation memory multilocation) — read-only function that, for a given chain defined as a multilocation, returns the transact information considering the three XCM instructions associated with the external call execution (transactExtraWeight). It also returns extra weight information associated with the DescendOrigin XCM instruction for the transact through signed extrinsic (transactExtraWeightSigned)
  • feePerSecond(Multilocation memory multilocation) — read-only function that, for a given asset as a multilocation, returns units of token per second of the XCM execution that is charged as the XCM execution fee. This is useful when, for a given chain, there are multiple assets that can be used for fee payment
  • transactThroughSignedMultilocation(Multilocation memory dest, Multilocation memory feeLocation, uint64 transactRequiredWeightAtMost, bytes memory call, uint256 feeAmount, uint64 overallWeight) — function that represents the transactThroughSigned method described in the previous example, setting the fee type to AsMultiLocation. You need to provide the asset multilocation of the token that is used for fee payment instead of the XC-20 token address
  • transactThroughSigned(Multilocation memory dest, address feeLocationAddress, uint64 transactRequiredWeightAtMost, bytes memory call, uint256 feeAmount, uint64 overallWeight) — function that represents the transactThroughSigned method described in the previous example, setting the fee type to AsCurrencyId. Instead of the asset ID, you'll need to provide the asset XC-20 address of the token that is used for fee payment
  • encodeUtilityAsDerivative(uint8 transactor, uint16 index, bytes memory innerCall) - encodes an asDerivative wrapped call given the transactor to be used, the index of the derivative account, and the inner call to be executed from the derivated address

Building the Precompile Multilocation

In the XCM Transactor Precompile interface, the Multilocation structure is defined as follows:

 struct Multilocation {
    uint8 parents;
    bytes[] interior;
}

Note that each multilocation has a parents element, defined in this case by a uint8, and an array of bytes. Parents refer to how many "hops" in the upwards direction you have to do if you are going through the relay chain. Being a uint8, the normal values you would see are:

Origin Destination Parents Value
Parachain A Parachain A 0
Parachain A Relay Chain 1
Parachain A Parachain B 1

The bytes array (bytes[]) defines the interior and its content within the multilocation. The size of the array defines the interior value as follows:

Array Size Interior Value
[] 0 Here
[XYZ] 1 X1
[XYZ, ABC] 2 X2
[XYZ, ... N] N XN

Note

Interior value Here is often used for the relay chain (either as a destination or to target the relay chain asset).

Suppose the bytes array contains data. Each element's first byte (2 hexadecimal numbers) corresponds to the selector of that XN field. For example:

Byte Value Selector Data Type
0x00 Parachain bytes4
0x01 AccountId32 bytes32
0x02 AccountIndex64 u64
0x03 AccountKey20 bytes20
0x04 PalletInstance byte
0x05 GeneralIndex u128
0x06 GeneralKey bytes[]

Next, depending on the selector and its data type, the following bytes correspond to the actual data being provided. Note that for AccountId32, AccountIndex64, and AccountKey20, the network field seen in the Polkadot.js Apps example is appended at the end. For example:

Selector Data Value Represents
Parachain "0x00+000007E7" Parachain ID 2023
AccountId32 "0x01+AccountId32+00" AccountId32, Network(Option) Null
AccountId32 "0x01+AccountId32+03" AccountId32, Network Polkadot
AccountKey20 "0x03+AccountKey20+00" AccountKey20, Network(Option) Null
PalletInstance "0x04+03" Pallet Instance 3

Note

The interior data usually needs to be wrapped around quotes. On the contrary, you might get an invalid tuple value error.

The following code snippet goes through some examples of Multilocation structures, as they would need to be fed into the XCM Transactor Precompile functions:

// Multilocation targeting the relay chain asset from a parachain
{
    1, // parents = 1
    [] // interior = here
}

// Multilocation targeting Moonbase Alpha DEV token from another parachain
{
    1, // parents = 1
    // Size of array is 2, meaning is an X2 interior
    [
        "0x00000003E8", // Selector Parachain, ID = 1000 (Moonbase Alpha)
        "0x0403" // Pallet Instance = 3
    ]
}

// Multilocation targeting aUSD asset on Acala
{
    1, // parents = 1
    // Size of array is 1, meaning is an X1 interior
    [
        "0x00000007D0", // Selector Parachain, ID = 2000 (Acala)
        "0x060001" // General Key Selector + Asset Key
    ]
}
Last update: May 10, 2023
| Created: May 25, 2022