Skip to content

Calculating Transaction Fees on Moonbeam

Transaction Fees Banner

Introduction

Similar to the Ethereum and Substrate APIs for sending transfers on Moonbeam, the Substrate and EVM layers on Moonbeam also have distinct transaction fee models that developers should be aware of when they need to calculate and keep track of the transaction fees of their transactions.

This guide assumes you are interacting with Moonbeam blocks via the Substrate API Sidecar service. There are other ways of interacting with Moonbeam blocks, such as using the Polkadot.js API library. The logic is identical once the blocks are retrieved.

You can reference the Substrate API Sidecar page for information on installing and running your own Sidecar service instance, as well as more details on how to decode Sidecar blocks for Moonbeam transactions.

Substrate API Transaction Fees

The fees of a transaction sent via the Substrate API on Moonbeam can be read directly from a Sidecar block JSON object. The nesting structure is as follows:

RESPONSE JSON Block Object:
    |--extrinsics
        |--{extrinsic number}
            |--method
            |--signature
            |--nonce
            |--args
            |--tip           
            |--hash
            |--info
            |--era
            |--events
                |--{event number}
                    |--method
                        |--pallet: "transactionPayment"
                        |--method: "TransactionFeePaid"
                    |--data
                        |--0
                        |--1
                        |--2
    ...

The object mappings are summarized as follows:

Tx Information Block JSON Field
Fee paying account extrinsics.{extrinsic number}.events.{event number}.data.0
Total fees paid extrinsics.{extrinsic number}.events.{event number}.data.1
Tip extrinsics.{extrinsic number}.events.{event number}.data.2

The transaction fee related information can be retrieved under the event of the relevant extrinsic where the method field is set to:

pallet: "transactionPayment", method: "TransactionFeePaid" 

And then the total transaction fee paid for this extrinsic is mapped to the following field of the block JSON object:

extrinsics.{extrinsic number}.events.{event number}.data.1

Ethereum API Transaction Fees

Calculating Ethereum API Transaction Fees

To calculate the fee incurred on a Moonbeam transaction sent via the Ethereum API, the following formula can be used:

Transaction Fee =(Base Fee + Max Priority Fee Per Gas) * Transaction Weight / 25000
Transaction Fee = Gas Price * Transaction Weight / 25000
Transaction Fee = Gas Price * Transaction Weight / 25000

The values of Gas Price and Max Priority Fee Per Gas for the applicable transaction types can be read from the block JSON object according to the structure described in the Sidecar API page, also truncated and reproduced below:

EVM Field Block JSON Field
Max priority fee per gas extrinsics.{extrinsic number}.args.transaction.eip1559.maxPriorityFeePerGas
EVM Field Block JSON Field
Gas price extrinsics.{extrinsic number}.args.transaction.legacy.gasPrice
EVM Field Block JSON Field
Gas price extrinsics.{extrinsic number}.args.transaction.eip2930.gasPrice

The Base Fee, introduced in EIP-1559, is a value set by the network itself. The Base Fee for EIP1559 type transactions is currently static on Moonbeam networks and has the following assigned value:

Variable Value
Base fee 100 Gwei
Variable Value
Base fee 1 Gwei
Variable Value
Base fee 1 Gwei

Transaction Weight is a Substrate mechanism used to measure the execution time a given transaction takes to be executed within a block. For all transactions types, Transaction Weight can be retrieved under the event of the relevant extrinsic where the method field is set to:

pallet: "system", method: "ExtrinsicSuccess" 

And then Transaction Weight is mapped to the following field of the block JSON object:

extrinsics.{extrinsic number}.events.{event number}.data.0.weight

Key Differences with Ethereum

As seen in the above section, there are some key differences between the transaction fee model on Moonbeam and the one on Ethereum that developers should be mindful of when developing on Moonbeam:

  • The network base fee on Moonbeam networks is currently static. This has many implications, one of which is that a transaction sent with a gas price set to a value lower than the network base fee will always fail, even if the blocks aren't currently full on the network. This behavior is different from Ethereum, which does not have a floor on the gas price of a transaction to be accepted.

    The network base fee could be changed to be variable in a future runtime update.

  • The amount of gas used in Moonbeam's transaction fee model is mapped from the transaction's Substrate extrinsic weight value via a fixed factor of 25000. This value is then multiplied with the unit gas price to calculate the transaction fee. This fee model means it can potentially be significantly cheaper to send transactions such as basic balance transfers via the Ethereum API than the Substrate API.

eth_feeHistory Endpoint

Moonbeam networks implement the eth_feeHistory JSON-RPC endpoint as a part of the support for EIP-1559.

eth_feeHistory returns a collection of historical gas information from which you can reference and calculate what to set for the Max Fee Per Gas and Max Priority Fee Per Gas fields when submitting EIP-1559 transactions.

The following curl example will return the gas information of the last 10 blocks starting from the latest block on the respective Moonbeam network using eth_feeHistory:

curl --location 
     --request POST 'RPC-API-ENDPOINT-HERE' \
     --header 'Content-Type: application/json' \
     --data-raw '{
        "jsonrpc": "2.0",
        "id": 1,
        "method": "eth_feeHistory",
        "params": ["0xa", "latest"]
     }'
curl --location 
     --request POST 'RPC-API-ENDPOINT-HERE' \
     --header 'Content-Type: application/json' \
     --data-raw '{
        "jsonrpc": "2.0",
        "id": 1,
        "method": "eth_feeHistory",
        "params": ["0xa", "latest"]
     }'
curl --location 
     --request POST 'https://rpc.api.moonbase.moonbeam.network' \
     --header 'Content-Type: application/json' \
     --data-raw '{
        "jsonrpc": "2.0",
        "id": 1,
        "method": "eth_feeHistory",
        "params": ["0xa", "latest"]
     }'
curl --location 
     --request POST 'http://127.0.0.1:9933' \
     --header 'Content-Type: application/json' \
     --data-raw '{
        "jsonrpc": "2.0",
        "id": 1,
        "method": "eth_feeHistory",
        "params": ["0xa", "latest"]
     }'

Sample Code for Calculating Transaction Fees

The following code snippet uses the Axios HTTP client to query the Sidecar endpoint /blocks/head for the latest finalized block. It then calculates the transaction fees of all transactions in the block according to the transaction type (for Ethereum API: legacy, EIP-1559 or EIP-2930 standards, and for Substrate API), as well as calculating the total transaction fees in the block.

The following code sample is for demo purposes only and should not be used without modification and further testing in a production environment.

import axios from 'axios';

// This script calculates the transaction fees of all transactions in a Moonbeam block according to the transaction type (For Ethereum API: legacy, EIP-1559 or EIP-2930 standards, and Substrate API), as well as calculating the total fees in the block. 

// Endpoint to retrieve the latest block
const endpointBlock = 'http://127.0.0.1:8080/blocks/head';
// Endpoint to retrieve the node client's information
const endpointNodeVersion = 'http://127.0.0.1:8080/node/version';

async function main() {

    try {
        // Create a variable to sum the transaction fees in the whole block
        var totalFees = 0;

        // Find which Moonbeam network the Sidecar is pointing to
        const response_client = await axios.get(endpointNodeVersion);
        const network = response_client.data.clientImplName;

        // Retrieve the block from the Sidecar endpoint
        const response_block = await axios.get(endpointBlock);
        // Retrieve the block height of the current block
        console.log('Block Height: '+response_block.data.number)

        // Iterate through all extrinsics in the block
        response_block.data.extrinsics.forEach(extrinsic => {
            // Create an object to store transaction information
            var transactionData = new Object()
            // Set the network field
            transactionData["network"] = network;

            // Filter for Ethereum Transfers
            if (extrinsic.method.pallet === 'ethereum' && extrinsic.method.method === 'transact'){

                // Iterate through the events to get non type specific parameters
                extrinsic.events.forEach(event => {
                    if (event.method.pallet === 'ethereum' && event.method.method === 'Executed') {
                        transactionData["hash"] = event.data[2];
                    }
                    if (event.method.pallet === 'system' && event.method.method === 'ExtrinsicSuccess') {
                        transactionData["weight"] = event.data[0].weight;
                    }
                });

                // Get the transaction type and type specific parameters and compute the transaction fee
                if (extrinsic.args.transaction.legacy) {
                    transactionData["txType"] = "legacy";
                    transactionData["gasPrice"] = extrinsic.args.transaction.legacy.gasPrice
                    transactionData["txFee"] = transactionData["gasPrice"] * transactionData["weight"] / 25000
                } 
                else if (extrinsic.args.transaction.eip1559) {
                    transactionData["txType"] = "eip1599";
                    transactionData["maxPriorityFeePerGas"] = extrinsic.args.transaction.eip1559.maxPriorityFeePerGas
                    // Set the network base fee
                    if (transactionData["network"] == "moonbeam"){
                        transactionData["baseFee"] = 100000000000;
                    } else {
                        transactionData["baseFee"] = 1000000000;
                    }
                    transactionData["txFee"] = (transactionData["baseFee"] + transactionData["maxPriorityFeePerGas"]) * transactionData["weight"] / 25000
                } 
                else if (extrinsic.args.transaction.eip2930) {
                    transactionData["txType"] = "eip2930";
                    transactionData["gasPrice"] = extrinsic.args.transaction.eip2930.gasPrice
                    transactionData["txFee"] = transactionData["gasPrice"] * transactionData["weight"] / 25000
                }

                // Increment totalFees
                totalFees += transactionData["txFee"]

                // Display the tx information to console
                console.log(transactionData)
            }
            // Filter for Substrate transactions, check if the extrinsic has a "TransactionFeePaid" event
            else {
                extrinsic.events.forEach(event => {
                    if (event.method.pallet === 'transactionPayment' && event.method.method === 'TransactionFeePaid') {
                        transactionData["txType"] = "substrate";
                        transactionData["txFee"] = event.data[1];
                        transactionData["tip"] = event.data[1];
                    }
                    if (event.method.pallet === 'system' && event.method.method === 'ExtrinsicSuccess') {
                        transactionData["weight"] = event.data[0].weight;
                    }
                });
            }

        });

        // Output the total amount of fees in the block
        console.log('Total fees in block: ' + totalFees)
    } catch (err) {
        console.log(err);
    }
}

main()
The information presented herein has been provided by third parties and is made available solely for general information purposes. Moonbeam does not endorse any project listed and described on the Moonbeam Doc Website (https://docs.moonbeam.network/). Moonbeam Foundation does not warrant the accuracy, completeness or usefulness of this information. Any reliance you place on such information is strictly at your own risk. Moonbeam Foundation disclaims all liability and responsibility arising from any reliance placed on this information by you or by anyone who may be informed of any of its contents. All statements and/or opinions expressed in these materials are solely the responsibility of the person or entity providing those materials and do not necessarily represent the opinion of Moonbeam Foundation. The information should not be construed as professional or financial advice of any kind. Advice from a suitably qualified professional should always be sought in relation to any particular matter or circumstance. The information herein may link to or integrate with other websites operated or content provided by third parties, and such other websites may link to this website. Moonbeam Foundation has no control over any such other websites or their content and will have no liability arising out of or related to such websites or their content. The existence of any such link does not constitute an endorsement of such websites, the content of the websites, or the operators of the websites. These links are being provided to you only as a convenience and you release and hold Moonbeam Foundation harmless from any and all liability arising from your use of this information or the information provided by any third-party website or service.