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 eitherInitOpen
,Accept
,Close
, orCancel
fee
- the asset to be used for fees. This contains thecurrency
and thefeeAmount
: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 assetForeignAsset
- uses an external XC-20. It requires you to specify the asset ID of the XC-20LocalAssetReserve
- deprecated - use Local XC-20s instead via theErc20
currency typeErc20
- 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. TheweightInfo
structure contains the following:transactRequiredWeightAtMost
— the weight required to perform the execution of theTransact
call. ThetransactRequiredWeightAtMost
structure contains the following:refTime
- the amount of computational time that can be used for executionproofSize
- 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 theTransact
call (transactRequiredWeightAtMost
). TheoverallWeight
can be defined as either:Unlimited
- allows an unlimited amount of weight that can be purchasedLimited
- limits the amount of weight that can be purchased by defining the following:refTime
- the amount of computational time that can be used for executionproofSize
- the amount of storage in bytes that can be used
import { ApiPromise, WsProvider } from '@polkadot/api';
const 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 forfeePerSecond
- 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 fortransactExtraWeight
— the weight to cover execution fees of the XCM instructions (WithdrawAsset
,BuyExecution
, andTransact
), which is estimated to be at least 10% over what the remote XCM instructions execution uses. ThetransactExtraWeight
structure contains the following:refTime
- the amount of computational time that can be used for executionproofSize
- the amount of storage in bytes that can be used
maxWeight
— maximum weight units allowed for the remote XCM execution. ThemaxWeight
structure also containsrefTime
andproofSize
transactExtraWeightSigned
— (optional) the weight to cover execution fees of the XCM instructions (DescendOrigin
,WithdrawAsset
,BuyExecution
, andTransact
), which is estimated to be at least 10% over what the remote XCM instructions execution uses. ThetransactExtraWeightSigned
structure also containsrefTime
andproofSize
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 thecurrency
and thefeeAmount
: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 assetForeignAsset
- uses an external XC-20. It requires you to specify the asset ID of the XC-20LocalAssetReserve
- deprecated - use Local XC-20s instead via theErc20
currency typeErc20
- 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 chainweightInfo
- the weight information to be used. TheweightInfo
structure contains the following:transactRequiredWeightAtMost
— the weight required to perform the execution of theTransact
call. ThetransactRequiredWeightAtMost
structure contains the following:refTime
- the amount of computational time that can be used for executionproofSize
- 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 theTransact
call (transactRequiredWeightAtMost
). TheoverallWeight
can be defined as either:Unlimited
- allows an unlimited amount of weight that can be purchasedLimited
- limits the amount of weight that can be purchased by defining the following:refTime
- the amount of computational time that can be used for executionproofSize
- the amount of storage in bytes that can be used
refund
- a boolean indicating whether or not to add theRefundSurplus
andDepositAsset
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 thefeePayer
, the XCM execution fees will be paid by the Sovereign account on the destination chainfee
- the asset to be used for fees. This contains thecurrency
and thefeeAmount
: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 assetForeignAsset
- uses an external XC-20. It requires you to specify the asset ID of the XC-20LocalAssetReserve
- deprecated - use Local XC-20s instead via theErc20
currency typeErc20
- 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 chainoriginKind
— dispatcher of the remote call in the destination chain. There are four types of dispatchers available:Native
,SovereignAccount
,Superuser
, orXcm
weightInfo
- the weight information to be used. TheweightInfo
structure contains the following:transactRequiredWeightAtMost
— the weight required to perform the execution of theTransact
call. ThetransactRequiredWeightAtMost
structure contains the following:refTime
- the amount of computational time that can be used for executionproofSize
- 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 theTransact
call (transactRequiredWeightAtMost
). TheoverallWeight
can be defined as either:Unlimited
- allows an unlimited amount of weight that can be purchasedLimited
- limits the amount of weight that can be purchased by defining the following:refTime
- the amount of computational time that can be used for executionproofSize
- the amount of storage in bytes that can be used
refund
- a boolean indicating whether or not to add theRefundSurplus
andDepositAsset
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 chainWithdrawAsset
- gets executed in the target chain. Removes assets and places them into a holding registerBuyExecution
- 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 chainTransact
- 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:
-
Define the destination multilocation, which will target parachain 888
const privateKey = 'INSERT_PRIVATE_KEY'; const dest = { V4: { parents: 1, interior: { X1: [{ Parachain: 888 }] }, },
-
Define the
fee
information, which will require you to define the currency and set the fee amountconst fee = { currency: { AsCurrencyId: { ForeignAsset: 35487752324713722007834302681851459189n }, }, feeAmount: 50000000000000000n, };
const fee = { currency: { AsCurrencyId: { Erc20: { contractAddress: ERC_20_ADDRESS} }, }, feeAmount: 50000000000000000n, };
-
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 thereconst call = '0x030044236223ab4291b93eed10e4b511b37a398dee5513000064a7b3b6e00d';
-
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 setrefTime
to1000000000
weight units andproofSize
to40000
- For
overallWeight
, the value must be the total oftransactRequiredWeightAtMost
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 totransactRequiredWeightAtMost
. For this example, you can set theoverallWeight
toUnlimited
, 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
andproofSize
figures fortransactRequiredAtMost
, you can use thepaymentInfo
method of the Polkadot.js API. - For
-
To refund any leftover XCM fees, you can set the
refund
value totrue
. Otherwise, set it tofalse
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:
- 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
- Create a Keyring instance that will be used to send the transaction
- Create the Polkadot.js API provider
- Craft the
xcmTransactor.transactThroughSigned
extrinsic with thedest
,fee
,call
,weightInfo
, andrefund
values - 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.
| Created: October 28, 2023