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交易费用

Moonbeam上通过Substrate API发送的交易费用可直接从Sidecar block JSON对象读取。嵌套结构如下所示:

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

对象映射总结如下:

交易信息 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交易费用

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

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

随着Runtime 1900的推出,Sidecar API报告的内容与用于EVM交易费用的内容之间存在“Transaction Weight”不匹配。 因此,您需要将以下金额添加至“Transaction Weight”:

86298000

RT2000升级已修复权重不匹配问题。 这意味着对于运行RT2000升级版的网络,您无需添加任何数。报告的值应是正确的,并可用于先前展示的计算。

适用交易类型的Gas Price, Max Fee Per GasMax Priority Fee Per Gas值可以根据Sidecar API页面描述的结构从Block JSON对象读取,且被截短后复制在下方:

EVM字段 JSON对象字段
Max Fee Per Gas extrinsics[extrinsic_number].args.transaction.eip1559.maxFeePerGas
Max Priority Fee Per Gas extrinsics[extrinsic_number].args.transaction.eip1559.maxPriorityFeePerGas
EVM字段 JSON对象字段
Gas Price extrinsics[extrinsic_number].args.transaction.legacy.gasPrice
EVM字段 JSON对象字段
Gas Price extrinsics[extrinsic_number].args.transaction.eip2930.gasPrice

EIP-1559中引入的Base Fee是由网络自设的一个值。EIP1559类型交易的Base Fee目前在Moonbeam网络上是静态的,并有以下指定的值:

变量
Base Fee 100 Gwei
变量
Base Fee 1 Gwei
变量
Base Fee 1 Gwei

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

pallet: "system", method: "ExtrinsicSuccess" 

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

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

Note

请记住,Runtime190X存在Transaction Weight不匹配。您需要为它的值添加一个常量。查看计算以太坊API交易费用了解更多信息。随后的RT2000升级修复了该问题。

与以太坊的关键性差异

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

  • Moonbeam网络上的网络基本费用目前是静态的。这可能有很多因素,其中之一是发送交易时设置的gas价格低于基本费用将导致交易失败,即使网络上的当前区块未满。这与以太坊不同,以太坊对要接受交易的gas价格没有限制。

    网络基本费用可能会在未来的Runtime更新中进行更新。

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

费用记录 端点

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

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

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

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"]
     }'

计算交易费用的示例代码

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

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

const axios = require("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.   
// Define static base fee per network   
const baseFee = {   
  moonbeam: 100000000000n,  
  moonriver: 1000000000n,   
  moonbase: 1000000000n,    
};  
// In RT1900 & RT1901 ONLY, there is a `Transaction Weight` mismatch between what is reported by the Sidecar API and what is used for the EVM transaction fee.  
// To get the correct calculation, you need to add the following constant per network to the `Transaction Weight` value:    
const weightCorrection = {  
  moonbeam: 86298000n,  
  moonriver: 86298000n, 
  moonbase: 250000000n, 
};  
// 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 
    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); 
    // 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) + weightCorrection[transactionData["network"]];    
          } 
        }); 
        // 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);    
          transactionData["baseFee"] = baseFee[transactionData["network"]]; 
          // 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对因您使用此信息或任何第三方网站或服务提供的信息而产生的所有责任概不负责。