remove Object.assign(), use BigNumber

This commit is contained in:
smart_ex 2022-05-12 20:40:55 +10:00
parent 6db517cde9
commit 1b59ac8923
4 changed files with 59 additions and 51 deletions

14
index.d.ts vendored
View File

@ -45,15 +45,15 @@ export interface TxManagerParams {
} }
export class TxManager { export class TxManager {
private _privateKey: string
config: TxManagerConfig config: TxManagerConfig
address: string address: string
private _privateKey: string _provider: providers.JsonRpcProvider
private _provider: providers.JsonRpcProvider _wallet: Wallet
private _wallet: Wallet _broadcastNodes: string[]
private _broadcastNodes: string[] _gasPriceOracle: GasPriceOracle
private _gasPriceOracle: GasPriceOracle _mutex: Mutex
private _mutex: Mutex _nonce: number
private _nonce: number
constructor(params?: TxManagerParams) constructor(params?: TxManagerParams)

View File

@ -1,3 +1,4 @@
'use strict'
const ethers = require('ethers') const ethers = require('ethers')
const { parseUnits, formatUnits } = ethers.utils const { parseUnits, formatUnits } = ethers.utils
const BigNumber = ethers.BigNumber const BigNumber = ethers.BigNumber
@ -25,7 +26,6 @@ const sameTxErrors = [
class Transaction { class Transaction {
constructor(tx, manager) { constructor(tx, manager) {
Object.assign(this, manager)
this.manager = manager this.manager = manager
this.tx = { ...tx } this.tx = { ...tx }
this._promise = PromiEvent() this._promise = PromiEvent()
@ -64,18 +64,18 @@ class Transaction {
return return
} }
if (!tx.gasLimit) { if (!tx.gasLimit) {
tx.gasLimit = await this._wallet.estimateGas(tx) tx.gasLimit = await this.manager._wallet.estimateGas(tx)
tx.gasLimit = Math.floor(tx.gasLimit * this.config.GAS_LIMIT_MULTIPLIER) tx.gasLimit = Math.floor(tx.gasLimit.mul(this.manager.config.GAS_LIMIT_MULTIPLIER).toNumber())
tx.gasLimit = Math.min(tx.gasLimit, this.config.BLOCK_GAS_LIMIT) tx.gasLimit = min(tx.gasLimit, this.manager.config.BLOCK_GAS_LIMIT)
} }
tx.nonce = this.tx.nonce // can be different from `this.manager._nonce` tx.nonce = this.tx.nonce // can be different from `this.manager._nonce`
// start no less than current tx gas params // start no less than current tx gas params
if (this.tx.gasPrice) { if (this.tx.gasPrice) {
tx.gasPrice = Math.max(this.tx.gasPrice, tx.gasPrice || 0) tx.gasPrice = max(this.tx.gasPrice, tx.gasPrice || 0)
} else { } else {
tx.maxFeePerGas = Math.max(this.tx.maxFeePerGas, tx.maxFeePerGas || 0) tx.maxFeePerGas = max(this.tx.maxFeePerGas, tx.maxFeePerGas || 0)
tx.maxPriorityFeePerGas = Math.max(this.tx.maxPriorityFeePerGas, tx.maxPriorityFeePerGas || 0) tx.maxPriorityFeePerGas = max(this.tx.maxPriorityFeePerGas, tx.maxPriorityFeePerGas || 0)
} }
this.tx = { ...tx } this.tx = { ...tx }
@ -89,8 +89,8 @@ class Transaction {
cancel() { cancel() {
console.log('Canceling the transaction') console.log('Canceling the transaction')
return this.replace({ return this.replace({
from: this.address, from: this.manager.address,
to: this.address, to: this.manager.address,
value: 0, value: 0,
gasLimit: 21000, gasLimit: 21000,
}) })
@ -123,16 +123,16 @@ class Transaction {
* @private * @private
*/ */
async _prepare() { async _prepare() {
if (!this.config.BLOCK_GAS_LIMIT) { if (!this.manager.config.BLOCK_GAS_LIMIT) {
const lastBlock = await this._provider.getBlock('latest') const lastBlock = await this.manager._provider.getBlock('latest')
this.config.BLOCK_GAS_LIMIT = Math.floor(lastBlock.gasLimit.toNumber() * 0.95) this.manager.config.BLOCK_GAS_LIMIT = Math.floor(lastBlock.gasLimit.toNumber() * 0.95)
} }
if (!this.tx.gasLimit || this.config.ESTIMATE_GAS) { if (!this.tx.gasLimit || this.manager.config.ESTIMATE_GAS) {
const gas = await this._wallet.estimateGas(this.tx) const gas = await this.manager._wallet.estimateGas(this.tx)
if (!this.tx.gasLimit) { if (!this.tx.gasLimit) {
const gasLimit = Math.floor(gas * this.config.GAS_LIMIT_MULTIPLIER) const gasLimit = Math.floor(gas * this.manager.config.GAS_LIMIT_MULTIPLIER)
this.tx.gasLimit = Math.min(gasLimit, this.config.BLOCK_GAS_LIMIT) this.tx.gasLimit = Math.min(gasLimit, this.manager.config.BLOCK_GAS_LIMIT)
} }
} }
@ -142,7 +142,7 @@ class Transaction {
this.tx.nonce = this.manager._nonce this.tx.nonce = this.manager._nonce
if (!this.manager._chainId) { if (!this.manager._chainId) {
const net = await this._provider.getNetwork() const net = await this.manager._provider.getNetwork()
this.manager._chainId = net.chainId this.manager._chainId = net.chainId
} }
this.tx.chainId = this.manager._chainId this.tx.chainId = this.manager._chainId
@ -164,7 +164,7 @@ class Transaction {
*/ */
async _send() { async _send() {
// todo throw is we attempt to send a tx that attempts to replace already mined tx // todo throw is we attempt to send a tx that attempts to replace already mined tx
const signedTx = await this._wallet.signTransaction(this.tx) const signedTx = await this.manager._wallet.signTransaction(this.tx)
this.submitTimestamp = Date.now() this.submitTimestamp = Date.now()
const txHash = ethers.utils.keccak256(signedTx) const txHash = ethers.utils.keccak256(signedTx)
this.hashes.push(txHash) this.hashes.push(txHash)
@ -190,7 +190,7 @@ class Transaction {
while (true) { while (true) {
// We are already waiting on certain tx hash // We are already waiting on certain tx hash
if (this.currentTxHash) { if (this.currentTxHash) {
const receipt = await this._provider.getTransactionReceipt(this.currentTxHash) const receipt = await this.manager._provider.getTransactionReceipt(this.currentTxHash)
if (!receipt) { if (!receipt) {
// We were waiting for some tx but it disappeared // We were waiting for some tx but it disappeared
@ -199,20 +199,20 @@ class Transaction {
continue continue
} }
const currentBlock = await this._provider.getBlockNumber() const currentBlock = await this.manager._provider.getBlockNumber()
const confirmations = Math.max(0, currentBlock - receipt.blockNumber) const confirmations = Math.max(0, currentBlock - receipt.blockNumber)
// todo don't emit repeating confirmation count // todo don't emit repeating confirmation count
this._emitter.emit('confirmations', confirmations) this._emitter.emit('confirmations', confirmations)
if (confirmations >= this.config.CONFIRMATIONS) { if (confirmations >= this.manager.config.CONFIRMATIONS) {
// Tx is mined and has enough confirmations // Tx is mined and has enough confirmations
if (this.config.THROW_ON_REVERT && Number(receipt.status) === 0) { if (this.manager.config.THROW_ON_REVERT && Number(receipt.status) === 0) {
throw new Error('EVM execution failed, so the transaction was reverted.') throw new Error('EVM execution failed, so the transaction was reverted.')
} }
return receipt return receipt
} }
// Tx is mined but doesn't have enough confirmations yet, keep waiting // Tx is mined but doesn't have enough confirmations yet, keep waiting
await sleep(this.config.POLL_INTERVAL) await sleep(this.manager.config.POLL_INTERVAL)
continue continue
} }
@ -221,7 +221,7 @@ class Transaction {
// todo optionally run estimateGas on each iteration and cancel the transaction if it fails // todo optionally run estimateGas on each iteration and cancel the transaction if it fails
// We were waiting too long, increase gas price and resubmit // We were waiting too long, increase gas price and resubmit
if (Date.now() - this.submitTimestamp >= this.config.GAS_BUMP_INTERVAL) { if (Date.now() - this.submitTimestamp >= this.manager.config.GAS_BUMP_INTERVAL) {
if (this._increaseGasPrice()) { if (this._increaseGasPrice()) {
console.log('Resubmitting with higher gas params') console.log('Resubmitting with higher gas params')
await this._send() await this._send()
@ -229,7 +229,7 @@ class Transaction {
} }
} }
// Tx is still pending, keep waiting // Tx is still pending, keep waiting
await sleep(this.config.POLL_INTERVAL) await sleep(this.manager.config.POLL_INTERVAL)
continue continue
} }
@ -267,7 +267,7 @@ class Transaction {
async _getReceipts() { async _getReceipts() {
for (const hash of this.hashes.reverse()) { for (const hash of this.hashes.reverse()) {
const receipt = await this._provider.getTransactionReceipt(hash) const receipt = await this.manager._provider.getTransactionReceipt(hash)
if (receipt) { if (receipt) {
return receipt return receipt
} }
@ -278,11 +278,11 @@ class Transaction {
/** /**
* Broadcasts tx to multiple nodes, waits for tx hash only on main node * Broadcasts tx to multiple nodes, waits for tx hash only on main node
*/ */
_broadcast(rawTx) { async _broadcast(rawTx) {
const main = this._provider.sendTransaction(rawTx) const main = await this.manager._provider.sendTransaction(rawTx)
for (const node of this._broadcastNodes) { for (const node of this.manager._broadcastNodes) {
try { try {
new ethers.providers.JsonRpcProvider(node).sendTransaction(rawTx) await new ethers.providers.JsonRpcProvider(node).sendTransaction(rawTx)
} catch (e) { } catch (e) {
console.log(`Failed to send transaction to node ${node}: ${e}`) console.log(`Failed to send transaction to node ${node}: ${e}`)
} }
@ -302,7 +302,7 @@ class Transaction {
// nonce is too low, trying to increase and resubmit // nonce is too low, trying to increase and resubmit
if (this._hasError(message, nonceErrors)) { if (this._hasError(message, nonceErrors)) {
console.log(`Nonce ${this.tx.nonce} is too low, increasing and retrying`) console.log(`Nonce ${this.tx.nonce} is too low, increasing and retrying`)
if (this.retries <= this.config.MAX_RETRIES) { if (this.retries <= this.manager.config.MAX_RETRIES) {
this.tx.nonce++ this.tx.nonce++
this.retries++ this.retries++
return this._send() return this._send()
@ -343,8 +343,8 @@ class Transaction {
} }
_increaseGasPrice() { _increaseGasPrice() {
const maxGasPrice = parseUnits(this.config.MAX_GAS_PRICE.toString(), 'gwei') const maxGasPrice = parseUnits(this.manager.config.MAX_GAS_PRICE.toString(), 'gwei')
const minGweiBump = parseUnits(this.config.MIN_GWEI_BUMP.toString(), 'gwei') const minGweiBump = parseUnits(this.manager.config.MIN_GWEI_BUMP.toString(), 'gwei')
if (this.tx.gasPrice) { if (this.tx.gasPrice) {
const oldGasPrice = BigNumber.from(this.tx.gasPrice) const oldGasPrice = BigNumber.from(this.tx.gasPrice)
@ -354,7 +354,7 @@ class Transaction {
} }
const newGasPrice = max( const newGasPrice = max(
oldGasPrice.mul(100 + this.config.GAS_BUMP_PERCENTAGE).div(100), oldGasPrice.mul(100 + this.manager.config.GAS_BUMP_PERCENTAGE).div(100),
oldGasPrice.add(minGweiBump), oldGasPrice.add(minGweiBump),
) )
this.tx.gasPrice = min(newGasPrice, maxGasPrice).toHexString() this.tx.gasPrice = min(newGasPrice, maxGasPrice).toHexString()
@ -368,11 +368,11 @@ class Transaction {
} }
const newMaxFeePerGas = max( const newMaxFeePerGas = max(
oldMaxFeePerGas.mul(100 + this.config.GAS_BUMP_PERCENTAGE).div(100), oldMaxFeePerGas.mul(100 + this.manager.config.GAS_BUMP_PERCENTAGE).div(100),
oldMaxFeePerGas.add(minGweiBump), oldMaxFeePerGas.add(minGweiBump),
) )
const newMaxPriorityFeePerGas = max( const newMaxPriorityFeePerGas = max(
oldMaxPriorityFeePerGas.mul(100 + this.config.GAS_BUMP_PERCENTAGE).div(100), oldMaxPriorityFeePerGas.mul(100 + this.manager.config.GAS_BUMP_PERCENTAGE).div(100),
oldMaxPriorityFeePerGas.add(minGweiBump), oldMaxPriorityFeePerGas.add(minGweiBump),
) )
@ -393,7 +393,7 @@ class Transaction {
* @private * @private
*/ */
async _getGasPrice(type) { async _getGasPrice(type) {
const gasPrices = await this._gasPriceOracle.gasPrices() const gasPrices = await this.manager._gasPriceOracle.gasPrices()
const result = gasPrices[type].toString() const result = gasPrices[type].toString()
console.log(`${type} gas price is now ${result} gwei`) console.log(`${type} gas price is now ${result} gwei`)
return parseUnits(result, 'gwei').toHexString() return parseUnits(result, 'gwei').toHexString()
@ -406,7 +406,7 @@ class Transaction {
* @private * @private
*/ */
_getLastNonce() { _getLastNonce() {
return this._wallet.getTransactionCount('latest') return this.manager._wallet.getTransactionCount('latest')
} }
/** /**
@ -416,14 +416,14 @@ class Transaction {
* @private * @private
*/ */
async _getGasParams() { async _getGasParams() {
const maxGasPrice = parseUnits(this.config.MAX_GAS_PRICE.toString(), 'gwei') const maxGasPrice = parseUnits(this.manager.config.MAX_GAS_PRICE.toString(), 'gwei')
const block = await this._provider.getBlock('latest') const block = await this.manager._provider.getBlock('latest')
// Check network support for EIP-1559 // Check network support for EIP-1559
if (block && block.baseFeePerGas) { if (block && block.baseFeePerGas) {
const maxPriorityFeePerGas = parseUnits(this.config.PRIORITY_FEE_GWEI.toString(), 'gwei') const maxPriorityFeePerGas = parseUnits(this.manager.config.PRIORITY_FEE_GWEI.toString(), 'gwei')
const maxFeePerGas = block.baseFeePerGas const maxFeePerGas = block.baseFeePerGas
.mul(100 + this.config.BASE_FEE_RESERVE_PERCENTAGE) .mul(100 + this.manager.config.BASE_FEE_RESERVE_PERCENTAGE)
.div(100) .div(100)
.add(maxPriorityFeePerGas) .add(maxPriorityFeePerGas)
return { return {

7
src/utils.d.ts vendored Normal file
View File

@ -0,0 +1,7 @@
import { BigNumberish } from 'ethers'
export function sleep(ms: number): Promise<any>
export function max(a: BigNumberish, b: BigNumberish): BigNumberish
export function min(a: BigNumberish, b: BigNumberish): BigNumberish

View File

@ -1,11 +1,12 @@
const { BigNumber } = require('ethers')
/** /**
* A promise that resolves after `ms` milliseconds * A promise that resolves after `ms` milliseconds
*/ */
const sleep = ms => new Promise(res => setTimeout(res, ms)) const sleep = ms => new Promise(res => setTimeout(res, ms))
const max = (a, b) => (a.gt(b) ? a : b) const max = (a, b) => (BigNumber.from(a).gt(b) ? a : b)
const min = (a, b) => (a.lt(b) ? a : b) const min = (a, b) => (BigNumber.from(a).lt(b) ? a : b)
module.exports = { module.exports = {
sleep, sleep,