merge 0.4.7

This commit is contained in:
smart_ex 2022-05-24 12:06:47 +10:00
commit e37e9c3c8d
6 changed files with 136 additions and 38 deletions

3
index.d.ts vendored
View File

@ -34,6 +34,9 @@ export interface TxManagerConfig {
BLOCK_GAS_LIMIT?: number
PRIORITY_FEE_GWEI?: number
BASE_FEE_RESERVE_PERCENTAGE?: number
ENABLE_EIP1559?: boolean
DEFAULT_PRIORITY_FEE?: number
PRIORITY_FEE_RESERVE_PERCENTAGE?: number
}
export interface TxManagerParams {

View File

@ -1,6 +1,6 @@
{
"name": "tx-manager",
"version": "0.4.6",
"version": "0.4.7",
"description": "",
"main": "index.js",
"types": "index.d.ts",
@ -24,7 +24,7 @@
"dependencies": {
"async-mutex": "^0.2.4",
"ethers": "^5.4.6",
"gas-price-oracle": "^0.4.6",
"gas-price-oracle": "^0.4.7",
"web3-core-promievent": "^1.3.0"
},
"devDependencies": {

View File

@ -1,4 +1,3 @@
'use strict'
const ethers = require('ethers')
const { parseUnits, formatUnits } = ethers.utils
const BigNumber = ethers.BigNumber
@ -7,21 +6,27 @@ const { sleep, min, max } = require('./utils')
const nonceErrors = [
'Transaction nonce is too low. Try incrementing the nonce.',
'nonce too low',
/nonce too low/i,
'nonce has already been used',
/OldNonce/,
'invalid transaction nonce',
]
const gasPriceErrors = [
'Transaction gas price supplied is too low. There is another transaction with same nonce in the queue. Try increasing the gas price or incrementing the nonce.',
'replacement transaction underpriced',
'transaction underpriced',
/replacement transaction underpriced/i,
/transaction underpriced/,
/Transaction gas price \d+wei is too low. There is another transaction with same nonce in the queue with gas price: \d+wei. Try increasing the gas price or incrementing the nonce./,
/FeeTooLow/,
/max fee per gas less than block base fee/,
]
// prettier-ignore
const sameTxErrors = [
'Transaction with the same hash was already imported.',
'already known',
'AlreadyKnown',
'Known transaction'
]
class Transaction {
@ -64,13 +69,16 @@ class Transaction {
this.tx = { ...tx }
return
}
if (!tx.gasLimit) {
const estimatedGasLimit = await this.manager._wallet.estimateGas(tx)
const estimatedGasLimit = await this._estimateGas(tx)
tx.gasLimit = min(
estimatedGasLimit.mul(this.manager.config.GAS_LIMIT_MULTIPLIER * 100).div(100),
this.manager.config.BLOCK_GAS_LIMIT,
)
}
tx.chainId = this.tx.chainId
tx.nonce = this.tx.nonce // can be different from `this.manager._nonce`
// start no less than current tx gas params
@ -95,7 +103,6 @@ class Transaction {
from: this.manager.address,
to: this.manager.address,
value: 0,
gasLimit: 21000,
})
}
@ -131,8 +138,14 @@ class Transaction {
this.manager.config.BLOCK_GAS_LIMIT = Math.floor(lastBlock.gasLimit.toNumber() * 0.95)
}
if (!this.manager._chainId) {
const net = await this.manager._provider.getNetwork()
this.manager._chainId = net.chainId
}
this.tx.chainId = this.manager._chainId
if (!this.tx.gasLimit || this.manager.config.ESTIMATE_GAS) {
const gas = await this.manager._wallet.estimateGas(this.tx)
const gas = await this._estimateGas(this.tx)
if (!this.tx.gasLimit) {
const gasLimit = Math.floor(gas * this.manager.config.GAS_LIMIT_MULTIPLIER)
this.tx.gasLimit = Math.min(gasLimit, this.manager.config.BLOCK_GAS_LIMIT)
@ -144,12 +157,6 @@ class Transaction {
}
this.tx.nonce = this.manager._nonce
if (!this.manager._chainId) {
const net = await this.manager._provider.getNetwork()
this.manager._chainId = net.chainId
}
this.tx.chainId = this.manager._chainId
if (this.tx.gasPrice || (this.tx.maxFeePerGas && this.tx.maxPriorityFeePerGas)) {
return
}
@ -175,7 +182,7 @@ class Transaction {
try {
await this._broadcast(signedTx)
} catch (e) {
return this._handleSendError(e)
return this._handleRpcError(e, '_send')
}
this._emitter.emit('transactionHash', txHash)
@ -293,7 +300,7 @@ class Transaction {
return main
}
_handleSendError(e) {
_handleRpcError(e, method) {
if (e.error.error) {
// Sometimes ethers wraps known errors, unwrap it in this case
e = e.error
@ -308,17 +315,20 @@ class Transaction {
if (this.retries <= this.manager.config.MAX_RETRIES) {
this.tx.nonce++
this.retries++
return this._send()
return this[method]()
}
}
// there is already a pending tx with higher gas price, trying to bump and resubmit
if (this._hasError(message, gasPriceErrors)) {
console.log(
`Gas price ${formatUnits(this.tx.gasPrice, 'gwei')} gwei is too low, increasing and retrying`,
`Gas price ${formatUnits(
this.tx.gasPrice || this.tx.maxFeePerGas,
'gwei',
)} gwei is too low, increasing and retrying`,
)
if (this._increaseGasPrice()) {
return this._send()
return this[method]()
} else {
throw new Error('Already at max gas price, but still not enough to submit the transaction')
}
@ -379,8 +389,10 @@ class Transaction {
oldMaxPriorityFeePerGas.add(minGweiBump),
)
this.tx.maxFeePerGas = min(newMaxFeePerGas, maxGasPrice).toHexString()
this.tx.maxPriorityFeePerGas = min(newMaxPriorityFeePerGas, this.tx.maxFeePerGas).toHexString()
const maxFeePerGas = min(newMaxFeePerGas, maxGasPrice)
this.tx.maxFeePerGas = maxFeePerGas.toHexString()
this.tx.maxPriorityFeePerGas = min(newMaxPriorityFeePerGas, maxFeePerGas).toHexString()
console.log(`Increasing maxFeePerGas to ${formatUnits(this.tx.maxFeePerGas, 'gwei')} gwei`)
}
@ -412,6 +424,33 @@ class Transaction {
return this.manager._wallet.getTransactionCount('latest')
}
/**
* Fetches priority fee from the chain
*
* @returns {Promise<BigNumber>}
* @private
*/
async _estimatePriorityFee() {
const defaultPriorityFee = parseUnits(this.manager.config.DEFAULT_PRIORITY_FEE.toString(), 'gwei')
try {
const estimatedPriorityFee = await this.manager._provider.send('eth_maxPriorityFeePerGas', [])
if (!estimatedPriorityFee || isNaN(estimatedPriorityFee)) {
return defaultPriorityFee
}
const bumpedPriorityFee = BigNumber.from(estimatedPriorityFee)
.mul(100 + this.manager.config.PRIORITY_FEE_RESERVE_PERCENTAGE)
.div(100)
return max(bumpedPriorityFee, defaultPriorityFee)
} catch (err) {
console.error('_estimatePriorityFee has error:', err.message)
return defaultPriorityFee
}
}
/**
* Choose network gas params
*
@ -423,12 +462,14 @@ class Transaction {
const block = await this.manager._provider.getBlock('latest')
// Check network support for EIP-1559
if (block && block.baseFeePerGas) {
const maxPriorityFeePerGas = parseUnits(this.manager.config.PRIORITY_FEE_GWEI.toString(), 'gwei')
if (this.manager.config.ENABLE_EIP1559 && block && block.baseFeePerGas) {
const maxPriorityFeePerGas = await this._estimatePriorityFee()
const maxFeePerGas = block.baseFeePerGas
.mul(100 + this.manager.config.BASE_FEE_RESERVE_PERCENTAGE)
.div(100)
.add(maxPriorityFeePerGas)
return {
maxFeePerGas: min(maxFeePerGas, maxGasPrice).toHexString(),
maxPriorityFeePerGas: min(maxPriorityFeePerGas, maxGasPrice).toHexString(),
@ -442,6 +483,14 @@ class Transaction {
}
}
}
async _estimateGas(tx) {
try {
return await this.manager._wallet.estimateGas(tx)
} catch (e) {
return this._handleRpcError(e, '_estimateGas')
}
}
}
module.exports = Transaction

View File

@ -15,8 +15,10 @@ const defaultConfig = {
ESTIMATE_GAS: true,
THROW_ON_REVERT: true,
BLOCK_GAS_LIMIT: null,
PRIORITY_FEE_GWEI: 3,
ENABLE_EIP1559: true,
DEFAULT_PRIORITY_FEE: 3,
BASE_FEE_RESERVE_PERCENTAGE: 50,
PRIORITY_FEE_RESERVE_PERCENTAGE: 10,
}
class TxManager {

View File

@ -1,19 +1,13 @@
require('dotenv').config()
require('chai').should()
const { providers } = require('ethers')
const { parseUnits } = require('ethers').utils
const TxManager = require('../src/TxManager')
// const Transaction = require('../src/Transaction')
const { RPC_URL, PRIVATE_KEY } = process.env
describe('TxManager', () => {
const manager = new TxManager({
privateKey: PRIVATE_KEY,
rpcUrl: RPC_URL,
config: {
CONFIRMATIONS: 1,
GAS_BUMP_INTERVAL: 1000 * 20,
},
})
let manager
const tx1 = {
value: 1,
@ -45,6 +39,26 @@ describe('TxManager', () => {
type: 2,
}
before(async () => {
const provider = new providers.JsonRpcProvider(RPC_URL)
const { name, chainId } = await provider.getNetwork()
console.log('\n\n', 'network', { name, chainId }, '\n\n')
manager = new TxManager({
privateKey: PRIVATE_KEY,
rpcUrl: RPC_URL,
config: {
CONFIRMATIONS: 1,
GAS_BUMP_INTERVAL: 1000 * 20,
},
gasPriceOracleConfig: {
chainId: chainId,
defaultRpc: RPC_URL,
},
})
})
describe('#transaction', () => {
it('should work legacy tx', async () => {
const tx = manager.createTx(tx1)
@ -122,6 +136,36 @@ describe('TxManager', () => {
console.log('receipt', receipt)
})
it('should increase nonce', async () => {
const currentNonce = await manager._wallet.getTransactionCount('latest')
manager._nonce = currentNonce - 1
const tx = manager.createTx(tx4)
const receipt = await tx
.send()
.on('transactionHash', hash => console.log('hash', hash))
.on('mined', receipt => console.log('Mined in block', receipt.blockNumber))
.on('confirmations', confirmations => console.log('confirmations', confirmations))
console.log('receipt', receipt)
})
it('should disable eip-1559 transactions', async () => {
manager.config.ENABLE_EIP1559 = false
const tx = manager.createTx(tx3)
const receipt = await tx
.send()
.on('transactionHash', hash => console.log('hash', hash))
.on('mined', receipt => console.log('Mined in block', receipt.blockNumber))
.on('confirmations', confirmations => console.log('confirmations', confirmations))
console.log('receipt', receipt)
manager.config.ENABLE_EIP1559 = true
})
it('should send multiple txs', async () => {
const genTx = value => ({
value,
@ -139,6 +183,6 @@ describe('TxManager', () => {
manager.createTx(genTx(9)).send(),
manager.createTx(genTx(10)).send(),
])
})
}).timeout(600000)
})
})

View File

@ -1056,10 +1056,10 @@ functional-red-black-tree@^1.0.1:
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
gas-price-oracle@^0.4.6:
version "0.4.6"
resolved "https://registry.yarnpkg.com/gas-price-oracle/-/gas-price-oracle-0.4.6.tgz#3e496092122896f1c80ea7eeeecea979d106b3aa"
integrity sha512-/z0wtzKa6FDTWmgikPnELWN8KiPHhCy3Z+waeKVMgvs5FBxibgwOUL1VlMsC4mVkXBoDadnBtFNOpUMgbt5pvg==
gas-price-oracle@^0.4.7:
version "0.4.7"
resolved "https://registry.yarnpkg.com/gas-price-oracle/-/gas-price-oracle-0.4.7.tgz#47406048083074bcab677efb9de08663e742153d"
integrity sha512-Ti8nhpATm83YebWU/Pz5xclZoTkzOblIhT504ZViZJUcd8jOxgj9pWtCasg8RYw+d0f19m0dJUPvdj04RC4o3A==
dependencies:
axios "^0.21.2"
bignumber.js "^9.0.0"