EIP-1559交易详解
EIP-1559是以太坊的重要升级,引入了新的交易类型和动态燃料定价机制。本文档将详细介绍EIP-1559交易的构建和发送流程。
EIP-1559概述
EIP-1559引入了以下改进:
- 动态燃料定价: 基于网络拥堵情况自动调整
- 基础费用: 网络自动销毁的基础燃料费用
- 优先费用: 用户支付给矿工的额外费用
- 更好的用户体验: 更可预测的Gas费用
核心概念
燃料费用计算
/**
* fee = gasLimit * gasPrice
* gasPrice = BaseFeePerGas + MaxPriorityFeePerGas
*/
- BaseFeePerGas: 网络自动计算的基础费用
- MaxPriorityFeePerGas: 用户设置的最大优先费用
- MaxFeePerGas: 用户愿意支付的最大总费用
依赖库
const { FeeMarketEIP1559Transaction: Transaction } = require('@ethereumjs/tx');
const { default: Common, Hardfork, Chain } = require('@ethereumjs/common');
const { bufferToHex, stripHexPrefix } = require('ethereumjs-util');
const { default: BigNumber } = require('bignumber.js');
交易构建流程
1. 设置网络和Common
const provider = 'https://ropsten.infura.io/v3/YOUR_KEY';
const web3 = new Web3(provider);
// 创建Common实例,支持London硬分叉
const common = new Common({
chain: 'ropsten',
hardfork: Hardfork.London
});
const txOptions = { common };
2. 准备交易数据
const from = '0xd73d9AA55ABBd6CFbeD3e9Ad7f8Be2f6D83C70dC';
const to = '0x370BA1dc25C07d0C77Ba9b83fcc75Fcc2a0aC243';
const tokenAddress = '0x5abe286f5ea6132b157cfd728834d493cbd43314';
// 计算代币数量(18位小数)
const quantity = new BigNumber(1).shiftedBy(18).toFixed();
console.log('quantity:', quantity);
3. 估算Gas费用
// 估算转账Gas
const txData = {
data: '0x',
from: from,
to: from, // 自转账用于估算
value: quantity,
common,
};
var estimateGas = await web3.eth.estimateGas(txData);
console.log('estimateGas:', estimateGas);
// 估算代币转账Gas
const raw = '0000000000000000000000000000000000000000000000000000000000000000';
const amount = new BigNumber(quantity).toString(16);
const input = '0xa9059cbb000000000000000000000000' + // transfer方法签名
stripHexPrefix(to) + // 目标地址
raw.substring(0, raw.length - amount.length) + // 占位符
amount; // 数量
var estimateGas = await web3.eth.estimateGas({
to: tokenAddress,
data: input,
gasPrice,
nonce,
common,
});
console.log('estimateGas:', estimateGas);
4. 获取燃料价格信息
// 获取最大优先费用
web3.eth.customRPC({
name: 'maxPriorityFeePerGas',
call: 'eth_maxPriorityFeePerGas'
});
const maxPriorityFeePerGas = await web3.eth.maxPriorityFeePerGas();
const gasLimit = toBN(estimateGas);
// 获取基础费用
const block = await web3.eth.getBlock('pending');
const baseFeePerGas = block['baseFeePerGas'];
// 计算最大费用
const maxFeePerGas = toBN(baseFeePerGas).add(toBN(maxPriorityFeePerGas));
5. 构建EIP-1559交易
const txData = {
from,
to: tokenAddress,
data: input,
value: '0x00',
nonce: nonce ? '0x' + new BigNumber(nonce).toString(16) : '0x',
// EIP-1559特有字段
maxPriorityFeePerGas: '0x9502f900',
maxFeePerGas: toHex(maxFeePerGas),
gasLimit: toHex(gasLimit),
};
// 使用实际计算的值
Object.assign(txData, {
maxPriorityFeePerGas: toHex(maxPriorityFeePerGas),
maxFeePerGas: toHex(maxFeePerGas),
gasLimit,
nonce,
});
console.log('maxPriorityFeePerGas:', toBN(maxPriorityFeePerGas).toString());
console.log('maxFeePerGas:', toBN(maxFeePerGas).toString());
console.log('txData:', txData);
交易签名和序列化
创建和签名交易
// 从交易数据创建EIP-1559交易
const tx = Transaction.fromTxData(txData, txOptions);
// 序列化未签名交易
const serialized = tx.serialize().toString('hex');
const tx_hash = tx.getMessageToSign(true).toString('hex');
console.log('toJSON:', tx.toJSON());
console.log('hash:', tx_hash);
console.log('serialized:', serialized);
// 使用私钥签名
const privatekey = Buffer.from(PRIVATE_KEY, 'hex');
const signedTx = tx.sign(privatekey);
const serializedTx = signedTx.serialize();
console.log('signed toJSON:', signedTx.toJSON());
console.log('signed serialized:', bufferToHex(serializedTx));
console.log('signed txhash:', signedTx.serialize().toString('hex'));
交易发送
发送签名交易
function sendTx(web3, data) {
return new Promise((resolve, reject) => {
web3.eth
.sendSignedTransaction(data)
.once('transactionHash', (hash) => {
resolve(hash);
})
.on('error', function (error) {
reject(error);
});
});
}
// 发送交易
const hash = await sendTx(web3, bufferToHex(serializedTx));
console.log('Transaction hash:', hash);
高级功能
自定义RPC方法
web3.eth.customRPC = function (opts) {
const _this = this;
const newMethod = new Method({
name: opts.name,
call: opts.call,
params: opts.params || 0,
inputFormatter: opts.inputFormatter || null,
outputFormatter: opts.outputFormatter || null,
});
newMethod.attachToObject(_this);
newMethod.setRequestManager(_this._requestManager, _this.accounts);
};
燃料费用优化
// 动态调整燃料费用
async function optimizeGasFees(web3, baseEstimate) {
const maxPriorityFeePerGas = await web3.eth.maxPriorityFeePerGas();
const block = await web3.eth.getBlock('pending');
const baseFeePerGas = block['baseFeePerGas'];
// 添加20%的缓冲
const gasLimit = toBN(baseEstimate).mul(120).div(100);
const maxFeePerGas = toBN(baseFeePerGas).mul(2).add(toBN(maxPriorityFeePerGas));
return {
gasLimit: toHex(gasLimit),
maxPriorityFeePerGas: toHex(maxPriorityFeePerGas),
maxFeePerGas: toHex(maxFeePerGas)
};
}
错误处理
常见错误类型
try {
const result = await sendTx(web3, signedTx);
return result;
} catch (error) {
if (error.message.includes('insufficient funds')) {
console.error('余额不足');
} else if (error.message.includes('nonce too low')) {
console.error('Nonce值过低');
} else if (error.message.includes('gas price too low')) {
console.error('燃料价格过低');
} else {
console.error('未知错误:', error.message);
}
throw error;
}
网络兼容性
支持的网络
- 主网: 支持EIP-1559
- Goerli: 测试网络,支持EIP-1559
- Sepolia: 测试网络,支持EIP-1559
- 本地网络: 需要配置London硬分叉
网络检测
async function checkEIP1559Support(web3) {
try {
const block = await web3.eth.getBlock('latest');
return block.baseFeePerGas !== undefined;
} catch (error) {
return false;
}
}
最佳实践
- Gas估算: 始终估算Gas费用并添加缓冲
- 费用设置: 合理设置最大费用和优先费用
- 网络选择: 确保网络支持EIP-1559
- 错误处理: 实现完善的错误处理机制
- 测试验证: 在测试网络上充分测试
常见问题
Q: 什么时候使用EIP-1559?
A: 当网络支持London硬分叉时,EIP-1559提供更好的费用控制。
Q: 如何设置合适的燃料费用?
A: 监控网络拥堵情况,动态调整优先费用。
Q: 交易卡住了怎么办?
A: 使用相同nonce发送更高优先费用的交易来替换。