Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37f6faa42d | ||
|
|
9599788224 | ||
|
|
72a665a19a | ||
|
|
f6a4e93a23 | ||
|
|
af7c597af9 | ||
|
|
221dce3d73 | ||
|
|
8d4bab7fc2 | ||
|
|
af65d78be9 | ||
|
|
bc0b369095 | ||
|
|
e1620e15c1 | ||
|
|
414fb28a5e | ||
|
|
780df01b43 | ||
|
|
c5e4d76dc5 | ||
|
|
8cb2bb0fbe | ||
|
|
6bb265d3b9 | ||
|
|
b940fad5e0 | ||
|
|
1634e5fb16 | ||
|
|
62bcd2aa95 | ||
|
|
1eec6aa329 | ||
|
|
715ad59273 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
node_modules
|
node_modules
|
||||||
|
.env
|
||||||
|
|||||||
10
package.json
10
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tx-manager",
|
"name": "tx-manager",
|
||||||
"version": "0.2.0",
|
"version": "0.3.2",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -13,15 +13,17 @@
|
|||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "Roman Semenov <semenov.roma@gmail.com>",
|
"author": "Roman Semenov <semenov.roma@gmail.com>",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"repository": "https://github.com/tornadocash/tx-manager",
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git://github.com/tornadocash/tx-manager.git"
|
||||||
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"src/*"
|
"src/*"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async-mutex": "^0.2.4",
|
"async-mutex": "^0.2.4",
|
||||||
"bn.js": "^5.1.3",
|
|
||||||
"ethers": "^5.0.17",
|
"ethers": "^5.0.17",
|
||||||
"gas-price-oracle": "^0.1.5",
|
"gas-price-oracle": "^0.3.4",
|
||||||
"web3-core-promievent": "^1.3.0"
|
"web3-core-promievent": "^1.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -4,21 +4,23 @@ const BigNumber = ethers.BigNumber
|
|||||||
const PromiEvent = require('web3-core-promievent')
|
const PromiEvent = require('web3-core-promievent')
|
||||||
const { sleep, min, max } = require('./utils')
|
const { sleep, min, max } = require('./utils')
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
const nonceErrors = [
|
const nonceErrors = [
|
||||||
'Transaction nonce is too low. Try incrementing the nonce.',
|
'Transaction nonce is too low. Try incrementing the nonce.',
|
||||||
'nonce too low'
|
'nonce too low',
|
||||||
|
'nonce has already been used',
|
||||||
]
|
]
|
||||||
|
|
||||||
const gasPriceErrors = [
|
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.',
|
'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',
|
'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./,
|
/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
|
// prettier-ignore
|
||||||
const sameTxErrors = [
|
const sameTxErrors = [
|
||||||
'Transaction with the same hash was already imported.',
|
'Transaction with the same hash was already imported.',
|
||||||
|
'already known',
|
||||||
]
|
]
|
||||||
|
|
||||||
class Transaction {
|
class Transaction {
|
||||||
@@ -64,6 +66,7 @@ class Transaction {
|
|||||||
if (!tx.gasLimit) {
|
if (!tx.gasLimit) {
|
||||||
tx.gasLimit = await this._wallet.estimateGas(tx)
|
tx.gasLimit = await this._wallet.estimateGas(tx)
|
||||||
tx.gasLimit = Math.floor(tx.gasLimit * this.config.GAS_LIMIT_MULTIPLIER)
|
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.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
|
tx.gasPrice = Math.max(this.tx.gasPrice, tx.gasPrice || 0) // start no less than current tx gas price
|
||||||
@@ -93,16 +96,16 @@ class Transaction {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
async _execute() {
|
async _execute() {
|
||||||
await this.manager._mutex.acquire()
|
const mutexRelease = await this.manager._mutex.acquire()
|
||||||
try {
|
try {
|
||||||
await this._prepare()
|
await this._prepare()
|
||||||
await this._send()
|
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
|
// we could have bumped nonce during execution, so get the latest one + 1
|
||||||
this.manager._nonce = this.tx.nonce + 1
|
this.manager._nonce = this.tx.nonce + 1
|
||||||
return receipt
|
return receipt
|
||||||
} finally {
|
} finally {
|
||||||
this.manager._mutex.release()
|
mutexRelease()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,14 +116,22 @@ class Transaction {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
async _prepare() {
|
async _prepare() {
|
||||||
|
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) {
|
if (!this.tx.gasLimit || this.config.ESTIMATE_GAS) {
|
||||||
const gas = await this._wallet.estimateGas(this.tx)
|
const gas = await this._wallet.estimateGas(this.tx)
|
||||||
if (!this.tx.gasLimit) {
|
if (!this.tx.gasLimit) {
|
||||||
this.tx.gasLimit = Math.floor(gas * this.config.GAS_LIMIT_MULTIPLIER)
|
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) {
|
if (!this.tx.gasPrice) {
|
||||||
this.tx.gasPrice = await this._getGasPrice('fast')
|
const fastGasPrice = BigNumber.from(await this._getGasPrice('fast'))
|
||||||
|
const maxGasPrice = parseUnits(this.config.MAX_GAS_PRICE.toString(), 'gwei')
|
||||||
|
this.tx.gasPrice = min(fastGasPrice, maxGasPrice).toHexString()
|
||||||
}
|
}
|
||||||
if (!this.manager._nonce) {
|
if (!this.manager._nonce) {
|
||||||
this.manager._nonce = await this._getLastNonce()
|
this.manager._nonce = await this._getLastNonce()
|
||||||
@@ -154,7 +165,6 @@ class Transaction {
|
|||||||
|
|
||||||
this._emitter.emit('transactionHash', txHash)
|
this._emitter.emit('transactionHash', txHash)
|
||||||
console.log(`Broadcasted transaction ${txHash}`)
|
console.log(`Broadcasted transaction ${txHash}`)
|
||||||
console.log(this.tx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -183,6 +193,9 @@ class Transaction {
|
|||||||
this._emitter.emit('confirmations', confirmations)
|
this._emitter.emit('confirmations', confirmations)
|
||||||
if (confirmations >= this.config.CONFIRMATIONS) {
|
if (confirmations >= this.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) {
|
||||||
|
throw new Error('EVM execution failed, so the transaction was reverted.')
|
||||||
|
}
|
||||||
return receipt
|
return receipt
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,7 +248,6 @@ class Transaction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Mined. Start waiting for confirmations...')
|
|
||||||
this._emitter.emit('mined', receipt)
|
this._emitter.emit('mined', receipt)
|
||||||
this.currentTxHash = receipt.transactionHash
|
this.currentTxHash = receipt.transactionHash
|
||||||
}
|
}
|
||||||
@@ -267,11 +279,13 @@ class Transaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_handleSendError(e) {
|
_handleSendError(e) {
|
||||||
console.log('Got error', e)
|
if (e.error.error) {
|
||||||
|
// Sometimes ethers wraps known errors, unwrap it in this case
|
||||||
|
e = e.error
|
||||||
|
}
|
||||||
|
|
||||||
if (e.code === 'SERVER_ERROR' && e.error) {
|
if (e.error && e.code === 'SERVER_ERROR') {
|
||||||
const message = e.error.message
|
const message = e.error.message
|
||||||
console.log('Error', e.error.code, e.error.message)
|
|
||||||
|
|
||||||
// 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)) {
|
||||||
@@ -288,8 +302,11 @@ class Transaction {
|
|||||||
console.log(
|
console.log(
|
||||||
`Gas price ${formatUnits(this.tx.gasPrice, 'gwei')} gwei is too low, increasing and retrying`,
|
`Gas price ${formatUnits(this.tx.gasPrice, 'gwei')} gwei is too low, increasing and retrying`,
|
||||||
)
|
)
|
||||||
this._increaseGasPrice()
|
if (this._increaseGasPrice()) {
|
||||||
return this._send()
|
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)) {
|
if (this._hasError(message, sameTxErrors)) {
|
||||||
@@ -314,17 +331,17 @@ class Transaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_increaseGasPrice() {
|
_increaseGasPrice() {
|
||||||
|
const maxGasPrice = parseUnits(this.config.MAX_GAS_PRICE.toString(), 'gwei')
|
||||||
const minGweiBump = parseUnits(this.config.MIN_GWEI_BUMP.toString(), 'gwei')
|
const minGweiBump = parseUnits(this.config.MIN_GWEI_BUMP.toString(), 'gwei')
|
||||||
const oldGasPrice = BigNumber.from(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(
|
const newGasPrice = max(
|
||||||
oldGasPrice.mul(100 + this.config.GAS_BUMP_PERCENTAGE).div(100),
|
oldGasPrice.mul(100 + this.config.GAS_BUMP_PERCENTAGE).div(100),
|
||||||
oldGasPrice.add(minGweiBump),
|
oldGasPrice.add(minGweiBump),
|
||||||
)
|
)
|
||||||
const maxGasPrice = parseUnits(this.config.MAX_GAS_PRICE.toString(), 'gwei')
|
|
||||||
if (oldGasPrice.eq(maxGasPrice)) {
|
|
||||||
console.log('Already at max gas price, not bumping')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
this.tx.gasPrice = min(newGasPrice, maxGasPrice).toHexString()
|
this.tx.gasPrice = min(newGasPrice, maxGasPrice).toHexString()
|
||||||
console.log(`Increasing gas price to ${formatUnits(this.tx.gasPrice, 'gwei')} gwei`)
|
console.log(`Increasing gas price to ${formatUnits(this.tx.gasPrice, 'gwei')} gwei`)
|
||||||
return true
|
return true
|
||||||
@@ -341,7 +358,7 @@ class Transaction {
|
|||||||
const gasPrices = await this._gasPriceOracle.gasPrices()
|
const gasPrices = await this._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(gasPrices[type], 'gwei').toHexString()
|
return parseUnits(result, 'gwei').toHexString()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -13,17 +13,19 @@ const defaultConfig = {
|
|||||||
POLL_INTERVAL: 5000,
|
POLL_INTERVAL: 5000,
|
||||||
CONFIRMATIONS: 8,
|
CONFIRMATIONS: 8,
|
||||||
ESTIMATE_GAS: true,
|
ESTIMATE_GAS: true,
|
||||||
|
THROW_ON_REVERT: true,
|
||||||
|
BLOCK_GAS_LIMIT: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
class TxManager {
|
class TxManager {
|
||||||
constructor({ privateKey, rpcUrl, broadcastNodes = [], config = {} }) {
|
constructor({ privateKey, rpcUrl, broadcastNodes = [], config = {}, gasPriceOracleConfig = {} }) {
|
||||||
this.config = Object.assign({ ...defaultConfig }, config)
|
this.config = Object.assign({ ...defaultConfig }, config)
|
||||||
this._privateKey = privateKey.startsWith('0x') ? privateKey : '0x' + privateKey
|
this._privateKey = privateKey.startsWith('0x') ? privateKey : '0x' + privateKey
|
||||||
this._provider = new ethers.providers.JsonRpcProvider(rpcUrl)
|
this._provider = new ethers.providers.JsonRpcProvider(rpcUrl)
|
||||||
this._wallet = new ethers.Wallet(this._privateKey, this._provider)
|
this._wallet = new ethers.Wallet(this._privateKey, this._provider)
|
||||||
this.address = this._wallet.address
|
this.address = this._wallet.address
|
||||||
this._broadcastNodes = broadcastNodes
|
this._broadcastNodes = broadcastNodes
|
||||||
this._gasPriceOracle = new GasPriceOracle({ defaultRpc: rpcUrl })
|
this._gasPriceOracle = new GasPriceOracle({ defaultRpc: rpcUrl, ...gasPriceOracleConfig })
|
||||||
this._mutex = new Mutex()
|
this._mutex = new Mutex()
|
||||||
this._nonce = null
|
this._nonce = null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
const BN = require('bn.js')
|
|
||||||
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) => BigNumber.from(BN.max(new BN(a.toString()), new BN(b.toString())).toString())
|
const max = (a, b) => (a.gt(b) ? a : b)
|
||||||
|
|
||||||
const min = (a, b) => BigNumber.from(BN.min(new BN(a.toString()), new BN(b.toString())).toString())
|
const min = (a, b) => (a.lt(b) ? a : b)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
sleep,
|
sleep,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ describe('TxManager', () => {
|
|||||||
privateKey: PRIVATE_KEY,
|
privateKey: PRIVATE_KEY,
|
||||||
rpcUrl: RPC_URL,
|
rpcUrl: RPC_URL,
|
||||||
config: {
|
config: {
|
||||||
CONFIRMATIONS: 3,
|
CONFIRMATIONS: 1,
|
||||||
GAS_BUMP_INTERVAL: 1000 * 15,
|
GAS_BUMP_INTERVAL: 1000 * 15,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -32,6 +32,11 @@ describe('TxManager', () => {
|
|||||||
to: '0x0039F22efB07A647557C7C5d17854CFD6D489eF3',
|
to: '0x0039F22efB07A647557C7C5d17854CFD6D489eF3',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tx4 = {
|
||||||
|
value: 1,
|
||||||
|
to: '0xA43Ce8Cc89Eff3AA5593c742fC56A30Ef2427CB0',
|
||||||
|
}
|
||||||
|
|
||||||
describe('#transaction', () => {
|
describe('#transaction', () => {
|
||||||
it('should work', async () => {
|
it('should work', async () => {
|
||||||
const tx = manager.createTx(tx1)
|
const tx = manager.createTx(tx1)
|
||||||
@@ -45,6 +50,18 @@ describe('TxManager', () => {
|
|||||||
console.log('receipt', receipt)
|
console.log('receipt', receipt)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should fetch gas price', 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 price', async () => {
|
it('should bump gas price', async () => {
|
||||||
const tx = manager.createTx(tx2)
|
const tx = manager.createTx(tx2)
|
||||||
|
|
||||||
@@ -84,5 +101,23 @@ describe('TxManager', () => {
|
|||||||
|
|
||||||
console.log('receipt', receipt)
|
console.log('receipt', receipt)
|
||||||
})
|
})
|
||||||
|
it.only('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(),
|
||||||
|
])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
13
yarn.lock
13
yarn.lock
@@ -593,11 +593,6 @@ bn.js@^4.4.0:
|
|||||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828"
|
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828"
|
||||||
integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==
|
integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==
|
||||||
|
|
||||||
bn.js@^5.1.3:
|
|
||||||
version "5.1.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.3.tgz#beca005408f642ebebea80b042b4d18d2ac0ee6b"
|
|
||||||
integrity sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==
|
|
||||||
|
|
||||||
brace-expansion@^1.1.7:
|
brace-expansion@^1.1.7:
|
||||||
version "1.1.11"
|
version "1.1.11"
|
||||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
||||||
@@ -1146,10 +1141,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"
|
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
|
||||||
integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
|
integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
|
||||||
|
|
||||||
gas-price-oracle@^0.1.5:
|
gas-price-oracle@^0.3.4:
|
||||||
version "0.1.5"
|
version "0.3.4"
|
||||||
resolved "https://registry.yarnpkg.com/gas-price-oracle/-/gas-price-oracle-0.1.5.tgz#09dd0d9806465c2f5e63b682e6742f96f6eb525c"
|
resolved "https://registry.yarnpkg.com/gas-price-oracle/-/gas-price-oracle-0.3.4.tgz#c98f6e24cf4bea96be2fe85383d53601b2ab5761"
|
||||||
integrity sha512-fkaTXnxJcSVco/tMPEcN5gieoUNs8O6JYMXflGLN2+3YeGZAucUI0fgCliazM3nRVAk//bBEm9819/Zb83xhrw==
|
integrity sha512-bba9zWd2hQ32AuPFDzL8E5QxAzXNfGKR1UZCa14fSYJWc9yo3k7wabITMkGEv0cbKpHmyK/kHhhDTBtugI6eAg==
|
||||||
dependencies:
|
dependencies:
|
||||||
axios "^0.19.2"
|
axios "^0.19.2"
|
||||||
bignumber.js "^9.0.0"
|
bignumber.js "^9.0.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user