29 Commits

Author SHA1 Message Date
poma
c6652baa7c inline _estimateFees() 2021-09-03 16:09:26 +03:00
Danil Kovtonyuk
a8e504054e fix: review 2021-09-03 22:58:13 +10:00
Danil Kovtonyuk
23e2e01172 feat: add EIP-1559 support 2021-09-03 22:58:13 +10:00
Danil Kovtonyuk
07752e0714 update gas price oracle 2021-08-25 22:35:05 +10:00
Danil Kovtonyuk
37f6faa42d bump gas price oracle 2021-08-17 17:23:54 +10:00
Danil Kovtonyuk
9599788224 bump gas price oracle 2021-06-15 13:31:41 +03:00
Danil Kovtonyuk
72a665a19a add gasPriceOracleConfig 2021-06-03 16:16:31 +03:00
Roman Storm
f6a4e93a23 fix mutex 2021-02-16 22:08:12 -08:00
Roman Storm
af7c597af9 Merge branch 'master' of github.com:tornadocash/tx-manager 2021-02-16 21:48:51 -08:00
Roman Storm
221dce3d73 add MAX_GAS_PRICE 2021-02-16 21:48:44 -08:00
poma
8d4bab7fc2 handle bump gas price error 2021-02-17 08:40:14 +03:00
poma
af65d78be9 fix await 2021-02-17 08:39:50 +03:00
Roman Storm
bc0b369095 fix gas price bump 2021-02-16 20:01:30 -08:00
Alexey Pertsev
e1620e15c1 Block gas limit (#1)
update gas-price-oracle dep, add BLOCK_GAS_LIMIT const
2020-12-24 08:39:07 +03:00
poma
414fb28a5e more general fix for tx error 2020-11-26 10:34:40 +03:00
Alexey
780df01b43 _handleSendError fix 2020-11-25 22:36:51 +01:00
Alexey
c5e4d76dc5 new 'nonce to low' error; remove console.log 2020-11-25 21:33:34 +01:00
Alexey
8cb2bb0fbe THROW_ON_REVERT feature 2020-11-19 20:33:58 +03:00
Alexey
6bb265d3b9 fix: throw error if transaction execution was failed 2020-11-19 14:37:18 +03:00
Alexey
b940fad5e0 remove an anoying console.log 2020-10-30 11:56:24 +03:00
poma
1634e5fb16 update gas price oracle 2020-10-20 09:39:28 +03:00
poma
62bcd2aa95 fix gas price 2020-10-20 09:28:59 +03:00
poma
1eec6aa329 simplify min and max 2020-10-17 14:37:22 +03:00
poma
715ad59273 fix repository 2020-10-17 05:29:46 +03:00
poma
c6344b40d5 add repository 2020-10-17 05:26:45 +03:00
poma
3b7d5ebd24 update version 2020-10-17 05:25:52 +03:00
poma
fcdaa2d52c remove web3-utils 2020-10-17 05:22:55 +03:00
poma
adf328f81c switch from web3 to ethers 2020-10-16 21:44:09 +03:00
poma
b0e25e800f fix package.json scripts 2020-10-15 02:30:51 +03:00
7 changed files with 673 additions and 2326 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
node_modules
.env

View File

@@ -1,27 +1,30 @@
{
"name": "tx-manager",
"version": "0.1.1",
"version": "0.4.1",
"description": "",
"main": "index.js",
"scripts": {
"eslint": "eslint --ext .js --ignore-path .gitignore .",
"prettier:check": "npx prettier --check . --config .prettierrc",
"prettier:fix": "npx prettier --write . --config .prettierrc",
"prettier:check": "prettier --check . --config .prettierrc",
"prettier:fix": "prettier --write . --config .prettierrc",
"lint": "yarn eslint && yarn prettier:check",
"test": "mocha --timeout 300000"
},
"keywords": [],
"author": "Roman Semenov <semenov.roma@gmail.com>",
"license": "ISC",
"repository": {
"type": "git",
"url": "git://github.com/tornadocash/tx-manager.git"
},
"files": [
"src/*"
],
"dependencies": {
"async-mutex": "^0.2.4",
"gas-price-oracle": "^0.1.5",
"web3": "^1.3.0",
"web3-core-promievent": "^1.3.0",
"web3-utils": "^1.3.0"
"ethers": "^5.4.6",
"gas-price-oracle": "^0.3.5",
"web3-core-promievent": "^1.3.0"
},
"devDependencies": {
"chai": "^4.2.0",

View File

@@ -1,22 +1,26 @@
const Web3 = require('web3')
const { toWei, toHex, toBN, BN, fromWei } = require('web3-utils')
const ethers = require('ethers')
const { parseUnits, formatUnits } = ethers.utils
const BigNumber = ethers.BigNumber
const PromiEvent = require('web3-core-promievent')
const { sleep, when } = require('./utils')
const { sleep, min, max } = require('./utils')
const nonceErrors = [
'Returned error: Transaction nonce is too low. Try incrementing the nonce.',
'Returned error: nonce too low',
'Transaction nonce is too low. Try incrementing the nonce.',
'nonce too low',
'nonce has already been used',
]
const gasPriceErrors = [
'Returned error: 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.',
'Returned error: replacement transaction underpriced',
/Returned error: 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./,
'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',
/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./,
]
// prettier-ignore
const sameTxErrors = [
'Returned error: Transaction with the same hash was already imported.',
'Transaction with the same hash was already imported.',
'already known',
]
class Transaction {
@@ -59,12 +63,20 @@ class Transaction {
this.tx = { ...tx }
return
}
if (!tx.gas) {
tx.gas = await this._web3.eth.estimateGas(tx)
tx.gas = Math.floor(tx.gas * 1.1)
if (!tx.gasLimit) {
tx.gasLimit = await this._wallet.estimateGas(tx)
tx.gasLimit = Math.floor(tx.gasLimit * this.config.GAS_LIMIT_MULTIPLIER)
tx.gasLimit = Math.min(tx.gasLimit, this.config.BLOCK_GAS_LIMIT)
}
tx.nonce = this.tx.nonce // can be different from `this.manager._nonce`
tx.gasPrice = Math.max(this.tx.gasPrice, tx.gasPrice || 0) // start no less than current tx gas price
// start no less than current tx gas params
if (this.tx.gasPrice) {
tx.gasPrice = Math.max(this.tx.gasPrice, tx.gasPrice || 0)
} else {
tx.maxFeePerGas = Math.max(this.tx.maxFeePerGas, tx.maxFeePerGas || 0)
tx.maxPriorityFeePerGas = Math.max(this.tx.maxPriorityFeePerGas, tx.maxPriorityFeePerGas || 0)
}
this.tx = { ...tx }
this._increaseGasPrice()
@@ -80,7 +92,7 @@ class Transaction {
from: this.address,
to: this.address,
value: 0,
gas: 21000,
gasLimit: 21000,
})
}
@@ -91,16 +103,16 @@ class Transaction {
* @private
*/
async _execute() {
await this.manager._mutex.acquire()
const mutexRelease = await this.manager._mutex.acquire()
try {
await this._prepare()
await this._send()
const receipt = this._waitForConfirmations()
const receipt = await this._waitForConfirmations()
// we could have bumped nonce during execution, so get the latest one + 1
this.manager._nonce = this.tx.nonce + 1
return receipt
} finally {
this.manager._mutex.release()
mutexRelease()
}
}
@@ -111,19 +123,37 @@ class Transaction {
* @private
*/
async _prepare() {
if (!this.tx.gas || this.config.ESTIMATE_GAS) {
const gas = await this._web3.eth.estimateGas(this.tx)
if (!this.tx.gas) {
this.tx.gas = Math.floor(gas * 1.1)
if (!this.config.BLOCK_GAS_LIMIT) {
const lastBlock = await this._provider.getBlock('latest')
this.config.BLOCK_GAS_LIMIT = Math.floor(lastBlock.gasLimit.toNumber() * 0.95)
}
if (!this.tx.gasLimit || this.config.ESTIMATE_GAS) {
const gas = await this._wallet.estimateGas(this.tx)
if (!this.tx.gasLimit) {
const gasLimit = Math.floor(gas * this.config.GAS_LIMIT_MULTIPLIER)
this.tx.gasLimit = Math.min(gasLimit, this.config.BLOCK_GAS_LIMIT)
}
}
if (!this.tx.gasPrice) {
this.tx.gasPrice = await this._getGasPrice('fast')
}
if (!this.manager._nonce) {
this.manager._nonce = await this._web3.eth.getTransactionCount(this.address, 'latest')
this.manager._nonce = await this._getLastNonce()
}
this.tx.nonce = this.manager._nonce
if (!this.manager._chainId) {
const net = await this._provider.getNetwork()
this.manager._chainId = net.chainId
}
this.tx.chainId = this.manager._chainId
if (this.tx.gasPrice || (this.tx.maxFeePerGas && this.tx.maxPriorityFeePerGas)) {
return
}
const gasParams = await this._getGasParams()
this.tx = Object.assign(this.tx, gasParams)
}
/**
@@ -134,20 +164,19 @@ class Transaction {
*/
async _send() {
// todo throw is we attempt to send a tx that attempts to replace already mined tx
const signedTx = await this._web3.eth.accounts.signTransaction(this.tx, this._privateKey)
const signedTx = await this._wallet.signTransaction(this.tx)
this.submitTimestamp = Date.now()
this.tx.hash = signedTx.transactionHash
this.hashes.push(signedTx.transactionHash)
const txHash = ethers.utils.keccak256(signedTx)
this.hashes.push(txHash)
try {
await this._broadcast(signedTx.rawTransaction)
await this._broadcast(signedTx)
} catch (e) {
return this._handleSendError(e)
}
this._emitter.emit('transactionHash', signedTx.transactionHash)
console.log(`Broadcasted transaction ${signedTx.transactionHash}`)
console.log(this.tx)
this._emitter.emit('transactionHash', txHash)
console.log(`Broadcasted transaction ${txHash}`)
}
/**
@@ -161,7 +190,7 @@ class Transaction {
while (true) {
// We are already waiting on certain tx hash
if (this.currentTxHash) {
const receipt = await this._web3.eth.getTransactionReceipt(this.currentTxHash)
const receipt = await this._provider.getTransactionReceipt(this.currentTxHash)
if (!receipt) {
// We were waiting for some tx but it disappeared
@@ -170,12 +199,15 @@ class Transaction {
continue
}
const currentBlock = await this._web3.eth.getBlockNumber()
const currentBlock = await this._provider.getBlockNumber()
const confirmations = Math.max(0, currentBlock - receipt.blockNumber)
// todo don't emit repeating confirmation count
this._emitter.emit('confirmations', confirmations)
if (confirmations >= this.config.CONFIRMATIONS) {
// Tx is mined and has enough confirmations
if (this.config.THROW_ON_REVERT && Number(receipt.status) === 0) {
throw new Error('EVM execution failed, so the transaction was reverted.')
}
return receipt
}
@@ -191,7 +223,7 @@ class Transaction {
// We were waiting too long, increase gas price and resubmit
if (Date.now() - this.submitTimestamp >= this.config.GAS_BUMP_INTERVAL) {
if (this._increaseGasPrice()) {
console.log('Resubmitting with higher gas price')
console.log('Resubmitting with higher gas params')
await this._send()
continue
}
@@ -228,7 +260,6 @@ class Transaction {
}
}
console.log('Mined. Start waiting for confirmations...')
this._emitter.emit('mined', receipt)
this.currentTxHash = receipt.transactionHash
}
@@ -236,7 +267,7 @@ class Transaction {
async _getReceipts() {
for (const hash of this.hashes.reverse()) {
const receipt = await this._web3.eth.getTransactionReceipt(hash)
const receipt = await this._provider.getTransactionReceipt(hash)
if (receipt) {
return receipt
}
@@ -248,43 +279,55 @@ class Transaction {
* Broadcasts tx to multiple nodes, waits for tx hash only on main node
*/
_broadcast(rawTx) {
const main = this._web3.eth.sendSignedTransaction(rawTx)
const main = this._provider.sendTransaction(rawTx)
for (const node of this._broadcastNodes) {
try {
new Web3(node).eth.sendSignedTransaction(rawTx)
new ethers.providers.JsonRpcProvider(node).sendTransaction(rawTx)
} catch (e) {
console.log(`Failed to send transaction to node ${node}: ${e}`)
}
}
return when(main, 'transactionHash')
return main
}
_handleSendError(e) {
console.log('Got error', e)
if (e.error.error) {
// Sometimes ethers wraps known errors, unwrap it in this case
e = e.error
}
// nonce is too low, trying to increase and resubmit
if (this._hasError(e.message, nonceErrors)) {
console.log(`Nonce ${this.tx.nonce} is too low, increasing and retrying`)
if (this.retries <= this.config.MAX_RETRIES) {
this.tx.nonce++
this.retries++
return this._send()
if (e.error && e.code === 'SERVER_ERROR') {
const message = e.error.message
// nonce is too low, trying to increase and resubmit
if (this._hasError(message, nonceErrors)) {
console.log(`Nonce ${this.tx.nonce} is too low, increasing and retrying`)
if (this.retries <= this.config.MAX_RETRIES) {
this.tx.nonce++
this.retries++
return this._send()
}
}
// 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`,
)
if (this._increaseGasPrice()) {
return this._send()
} else {
throw new Error('Already at max gas price, but still not enough to submit the transaction')
}
}
if (this._hasError(message, sameTxErrors)) {
console.log('Same transaction is already in mempool, skipping submit')
return // do nothing
}
}
// there is already a pending tx with higher gas price, trying to bump and resubmit
if (this._hasError(e.message, gasPriceErrors)) {
console.log(`Gas price ${fromWei(this.tx.gasPrice, 'gwei')} gwei is too low, increasing and retrying`)
this._increaseGasPrice()
return this._send()
}
if (this._hasError(e.message, sameTxErrors)) {
console.log('Same transaction is already in mempool, skipping submit')
return // do nothing
}
throw new Error(`Send error: ${e.message}`)
throw new Error(`Send error: ${e}`)
}
/**
@@ -300,19 +343,45 @@ class Transaction {
}
_increaseGasPrice() {
const minGweiBump = toBN(toWei(this.config.MIN_GWEI_BUMP.toString(), 'Gwei'))
const oldGasPrice = toBN(this.tx.gasPrice)
const newGasPrice = BN.max(
oldGasPrice.mul(toBN(100 + this.config.GAS_BUMP_PERCENTAGE)).div(toBN(100)),
oldGasPrice.add(minGweiBump),
)
const maxGasPrice = toBN(toWei(this.config.MAX_GAS_PRICE.toString(), 'gwei'))
if (toBN(this.tx.gasPrice).eq(maxGasPrice)) {
console.log('Already at max gas price, not bumping')
return false
const maxGasPrice = parseUnits(this.config.MAX_GAS_PRICE.toString(), 'gwei')
const minGweiBump = parseUnits(this.config.MIN_GWEI_BUMP.toString(), 'gwei')
if (this.tx.gasPrice) {
const oldGasPrice = BigNumber.from(this.tx.gasPrice)
if (oldGasPrice.gte(maxGasPrice)) {
console.log('Already at max gas price, not bumping')
return false
}
const newGasPrice = max(
oldGasPrice.mul(100 + this.config.GAS_BUMP_PERCENTAGE).div(100),
oldGasPrice.add(minGweiBump),
)
this.tx.gasPrice = min(newGasPrice, maxGasPrice).toHexString()
console.log(`Increasing gas price to ${formatUnits(this.tx.gasPrice, 'gwei')} gwei`)
} else {
const oldMaxFeePerGas = BigNumber.from(this.tx.maxFeePerGas)
const oldMaxPriorityFeePerGas = BigNumber.from(this.tx.maxPriorityFeePerGas)
if (oldMaxFeePerGas.gte(maxGasPrice)) {
console.log('Already at max fee per gas, not bumping')
return false
}
const newMaxFeePerGas = max(
oldMaxFeePerGas.mul(100 + this.config.GAS_BUMP_PERCENTAGE).div(100),
oldMaxFeePerGas.add(minGweiBump),
)
const newMaxPriorityFeePerGas = max(
oldMaxPriorityFeePerGas.mul(100 + this.config.GAS_BUMP_PERCENTAGE).div(100),
oldMaxPriorityFeePerGas.add(minGweiBump),
)
this.tx.maxFeePerGas = min(newMaxFeePerGas, maxGasPrice).toHexString()
this.tx.maxPriorityFeePerGas = min(newMaxPriorityFeePerGas, this.tx.maxFeePerGas).toHexString()
console.log(`Increasing maxFeePerGas to ${formatUnits(this.tx.maxFeePerGas, 'gwei')} gwei`)
}
this.tx.gasPrice = toHex(BN.min(newGasPrice, maxGasPrice))
console.log(`Increasing gas price to ${fromWei(this.tx.gasPrice, 'gwei')} gwei`)
return true
}
@@ -327,7 +396,7 @@ class Transaction {
const gasPrices = await this._gasPriceOracle.gasPrices()
const result = gasPrices[type].toString()
console.log(`${type} gas price is now ${result} gwei`)
return toHex(toWei(gasPrices[type].toString(), 'gwei'))
return parseUnits(result, 'gwei').toHexString()
}
/**
@@ -337,7 +406,38 @@ class Transaction {
* @private
*/
_getLastNonce() {
return this._web3.eth.getTransactionCount(this.address, 'latest')
return this._wallet.getTransactionCount('latest')
}
/**
* Choose network gas params
*
* @returns {Promise<object>}
* @private
*/
async _getGasParams() {
const maxGasPrice = parseUnits(this.config.MAX_GAS_PRICE.toString(), 'gwei')
const block = await this._provider.getBlock('latest')
// Check network support for EIP-1559
if (block && block.baseFeePerGas) {
const maxPriorityFeePerGas = parseUnits(this.config.PRIORITY_FEE_GWEI.toString(), 'gwei')
const maxFeePerGas = block.baseFeePerGas
.mul(100 + this.config.BASE_FEE_RESERVE_PERCENTAGE)
.div(100)
.add(maxPriorityFeePerGas)
return {
maxFeePerGas: min(maxFeePerGas, maxGasPrice).toHexString(),
maxPriorityFeePerGas: min(maxPriorityFeePerGas, maxGasPrice).toHexString(),
type: 2,
}
} else {
const fastGasPrice = BigNumber.from(await this._getGasPrice('fast'))
return {
gasPrice: min(fastGasPrice, maxGasPrice).toHexString(),
type: 0,
}
}
}
}

View File

@@ -1,4 +1,4 @@
const Web3 = require('web3')
const ethers = require('ethers')
const { Mutex } = require('async-mutex')
const { GasPriceOracle } = require('gas-price-oracle')
const Transaction = require('./Transaction')
@@ -9,21 +9,25 @@ const defaultConfig = {
MIN_GWEI_BUMP: 1,
GAS_BUMP_INTERVAL: 1000 * 60 * 5,
MAX_GAS_PRICE: 1000,
GAS_LIMIT_MULTIPLIER: 1.1,
POLL_INTERVAL: 5000,
CONFIRMATIONS: 8,
ESTIMATE_GAS: true,
THROW_ON_REVERT: true,
BLOCK_GAS_LIMIT: null,
PRIORITY_FEE_GWEI: 3,
BASE_FEE_RESERVE_PERCENTAGE: 50,
}
class TxManager {
constructor({ privateKey, rpcUrl, broadcastNodes = [], config = {} }) {
constructor({ privateKey, rpcUrl, broadcastNodes = [], config = {}, gasPriceOracleConfig = {} }) {
this.config = Object.assign({ ...defaultConfig }, config)
this._privateKey = '0x' + privateKey
this._web3 = new Web3(rpcUrl)
this._privateKey = privateKey.startsWith('0x') ? privateKey : '0x' + privateKey
this._provider = new ethers.providers.JsonRpcProvider(rpcUrl)
this._wallet = new ethers.Wallet(this._privateKey, this._provider)
this.address = this._wallet.address
this._broadcastNodes = broadcastNodes
this.address = this._web3.eth.accounts.privateKeyToAccount(this._privateKey).address
this._web3.eth.accounts.wallet.add(this._privateKey)
this._web3.eth.defaultAccount = this.address
this._gasPriceOracle = new GasPriceOracle({ defaultRpc: rpcUrl })
this._gasPriceOracle = new GasPriceOracle({ defaultRpc: rpcUrl, ...gasPriceOracleConfig })
this._mutex = new Mutex()
this._nonce = null
}

View File

@@ -3,13 +3,12 @@
*/
const sleep = ms => new Promise(res => setTimeout(res, ms))
/**
* A promise that resolves when the source emits specified event
*/
const when = (source, event) =>
new Promise((resolve, reject) => source.once(event, resolve).on('error', reject))
const max = (a, b) => (a.gt(b) ? a : b)
const min = (a, b) => (a.lt(b) ? a : b)
module.exports = {
sleep,
when,
max,
min,
}

View File

@@ -1,6 +1,6 @@
require('dotenv').config()
require('chai').should()
const { toHex, toWei } = require('web3-utils')
const { parseUnits } = require('ethers').utils
const TxManager = require('../src/TxManager')
// const Transaction = require('../src/Transaction')
const { RPC_URL, PRIVATE_KEY } = process.env
@@ -10,24 +10,43 @@ describe('TxManager', () => {
privateKey: PRIVATE_KEY,
rpcUrl: RPC_URL,
config: {
CONFIRMATIONS: 3,
GAS_BUMP_INTERVAL: 1000 * 15,
CONFIRMATIONS: 1,
GAS_BUMP_INTERVAL: 1000 * 20,
},
})
const tx1 = {
value: 1,
gasPrice: toHex(toWei('0.5', 'gwei')),
gasPrice: parseUnits('2', 'gwei').toHexString(),
to: '0xA43Ce8Cc89Eff3AA5593c742fC56A30Ef2427CB0',
}
const tx2 = {
value: 1,
gasPrice: parseUnits('0.5', 'gwei').toHexString(),
to: '0xA43Ce8Cc89Eff3AA5593c742fC56A30Ef2427CB0',
}
const tx3 = {
value: 2,
to: '0x0039F22efB07A647557C7C5d17854CFD6D489eF3',
}
const tx4 = {
value: 1,
to: '0xA43Ce8Cc89Eff3AA5593c742fC56A30Ef2427CB0',
}
const tx5 = {
value: 1,
to: '0xA43Ce8Cc89Eff3AA5593c742fC56A30Ef2427CB0',
maxFeePerGas: parseUnits('7', 'gwei').toHexString(),
maxPriorityFeePerGas: parseUnits('1', 'gwei').toHexString(),
type: 2,
}
describe('#transaction', () => {
it('should work', async () => {
it('should work legacy tx', async () => {
const tx = manager.createTx(tx1)
const receipt = await tx
@@ -39,8 +58,44 @@ describe('TxManager', () => {
console.log('receipt', receipt)
})
it('should work eip-1559 tx', async () => {
const tx = manager.createTx(tx5)
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 fetch gas params', async () => {
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 bump gas params', async () => {
const tx = manager.createTx(tx2)
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 cancel', async () => {
const tx = manager.createTx(tx1)
const tx = manager.createTx(tx2)
setTimeout(() => tx.cancel(), 1000)
@@ -54,9 +109,9 @@ describe('TxManager', () => {
})
it('should replace', async () => {
const tx = manager.createTx(tx1)
const tx = manager.createTx(tx2)
setTimeout(() => tx.replace(tx2), 1000)
setTimeout(() => tx.replace(tx3), 1000)
const receipt = await tx
.send()
@@ -66,5 +121,24 @@ describe('TxManager', () => {
console.log('receipt', receipt)
})
it('should send multiple txs', async () => {
const genTx = value => ({
value,
to: '0x0039F22efB07A647557C7C5d17854CFD6D489eF3',
})
await Promise.all([
manager.createTx(genTx(1)).send(),
manager.createTx(genTx(2)).send(),
manager.createTx(genTx(3)).send(),
manager.createTx(genTx(4)).send(),
manager.createTx(genTx(5)).send(),
manager.createTx(genTx(6)).send(),
manager.createTx(genTx(7)).send(),
manager.createTx(genTx(8)).send(),
manager.createTx(genTx(9)).send(),
manager.createTx(genTx(10)).send(),
])
})
})
})

2610
yarn.lock

File diff suppressed because it is too large Load Diff