Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 05d758a01e | |||
| 8637327994 | |||
|
|
4861f36c56 | ||
|
|
06b380b488 |
27
README.md
27
README.md
@ -8,12 +8,11 @@ This is a library with a collection of onchain and offchain gas price oracle URL
|
||||
|
||||
Current offchain list:
|
||||
|
||||
- https://ethgasstation.info/json/ethgasAPI.json
|
||||
- https://etherchain.org/api/gasnow
|
||||
|
||||
Current onchain list:
|
||||
|
||||
- [chainlink](https://etherscan.io/address/0x169e633a2d1e6c10dd91238ba11c4a708dfef37c#readContract)
|
||||
- [Chainlink aggregator](https://etherscan.io/address/0x169e633a2d1e6c10dd91238ba11c4a708dfef37c#readContract)
|
||||
|
||||
### Binance Smart Chain
|
||||
|
||||
@ -25,13 +24,14 @@ Current offchain list:
|
||||
|
||||
Current offchain list:
|
||||
|
||||
- https://blockscout.com/xdai/mainnet/api/v1/gas-price-oracle
|
||||
- https://gnosis.blockscout.com/api/v1/gas-price-oracle
|
||||
|
||||
### Polygon (Matic) Network
|
||||
|
||||
Current offchain list:
|
||||
|
||||
- https://gasstation-mainnet.matic.network/
|
||||
- https://gasstation.polygon.technology/v2
|
||||
- https://matic-gas-station.tornado.ws
|
||||
|
||||
### Avalanche C Network
|
||||
|
||||
@ -77,6 +77,8 @@ type GasOracleOptions = {
|
||||
defaultRpc?: string
|
||||
blocksCount?: number
|
||||
percentile?: number
|
||||
blockTime?: number // seconds
|
||||
shouldCache?: boolean
|
||||
fallbackGasPrices?: FallbackGasPrices
|
||||
}
|
||||
|
||||
@ -85,7 +87,10 @@ const options: GasOracleOptions = {
|
||||
percentile: 5, // Which percentile of effective priority fees to include
|
||||
blocksCount: 10, // How many blocks to consider for priority fee estimation
|
||||
defaultRpc: 'https://api.mycryptoapi.com/eth',
|
||||
blockTime: 10, // seconds
|
||||
shouldCache: false,
|
||||
timeout: 10000, // specifies the number of milliseconds before the request times out.
|
||||
minPriority: 0, // specifies the min maxPriorityFeePerGas.
|
||||
fallbackGasPrices: {
|
||||
gasPrices: {
|
||||
instant: 28,
|
||||
@ -101,6 +106,14 @@ const options: GasOracleOptions = {
|
||||
}
|
||||
```
|
||||
|
||||
### The Oracle can cache rpc calls
|
||||
|
||||
For caching needs to provide to GasOracleOptions
|
||||
|
||||
`shouldCache: true`
|
||||
|
||||
`blockTime: <Chain block time duration>`
|
||||
|
||||
### EIP-1559 (estimated) gasPrice only
|
||||
|
||||
```typescript
|
||||
@ -239,7 +252,9 @@ type GetTxGasParamsRes =
|
||||
}
|
||||
|
||||
const gasParams: GetTxGasParamsRes = await oracle.getTxGasParams({ legacySpeed: 'fast', bumpPercent: 30 })
|
||||
console.log(gasParams) // { maxFeePerGas: 17, maxPriorityFeePerGas: 3 } || { gasPrice: 19 }
|
||||
console.log(gasParams)
|
||||
// { maxFeePerGas: '3f5476a00', maxPriorityFeePerGas: 'b2d05e00' } || { gasPrice: '46c7cfe00' }
|
||||
// equal to: { maxFeePerGas: 17 gwei, maxPriorityFeePerGas: 3 gwei } || { gasPrice: 19 gwei }
|
||||
|
||||
web3.eth.sendTransaction({
|
||||
from: '0xEA674fdDe714fd979de3EdF0F56AA9716B898ec8',
|
||||
@ -254,6 +269,8 @@ web3.eth.sendTransaction({
|
||||
`bumpPercent` argument (`0` by default) - response data will increase by `bumpPercent`%.
|
||||
`legacySpeed` argument (`fast` by default) - select the speed of legacy gasPrice.
|
||||
|
||||
Returns gas info in `wei`, hex-format.
|
||||
|
||||
### Offchain oracles only
|
||||
|
||||
```typescript
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "gas-price-oracle",
|
||||
"version": "0.5.0",
|
||||
"version": "0.5.3",
|
||||
"description": "Gas Price Oracle library for Ethereum dApps.",
|
||||
"homepage": "https://github.com/peppersec/gas-price-oracle",
|
||||
"homepage": "https://git.tornado.ws/tornado-packages/gas-price-oracle",
|
||||
"main": "./lib/index.js",
|
||||
"module": "./lib/esm/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/peppersec/gas-price-oracle.git"
|
||||
"url": "https://git.tornado.ws/tornado-packages/gas-price-oracle"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "ts-mocha --timeout 30000 --paths 'src/tests/*.test.ts'",
|
||||
@ -57,7 +57,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.21.2",
|
||||
"bignumber.js": "^9.0.0"
|
||||
"bignumber.js": "^9.0.0",
|
||||
"node-cache": "^5.1.2"
|
||||
},
|
||||
"files": [
|
||||
"lib/**/*"
|
||||
|
||||
@ -1,16 +1,5 @@
|
||||
import { OffChainOracle, OffChainOracles, OnChainOracle, OnChainOracles } from '@/services'
|
||||
|
||||
const ethgasstation: OffChainOracle = {
|
||||
name: 'ethgasstation',
|
||||
url: 'https://ethgasstation.info/json/ethgasAPI.json',
|
||||
instantPropertyName: 'fastest',
|
||||
fastPropertyName: 'fast',
|
||||
standardPropertyName: 'average',
|
||||
lowPropertyName: 'safeLow',
|
||||
denominator: 10,
|
||||
additionalDataProperty: null,
|
||||
}
|
||||
|
||||
const etherchain: OffChainOracle = {
|
||||
name: 'etherchain',
|
||||
url: 'https://etherchain.org/api/gasnow',
|
||||
@ -30,7 +19,6 @@ const chainlink: OnChainOracle = {
|
||||
}
|
||||
|
||||
export const offChainOracles: OffChainOracles = {
|
||||
ethgasstation,
|
||||
etherchain,
|
||||
}
|
||||
|
||||
|
||||
@ -2,10 +2,21 @@ import { OffChainOracle, OffChainOracles, OnChainOracles } from '@/services'
|
||||
|
||||
const maticGasStation: OffChainOracle = {
|
||||
name: 'maticGasStation',
|
||||
url: 'https://gasstation-mainnet.matic.network',
|
||||
instantPropertyName: 'fastest',
|
||||
fastPropertyName: 'fast',
|
||||
standardPropertyName: 'standard',
|
||||
url: 'https://gasstation.polygon.technology/v2',
|
||||
instantPropertyName: 'fast.maxFee',
|
||||
fastPropertyName: 'fast.maxFee',
|
||||
standardPropertyName: 'standard.maxFee',
|
||||
lowPropertyName: 'safeLow.maxFee',
|
||||
denominator: 1,
|
||||
additionalDataProperty: null,
|
||||
}
|
||||
|
||||
const tornadoMaticGasStation: OffChainOracle = {
|
||||
name: 'tornadoMGasStation',
|
||||
url: 'https://matic-gas-station.tornado.ws',
|
||||
instantPropertyName: 'standard',
|
||||
fastPropertyName: 'standard',
|
||||
standardPropertyName: 'safeLow',
|
||||
lowPropertyName: 'safeLow',
|
||||
denominator: 1,
|
||||
additionalDataProperty: null,
|
||||
@ -13,6 +24,7 @@ const maticGasStation: OffChainOracle = {
|
||||
|
||||
export const offChainOracles: OffChainOracles = {
|
||||
maticGasStation,
|
||||
tornadoMaticGasStation,
|
||||
}
|
||||
|
||||
export const onChainOracles: OnChainOracles = {}
|
||||
|
||||
@ -2,7 +2,7 @@ import { OffChainOracle, OffChainOracles, OnChainOracles } from '@/services'
|
||||
|
||||
const blockscout: OffChainOracle = {
|
||||
name: 'blockscout',
|
||||
url: 'https://blockscout.com/xdai/mainnet/api/v1/gas-price-oracle',
|
||||
url: 'https://gnosis.blockscout.com/api/v1/gas-price-oracle',
|
||||
instantPropertyName: 'fast',
|
||||
fastPropertyName: 'average',
|
||||
standardPropertyName: 'slow',
|
||||
|
||||
@ -11,4 +11,17 @@ const INT_PRECISION = 0
|
||||
const SUCCESS_STATUS = 200
|
||||
const BG_ZERO = new BigNumber(0)
|
||||
const PERCENT_MULTIPLIER = 100
|
||||
export { GWEI, DEFAULT_TIMEOUT, ROUND_UP, ROUND_DOWN, GWEI_PRECISION, INT_PRECISION, SUCCESS_STATUS, BG_ZERO, PERCENT_MULTIPLIER }
|
||||
const DEFAULT_BLOCK_DURATION = 10
|
||||
|
||||
export {
|
||||
GWEI,
|
||||
DEFAULT_TIMEOUT,
|
||||
ROUND_UP,
|
||||
ROUND_DOWN,
|
||||
GWEI_PRECISION,
|
||||
INT_PRECISION,
|
||||
SUCCESS_STATUS,
|
||||
BG_ZERO,
|
||||
PERCENT_MULTIPLIER,
|
||||
DEFAULT_BLOCK_DURATION,
|
||||
}
|
||||
|
||||
20
src/services/cacher/cacheNode.ts
Normal file
20
src/services/cacher/cacheNode.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import NodeCache, { Options } from 'node-cache'
|
||||
|
||||
export class NodeJSCache<T> {
|
||||
private nodeCache: NodeCache
|
||||
constructor(params: Options) {
|
||||
this.nodeCache = new NodeCache(params)
|
||||
}
|
||||
|
||||
async get(key: string): Promise<T | undefined> {
|
||||
return await this.nodeCache.get<T>(key)
|
||||
}
|
||||
|
||||
async set(key: string, value: T): Promise<boolean> {
|
||||
return await this.nodeCache.set(key, value)
|
||||
}
|
||||
|
||||
async has(key: string): Promise<boolean> {
|
||||
return await this.nodeCache.has(key)
|
||||
}
|
||||
}
|
||||
1
src/services/cacher/index.ts
Normal file
1
src/services/cacher/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './cacheNode'
|
||||
@ -3,23 +3,29 @@ import BigNumber from 'bignumber.js'
|
||||
import { FeeHistory, Block } from '@/types'
|
||||
import { Config, EstimateOracle, EstimatedGasPrice, CalculateFeesParams, GasEstimationOptionsPayload } from './types'
|
||||
|
||||
import { RpcFetcher } from '@/services'
|
||||
import { ChainId, NETWORKS } from '@/config'
|
||||
import { BG_ZERO, PERCENT_MULTIPLIER } from '@/constants'
|
||||
import { RpcFetcher, NodeJSCache } from '@/services'
|
||||
import { findMax, fromNumberToHex, fromWeiToGwei, getMedian } from '@/utils'
|
||||
import { BG_ZERO, DEFAULT_BLOCK_DURATION, PERCENT_MULTIPLIER } from '@/constants'
|
||||
|
||||
import { DEFAULT_PRIORITY_FEE, PRIORITY_FEE_INCREASE_BOUNDARY, FEE_HISTORY_BLOCKS, FEE_HISTORY_PERCENTILE } from './constants'
|
||||
|
||||
// !!! MAKE SENSE ALL CALCULATIONS IN GWEI !!!
|
||||
export class Eip1559GasPriceOracle implements EstimateOracle {
|
||||
public configuration: Config = {
|
||||
shouldCache: false,
|
||||
chainId: ChainId.MAINNET,
|
||||
fallbackGasPrices: undefined,
|
||||
minPriority: DEFAULT_PRIORITY_FEE,
|
||||
blockTime: DEFAULT_BLOCK_DURATION,
|
||||
blocksCount: NETWORKS[ChainId.MAINNET].blocksCount,
|
||||
percentile: NETWORKS[ChainId.MAINNET].percentile,
|
||||
fallbackGasPrices: undefined,
|
||||
}
|
||||
private fetcher: RpcFetcher
|
||||
|
||||
private cache: NodeJSCache<EstimatedGasPrice>
|
||||
private FEES_KEY = (chainId: ChainId) => `estimate-fee-${chainId}`
|
||||
|
||||
constructor({ fetcher, ...options }: GasEstimationOptionsPayload) {
|
||||
this.fetcher = fetcher
|
||||
const chainId = options?.chainId || this.configuration.chainId
|
||||
@ -29,10 +35,19 @@ export class Eip1559GasPriceOracle implements EstimateOracle {
|
||||
if (options) {
|
||||
this.configuration = { ...this.configuration, ...options }
|
||||
}
|
||||
|
||||
this.cache = new NodeJSCache({ stdTTL: this.configuration.blockTime, useClones: false })
|
||||
}
|
||||
|
||||
public async estimateFees(fallbackGasPrices?: EstimatedGasPrice): Promise<EstimatedGasPrice> {
|
||||
try {
|
||||
const cacheKey = this.FEES_KEY(this.configuration.chainId)
|
||||
const cachedFees = await this.cache.get(cacheKey)
|
||||
|
||||
if (cachedFees) {
|
||||
return cachedFees
|
||||
}
|
||||
|
||||
const { data: latestBlock } = await this.fetcher.makeRpcCall<{ result: Block }>({
|
||||
method: 'eth_getBlockByNumber',
|
||||
params: ['latest', false],
|
||||
@ -52,7 +67,12 @@ export class Eip1559GasPriceOracle implements EstimateOracle {
|
||||
params: [blockCount, 'latest', rewardPercentiles],
|
||||
})
|
||||
|
||||
return this.calculateFees({ baseFee, feeHistory: data.result })
|
||||
const fees = await this.calculateFees({ baseFee, feeHistory: data.result })
|
||||
if (this.configuration.shouldCache) {
|
||||
await this.cache.set(cacheKey, fees)
|
||||
}
|
||||
|
||||
return fees
|
||||
} catch (err) {
|
||||
if (fallbackGasPrices) {
|
||||
return fallbackGasPrices
|
||||
@ -116,8 +136,10 @@ export class Eip1559GasPriceOracle implements EstimateOracle {
|
||||
private async calculateFees({ baseFee, feeHistory }: CalculateFeesParams): Promise<EstimatedGasPrice> {
|
||||
const estimatedPriorityFee = await this.getPriorityFromChain(feeHistory)
|
||||
|
||||
const { highest: maxPriorityFeePerGas } = findMax([estimatedPriorityFee ?? BG_ZERO, new BigNumber(DEFAULT_PRIORITY_FEE)])
|
||||
|
||||
const { highest: maxPriorityFeePerGas } = findMax([
|
||||
estimatedPriorityFee ?? BG_ZERO,
|
||||
new BigNumber(this.configuration.minPriority),
|
||||
])
|
||||
const maxFeePerGas = baseFee.plus(maxPriorityFeePerGas)
|
||||
|
||||
if (this.checkIsGreaterThanMax(maxFeePerGas) || this.checkIsGreaterThanMax(maxPriorityFeePerGas)) {
|
||||
|
||||
@ -23,6 +23,8 @@ export type Options = {
|
||||
chainId?: number
|
||||
blocksCount?: number
|
||||
percentile?: number
|
||||
blockTime?: number
|
||||
shouldCache?: boolean
|
||||
fallbackGasPrices: EstimatedGasPrice | undefined
|
||||
}
|
||||
|
||||
@ -30,7 +32,7 @@ export type GasEstimationOptionsPayload = Options & {
|
||||
fetcher: RpcFetcher
|
||||
}
|
||||
|
||||
export type Config = Required<Options> & { fallbackGasPrices?: EstimatedGasPrice }
|
||||
export type Config = Required<Options> & { fallbackGasPrices?: EstimatedGasPrice; minPriority: number }
|
||||
export abstract class EstimateOracle {
|
||||
public configuration: Config
|
||||
public abstract estimateFees(fallbackGasPrices?: EstimatedGasPrice): Promise<EstimatedGasPrice>
|
||||
|
||||
@ -36,6 +36,9 @@ export type GasOracleOptions = {
|
||||
defaultRpc?: string
|
||||
blocksCount?: number
|
||||
percentile?: number
|
||||
blockTime?: number
|
||||
shouldCache?: boolean
|
||||
minPriority?: number
|
||||
fallbackGasPrices?: FallbackGasPrices
|
||||
}
|
||||
|
||||
|
||||
@ -3,4 +3,5 @@ export * from './gas-price-oracle'
|
||||
export * from './gas-estimation'
|
||||
export * from './legacy-gas-price'
|
||||
|
||||
export * from './cacher'
|
||||
export * from './rpcFetcher'
|
||||
|
||||
@ -2,9 +2,9 @@ const DEFAULT_GAS_PRICE = { instant: 0, fast: 0, standard: 0, low: 0 }
|
||||
|
||||
const MULTIPLIERS = {
|
||||
instant: 1.3,
|
||||
standard: 0.85,
|
||||
low: 0.5,
|
||||
fast: 1,
|
||||
fast: 1.2,
|
||||
standard: 1.1,
|
||||
low: 1,
|
||||
}
|
||||
|
||||
export { MULTIPLIERS, DEFAULT_GAS_PRICE }
|
||||
|
||||
@ -14,9 +14,10 @@ import {
|
||||
GetGasPriceFromRespInput,
|
||||
} from './types'
|
||||
|
||||
import { RpcFetcher } from '@/services'
|
||||
import { ChainId, NETWORKS } from '@/config'
|
||||
import { GWEI, DEFAULT_TIMEOUT, GWEI_PRECISION } from '@/constants'
|
||||
import { RpcFetcher, NodeJSCache } from '@/services'
|
||||
import { resolvePropertyPath } from '@/utils'
|
||||
import { GWEI, DEFAULT_TIMEOUT, GWEI_PRECISION, DEFAULT_BLOCK_DURATION } from '@/constants'
|
||||
|
||||
import { MULTIPLIERS, DEFAULT_GAS_PRICE } from './constants'
|
||||
|
||||
@ -95,22 +96,27 @@ export class LegacyGasPriceOracle implements LegacyOracle {
|
||||
public onChainOracles: OnChainOracles = {}
|
||||
public offChainOracles: OffChainOracles = {}
|
||||
public configuration: Required<LegacyOptions> = {
|
||||
shouldCache: false,
|
||||
chainId: ChainId.MAINNET,
|
||||
timeout: DEFAULT_TIMEOUT,
|
||||
blockTime: DEFAULT_BLOCK_DURATION,
|
||||
defaultRpc: NETWORKS[ChainId.MAINNET].rpcUrl,
|
||||
fallbackGasPrices: LegacyGasPriceOracle.getMultipliedPrices(NETWORKS[ChainId.MAINNET].defaultGasPrice),
|
||||
}
|
||||
|
||||
private readonly fetcher: RpcFetcher
|
||||
|
||||
private cache: NodeJSCache<GasPrice>
|
||||
private LEGACY_KEY = (chainId: ChainId) => `legacy-fee-${chainId}`
|
||||
|
||||
constructor({ fetcher, ...options }: LegacyOptionsPayload) {
|
||||
this.fetcher = fetcher
|
||||
if (options) {
|
||||
this.configuration = { ...this.configuration, ...options }
|
||||
}
|
||||
|
||||
const fallbackGasPrices =
|
||||
this.configuration.fallbackGasPrices || LegacyGasPriceOracle.getMultipliedPrices(NETWORKS[ChainId.MAINNET].defaultGasPrice)
|
||||
const { defaultGasPrice } = NETWORKS[ChainId.MAINNET]
|
||||
const fallbackGasPrices = this.configuration.fallbackGasPrices || LegacyGasPriceOracle.getMultipliedPrices(defaultGasPrice)
|
||||
this.configuration.fallbackGasPrices = LegacyGasPriceOracle.normalize(fallbackGasPrices)
|
||||
|
||||
const network = NETWORKS[this.configuration.chainId]?.oracles
|
||||
@ -118,6 +124,8 @@ export class LegacyGasPriceOracle implements LegacyOracle {
|
||||
this.offChainOracles = { ...network.offChainOracles }
|
||||
this.onChainOracles = { ...network.onChainOracles }
|
||||
}
|
||||
|
||||
this.cache = new NodeJSCache({ stdTTL: this.configuration.blockTime, useClones: false })
|
||||
}
|
||||
|
||||
public addOffChainOracle(oracle: OffChainOracle): void {
|
||||
@ -228,9 +236,19 @@ export class LegacyGasPriceOracle implements LegacyOracle {
|
||||
this.lastGasPrice = fallbackGasPrices || this.configuration.fallbackGasPrices
|
||||
}
|
||||
|
||||
const cacheKey = this.LEGACY_KEY(this.configuration.chainId)
|
||||
const cachedFees = await this.cache.get(cacheKey)
|
||||
|
||||
if (cachedFees) {
|
||||
return cachedFees
|
||||
}
|
||||
|
||||
if (Object.keys(this.offChainOracles).length > 0) {
|
||||
try {
|
||||
this.lastGasPrice = await this.fetchGasPricesOffChain(shouldGetMedian)
|
||||
if (this.configuration.shouldCache) {
|
||||
await this.cache.set(cacheKey, this.lastGasPrice)
|
||||
}
|
||||
return this.lastGasPrice
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch gas prices from offchain oracles...')
|
||||
@ -240,7 +258,11 @@ export class LegacyGasPriceOracle implements LegacyOracle {
|
||||
if (Object.keys(this.onChainOracles).length > 0) {
|
||||
try {
|
||||
const fastGas = await this.fetchGasPricesOnChain()
|
||||
|
||||
this.lastGasPrice = LegacyGasPriceOracle.getCategorize(fastGas)
|
||||
if (this.configuration.shouldCache) {
|
||||
await this.cache.set(cacheKey, this.lastGasPrice)
|
||||
}
|
||||
return this.lastGasPrice
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch gas prices from onchain oracles...')
|
||||
@ -249,7 +271,11 @@ export class LegacyGasPriceOracle implements LegacyOracle {
|
||||
|
||||
try {
|
||||
const fastGas = await this.fetchGasPriceFromRpc()
|
||||
|
||||
this.lastGasPrice = LegacyGasPriceOracle.getCategorize(fastGas)
|
||||
if (this.configuration.shouldCache) {
|
||||
await this.cache.set(cacheKey, this.lastGasPrice)
|
||||
}
|
||||
return this.lastGasPrice
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch gas prices from default RPC. Last known gas will be returned')
|
||||
@ -272,17 +298,17 @@ export class LegacyGasPriceOracle implements LegacyOracle {
|
||||
const response = await axios.get(url, { timeout: this.configuration.timeout })
|
||||
|
||||
if (response.status === 200) {
|
||||
const gas = additionalDataProperty ? response.data[additionalDataProperty] : response.data
|
||||
const gas = resolvePropertyPath(response.data, additionalDataProperty)
|
||||
|
||||
if (Number(gas[fastPropertyName]) === 0) {
|
||||
if (Number(resolvePropertyPath(gas, fastPropertyName)) === 0) {
|
||||
throw new Error(`${name} oracle provides corrupted values`)
|
||||
}
|
||||
|
||||
const gasPrices: GasPrice = {
|
||||
instant: parseFloat(gas[instantPropertyName]) / denominator,
|
||||
fast: parseFloat(gas[fastPropertyName]) / denominator,
|
||||
standard: parseFloat(gas[standardPropertyName]) / denominator,
|
||||
low: parseFloat(gas[lowPropertyName]) / denominator,
|
||||
instant: parseFloat(resolvePropertyPath(gas, instantPropertyName)) / denominator,
|
||||
fast: parseFloat(resolvePropertyPath(gas, fastPropertyName)) / denominator,
|
||||
standard: parseFloat(resolvePropertyPath(gas, standardPropertyName)) / denominator,
|
||||
low: parseFloat(resolvePropertyPath(gas, lowPropertyName)) / denominator,
|
||||
}
|
||||
return LegacyGasPriceOracle.normalize(gasPrices)
|
||||
} else {
|
||||
|
||||
@ -36,7 +36,9 @@ export type GasPrice = Record<GasPriceKey, number>
|
||||
export type LegacyOptions = {
|
||||
chainId?: number
|
||||
timeout?: number
|
||||
blockTime?: number
|
||||
defaultRpc?: string
|
||||
shouldCache?: boolean
|
||||
fallbackGasPrices?: GasPrice
|
||||
}
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@ import chaiAsPromised from 'chai-as-promised'
|
||||
import mockery from 'mockery'
|
||||
import { before, describe } from 'mocha'
|
||||
|
||||
import { sleep } from '@/utils'
|
||||
import { ChainId, NETWORKS } from '@/config'
|
||||
import { GWEI_PRECISION } from '@/constants'
|
||||
|
||||
@ -32,7 +33,7 @@ beforeEach('beforeEach', function () {
|
||||
oracle = new GasPriceOracle()
|
||||
})
|
||||
|
||||
const INJECTED_RPC_URL = 'https://ethereum-rpc.trustwalletapp.com'
|
||||
const INJECTED_RPC_URL = 'https://cloudflare-eth.com'
|
||||
describe('eip-1559 gasOracle', function () {
|
||||
describe('eip constructor', function () {
|
||||
it('should set default values', function () {
|
||||
@ -61,14 +62,14 @@ describe('eip-1559 gasOracle', function () {
|
||||
|
||||
describe(`estimateGas ${chainId}`, function () {
|
||||
it('should return error if not eip-1559 not supported', async function () {
|
||||
if (chainId === ChainId.OPTIMISM || chainId === ChainId.ARBITRUM || chainId === ChainId.BSC) {
|
||||
if (chainId === ChainId.OPTIMISM || chainId === ChainId.BSC) {
|
||||
await eipOracle.eip1559
|
||||
.estimateFees()
|
||||
.should.be.rejectedWith('An error occurred while fetching current base fee, falling back')
|
||||
}
|
||||
})
|
||||
|
||||
if (chainId === ChainId.OPTIMISM || chainId === ChainId.ARBITRUM || chainId === ChainId.BSC) {
|
||||
if (chainId === ChainId.OPTIMISM || chainId === ChainId.BSC) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -122,6 +123,34 @@ describe('eip-1559 gasOracle', function () {
|
||||
estimateGas.maxFeePerGas.should.be.at.equal(estimatedMaxFee)
|
||||
}
|
||||
})
|
||||
it('should cache', async function () {
|
||||
eipOracle = new GasPriceOracle({ shouldCache: true, chainId })
|
||||
const estimateGasFirst: EstimatedGasPrice = await eipOracle.eip1559.estimateFees()
|
||||
|
||||
await sleep(2000)
|
||||
const estimateGasSecond: EstimatedGasPrice = await eipOracle.eip1559.estimateFees()
|
||||
|
||||
if (estimateGasFirst?.maxFeePerGas) {
|
||||
estimateGasFirst.maxFeePerGas.should.be.at.equal(estimateGasSecond?.maxFeePerGas)
|
||||
}
|
||||
|
||||
await sleep(4000)
|
||||
const estimateGasThird: EstimatedGasPrice = await eipOracle.eip1559.estimateFees()
|
||||
|
||||
if (estimateGasSecond?.maxFeePerGas) {
|
||||
estimateGasSecond.maxFeePerGas.should.be.at.equal(estimateGasThird?.maxFeePerGas)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('estimate ARBITRUM', function () {
|
||||
it('should be priority 0', async function () {
|
||||
const eipOracle = new GasPriceOracle({ minPriority: 0, chainId: ChainId.ARBITRUM })
|
||||
const estimateGas: EstimatedGasPrice = await eipOracle.eip1559.estimateFees(FALLBACK_ESTIMATE)
|
||||
|
||||
console.log('estimateGas.maxPriorityFeePerGas', estimateGas.maxPriorityFeePerGas)
|
||||
estimateGas.maxPriorityFeePerGas.should.be.at.equal(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -8,6 +8,7 @@ import mockery from 'mockery'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { before, describe } from 'mocha'
|
||||
|
||||
import { sleep } from '@/utils'
|
||||
import { ChainId, NETWORKS } from '@/config'
|
||||
import { DEFAULT_TIMEOUT } from '@/constants'
|
||||
import { GasPriceOracle } from '@/services/gas-price-oracle'
|
||||
@ -41,7 +42,7 @@ beforeEach('beforeEach', function () {
|
||||
;({ onChainOracles, offChainOracles } = oracle.legacy)
|
||||
})
|
||||
|
||||
const INJECTED_RPC_URL = 'https://ethereum-rpc.trustwalletapp.com'
|
||||
const INJECTED_RPC_URL = 'https://cloudflare-eth.com'
|
||||
|
||||
describe('legacy gasOracle', function () {
|
||||
describe('legacy constructor', function () {
|
||||
@ -77,6 +78,7 @@ describe('legacy gasOracle', function () {
|
||||
mockery.enable({ useCleanCache: true, warnOnUnregistered: false })
|
||||
const { GasPriceOracle } = require('../index')
|
||||
oracle = new GasPriceOracle()
|
||||
// @ts-ignore
|
||||
await oracle.legacy.fetchGasPricesOffChain(true).should.be.rejectedWith('All oracles are down. Probably a network error.')
|
||||
mockery.disable()
|
||||
})
|
||||
@ -105,6 +107,7 @@ describe('legacy gasOracle', function () {
|
||||
it('should remove oracle', async function () {
|
||||
await oracle.legacy.fetchGasPricesOnChain()
|
||||
oracle.legacy.removeOnChainOracle('chainlink')
|
||||
// @ts-ignore
|
||||
await oracle.legacy.fetchGasPricesOnChain().should.be.rejectedWith('All oracles are down. Probably a network error.')
|
||||
})
|
||||
|
||||
@ -113,6 +116,7 @@ describe('legacy gasOracle', function () {
|
||||
await oracle.legacy.fetchGasPricesOnChain()
|
||||
oracle.legacy.removeOnChainOracle('chainlink')
|
||||
|
||||
// @ts-ignore
|
||||
await oracle.legacy.fetchGasPricesOnChain().should.be.rejectedWith('All oracles are down. Probably a network error.')
|
||||
|
||||
oracle.legacy.addOnChainOracle(toAdd)
|
||||
@ -127,6 +131,7 @@ describe('legacy gasOracle', function () {
|
||||
const { GasPriceOracle } = require('../index')
|
||||
|
||||
oracle = new GasPriceOracle()
|
||||
// @ts-ignore
|
||||
await oracle.legacy.fetchGasPricesOnChain().should.be.rejectedWith('All oracles are down. Probably a network error.')
|
||||
mockery.disable()
|
||||
})
|
||||
@ -157,6 +162,7 @@ describe('legacy gasOracle', function () {
|
||||
const { GasPriceOracle } = require('../index')
|
||||
|
||||
oracle = new GasPriceOracle()
|
||||
// @ts-ignore
|
||||
await oracle.legacy.fetchGasPriceFromRpc().should.be.rejectedWith('Default RPC is down. Probably a network error.')
|
||||
mockery.disable()
|
||||
})
|
||||
@ -185,7 +191,7 @@ describe('legacy gasOracle', function () {
|
||||
|
||||
const gas = (await oracle.gasPrices({ isLegacy: true })) as unknown as GasPrice
|
||||
|
||||
const shouldBe = LegacyGasPriceOracle.getMultipliedPrices(NETWORKS[ChainId.MAINNET].defaultGasPrice)
|
||||
const shouldBe = LegacyGasPriceOracle.getCategorize(NETWORKS[ChainId.MAINNET].defaultGasPrice)
|
||||
|
||||
gas.instant.should.be.equal(shouldBe.instant)
|
||||
gas.fast.should.be.equal(shouldBe.fast)
|
||||
@ -217,6 +223,25 @@ describe('legacy gasOracle', function () {
|
||||
|
||||
mockery.disable()
|
||||
})
|
||||
|
||||
it('should cache', async function () {
|
||||
const oracle = new GasPriceOracle({ shouldCache: true })
|
||||
const gasPricesFirst = await oracle.legacy.gasPrices()
|
||||
|
||||
await sleep(2000)
|
||||
const gasPricesSecond = await oracle.legacy.gasPrices()
|
||||
|
||||
if (gasPricesFirst.fast) {
|
||||
gasPricesFirst.fast.should.be.at.equal(gasPricesSecond?.fast)
|
||||
}
|
||||
|
||||
await sleep(4000)
|
||||
const gasPricesThird = await oracle.legacy.gasPrices()
|
||||
|
||||
if (gasPricesSecond.fast) {
|
||||
gasPricesSecond.fast.should.be.at.equal(gasPricesThird?.fast)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('median', function () {
|
||||
|
||||
@ -1,2 +1,47 @@
|
||||
export * from './math'
|
||||
export * from './crypto'
|
||||
|
||||
const sleep = (time: number): Promise<boolean> => {
|
||||
return new Promise((res) => setTimeout(() => res(true), time))
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the recursively resolved value of the object's subproperty given the properties path, separated by dots.
|
||||
* If properties path is null, undefined or an empty string - returns the object itself
|
||||
* @param obj - The object, from which get property or subproperty
|
||||
* @param propertiesInString - Property name or properties chained string, like 'user.email.domain'
|
||||
* @returns The value of the subproperty by path or object itself
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ### Object
|
||||
* ```ts
|
||||
* const x = {
|
||||
* y: {
|
||||
* z: 11
|
||||
* }
|
||||
* };
|
||||
* ```
|
||||
*
|
||||
* ### Property path in object
|
||||
* ```ts
|
||||
* const propertyPath = 'y.z';
|
||||
* ```
|
||||
*
|
||||
* ### Usage
|
||||
* ```ts
|
||||
* const subpropertyValue = resolvePropertyPath(x, propertyPath);
|
||||
* ```
|
||||
*
|
||||
* ### Result (subproperty value)
|
||||
* ```ts
|
||||
* 11
|
||||
* ```
|
||||
*/
|
||||
const resolvePropertyPath = (obj: object, propertyPath: string | undefined | null): any => {
|
||||
const properties = propertyPath?.split('.') || []
|
||||
|
||||
return properties.reduce((curr: { [key: string]: any }, nextProperty: string) => curr[nextProperty], obj)
|
||||
}
|
||||
|
||||
export { sleep, resolvePropertyPath }
|
||||
|
||||
12
yarn.lock
12
yarn.lock
@ -467,6 +467,11 @@ cliui@^5.0.0:
|
||||
strip-ansi "^5.2.0"
|
||||
wrap-ansi "^5.1.0"
|
||||
|
||||
clone@2.x:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
|
||||
integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==
|
||||
|
||||
color-convert@^1.9.0:
|
||||
version "1.9.3"
|
||||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
|
||||
@ -1520,6 +1525,13 @@ natural-compare@^1.4.0:
|
||||
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
|
||||
|
||||
node-cache@^5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/node-cache/-/node-cache-5.1.2.tgz#f264dc2ccad0a780e76253a694e9fd0ed19c398d"
|
||||
integrity sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==
|
||||
dependencies:
|
||||
clone "2.x"
|
||||
|
||||
node-environment-flags@1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.6.tgz#a30ac13621f6f7d674260a54dede048c3982c088"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user