计算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交易费用¶
所有关于通过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
, MaxFeePerGas
和MaxPriorityFeePerGas
的值可以根据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上构建时需要注意以下部分:
-
Moonbeam交易费用模型中使用的gas数量是通过固定比例25000从交易的Substrate extrinsic权重值映射而来。通过此数值乘以单位gas价格来计算交易费用。此费用模型意味着通过以太坊API发送如基本转账等交易可能会比Substrate API更为便宜。
费用记录端点¶
Moonbeam网络实施eth_feeHistory
JSON-RPC端点作为对EIP-1559支持的一部分。
eth_feeHistory
返回一系列的历史gas信息,可供您参考和计算在提交EIP-1559交易时为MaxFeePerGas
和MaxPriorityFeePerGas
字段设置的内容。
以下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();
| Created: September 15, 2022