Skip to content

Using the XCM Transactor Pallet for Remote Execution

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. Remote execution involves executing operations or actions on one blockchain from another blockchain while maintaining the integrity of the sender's identity and permissions.

Typically, XCM messages are sent from the root origin (that is, SUDO or through governance), which is not ideal for projects that want to leverage remote cross-chain calls via a simple transaction. The XCM Transactor Pallet makes it easy to transact on a remote chain through either the Sovereign account, which should only be allowed through governance, or a Computed Origin account via a simple transaction from the source chain.

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. 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 to 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 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

On Moonbeam or Moonriver, this function must be executed via governance through the General Admin or the Root Track. On Moonbase Alpha or a Moonbeam development node, this function can also be executed via sudo.

  • action - the action to execute. Can be either InitOpen, Accept, Close, or Cancel
  • fee - the asset to be used for fees. This contains the currency and the feeAmount:
    • currency - defines how you are specifying the token to use to pay for the fees, which can be either of the following:
      • AsCurrencyId - the currency ID of the asset to use for the fees. The currency ID can be either:
        • SelfReserve - uses the native asset
        • ForeignAsset - uses an external XC-20. It requires you to specify the asset ID of the XC-20
        • LocalAssetReserve - deprecated - use Local XC-20s instead via the Erc20 currency type
        • Erc20 - uses a local XC-20. It requires you to specify the contract address of the local XC-20
      • AsMultiLocation - the XCM versioned multilocation for the asset to use for the fees
    • feeAmount - (optional) the amount to use for fees
  • weightInfo - the weight information to be used. The weightInfo structure contains the following:
    • transactRequiredWeightAtMost — the weight required to perform the execution of the Transact call. The transactRequiredWeightAtMost structure contains the following:
      • refTime - the amount of computational time that can be used for execution
      • proofSize - the amount of storage in bytes that can be used
    • overallWeight — (optional) the total weight the extrinsic can use to execute all the XCM instructions, plus the weight of the Transact call (transactRequiredWeightAtMost). The overallWeight can be defined as either:
      • Unlimited - allows an unlimited amount of weight that can be purchased
      • Limited - limits the amount of weight that can be purchased by defining the following:
        • refTime - the amount of computational time that can be used for execution
        • proofSize - the amount of storage in bytes that can be used
import { ApiPromise, WsProvider } from '@polkadot/api';

const action = 'InitOpen'; // Or 'Accept', 'Close', or 'Cancel'
const fee = {
  currency: {
    AsCurrencyId: { ForeignAsset: INSERT_ASSET_ID },
  },
  feeAmount: INSERT_FEE_AMOUNT,
};
const weightInfo = {
  transactRequiredWeightAtMost: {
    refTime: INSERT_REF_TIME,
    proofSize: INSERT_PROOF_SIZE,
  },
  overallWeight: { Unlimited: null },
};

const main = async () => {
  const api = await ApiPromise.create({
    provider: new WsProvider('INSERT_WSS_ENDPOINT'),
  });
  const tx = api.tx.xcmTransactor.hrmpManage(action, fee, weightInfo);
  const txHash = await tx.signAndSend('INSERT_ACCOUNT_OR_KEYRING');
};
main();
removeFeePerSecond(assetLocation) — remove the fee per second information for a given asset in its reserve chain

On Moonbeam or Moonriver, this function must be executed via governance through the General Admin or the Root Track. On Moonbase Alpha or a Moonbeam development node, this function can also be executed via sudo.

  • assetLocation - the XCM versioned multilocation of the asset to remove the fee per second information for
import { ApiPromise, WsProvider } from '@polkadot/api';

const assetLocation = {
  V4: {
    parents: INSERT_PARENTS,
    interior: INSERT_INTERIOR,
  },
};;

const main = async () => {
  const api = await ApiPromise.create({
    provider: new WsProvider('INSERT_WSS_ENDPOINT'),
  });
  const tx = api.tx.xcmTransactor.removeFeePerSecond(assetLocation);
  const txHash = await tx.signAndSend('INSERT_ACCOUNT_OR_KEYRING');
};
main();
removeTransactInfo(location) — remove the transact information for a given chain

On Moonbeam or Moonriver, this function must be executed via governance through the General Admin or the Root Track. On Moonbase Alpha or a Moonbeam development node, this function can also be executed via sudo.

  • location - the XCM versioned multilocation of a given chain that you wish to remove the transact information for
import { ApiPromise, WsProvider } from '@polkadot/api';

const assetLocation = {
  V4: {
    parents: INSERT_PARENTS,
    interior: INSERT_INTERIOR,
  },
};;

const main = async () => {
  const api = await ApiPromise.create({
    provider: new WsProvider('INSERT_WSS_ENDPOINT'),
  });
  const tx = api.tx.xcmTransactor.removeTransactInfo(assetLocation);
  const txHash = await tx.signAndSend('INSERT_ACCOUNT_OR_KEYRING');
};
main();
setFeePerSecond(assetLocation, feePerSecond) — sets the fee per second for a given asset on its reserve chain. The fee per second information typically relates to the cost of executing XCM instructions

On Moonbeam or Moonriver, this function must be executed via governance through the General Admin or the Root Track. On Moonbase Alpha or a Moonbeam development node, this function can also be executed via sudo.

  • assetLocation - the XCM versioned multilocation of the asset to remove the fee per second information for
  • feePerSecond - the number of token units per second of XCM execution that will be charged to the sender of the extrinsic when executing XCM instructions
import { ApiPromise, WsProvider } from '@polkadot/api';

const assetLocation = {
  V4: {
    parents: INSERT_PARENTS,
    interior: INSERT_INTERIOR,
  },
};
const feePerSecond = INSERT_FEE_PER_SECOND;

const main = async () => {
  const api = await ApiPromise.create({
    provider: new WsProvider('INSERT_WSS_ENDPOINT'),
  });
  const tx = api.tx.xcmTransactor.setFeePerSecond(assetLocation, feePerSecond);
  const txHash = await tx.signAndSend('INSERT_ACCOUNT_OR_KEYRING');
};

main();
setTransactInfo(location, transactExtraWeight, maxWeight) — sets the transact information for a given chain. The transact information typically includes details about the weight required for executing XCM instructions as well as the maximum weight allowed for remote XCM execution on the target chain

On Moonbeam or Moonriver, this function must be executed via governance through the General Admin or the Root Track. On Moonbase Alpha or a Moonbeam development node, this function can also be executed via sudo.

  • location - the XCM versioned multilocation of a given chain that you wish to set the transact information for
  • transactExtraWeight — the 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. The transactExtraWeight structure contains the following:
    • refTime - the amount of computational time that can be used for execution
    • proofSize - the amount of storage in bytes that can be used
  • maxWeight — maximum weight units allowed for the remote XCM execution. The maxWeight structure also contains refTime and proofSize
  • transactExtraWeightSigned — (optional) the 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. The transactExtraWeightSigned structure also contains refTime and proofSize
import { ApiPromise, WsProvider } from '@polkadot/api';

const location = INSERT_MULTILOCATION;
const transactExtraWeight = {
  refTime: INSERT_REF_TIME,
  proofSize: INSERT_PROOF_SIZE,
};
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.xcmTransactor.setTransactInfo(
    location,
    transactExtraWeight,
    maxWeight
  );
  const txHash = await tx.signAndSend('INSERT_ACCOUNT_OR_KEYRING');
};

main();
transactThroughSigned(destination, fee, call, weightInfo, refund) — sends an XCM message with instructions to remotely execute a call in the destination chain. The remote call will be signed and executed by a new account that the destination parachain must compute. Moonbeam-based networks follow the Computed Origins standard set by Polkadot
  • dest - the XCM versioned multilocation for a chain in the ecosystem where the XCM message is being sent to (the target chain)
  • fee - the asset to be used for fees. This contains the currency and the feeAmount:
    • currency - defines how you are specifying the token to use to pay for the fees, which can be either of the following:
      • AsCurrencyId - the currency ID of the asset to use for the fees. The currency ID can be either:
        • SelfReserve - uses the native asset
        • ForeignAsset - uses an external XC-20. It requires you to specify the asset ID of the XC-20
        • LocalAssetReserve - deprecated - use Local XC-20s instead via the Erc20 currency type
        • Erc20 - uses a local XC-20. It requires you to specify the contract address of the local XC-20
      • AsMultiLocation - the XCM versioned multilocation for the asset to use for the fees
    • feeAmount - (optional) the amount to use for fees
  • call - encoded call data of the call that will be executed in the target chain
  • weightInfo - the weight information to be used. The weightInfo structure contains the following:
    • transactRequiredWeightAtMost — the weight required to perform the execution of the Transact call. The transactRequiredWeightAtMost structure contains the following:
      • refTime - the amount of computational time that can be used for execution
      • proofSize - the amount of storage in bytes that can be used
    • overallWeight — (optional) the total weight the extrinsic can use to execute all the XCM instructions, plus the weight of the Transact call (transactRequiredWeightAtMost). The overallWeight can be defined as either:
      • Unlimited - allows an unlimited amount of weight that can be purchased
      • Limited - limits the amount of weight that can be purchased by defining the following:
        • refTime - the amount of computational time that can be used for execution
        • proofSize - the amount of storage in bytes that can be used
  • refund - a boolean indicating whether or not to add the RefundSurplus and DepositAsset instructions to the XCM message to refund any leftover fees
import { ApiPromise, WsProvider } from '@polkadot/api';

const dest = {
  V4: {
    parents: INSERT_PARENTS,
    interior: INSERT_INTERIOR,
  },
};
const fee = {
  currency: {
    AsCurrencyId: { ForeignAsset: INSERT_ASSET_ID },
  },
  feeAmount: INSERT_FEE_AMOUNT,
};
const call = 'INSERT_ENCODED_CALL_DATA';
const weightInfo = {
  transactRequiredWeightAtMost: {
    refTime: INSERT_REF_TIME,
    proofSize: INSERT_PROOF_SIZE,
  },
  overallWeight: { Unlimited: null },
};
const refund = INSERT_BOOLEAN_FOR_REFUND;

const main = async () => {
  const api = await ApiPromise.create({
    provider: new WsProvider('INSERT_WSS_ENDPOINT'),
  });
  const tx = api.tx.xcmTransactor.transactThroughSigned(
    dest,
    fee,
    call,
    weightInfo,
    refund
  );
  const txHash = await tx.signAndSend('INSERT_ACCOUNT_OR_KEYRING');
};

main();      

Note

In the following sections, you'll learn exactly how to retrieve all of the arguments needed to build and send an XCM message using this extrinsic.

transactThroughSovereign(dest, feePayer, fee, call, originKind, weightInfo, refund) — sends an XCM message with instructions to remotely execute a given call at 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
  • dest - the XCM versioned multilocation for a chain in the ecosystem where the XCM message is being sent to (the target chain)
  • feePayer - (optional) the address that will pay for the remote XCM execution in the corresponding XC-20 token. If you don't specify the feePayer, the XCM execution fees will be paid by the Sovereign account on the destination chain
  • fee - the asset to be used for fees. This contains the currency and the feeAmount:
    • currency - defines how you are specifying the token to use to pay for the fees, which can be either of the following:
      • AsCurrencyId - the currency ID of the asset to use for the fees. The currency ID can be either:
        • SelfReserve - uses the native asset
        • ForeignAsset - uses an external XC-20. It requires you to specify the asset ID of the XC-20
        • LocalAssetReserve - deprecated - use Local XC-20s instead via the Erc20 currency type
        • Erc20 - uses a local XC-20. It requires you to specify the contract address of the local XC-20
      • AsMultiLocation - the XCM versioned multilocation for the asset to use for the fees
    • feeAmount - (optional) the amount to use for fees
  • call - encoded call data of the call that will be executed in the target chain
  • originKind — dispatcher of the remote call in the destination chain. There are four types of dispatchers available: Native, SovereignAccount, Superuser, or Xcm
  • weightInfo - the weight information to be used. The weightInfo structure contains the following:
    • transactRequiredWeightAtMost — the weight required to perform the execution of the Transact call. The transactRequiredWeightAtMost structure contains the following:
      • refTime - the amount of computational time that can be used for execution
      • proofSize - the amount of storage in bytes that can be used
    • overallWeight — (optional) the total weight the extrinsic can use to execute all the XCM instructions, plus the weight of the Transact call (transactRequiredWeightAtMost). The overallWeight can be defined as either:
      • Unlimited - allows an unlimited amount of weight that can be purchased
      • Limited - limits the amount of weight that can be purchased by defining the following:
        • refTime - the amount of computational time that can be used for execution
        • proofSize - the amount of storage in bytes that can be used
  • refund - a boolean indicating whether or not to add the RefundSurplus and DepositAsset instructions to the XCM message to refund any leftover fees
import { ApiPromise, WsProvider } from '@polkadot/api';

const dest = {
  V4: {
    parents: INSERT_PARENTS,
    interior: INSERT_INTERIOR,
  },
};
const fee = {
  currency: {
    AsCurrencyId: { ForeignAsset: INSERT_ASSET_ID },
  },
  feeAmount: INSERT_FEE_AMOUNT,
};
const feePayer = 'INSERT_ADDRESS_RESPONSIBLE_FOR_FEES';
const call = 'INSERT_ENCODED_CALL_DATA';
const originKind = 'INSERT_ORIGIN_KIND';
const weightInfo = {
  transactRequiredWeightAtMost: {
    refTime: INSERT_REF_TIME,
    proofSize: INSERT_PROOF_SIZE,
  },
  overallWeight: { Unlimited: null },
};
const refund = INSERT_BOOLEAN_FOR_REFUND;

const main = async () => {
  const api = await ApiPromise.create({
    provider: new WsProvider('INSERT_WSS_ENDPOINT'),
  });
  const tx = api.tx.xcmTransactor.transactThroughSovereign(
    dest,
    feePayer,
    fee,
    call,
    originKind,
    weightInfo,
    refund
  );
  const txHash = await tx.signAndSend('INSERT_ACCOUNT_OR_KEYRING');
};

main();

Storage Methods

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

destinationAssetFeePerSecond(location) - returns the fee per second for a given asset
  • location - (optional) the XCM versioned multilocation for a specific destination asset

A number representing the value for fee per second of the given asset. This value may be returned in a different format depending on the chain and how they store their data. You can use the @polkadot/util library for a variety of conversions, for example, to convert a hex value to a big integer using the hexToBigInt method.

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

const location = {
  parents: INSERT_PARENTS,
  interior: INSERT_INTERIOR,
};

const main = async () => {
  const api = await ApiPromise.create({
    provider: new WsProvider('INSERT_WSS_ENDPOINT'),
  });
  const feePerSecond =
    await api.query.xcmTransactor.destinationAssetFeePerSecond(location);

  if (feePerSecond.isSome) {
    const data = feePerSecond.unwrap();
    console.log(data.toJSON());
  }
};

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.xcmTransactor.palletVersion();
};

main();
transactInfoWithWeightLimit(location) — returns the transact information for a given multilocation
  • location - (optional) the XCM versioned multilocation for a specific destination asset

The transact information object.

// If using Polkadot.js API and calling toJSON() on the unwrapped value
{
  transactExtraWeight: { refTime: 3000000000, proofSize: 131072 },
  maxWeight: { refTime: 20000000000, proofSize: 131072 },
  transactExtraWeightSigned: { refTime: 4000000000, proofSize: 131072 },
}
import { ApiPromise, WsProvider } from '@polkadot/api';

const location = {
  parents: INSERT_PARENTS,
  interior: INSERT_INTERIOR,
};

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

  if (transactInfoWithWeightLimit.isSome) {
    const data = transactInfoWithWeightLimit.unwrap();
    console.log(data.toJSON());
  }
};

main();

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

The base XCM weight object.

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

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

  const baseXcmWeight = api.consts.xcmTransactor.baseXcmWeight;
  console.log(baseXcmWeight.toJSON());
};

main();
selfLocation() - returns the multilocation of the chain

The self-location multilocation object.

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

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

  const selfLocation = api.consts.xcmTransactor.selfLocation;
  console.log(selfLocation.toJSON());
};

main();

XCM Instructions for Remote Execution

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

  • DescendOrigin - gets executed in the target chain. It mutates the origin on the target chain to match the origin on the source chain, ensuring execution on the target chain occurs on behalf of the same entity initiating the XCM message on the source chain
  • WithdrawAsset - gets executed in the target chain. Removes assets and places them into a 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 encoded call data from a given origin, allowing for the execution of specific operations or functions

Transact through a Computed Origin Account

This section covers building an XCM message for remote execution using the XCM Transactor Pallet, specifically with the transactThroughSigned function. This function uses a Computed Origin account on the destination chain to dispatch the remote call.

The example in this section uses a destination parachain that is not publicly available, so you won't be able to follow along exactly. You can modify the example as needed for your own use case.

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:

  • An account in the origin chain with funds
  • Funds in the Computed Origin account on the target chain. To learn how to calculate the address of the Computed Origin account, please refer to the How to Calculate the Computed Origin documentation

For this example, the following accounts will be used:

  • Alice's account in the origin parachain (Moonbase Alpha): 0x44236223aB4291b93EEd10E4B511B37a398DEE55
  • Her Computed Origin address in the target parachain (Parachain 888): 0x5c27c4bb7047083420eddff9cddac4a0a120b45c

Building the XCM

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

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

    const privateKey = 'INSERT_PRIVATE_KEY';
    const dest = {
      V4: {
        parents: 1,
        interior: { X1: [{ Parachain: 888 }] },
      },
    
  2. Define the fee information, which will require you to define the currency and 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, which is the encoded call data of the pallet, method, and input to be called. It can be constructed in Polkadot.js Apps (which 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 weight specific to the inner call (transactRequiredWeightAtMost) and the optional overall weight of the transact plus XCM execution (overallWeight). For each parameter, you can follow these guidelines:

    • For transactRequiredAtMost, you can set refTime to 1000000000 weight units and proofSize to 40000
    • For overallWeight, the value must be the total of transactRequiredWeightAtMost plus the weight needed to cover the execution costs for the XCM instructions in the destination chain. If you do not provide this value, the pallet will use the element in storage (if it exists) and add it to transactRequiredWeightAtMost. For this example, you can set the overallWeight to Unlimited, which removes the need to know how much weight the destination chain will require to execute the XCM
    const weightInfo = {
      transactRequiredWeightAtMost: { refTime: 1000000000n, proofSize: 40000n },
      overallWeight: { Unlimited: null },
    };
    

    Note

    For accurate estimates of the refTime and proofSize figures for transactRequiredAtMost, you can use the paymentInfo method of the Polkadot.js API.

  5. To refund any leftover XCM fees, you can set the refund value to true. Otherwise, set it to false

    const refund = true;
    

Sending the XCM

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, weightInfo, and refund 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 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 dest = {
  V4: {
    parents: 1,
    interior: { X1: [{ Parachain: 888 }] },
  },
};
const fee = {
  currency: {
    AsCurrencyId: { ForeignAsset: 35487752324713722007834302681851459189n },
  },
  feeAmount: 50000000000000000n,
};
const call = '0x030044236223ab4291b93eed10e4b511b37a398dee5513000064a7b3b6e00d';
const weightInfo = {
  transactRequiredWeightAtMost: { refTime: 1000000000n, proofSize: 40000n },
  overallWeight: { Unlimited: null },
};
const refund = true;

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

  // 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 one token to Alice's Computed Origin account on parachain 888, on Polkadot.js Apps using the following encoded calldata: 0x210604010100e10d00017576e5e612ff054915d426c546b1b21a010000c52ebca2b10000000000000000007c030044236223ab4291b93eed10e4b511b37a398dee5513000064a7b3b6e00d02286bee02710200010001.

XCM Transact through Computed Origin Fees

When transacting through the Computed Origin account, the transaction fees are paid by the same account from which the call is dispatched, which is a Computed Origin account in the destination chain. Consequently, the Computed Origin account must hold the necessary funds to pay for the entire execution. Note that the destination token, for which fees are paid, does not need to be registered as an XC-20 in the origin chain.

To estimate the amount of tokens Alice's Computed Origin account will need to execute the remote call, you need to check the transact information specific to the destination chain. You can use the following script to get the transact information for parachain 888:

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

const providerWsURL = 'wss://wss.api.moonbase.moonbeam.network';

const location = { parents: 1, interior: { X1: [{ Parachain: 888 }] } };

const main = async () => {
  const substrateProvider = new WsProvider(providerWsURL);
  const api = await ApiPromise.create({ provider: substrateProvider });

  const transactInfoWithWeightLimit =
    await api.query.xcmTransactor.transactInfoWithWeightLimit(location);

  if (transactInfoWithWeightLimit.isSome) {
    const data = transactInfoWithWeightLimit.unwrap();
    const transactExtraWeightSigned =
      data.toJSON().transactExtraWeightSigned.refTime;
    console.log(transactExtraWeightSigned);
  }

  api.disconnect();
};

main();

From the response, you can see that the transactExtraWeightSigned is 400,000,000. This is the weight needed to execute the four XCM instructions for this remote call in that specific destination chain. Next, you need to find out how much the destination chain charges per weight of XCM execution. Normally, you would look into the units per second for that particular chain. But in this scenario, no XC-20 tokens are burned. Therefore, units per second can be used for reference, but it does not ensure that the estimated number of tokens is correct. To get the units per second as a reference value, you can use the following script:

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

const providerWsURL = 'wss://wss.api.moonbase.moonbeam.network';

const location = {
  parents: 1,
  interior: { X2: [{ Parachain: 888 }, { PalletInstance: 3 }] },
};

const main = async () => {
  const substrateProvider = new WsProvider(providerWsURL);
  const api = await ApiPromise.create({ provider: substrateProvider });

  const destinationAssetFeePerSecond =
    await api.query.xcmTransactor.destinationAssetFeePerSecond(location);

  if (destinationAssetFeePerSecond.isSome) {
    const data = destinationAssetFeePerSecond.unwrap();
    const unitsPerSecond = data.toString();
    console.log(unitsPerSecond);
  }

  api.disconnect();
};

main();

Note that the units per second value is related to the cost estimated in the Relay Chain XCM Fee Calculation section or to the one shown in the Units per weight section if the target is another parachain. You'll need to find the correct value to ensure that the amount of tokens the Computed Origin account holds is correct. Calculating the associated XCM execution fee is as simple as multiplying the transactExtraWeightSigned times the unitsPerSecond (for an estimation):

XCM-Wei-Token-Cost = transactExtraWeightSigned * unitsPerSecond
XCM-Token-Cost = XCM-Wei-Token-Cost / DecimalConversion

Therefore, the actual calculation for one XCM Transactor transact through derivative call is:

XCM-Wei-Token-Cost = 400000000 * 50000000000000000
XCM-Token-Cost = 20000000000000 / 10^18

The cost of transacting through a Computed Origin is 0.00002 TOKEN. Note that this does not include the cost of the call being remotely executed, only XCM execution fees.

Last update: October 2, 2024
| Created: October 28, 2023