共计 8072 个字符,预计需要花费 21 分钟才能阅读完成。
传统的 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); | |
} | |
}) |
正文完