Skip to content

计算Moonbeam上的交易费用

Transaction Fees Banner

概览

与Moonbeam上用于发送转账的以太坊和Substrate API类似,Moonbeam上的Substrate和EVM也有不同的交易费用模型,开发者应知道何时需要计算和继续追踪其交易的交易费用。

本教程假设您通过Substrate API Sidecar服务与Moonbeam区块交互。也有其他与Moonbeam区块交互的方式,例如使用Polkadot.js API library。检索区块后,两种方式的逻辑都是相同的。

您可以参考Substrate API Sidecar页面获取关于安装和运行自己的Sidecar服务实例,以及如何为Moonbeam交易编码Sidecar区块的更多细节。

请注意,此页面信息假定您运行的是版本14.1.1 的Substrate Sidecar REST API。

Substrate API交易费用

所有关于通过Substrate API发送的交易费用数据的信息都可以从以下区块端点中提取:

GET /blocks/{blockId}

区块端点将返回与一个或多个区块相关的数据。您可以在Sidecar官方文档上阅读有关区块端点的更多信息。读取结果为JSON对象,相关嵌套结构如下所示:

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

对象映射总结如下:

交易信息 JSON对象字段
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]

交易费用相关信息可以在相关extrinsic的事件下获取,其中method字段设置如下:

pallet: "transactionPayment", method: "TransactionFeePaid" 

随后,将用于支付此extrinsic的总交易费用映射至Block JSON对象的以下字段中:

extrinsics[extrinsic_number].events[event_number].data[1]

以太坊API交易费用

要计算通过以太坊API在Moonbeam交易产生的费用,可以使用以下计算公式:

GasPrice = BaseFee + MaxPriorityFeePerGas < MaxFeePerGas ? 
            BaseFee + MaxPriorityFeePerGas : 
            MaxFeePerGas;
Transaction Fee = (GasPrice * TransactionWeight) / 25000
Transaction Fee = (GasPrice * TransactionWeight) / 25000
Transaction Fee = (GasPrice * TransactionWeight) / 25000

以下部分更详细地描述了计算交易费用的每个组成部分。

基础费用

EIP-1559中引入的Base Fee是由网络自设的一个值。Moonbeam有自己的动态费用机制计算基础费用,它是根据区块拥塞情况来进行调整。从runtime 2300(运行时2300)开始,动态费用机制已推广到所有基于Moonbeam的网络。

每个网络的最低汽油价格(Minimum Gas Price)如下:

变量
Minimum Gas Price 125 Gwei
变量
Minimum Gas Price 1.25 Gwei
变量
Minimum Gas Price 0.125 Gwei

要计算动态基本费用,请使用以下计算:

BaseFee = NextFeeMultiplier * 125000000000 / 10^18
BaseFee = NextFeeMultiplier * 1250000000 / 10^18
BaseFee = NextFeeMultiplier * 125000000 / 10^18

通过以下端点,可以从Substrate Sidecar API检索NextFeeMultiplier的值:

GET /pallets/transaction-payment/storage/nextFeeMultiplier?at={blockId}

Sidecar的pallet端点返回与pallet相关的数据,例如pallet存储中的数据。您可以在Sidecar官方文档中阅读更多关于pallet端点的信息。需要从存储中获取的手头数据是nextFeeMultiplier,它可以在transaction-payment pallet中找到。存储的nextFeeMultiplier值可以直接从Sidecar存储结构中读取。读取结果为JSON对象,相关嵌套结构如下:

RESPONSE JSON Storage Object:
    |--at
        |--hash
        |--height
    |--pallet
    |--palletIndex
    |--storageItem
    |--keys
    |--value

相关数据将存储在JSON对象的value键中。该值是定点数据类型,因此实际值是通过将value除以10^18得到的。这就是为什么BaseFee的计算包括这样的操作。

GasPrice,MaxFeePerGas和MaxPriorityFeePerGas

适用交易类型的GasPrice, MaxFeePerGasMaxPriorityFeePerGas的值可以根据Sidecar API页面描述的结构从Block JSON对象读取,特定区块中以太坊交易的数据可以从以下区块端点中提取:

GET /blocks/{blockId}

相关值的路径也被截短后复制在下方:

EVM字段 JSON对象字段
MaxFeePerGas extrinsics[extrinsic_number].args.transaction.eip1559.maxFeePerGas
MaxPriorityFeePerGas extrinsics[extrinsic_number].args.transaction.eip1559.maxPriorityFeePerGas
EVM字段 JSON对象字段
GasPrice extrinsics[extrinsic_number].args.transaction.legacy.gasPrice
EVM字段 JSON对象字段
GasPrice extrinsics[extrinsic_number].args.transaction.eip2930.gasPrice

交易权重

TransactionWeight是一类Substrate机制,用于衡量给定交易在一个区块内执行所需的执行时间。对于所有交易类型,TransactionWeight可以在相关extrinsic的事件下获取,其中method字段设置如下:

pallet: "system", method: "ExtrinsicSuccess" 

随后,TransactionWeight将被映射至Block JSON对象的以下字段中:

extrinsics[extrinsic_number].events[event_number].data[0].weight

与以太坊的关键性差异

如上所述,Moonbeam和以太坊上的交易费用模型有一些关键性的差异,开发者在Moonbeam上构建时需要注意以下部分:

  • 动态费用机制类似于EIP-1559,但实现不同

  • Moonbeam交易费用模型中使用的gas数量是通过固定比例25000从交易的Substrate extrinsic权重值映射而来。通过此数值乘以单位gas价格来计算交易费用。此费用模型意味着通过以太坊API发送如基本转账等交易可能会比Substrate API更为便宜。

费用记录端点

Moonbeam网络实施eth_feeHistory JSON-RPC端点作为对EIP-1559支持的一部分。

eth_feeHistory返回一系列的历史gas信息,可供您参考和计算在提交EIP-1559交易时为MaxFeePerGasMaxPriorityFeePerGas字段设置的内容。

以下curl示例将使用eth_feeHistory返回从各自Moonbeam网络上的最新区块开始的最后10个区块的gas信息:

curl --location \
     --request POST 'INSERT_RPC_API_ENDPOINT' \
     --header 'Content-Type: application/json' \
     --data-raw '{
        "jsonrpc": "2.0",
        "id": 1,
        "method": "eth_feeHistory",
        "params": ["0xa", "latest"]
     }'
curl --location \
     --request POST 'INSERT_RPC_API_ENDPOINT' \
     --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:9944' \
     --header 'Content-Type: application/json' \
     --data-raw '{
        "jsonrpc": "2.0",
        "id": 1,
        "method": "eth_feeHistory",
        "params": ["0xa", "latest"]
     }'

计算交易费用的示例代码

以下代码片段使用Axios HTTP客户端来为最终区块查询Sidecar端点/blocks/head。随后,根据交易类型(以太坊API:legacy、EIP-1559或EIP-2930标准以及Substrate API)计算区块中所有交易的交易费用,以及区块中的总交易费用。

以下代码示例仅用于演示目的,代码需进行修改并进一步测试后才可正式用于生产环境。

您可以将以下代码片段用于任何基于Moonbeam的网络,但您需要相应地修改baseFee。您可以参考基本费用部分以获取每个网络的计算结果。

const axios = require('axios');

// This script calculates the transaction fees of all transactions in a block
// according to the transaction type (for Ethereum API: legacy, EIP-1559 or
// EIP-2930 standards, and Substrate API) using the dynamic fee mechanism.
// It also calculates 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 latest nextFeeMultiplier
const endpointPallet =
  'http://127.0.0.1:8080/pallets/transaction-payment/storage/nextFeeMultiplier?at=';
// Endpoint to retrieve the node client's information
const endpointNodeVersion = 'http://127.0.0.1:8080/node/version';

// Define the minimum base fee for each network
const baseFee = {
  moonbeam: 125000000000n,
  moonriver: 1250000000n,
  moonbase: 125000000n
}

async function main() {
  try {
    // Create a variable to sum the transaction fees in the whole block
    let totalFees = 0n;

    // 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);

    // Find the block's nextFeeMultiplier
    const response_pallet = await axios.get(
      endpointPallet + response_block.data.number
    );

    // Iterate through all extrinsics in the block
    response_block.data.extrinsics.forEach((extrinsic) => {
      // Create an object to store transaction information
      let 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'
          ) {
            // Get Transaction Hash
            transactionData['hash'] = event.data[2];
          }
          if (
            event.method.pallet === 'system' &&
            event.method.method === 'ExtrinsicSuccess'
          ) {
            // Add correction weight if needed to Transaction Weight!
            transactionData['weight'] = BigInt(event.data[0].weight.refTime);
          }
        });

        // Get the transaction type and type specific parameters and compute the
        // transaction fee
        if (extrinsic.args.transaction.legacy) {
          transactionData['txType'] = 'legacy';
          transactionData['gasPrice'] = BigInt(
            extrinsic.args.transaction.legacy.gasPrice
          );
          transactionData['txFee'] =
            (transactionData['gasPrice'] * transactionData['weight']) / 25000n;
        } else if (extrinsic.args.transaction.eip1559) {
          transactionData['txType'] = 'eip1599';
          transactionData['maxFeePerGas'] = BigInt(
            extrinsic.args.transaction.eip1559.maxFeePerGas
          );
          transactionData['maxPriorityFeePerGas'] = BigInt(
            extrinsic.args.transaction.eip1559.maxPriorityFeePerGas
          );
          // Update based on the network you're getting tx fees for
          transactionData['baseFee'] =
            (BigInt(response_pallet.data.value) * baseFee.moonbeam) /
            BigInt('1000000000000000000');

          // Gas price dependes on the MaxFeePerGas and MaxPriorityFeePerGas set
          transactionData['gasPrice'] =
            transactionData['baseFee'] +
              transactionData['maxPriorityFeePerGas'] <
            transactionData['maxFeePerGas']
              ? transactionData['baseFee'] +
                transactionData['maxPriorityFeePerGas']
              : transactionData['maxFeePerGas'];

          transactionData['txFee'] =
            (transactionData['gasPrice'] * transactionData['weight']) / 25000n;
        } else if (extrinsic.args.transaction.eip2930) {
          transactionData['txType'] = 'eip2930';
          transactionData['gasPrice'] = BigInt(
            extrinsic.args.transaction.eip2930.gasPrice
          );
          transactionData['txFee'] =
            (transactionData['gasPrice'] * transactionData['weight']) / 25000n;
        }

        // 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.refTime;
          }
        });
      }
    });

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

main();
本网站的所有信息由第三方提供,仅供参考之用。Moonbeam文档网站(https://docs.moonbeam.network/)上列出和描述的任何项目与Moonbeam立场无关。Moonbeam Foundation不保证网站信息的准确性、完整性或真实性。如使用或依赖本网站信息,需自行承担相关风险,Moonbeam Foundation不承担任何责任和义务。这些材料的所有陈述和/或意见由提供方个人或实体负责,与Moonbeam Foundation立场无关,概不构成任何投资建议。对于任何特定事项或情况,应寻求专业权威人士的建议。此处的信息可能会包含或链接至第三方提供的信息与/或第三方服务(包括任何第三方网站等)。这类链接网站不受Moonbeam Foundation控制。Moonbeam Foundation对此类链接网站的内容(包括此类链接网站上包含的任何信息或资料)概不负责也不认可。这些链接内容仅为方便访客而提供,Moonbeam Foundation对因您使用此信息或任何第三方网站或服务提供的信息而产生的所有责任概不负责。
Last update: May 17, 2023
| Created: September 15, 2022