XCM Fees on Moonbeam¶
Introduction¶
XCM aims to be a language that communicates ideas between consensus systems. Sending an XCM message consists of a series of instructions that are executed in both the origin and the destination chains. The combination of XCM instructions results in actions such as token transfers. In order to process and execute each XCM instruction, there are typically associated fees that must be paid.
However, XCM is designed to be general, extensible, and efficient so that it remains valuable and future-proof throughout a growing ecosystem. As such, the generality applies to concepts including payments of fees for XCM execution. In Ethereum, fees are baked into the transaction protocol, whereas in the Polkadot ecosystem, each chain has the flexibility to define how XCM fees are handled.
This guide will cover aspects of fee payment, such as who is responsible for paying XCM execution fees, how it is paid for, and how the fees are calculated on Moonbeam.
Note
The following information is provided for general information purposes only. The weight and extrinsic base cost might have changed since the time of writing. Please ensure you check the actual values, and never use the following information for production apps.
Payment of Fees¶
Generally speaking, the fee payment process can be described as follows:
- Some assets need to be provided
- The exchange of assets for computing time (or weight) must be negotiated
- The XCM operations will be performed as instructed, with the provided weight limit or funds available for execution
Each chain can configure what happens with the XCM fees and in which tokens they can be paid (either the native reserve token or an external one). For example:
- Polkadot and Kusama - the fees are paid in DOT or KSM (respectively) and given to the validator of the block
- Moonbeam and Moonriver - the XCM execution fees can be paid in the reserve asset (GLMR or MOVR, respectively), but also in assets originated in other chains if they are registered as an XCM execution asset. When XCM execution (token transfers or remote execution) is paid in the native chain reserve asset (GLMR or MOVR), 80% is burned, while 20% is sent to the treasury. When XCM execution is paid in a foreign asset, the fee is sent to the treasury
Consider the following scenario: Alice has some DOT on Polkadot, and she wants to transfer it to Alith on Moonbeam. She sends an XCM message with a set of XCM instructions that will retrieve a given amount of DOT from her account on Polkadot and mint them as xcDOT into Alith's account. Part of the instructions are executed on Polkadot, and the other part is executed on Moonbeam.
How does Alice pay Moonbeam to execute these instructions and fulfill her request? Her request is fulfilled through a series of XCM instructions that are included in the XCM message, which enables her to buy execution time minus any related XCM execution fees. The execution time is used to issue and transfer xcDOT, a representation of DOT on Moonbeam. This means that when Alice sends some DOT to Alith's account on Moonbeam, she'll receive a 1:1 representation of her DOT as xcDOT minus any XCM execution fees. Note that in this scenario, XCM execution fees are paid in xcDOT and sent to the treasury.
The exact process for Alice's transfer is as follows:
- Assets are sent to an account on Polkadot that is owned by Moonbeam, known as the Sovereign account. After the assets are received, an XCM message is sent to Moonbeam
- The XCM message in Moonbeam will:
- Mint the corresponding asset representation
- Buy the corresponding execution time
- Use that execution time to deposit the representation (minus fees) to the destination account
XCM Instructions¶
An XCM message is comprised of a series of XCM instructions. As a result, different combinations of XCM instructions result in different actions. For example, to move DOT to Moonbeam, the following XCM instructions are used:
For example, to move DOT to Moonbeam, the following XCM instructions are used:
TransferReserveAsset
- gets executed in Polkadot. Assets are transferred from the origin account and deposited into Moonbeam's Sovereign account on PolkadotReserveAssetDeposited
- gets executed in Moonbeam. Mints the DOT representation on Moonbeam, called xcDOTClearOrigin
- gets executed in Moonbeam. Clears origin information, which was Polkadot's Sovereign account on MoonbeamBuyExecution
- gets executed in Moonbeam. As such, execution fees are determined by Moonbeam. In this particular scenario, part of the minted xcDOT is used to pay for XCM executionDepositAsset
- gets executed in Moonbeam. Ultimately, it sends assets to a destination account on Moonbeam
To check how the instructions for an XCM message are built to transfer self-reserve assets to a target chain, such as DOT to Moonbeam, you can refer to the X-Tokens Open Runtime Module Library repository (as an example). You'll want to take a look at the transfer_self_reserve_asset
function. You'll notice it calls TransferReserveAsset
and passes in assets
, dest
, and xcm
as parameters. In particular, the xcm
parameter includes the BuyExecution
and DepositAsset
instructions. If you then head over to the Polkadot GitHub repository, you can find the TransferReserveAsset
instruction. The XCM message is constructed by combining the ReserveAssetDeposited
and ClearOrigin
instructions with the xcm
parameter, which as mentioned includes the BuyExecution
and DepositAsset
instructions.
To move xcDOT from Moonbeam back to Polkadot, the instructions that are used are:
WithdrawAsset
- gets executed in Moonbeam. Takes the funds from the senderInitiateReserveWithdraw
- gets executed in Moonbeam. Burns the funds while sending an XCM message to the destination chain to execute the remainder of the token transferWithdrawAsset
- gets executed in Polkadot. Takes the funds from Moonbeam's Sovereign account on PolkadotClearOrigin
- gets executed in Polkadot. Clears origin information, which was Moonbeam's Sovereign account on MoonbeamBuyExecution
- gets executed in Polkadot. As such, Polkadot determines the execution fees. In this scenario, part of the DOTs being sent are used to pay for the execution of the XCMDepositAsset
- gets executed in Polkadot. Ultimately, it sends assets to a destination account on Polkadot
To check how the instructions for an XCM message are built to transfer reserve assets to a target chain, such as xcDOT to Polkadot, you can refer to the X-Tokens Open Runtime Module Library repository. You'll want to take a look at the transfer_to_reserve
function. You'll notice that it calls WithdrawAsset
, then InitiateReserveWithdraw
and passes in assets
, dest
, and xcm
as parameters. In particular, the xcm
parameter includes the BuyExecution
and DepositAsset
instructions. If you then head over to the Polkadot GitHub repository, you can find the InitiateReserveWithdraw
instruction. The XCM message is constructed by combining the WithdrawAsset
and ClearOrigin
instructions with the xcm
parameter, which as mentioned includes the BuyExecution
and DepositAsset
instructions.
Relay Chain XCM Fee Calculation¶
Substrate has introduced a weight system that determines how heavy or, in other words, how expensive from a computational cost perspective an extrinsic is. One unit of weight is defined as one picosecond of execution time. When it comes to paying fees, users will pay a transaction fee based on the weight of the call that is being made, in addition to factors such as network congestion.
The following sections will break down how to calculate XCM fees for Polkadot and Kusama. It's important to note that Kusama, in particular, uses benchmarked data to determine the total weight costs for XCM instructions and that some XCM instructions might include database reads and writes, which add weight to the call.
There are two databases available in Polkadot and Kusama: RocksDB (which is the default) and ParityDB, both of which have their own associated weight costs for each network.
Polkadot¶
The total weight costs on Polkadot take into consideration database reads and writes in addition to the weight required for a given instruction. Polkadot uses benchmarked weights for instructions and database read and write operations. The breakdown of weight costs for the database operations is as follows:
Database | Read | Write |
---|---|---|
RocksDB (default) | 20,499,000 | 83,471,000 |
ParityDB | 11,826,000 | 38,052,000 |
Now that you are aware of the weight costs for database reads and writes on Polkadot, you can calculate the weight cost for a given instruction using the base weight for instructions.
For example, the WithdrawAsset
instruction has a base weight of 28,457,000
, and performs one database read and one database write. Therefore, the total weight cost of the WithdrawAsset
instruction is calculated as:
28457000 + 20499000 + 83471000 = 132427000
The BuyExecution
instruction has a base weight of 1,444,000
and doesn't include any database reads or writes. Therefore, the total weight cost of the BuyExecution
instruction is 1,444,000
.
On Polkadot, the benchmarked base weights are broken up into two categories: fungible and generic. Fungible weights are for XCM instructions that involve moving assets, and generic weights are for everything else. You can view the current weights for fungible assets and generic assets directly in the Polkadot Runtime code.
With the instruction weight cost established, you can calculate the cost of each instruction in DOT.
In Polkadot, the ExtrinsicBaseWeight
is set to 126,045,000
which is mapped to 1/10th of a cent. Where 1 cent is 10^10 / 100
.
Therefore, to calculate the cost of executing an XCM instruction, you can use the following formula:
XCM-DOT-Cost = XCMInstrWeight * DOTWeightToFeeCoefficient
Where DOTWeightToFeeCoefficient
is a constant (map to 1 cent), and can be calculated as:
DOTWeightToFeeCoefficient = 10^10 / ( 10 * 100 * DOTExtrinsicBaseWeight )
Using the actual values:
DOTWeightToFeeCoefficient = 10^10 / ( 10 * 100 * 126045000 )
As a result, DOTWeightToFeeCoefficient
is equal to 0.0793367448 Planck-DOT
. Now, you can begin to calculate the final fee in DOT, using DOTWeightToFeeCoefficient
as a constant and TotalWeight
as the variable:
XCM-Planck-DOT-Cost = TotalWeight * DOTWeightToFeeCoefficient
XCM-DOT-Cost = XCM-Planck-DOT-Cost / DOTDecimalConversion
Therefore, the actual calculation for the WithdrawAsset
instruction is:
XCM-Planck-DOT-Cost = 132427000 * 0.0793367448
XCM-DOT-Cost = 10506327.1036296 / 10^10
The total cost for that particular instruction is 0.0010506327 DOT
.
As an example, you can calculate the total cost of DOT for sending an XCM message that transfers xcDOT to DOT on Polkadot using the following weights and instruction costs:
Instruction | Weight | Cost |
---|---|---|
WithdrawAsset | 132,427,000 | 0.0010506327 DOT |
ClearOrigin | 1,354,000 | 0.0000107422 DOT |
BuyExecution | 1,444,000 | 0.0000114562 DOT |
DepositAsset | 125,695,000 | 0.0009972232 DOT |
TOTAL | 260,920,000 | 0.0020700543 DOT |
Kusama¶
The total weight costs on Kusama take into consideration database reads and writes in addition to the weight required for a given instruction. Database read and write operations have not been benchmarked, while instruction weights have been. The breakdown of weight costs for the database operations is as follows:
Database | Read | Write |
---|---|---|
RocksDB (default) | 25,000,000 | 100,000,000 |
ParityDB | 8,000,000 | 50,000,000 |
Now that you are aware of the weight costs for database reads and writes on Kusama, you can calculate the weight cost for a given instruction using the base weight for instructions.
For example, the WithdrawAsset
instruction has a base weight of 29,195,000
, and performs one database read and one database write. Therefore, the total weight cost of the WithdrawAsset
instruction is calculated as:
21195000 + 25000000 + 100000000 = 154195000
The BuyExecution
instruction has a base weight of 1,415,000
and doesn't include any database reads or writes. Therefore, the total weight cost of the BuyExecution
instruction is 1,415,000
.
On Kusama, the benchmarked base weights are broken up into two categories: fungible and generic. Fungible weights are for XCM instructions that involve moving assets, and generic weights are for everything else. You can view the current weights for fungible assets and generic assets directly in the Kusama Runtime code.
With the instruction weight cost established, you can calculate the cost of the instruction in KSM.
In Kusama, the ExtrinsicBaseWeight
is set to 124,706,000
which is mapped to 1/10th of a cent. Where 1 cent is 10^12 / 30,000
.
Therefore, to calculate the cost of executing an XCM instruction, you can use the following formula:
XCM-KSM-Cost = XCMInstrWeight * KSMWeightToFeeCoefficient
Where KSMWeightToFeeCoefficient
is a constant (map to 1 cent), and can be calculated as:
KSMWeightToFeeCoefficient = 10^12 / ( 10 * 3000 * KSMExtrinsicBaseWeight )
Using the actual values:
KSMWeightToFeeCoefficient = 10^12 / ( 10 * 3000 * 124706000 )
As a result, KSMWeightToFeeCoefficient
is equal to 0.267295345319 Planck-KSM
. Now, you can begin to calculate the final fee in KSM, using KSMWeightToFeeCoefficient
as a constant and TotalWeight
(154,195,000) as the variable:
XCM-Planck-KSM-Cost = TotalWeight * KSMWeightToFeeCoefficient
XCM-KSM-Cost = XCM-Planck-KSM-Cost / KSMDecimalConversion
Therefore, the actual calculation for the WithdrawAsset
instruction is:
XCM-Planck-KSM-Cost = 154195000 * 0.267295345319
XCM-KSM-Cost = 39077243.0089112 / 10^12
The total cost for that particular instruction is 0.000041215606 KSM
.
As an example, you can calculate the total cost of KSM for sending an XCM message that transfers xcKSM to KSM on Kusama using the following weights and instruction costs:
Instruction | Weight | Cost |
---|---|---|
WithdrawAsset | 154,195,000 | 0.000041215606 KSM |
ClearOrigin | 1,315,000 | 0.000000351493 KSM |
BuyExecution | 1,415,000 | 0.000000378223 KSM |
DepositAsset | 146,745,000 | 0.000039224255 KSM |
TOTAL | 303,670,000 | 0.000081169577 KSM |
Moonbeam-based Networks XCM Fee Calculation¶
Substrate has introduced a weight system that determines how heavy or, in other words, how expensive an extrinsic is from a computational cost perspective. One unit of weight is defined as one picosecond of execution time. When it comes to paying fees, users will pay a transaction fee based on the weight of the call that is being made, and each parachain can decide how to convert from weight to fee, for example, accounting for additional costs for transaction size and storage costs.
For all Moonbeam-based networks, the generic XCM instructions are benchmarked, while the fungible XCM instructions still use a fixed amount of weight per instruction. Consequently, the total weight cost of the benchmarked XCM instructions considers the number of database reads and writes in addition to the weight required for a given instruction. The breakdown of weight cost for database operations is as follows:
Database | Read | Write |
---|---|---|
RocksDB (default) | 41,742,000 | 81,283,000 |
Now that you know the weight costs for database reads and writes for Moonbase Alpha, you can calculate the weight cost for both fungible and generic XCM instructions using the base weight for instruction and the extra database reads and writes if applicable.
For example, the WithdrawAsset
instruction is part of the fungible XCM instructions. Therefore, it is not benchmarked, and the total weight cost of the WithdrawAsset
instruction is 200,000,000
, except for when transferring local XC-20s. The total weight cost for the WithdrawAsset
instruction for local XC-20s is based on a conversion of Ethereum gas to Substrate weight.
The BuyExecution
instruction has a base weight of 181,080,000
, and performs four database reads (assetManager
pallet to get the unitsPerSecond
). Therefore, the total weight cost of the BuyExecution
instruction is calculated as follows:
181080000 + 4 * 41742000 = 281,080,000
You can find all the weight values for all the XCM instructions in the following table, which apply to all Moonbeam-based networks:
Benchmarked Instructions | Non-Benchmarked Instructions |
---|---|
Generic XCM Instructions | Fungible XCM Instructions |
The following sections will break down how to calculate XCM fees for Moonbeam-based networks. There are two main scenarios:
- Fees paid in the reserve token (native tokens like GLMR, MOVR, or DEV)
- Fees paid in external assets (XC-20s)
Fee Calculation for Reserve Assets¶
For each XCM instruction, the weight units are converted to balance units as part of the fee calculation. The amount of Wei per weight unit for each of the Moonbeam-based networks is as follows:
Moonbeam | Moonriver | Moonbase Alpha |
---|---|---|
1,250,000 | 12,500 | 12,500 |
This means that on Moonbeam, for example, the formula to calculate the cost of one XCM instruction in the reserve asset is as follows:
XCM-Wei-Cost = XCMInstrWeight * WeiPerWeight
XCM-GLMR-Cost = XCM-Wei-Cost / 10^18
Therefore, the actual calculation for fungible instructions, for example, is:
XCM-Wei-Cost = 200000000 * 1250000
XCM-GLMR-Cost = 250000000000000 / 10^18
The total cost is 0.00025 GLMR
for an XCM instruction on Moonbeam.
Fee Calculation for External Assets¶
Moonbeam charges fees for external assets based on the weight of the call. Weight is a struct that contains two fields, refTime
and proofSize
. refTime
refers to the amount of computational time that can be used for execution. proofSize
refers to the size of the PoV (Proof of Validity) of the Moonbeam block that gets submitted to the Polkadot Relay Chain for validation. Since both refTime
and proofSize
are integral components of determining a weight, it is impossible to obtain an accurate weight value with just one of these values.
You can query the refTime
and proofSize
of an XCM instruction with the queryXcmWeight
method of the xcmPaymentApi
. You can do this programmatically or by visiting the Runtime Calls tab of Polkadot.js Apps. The queryXcmWeight
method takes an XCM version and instruction has a parameter and returns the corresponding refTime
and proofSize
values.
Weight to Gas Mapping¶
For calls that are derived from EVM operations, such as the DepositAsset
instruction which relies on the EVM operation MintInto
, you can calculate their respective weight values by multiplying the gas limit by weight multipliers. For refTime
, you'll need to multiply the gas limit by 25000
and for proofSize
you'll need to multiply the gas limit by 16
. A chart is included below for convenience.
Weight Type | Multiplier Value |
---|---|
Ref Time | 25,000 |
Proof Size | 16 |
To determine the total weight for Alice's transfer of DOT to Moonbeam, you'll need the weight for each of the four XCM instructions required for the transfer. Note that while the first three instructions have specific refTime
and proofSize
values corresponding to these instructions that can be retrieved via queryXcmWeight
method of the xcmPaymentApi
, DepositAsset
relies on the EVM operation MintInto
and a WeightPerGas
conversion of 25,000
per gas. The refTime
of DepositAsset
can thus be calculated as:
155000 gas * 25000 weight per gas = 3875000000
And the proofSize
of DepositAsset
can be calculated as:
155000 gas * 16 weight per gas = 2480000
The weights for each of the four XCM instructions for Alice's DOT transfer to Moonbeam can be found below:
Instruction | Ref Time | Proof Size |
---|---|---|
ReserveAssetDeposited | 200,000,000 | 0 |
ClearOrigin | 5,194,000 | 0 |
BuyExecution | 281,080,000 | 19,056 |
DepositAsset | 3,875,000,000 | 2,480,000 |
TOTAL | 4,428,242,000 | 2,499,056 |
Note
For the BuyExecution
instruction, the units of weight for the four database reads are accounted for in the above table.
Weight to Asset Fee Conversion¶
Once you have the sum of the refTime
and proofSize
values, you can easily retrive the required commensurate fee amount. The queryWeightToAssetFee
method of the xcmPaymentApi
takes a refTime
, proofSize
and asset multilocation as parameters and returns the commensurate fee. By providing the amounts obtained above of 4,428,242,000
refTime
and 2,499,056
proofSize
, and the asset multilocation for DOT, we get a fee amount of 88,920,522
Plank, which is the smallest unit in Polkadot. We can convert this to DOT by dividing by 10^10
which gets us a DOT fee amount of 0.008892
DOT.
XCM Payment API Expanded Examples¶
The XCM Payment API methods provide various helpful ways to calculate fees, evaluate acceptable fee payment currencies, and more. Remember that in addition to accessing this via API, you can also interact with the XCM Payment API via the Runtime Calls tab of Polkadot.js Apps.
Query Acceptable Fee Payment Assets¶
This function takes the XCM Version as a parameter and returns a list of acceptable fee assets in multilocation form.
const allowedAssets =
await api.call.xcmPaymentApi.queryAcceptablePaymentAssets(3);
console.log(allowedAssets);
View the complete script
import { ApiPromise, WsProvider } from '@polkadot/api';
const main = async () => {
// Construct API provider
const wsProvider = new WsProvider('INSERT_WSS_ENDPOINT');
const api = await ApiPromise.create({ provider: wsProvider });
const allowedAssets =
await api.call.xcmPaymentApi.queryAcceptablePaymentAssets(4);
console.log(allowedAssets);
// Disconnect the API
await api.disconnect();
};
main();
Weight to Asset Fee Conversion¶
This method converts a weight into a fee for the specified asset. It takes as parameters a weight and an asset multilocation and returns the respective fee amount.
const fee = await api.call.xcmPaymentApi.queryWeightToAssetFee(
{
refTime: 10_000_000_000n,
proofSize: 0n,
},
{
V3: {
Concrete: { parents: 1, interior: 'Here' },
},
}
);
console.log(fee);
View the complete script
import { ApiPromise, WsProvider } from '@polkadot/api';
const main = async () => {
// Construct API provider
const wsProvider = new WsProvider('INSERT_WSS_ENDPOINT');
const api = await ApiPromise.create({ provider: wsProvider });
const fee = await api.call.xcmPaymentApi.queryWeightToAssetFee(
{
refTime: 10_000_000_000n,
proofSize: 0n,
},
{
V3: {
Concrete: { parents: 1, interior: 'Here' },
},
}
);
console.log(fee);
// Disconnect the API
await api.disconnect();
};
main();
Query XCM Weight¶
This method takes an XCM message as a parameter and returns the weight of the message.
const message = { V3: [instr1, instr2] };
const theWeight = await api.call.xcmPaymentApi.queryXcmWeight(message);
console.log(theWeight);
View the complete script
import { ApiPromise, WsProvider } from '@polkadot/api';
const main = async () => {
// Construct API provider
const wsProvider = new WsProvider('INSERT_WSS_ENDPOINT');
const api = await ApiPromise.create({ provider: wsProvider });
const amountToSend = BigInt(1 * 10 ** 12); // Sending 1 token (assuming 12 decimal places)
const assetMultiLocation = {
parents: 0,
interior: { X1: { PalletInstance: 3 } },
}; // The asset's location (adjust PalletInstance as needed)
const recipientAccount = '0x1234567890abcdef1234567890abcdef12345678'; // The recipient's account on the destination chain
// 2. XCM Destination (e.g., Parachain ID 2000)
const dest = { V3: { parents: 1, interior: { X1: { Parachain: 2000 } } } };
// 3. XCM Instruction 1: Withdraw the asset from the sender
const instr1 = {
WithdrawAsset: [
{
id: { Concrete: assetMultiLocation },
fun: { Fungible: amountToSend },
},
],
};
// 4. XCM Instruction 2: Deposit the asset into the recipient's account on the destination chain
const instr2 = {
DepositAsset: {
assets: { Wild: 'All' }, // Sending all withdrawn assets (in this case, 1 token)
beneficiary: {
parents: 0,
interior: { X1: { AccountKey20: { key: recipientAccount } } },
},
},
};
// 5. Build the XCM Message
const message = { V3: [instr1, instr2] };
const theWeight = await api.call.xcmPaymentApi.queryXcmWeight(message);
console.log(theWeight);
// Disconnect the API
await api.disconnect();
};
main();
| Created: July 25, 2022