传统的gas定价模型(Txn Type===0)
import Web3 from 'web3';
import { AbiItem } from 'web3-utils/types/index.d';
import erc20 from '../abis/erc20.json';
import { FeeMarketEIP1559Transaction as Tx } from '@ethereumjs/tx';
import { Common, Chain, Hardfork } from '@ethereumjs/common';
import config from '../constants/service.config';
const webSocketProvider = (rpcUrl: string) => {
return new Web3.providers.HttpProvider(rpcUrl);
};
const provider = (rpcUrl: string = config.alchemyEthBaseUrl) => {
return new Web3(webSocketProvider(rpcUrl));
};
const transfer = async (
{ ...rest }: any
) => {
const {
privateKey,
contractAddress,
tokenAddress,
recipientAddress,
rpcUrl,
senderAddress,
amount,
...args
} = rest;
const web3 = provider();
const { toHex, toWei, hexToNumber } = web3.utils;
const gasPrice = await web3.eth.getGasPrice();
try {
const nounce = await web3.eth.getTransactionCount(senderAddress, 'pending');
let rawTx = {};
if (['eth', 'matic'].includes(tokenAddress.toLowerCase())) {
// 以太坊主币
rawTx = {
to: recipientAddress,
value: toHex(toWei(String(amount))),
};
} else {
// 符合erc20规范的代币
const contract = new web3.eth.Contract(erc20 as AbiItem[], tokenAddress, {
from: senderAddress,
});
const transferEvent = contract.methods.transfer(
recipientAddress,
toHex(toWei(String(amount)))
);
const limit = await transferEvent.estimateGas(); // gas limit
rawTx = {
to: tokenAddress,
gasLimit: toHex(Math.ceil(limit * 1.2)),
data: transferEvent.encodeABI(),
};
}
/**
* 组装交易数据:
* to:
* 如果是以太坊主币,to是收款人地址
* 如果是erc20代码,to是协议地址,收款人地址在合约方法:contrack.methods.transfer中定义
* value:
* 如果是以太坊主币,value是转账金额
* 如果是erc20代码,value是`toHex(0)`,转账金额在合约方法:contrack.methods.transfer中定义
* maxPriorityFeePerGas
* Txn Type=2所必需,提高优先级,给予矿工的小费
* maxFeePerGas
* Txn Type=2所必需,提高优先级,给予矿工小费的最大值
*/
rawTx = {
from: senderAddress,
nonce: toHex(nounce),
gasLimit: toHex(21000), //"21000",
gasPrice: toHex(gasPrice),
value: '0x00', //"10000000",//'0x00',
...rawTx,
};
/**
* 以太坊资源的通用实现类;
* 创建以太坊链和分叉的实例,实现EIP1559功能需要选择London硬分叉
* new Common({ chain: Chain.Mainnet, eips: [1559] })
*/
const common = new Common({
chain: Chain.Goerli,
hardfork: Hardfork.London,
});
const unsignedTx = Tx.fromTxData(rawTx, { common });
const secretKey = Buffer.from(privateKey.slice(2), 'hex');
const signedTx = unsignedTx.sign(secretKey);
const serializedTx = signedTx.serialize();
const signedTransactionData = '0x' + serializedTx.toString('hex');
web3.eth
.sendSignedTransaction(signedTransactionData, async function (err, hash) {
if (!err) {
/**
* gasfee = `${web3.utils.fromWei(
(Number((rawTx as any).gasLimit) * Number(gasPrice)).toString()
)}${wallet.unit}`;
*/
console.log('-----hash-------', hash);
const url = `https://goerli.etherscan.io/tx/${hash}`;
const accepted = `https://etherscan.io/api?module=localchk&action=txexist&txhash=${hash}`;
console.log('TX Link', url);
console.log('Accepted', accepted);
} else {
console.log('-----error', err);
}
})
.on('receipt', (receipt) => {
console.log('TX receipt', receipt);
// blockHash: ""
// blockNumber:
// contractAddress: null
// cumulativeGasUsed:
// effectiveGasPrice:
// from: ""
// gasUsed: 21000
// logs: []
// logsBloom: ""
// status: true
// to: ""
// transactionHash: ""
// transactionIndex:
// type: "0x0"
const { gasUsed, status } = receipt;
/**
* gasfee = `${web3.utils.fromWei(
(gasUsed * Number(gasPrice)).toString()
)}${wallet.unit}`;
*/
});
} catch (e) {
console.log(e);
}
};
伦敦硬分叉EIP1559(Txn Type===2)
import Web3 from 'web3';
import { AbiItem } from 'web3-utils/types/index.d';
import erc20 from '../abis/erc20.json';
import { FeeMarketEIP1559Transaction as Tx } from '@ethereumjs/tx';
import { Common, Chain, Hardfork } from '@ethereumjs/common';
import config from '../constants/service.config';
const webSocketProvider = (rpcUrl: string) => {
return new Web3.providers.HttpProvider(rpcUrl);
};
const provider = (rpcUrl: string = config.alchemyEthBaseUrl) => {
return new Web3(webSocketProvider(rpcUrl));
};
const transfer = async (
{ ...rest }: any
) => {
const {
privateKey,
contractAddress,
tokenAddress,
recipientAddress,
rpcUrl,
senderAddress,
amount,
...args
} = rest;
const web3 = provider();
const { toHex, toWei, hexToNumber } = web3.utils;
const gasPrice = await web3.eth.getGasPrice();
try {
const nounce = await web3.eth.getTransactionCount(senderAddress, 'pending');
let rawTx = {};
if (['eth', 'matic'].includes(tokenAddress.toLowerCase())) {
// 以太坊主币
rawTx = {
to: recipientAddress,
value: toHex(toWei(String(amount))),
};
} else {
// 符合erc20规范的代币
const contract = new web3.eth.Contract(erc20 as AbiItem[], tokenAddress, {
from: senderAddress,
});
const transferEvent = contract.methods.transfer(
recipientAddress,
toHex(toWei(String(amount)))
);
const limit = await transferEvent.estimateGas(); // gas limit
rawTx = {
to: tokenAddress,
gasLimit: toHex(Math.ceil(limit * 1.2)),
data: transferEvent.encodeABI(),
};
}
// maxPriorityFeePerGas: Txn Type === 2所需要的字段
const maxPriorityFeePerGasRes = request.post(config.alchemyEthBaseUrl, {
id: 1,
jsonrpc: '2.0',
method: 'eth_maxPriorityFeePerGas',
});
const maxPriorityFeePerGas = ((await maxPriorityFeePerGasRes) as any)
.result;
const getBlockRes = web3.eth.getBlock('latest');
const baseFeePerGas = ((await getBlockRes) as any).baseFeePerGas - 1;
/**
* 组装交易数据:
* to:
* 如果是以太坊主币,to是收款人地址
* 如果是erc20代码,to是协议地址,收款人地址在合约方法:contrack.methods.transfer中定义
* value:
* 如果是以太坊主币,value是转账金额
* 如果是erc20代码,value是`toHex(0)`,转账金额在合约方法:contrack.methods.transfer中定义
* maxPriorityFeePerGas
* Txn Type=2所必需,提高优先级,给予矿工的小费
* maxFeePerGas
* Txn Type=2所必需,提高优先级,给予矿工小费的最大值
*/
rawTx = {
from: senderAddress,
nonce: toHex(nounce),
gasLimit: toHex(21000), //"21000",
maxPriorityFeePerGas: maxPriorityFeePerGas,
maxFeePerGas: toHex(
2 * baseFeePerGas + hexToNumber(maxPriorityFeePerGas)
),
value: '0x00', //"10000000",//'0x00',
...rawTx,
};
/**
* 以太坊资源的通用实现类;
* 创建以太坊链和分叉的实例,实现EIP1559功能需要选择London硬分叉
* new Common({ chain: Chain.Mainnet, eips: [1559] })
*/
const common = new Common({
chain: Chain.Goerli,
hardfork: Hardfork.London,
});
const unsignedTx = Tx.fromTxData(rawTx, { common });
const secretKey = Buffer.from(privateKey.slice(2), 'hex');
const signedTx = unsignedTx.sign(secretKey);
const serializedTx = signedTx.serialize();
const signedTransactionData = '0x' + serializedTx.toString('hex');
web3.eth
.sendSignedTransaction(signedTransactionData, async function (err, hash) {
if (!err) {
/**
* gasfee = `${web3.utils.fromWei(
(Number((rawTx as any).gasLimit) * Number(gasPrice)).toString()
)}${wallet.unit}`;
*/
console.log('-----hash-------', hash);
const url = `https://goerli.etherscan.io/tx/${hash}`;
const accepted = `https://etherscan.io/api?module=localchk&action=txexist&txhash=${hash}`;
console.log('TX Link', url);
console.log('Accepted', accepted);
} else {
console.log('-----error', err);
}
})
.on('receipt', (receipt) => {
console.log('TX receipt', receipt);
// blockHash: ""
// blockNumber:
// contractAddress: null
// cumulativeGasUsed:
// effectiveGasPrice:
// from: ""
// gasUsed: 21000
// logs: []
// logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
// status: true
// to: ""
// transactionHash: ""
// transactionIndex:
// type: "0x0"
const { gasUsed, status } = receipt;
/**
* gasfee = `${web3.utils.fromWei(
(gasUsed * Number(gasPrice)).toString()
)}${wallet.unit}`;
*/
});
} catch (e) {
console.log(e);
}
};
外部签名
import Web3 from 'web3';
import { bufArrToArr, toBuffer, bigIntToHex, addHexPrefix } from '@ethereumjs/util'
...
// signatures是外部服务签名
const { toHex, toWei, toDecimal } = web3.utils;
const { r_hex: r, s_hex: s, recovery_id_hex } = signatures
const chainId = unsignedTx.common.chainId()
const v = chainId === undefined
? BigInt(toDecimal(addHexPrefix(recovery_id_hex)) + 27)
: BigInt(toDecimal(addHexPrefix(recovery_id_hex)) + 35) + BigInt(chainId) * BigInt(2)
const toSignTx = {
nonce: unsignedTx.nonce,
gasPrice: unsignedTx.gasPrice,
gasLimit: unsignedTx.gasLimit,
to: unsignedTx.to,
value: unsignedTx.value,
data: unsignedTx.data,
v,
r: addHexPrefix(r),
s: addHexPrefix(s),
}
const signedTx = Tx.fromTxData(toSignTx, { common })
const serializeSignedTx = signedTx.serialize()
const signedTxHex = addHexPrefix(serializeSignedTx.toString('hex'))
web3.eth.sendSignedTransaction(signedTxHex, async function (err, hash) {
if (!err) {
console.log('-----hash-------', hash);
const url = `https://goerli.etherscan.io/tx/${hash}`;
const accepted = `https://etherscan.io/api?module=localchk&action=txexist&txhash=${hash}`;
console.log('TX Link', url);
console.log('Accepted', accepted);
} else {
console.log('-----error', err);
}
})