计算Moonbeam上的交易费用¶
概览¶
与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 Gas
和Max 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 Gas
和Max 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();