new architecture (createTx); replace, cancel methods
This commit is contained in:
parent
6c514dc904
commit
d888fdbd44
@ -1,7 +1,7 @@
|
|||||||
const Web3 = require('web3')
|
const Web3 = require('web3')
|
||||||
const { Mutex } = require('async-mutex')
|
const { Mutex } = require('async-mutex')
|
||||||
const { GasPriceOracle } = require('gas-price-oracle')
|
const { GasPriceOracle } = require('gas-price-oracle')
|
||||||
const { toWei, toHex, toBN, BN } = require('web3-utils')
|
const { toWei, toHex, toBN, BN, fromWei } = require('web3-utils')
|
||||||
const PromiEvent = require('web3-core-promievent')
|
const PromiEvent = require('web3-core-promievent')
|
||||||
const { sleep, when } = require('./utils')
|
const { sleep, when } = require('./utils')
|
||||||
|
|
||||||
@ -18,6 +18,7 @@ const gasPriceErrors = [
|
|||||||
const defaultConfig = {
|
const defaultConfig = {
|
||||||
MAX_RETRIES: 10,
|
MAX_RETRIES: 10,
|
||||||
GAS_BUMP_PERCENTAGE: 5,
|
GAS_BUMP_PERCENTAGE: 5,
|
||||||
|
MIN_GWEI_BUMP: 1,
|
||||||
GAS_BUMP_INTERVAL: 1000 * 60 * 5,
|
GAS_BUMP_INTERVAL: 1000 * 60 * 5,
|
||||||
MAX_GAS_PRICE: 1000,
|
MAX_GAS_PRICE: 1000,
|
||||||
POLL_INTERVAL: 5000,
|
POLL_INTERVAL: 5000,
|
||||||
@ -39,47 +40,79 @@ class TxManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Submits transaction to Ethereum network. Resolves when tx gets enough confirmations.
|
* Creates Transaction class instance.
|
||||||
* Emits progress events.
|
|
||||||
*
|
*
|
||||||
* @param tx Transaction to send
|
* @param tx Transaction to send
|
||||||
*/
|
*/
|
||||||
submit(tx) {
|
async createTx(tx) {
|
||||||
const promiEvent = PromiEvent()
|
|
||||||
this._submit(tx, promiEvent.eventEmitter).then(promiEvent.resolve).catch(promiEvent.reject)
|
|
||||||
return promiEvent.eventEmitter
|
|
||||||
}
|
|
||||||
|
|
||||||
async _submit(tx, emitter) {
|
|
||||||
const release = await this._mutex.acquire()
|
|
||||||
try {
|
try {
|
||||||
|
await this._mutex.acquire()
|
||||||
if (!this.nonce) {
|
if (!this.nonce) {
|
||||||
this.nonce = await this._web3.eth.getTransactionCount(this.address, 'latest')
|
this.nonce = await this._web3.eth.getTransactionCount(this.address, 'latest')
|
||||||
}
|
}
|
||||||
return new Transaction(tx, emitter, this).submit()
|
return new Transaction(tx, this)
|
||||||
} finally {
|
} catch (e) {
|
||||||
release()
|
console.log('e', e)
|
||||||
|
this._mutex.release()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Transaction {
|
class Transaction {
|
||||||
constructor(tx, emitter, manager) {
|
constructor(tx, manager) {
|
||||||
Object.assign(this, manager)
|
Object.assign(this, manager)
|
||||||
this.manager = manager
|
this.manager = manager
|
||||||
this.tx = tx
|
this.tx = tx
|
||||||
this.emitter = emitter
|
this.promiReceipt = PromiEvent()
|
||||||
|
this.emitter = this.promiReceipt.eventEmitter
|
||||||
this.retries = 0
|
this.retries = 0
|
||||||
this.hash = null
|
|
||||||
// store all submitted hashes to catch cases when an old tx is mined
|
// store all submitted hashes to catch cases when an old tx is mined
|
||||||
// todo: what to do if old tx with the same nonce was submitted
|
// todo: what to do if old tx with the same nonce was submitted
|
||||||
// by other client and we don't have its hash?
|
// by other client and we don't have its hash?
|
||||||
this.hashes = []
|
this.hashes = []
|
||||||
}
|
}
|
||||||
|
|
||||||
async submit() {
|
/**
|
||||||
await this._prepare()
|
* Submits transaction to Ethereum network. Resolves when tx gets enough confirmations.
|
||||||
return this._send()
|
* Emits progress events.
|
||||||
|
*/
|
||||||
|
send() {
|
||||||
|
this._prepare()
|
||||||
|
.then(() => {
|
||||||
|
this._send()
|
||||||
|
.then((result) => this.promiReceipt.resolve(result))
|
||||||
|
.catch((e) => this.promiReceipt.reject(e))
|
||||||
|
})
|
||||||
|
.catch((e) => this.promiReceipt.reject(e))
|
||||||
|
.finally(this.manager._mutex.release())
|
||||||
|
|
||||||
|
return this.emitter
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces pending tx.
|
||||||
|
*
|
||||||
|
* @param tx Transaction to send
|
||||||
|
*/
|
||||||
|
replace(tx) {
|
||||||
|
// todo check if it's not mined yet
|
||||||
|
console.log('Replacing...')
|
||||||
|
this.tx = tx
|
||||||
|
return this.send()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels pending tx.
|
||||||
|
*/
|
||||||
|
cancel() {
|
||||||
|
// todo check if it's not mined yet
|
||||||
|
console.log('Canceling...')
|
||||||
|
this.tx = {
|
||||||
|
to: this.address,
|
||||||
|
gasPrice: this.tx.gasPrice,
|
||||||
|
}
|
||||||
|
this._increaseGasPrice()
|
||||||
|
return this.send()
|
||||||
}
|
}
|
||||||
|
|
||||||
async _prepare() {
|
async _prepare() {
|
||||||
@ -87,7 +120,7 @@ class Transaction {
|
|||||||
if (!this.tx.gasPrice) {
|
if (!this.tx.gasPrice) {
|
||||||
this.tx.gasPrice = await this._getGasPrice('fast')
|
this.tx.gasPrice = await this._getGasPrice('fast')
|
||||||
}
|
}
|
||||||
this.tx.nonce = this.nonce
|
this.tx.nonce = this.manager.nonce
|
||||||
}
|
}
|
||||||
|
|
||||||
async _send() {
|
async _send() {
|
||||||
@ -101,8 +134,8 @@ class Transaction {
|
|||||||
await this._broadcast(signedTx.rawTransaction)
|
await this._broadcast(signedTx.rawTransaction)
|
||||||
console.log('Broadcasted. Start waiting for mining...')
|
console.log('Broadcasted. Start waiting for mining...')
|
||||||
// The most reliable way to see if one of our tx was mined is to track current nonce
|
// The most reliable way to see if one of our tx was mined is to track current nonce
|
||||||
let latestNonce = await this._getLastNonce()
|
|
||||||
while (this.tx.nonce > latestNonce) {
|
while (this.tx.nonce >= (await this._getLastNonce())) {
|
||||||
if (Date.now() - this.tx.date >= this.config.GAS_BUMP_INTERVAL) {
|
if (Date.now() - this.tx.date >= this.config.GAS_BUMP_INTERVAL) {
|
||||||
if (this._increaseGasPrice()) {
|
if (this._increaseGasPrice()) {
|
||||||
console.log('Resubmit with higher gas price')
|
console.log('Resubmit with higher gas price')
|
||||||
@ -116,7 +149,6 @@ class Transaction {
|
|||||||
let receipt = await this._getReceipts()
|
let receipt = await this._getReceipts()
|
||||||
let retryAttempt = 5
|
let retryAttempt = 5
|
||||||
while (retryAttempt >= 0 && !receipt) {
|
while (retryAttempt >= 0 && !receipt) {
|
||||||
console.log('retryAttempt', retryAttempt)
|
|
||||||
await sleep(1000)
|
await sleep(1000)
|
||||||
receipt = await this._getReceipts()
|
receipt = await this._getReceipts()
|
||||||
retryAttempt--
|
retryAttempt--
|
||||||
@ -199,14 +231,19 @@ class Transaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_increaseGasPrice() {
|
_increaseGasPrice() {
|
||||||
const newGasPrice = toBN(this.tx.gasPrice).mul(toBN(this.config.GAS_BUMP_PERCENTAGE)).div(toBN(100))
|
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'))
|
const maxGasPrice = toBN(toWei(this.config.MAX_GAS_PRICE.toString(), 'gwei'))
|
||||||
if (toBN(this.tx.gasPrice).eq(maxGasPrice)) {
|
if (toBN(this.tx.gasPrice).eq(maxGasPrice)) {
|
||||||
console.log('Already at max gas price, not bumping')
|
console.log('Already at max gas price, not bumping')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
this.tx.gasPrice = toHex(BN.min(newGasPrice, maxGasPrice))
|
this.tx.gasPrice = toHex(BN.min(newGasPrice, maxGasPrice))
|
||||||
console.log(`Increasing gas price to ${this.tx.gasPrice}`)
|
console.log(`Increasing gas price to ${fromWei(this.tx.gasPrice, 'Gwei')}`)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,20 +6,27 @@ const TxM = new TxManager({
|
|||||||
privateKey,
|
privateKey,
|
||||||
rpcUrl,
|
rpcUrl,
|
||||||
config: {
|
config: {
|
||||||
CONFIRMATIONS: 2,
|
CONFIRMATIONS: 1,
|
||||||
GAS_BUMP_INTERVAL: 1000 * 15,
|
GAS_BUMP_INTERVAL: 1000 * 15,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const tx = {
|
const tx = {
|
||||||
from: '0x03Ebd0748Aa4D1457cF479cce56309641e0a98F5',
|
|
||||||
value: 0,
|
value: 0,
|
||||||
|
gasPrice: toHex(toWei('0.1', 'gwei')),
|
||||||
|
to: '0xA43Ce8Cc89Eff3AA5593c742fC56A30Ef2427CB0',
|
||||||
|
}
|
||||||
|
|
||||||
|
const tx2 = {
|
||||||
|
value: 1,
|
||||||
// gasPrice: toHex(toWei('0.1', 'gwei')),
|
// gasPrice: toHex(toWei('0.1', 'gwei')),
|
||||||
to: '0x03Ebd0748Aa4D1457cF479cce56309641e0a98F5',
|
to: '0x0039F22efB07A647557C7C5d17854CFD6D489eF3',
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const receipt = await TxM.submit(tx)
|
const Tx = await TxM.createTx(tx)
|
||||||
|
|
||||||
|
const receipt1 = await Tx.send()
|
||||||
.on('transactionHash', (hash) => {
|
.on('transactionHash', (hash) => {
|
||||||
console.log('hash', hash)
|
console.log('hash', hash)
|
||||||
})
|
})
|
||||||
@ -29,7 +36,22 @@ async function main() {
|
|||||||
.on('confirmations', (confirmations) => {
|
.on('confirmations', (confirmations) => {
|
||||||
console.log('confirmations', confirmations)
|
console.log('confirmations', confirmations)
|
||||||
})
|
})
|
||||||
console.log('receipt', receipt)
|
|
||||||
|
// setTimeout(async () => await Tx.cancel(), 800)
|
||||||
|
|
||||||
|
// const receipt2 = await Tx.replace(tx2)
|
||||||
|
// .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('receipt2', receipt2)
|
||||||
|
console.log('receipt1', await receipt1)
|
||||||
}
|
}
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
Loading…
Reference in New Issue
Block a user