From 456a8bec71486a62beb92646de8cc276c6204d56 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Wed, 20 May 2020 18:02:15 +1000 Subject: [PATCH] isNil MAX gas gwei cached gas price --- CHANGELOG.md | 8 +- src/config.ts | 16 ++- src/constants.ts | 10 +- src/handlers/meta_transaction_handlers.ts | 2 +- src/services/meta_transaction_service.ts | 16 ++- .../transaction_watcher_signer_service.ts | 108 +++++++++--------- src/types.ts | 1 + src/utils/gas_station_utils.ts | 37 ++++-- src/utils/signer.ts | 2 +- src/utils/utils.ts | 5 + test/meta_transaction_test.ts | 4 +- 11 files changed, 121 insertions(+), 88 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ae8e2c18..1b2fc3314 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,13 @@ # [1.4.0](https://github.com/0xProject/0x-api/compare/v1.3.0...v1.4.0) (2020-05-18) - ### Bug Fixes -* re-allow unknown tokens to be queried in the swap/quote endpoint ([#226](https://github.com/0xProject/0x-api/issues/226)) ([1379e63](https://github.com/0xProject/0x-api/commit/1379e638693e030ab343adfa9893a6f42081ea01)) - +- re-allow unknown tokens to be queried in the swap/quote endpoint ([#226](https://github.com/0xProject/0x-api/issues/226)) ([1379e63](https://github.com/0xProject/0x-api/commit/1379e638693e030ab343adfa9893a6f42081ea01)) ### Features -* add transaction watcher service ([#215](https://github.com/0xProject/0x-api/issues/215)) ([7bbb9c6](https://github.com/0xProject/0x-api/commit/7bbb9c6f0992ae2a9a9ae9f2fe6d59f99a8e121a)) -* Improve RFQ-T logging ([#219](https://github.com/0xProject/0x-api/issues/219)) ([22b6b0c](https://github.com/0xProject/0x-api/commit/22b6b0c1fb15513bc1225ca5f3a8918639fe8f32)) +- add transaction watcher service ([#215](https://github.com/0xProject/0x-api/issues/215)) ([7bbb9c6](https://github.com/0xProject/0x-api/commit/7bbb9c6f0992ae2a9a9ae9f2fe6d59f99a8e121a)) +- Improve RFQ-T logging ([#219](https://github.com/0xProject/0x-api/issues/219)) ([22b6b0c](https://github.com/0xProject/0x-api/commit/22b6b0c1fb15513bc1225ca5f3a8918639fe8f32)) # [1.3.0](https://github.com/0xProject/0x-api/compare/v1.2.0...v1.3.0) (2020-05-11) diff --git a/src/config.ts b/src/config.ts index 9f9ecbd7b..68ddc0e8a 100644 --- a/src/config.ts +++ b/src/config.ts @@ -172,12 +172,12 @@ export const RFQT_SKIP_BUY_REQUESTS: boolean = _.isEmpty(process.env.RFQT_SKIP_B : assertEnvVarType('RFQT_SKIP_BUY_REQUESTS', process.env.RFQT_SKIP_BUY_REQUESTS, EnvVarType.Boolean); // Whitelisted 0x API keys that can use the meta-txn /submit endpoint -export const WHITELISTED_API_KEYS_META_TXN_SUBMIT: string[] = - process.env.WHITELISTED_API_KEYS_META_TXN_SUBMIT === undefined +export const META_TXN_SUBMIT_WHITELISTED_API_KEYS: string[] = + process.env.META_TXN_SUBMIT_WHITELISTED_API_KEYS === undefined ? [] : assertEnvVarType( - 'WHITELISTED_API_KEYS_META_TXN_SUBMIT', - process.env.WHITELISTED_API_KEYS_META_TXN_SUBMIT, + 'META_TXN_SUBMIT_WHITELISTED_API_KEYS', + process.env.META_TXN_SUBMIT_WHITELISTED_API_KEYS, EnvVarType.APIKeys, ); @@ -196,9 +196,13 @@ export const META_TXN_RELAY_EXPECTED_MINED_SEC: number = _.isEmpty(process.env.M ); // Should TransactionWatcherSignerService sign transactions // tslint:disable-next-line:boolean-naming -export const ENABLE_TRANSACTION_SIGNING: boolean = _.isEmpty(process.env.ENABLE_TRANSACTION_SIGNING) +export const META_TXN_SIGNING_ENABLED: boolean = _.isEmpty(process.env.META_TXN_SIGNING_ENABLED) ? true - : assertEnvVarType('ENABLE_TRANSACTION_SIGNING', process.env.ENABLE_TRANSACTION_SIGNING, EnvVarType.Boolean); + : assertEnvVarType('META_TXN_SIGNING_ENABLED', process.env.META_TXN_SIGNING_ENABLED, EnvVarType.Boolean); +// The maximum gas price (in gwei) the service will allow +export const META_TXN_MAX_GAS_PRICE_GWEI: BigNumber = _.isEmpty(process.env.META_TXN_MAX_GAS_PRICE_GWEI) + ? new BigNumber(50) + : assertEnvVarType('META_TXN_MAX_GAS_PRICE_GWEI', process.env.META_TXN_MAX_GAS_PRICE_GWEI, EnvVarType.UnitAmount); // Whether or not prometheus metrics should be enabled. // tslint:disable-next-line:boolean-naming diff --git a/src/constants.ts b/src/constants.ts index 7476a5f93..5aaeb327b 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -54,8 +54,8 @@ export const META_TRANSACTION_DOCS_URL = 'https://0x.org/docs/api#meta_transacti export const ETH_GAS_STATION_API_BASE_URL = 'https://ethgasstation.info'; export const UNSTICKING_TRANSACTION_GAS_MULTIPLIER = 1.1; export const ETH_TRANSFER_GAS_LIMIT = 21000; -export const STUCK_TX_POLLING_INTERVAL_MS = 5 * 1000; -export const TX_HASH_RESPONSE_WAIT_TIME_MS = 100 * 1000; +export const STUCK_TX_POLLING_INTERVAL_MS = ONE_SECOND_MS * 5; +export const TX_HASH_RESPONSE_WAIT_TIME_MS = ONE_SECOND_MS * 100; export const SUBMITTED_TX_DB_POLLING_INTERVAL_MS = 200; export const PUBLIC_ADDRESS_FOR_ETH_CALLS = '0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B'; @@ -63,11 +63,11 @@ export const PUBLIC_ADDRESS_FOR_ETH_CALLS = '0xAb5801a7D398351b8bE11C439e05C5B32 // The expected time of a transaction to be mined according to ETHGasStation // "Fast" gas price estimations multiplied by a safety margin. export const DEFAULT_EXPECTED_MINED_SEC = 120 * 1.5; -export const TX_WATCHER_POLLING_INTERVAL_MS = 5 * 1000; +export const TX_WATCHER_POLLING_INTERVAL_MS = ONE_SECOND_MS * 5; export const NUMBER_OF_BLOCKS_UNTIL_CONFIRMED = 3; -export const TX_WATCHER_UPDATE_METRICS_INTERVAL_MS = 30 * 1000; +export const TX_WATCHER_UPDATE_METRICS_INTERVAL_MS = ONE_SECOND_MS * 30; export const ETH_DECIMALS = 18; export const GWEI_DECIMALS = 9; -export const SIGNER_ETH_BALANCE_CONSIDERED_CRITICAL = 0.1; +export const META_TXN_MIN_SIGNER_ETH_BALANCE = 0.1; export const SIGNER_STATUS_DB_KEY = 'signer_status'; export const SIGNER_KILL_SWITCH_KEY = 'signer_kill_switch_on'; diff --git a/src/handlers/meta_transaction_handlers.ts b/src/handlers/meta_transaction_handlers.ts index 3bf92f6df..9378a0d01 100644 --- a/src/handlers/meta_transaction_handlers.ts +++ b/src/handlers/meta_transaction_handlers.ts @@ -3,7 +3,6 @@ import { SwapQuoterError } from '@0x/asset-swapper'; import { BigNumber } from '@0x/utils'; import * as express from 'express'; import * as HttpStatus from 'http-status-codes'; -import * as _ from 'lodash'; import * as isValidUUID from 'uuid-validate'; import { CHAIN_ID } from '../config'; @@ -236,6 +235,7 @@ export class MetaTransactionHandlers { res.status(HttpStatus.OK).send({ ethereumTransactionHash, signedEthereumTransaction, + zeroExTransactionHash, }); } else { const ethereumTxn = await this._metaTransactionService.generatePartialExecuteTransactionEthereumTransactionAsync( diff --git a/src/services/meta_transaction_service.ts b/src/services/meta_transaction_service.ts index 0e4261541..28df3da83 100644 --- a/src/services/meta_transaction_service.ts +++ b/src/services/meta_transaction_service.ts @@ -13,10 +13,12 @@ import { ASSET_SWAPPER_MARKET_ORDERS_OPTS, CHAIN_ID, LIQUIDITY_POOL_REGISTRY_ADDRESS, + META_TXN_MAX_GAS_PRICE_GWEI, META_TXN_RELAY_EXPECTED_MINED_SEC, - WHITELISTED_API_KEYS_META_TXN_SUBMIT, + META_TXN_SUBMIT_WHITELISTED_API_KEYS, } from '../config'; import { + GWEI_DECIMALS, ONE_GWEI, ONE_SECOND_MS, PUBLIC_ADDRESS_FOR_ETH_CALLS, @@ -53,7 +55,7 @@ export class MetaTransactionService { private readonly _kvRepository: Repository; public static isEligibleForFreeMetaTxn(apiKey: string): boolean { - return WHITELISTED_API_KEYS_META_TXN_SUBMIT.includes(apiKey); + return META_TXN_SUBMIT_WHITELISTED_API_KEYS.includes(apiKey); } private static _calculateProtocolFee(numOrders: number, gasPrice: BigNumber): BigNumber { return new BigNumber(150000).times(gasPrice).times(numOrders); @@ -221,8 +223,11 @@ export class MetaTransactionService { const gasPrice = zeroExTransaction.gasPrice; const currentFastGasPrice = await ethGasStationUtils.getGasPriceOrThrowAsync(); // Make sure gasPrice is not 3X the current fast EthGasStation gas price - // tslint:disable-next-line:custom-no-magic-numbers - if (currentFastGasPrice.lt(gasPrice) && gasPrice.minus(currentFastGasPrice).gt(currentFastGasPrice.times(3))) { + if ( + Web3Wrapper.toUnitAmount(gasPrice, GWEI_DECIMALS).gte(META_TXN_MAX_GAS_PRICE_GWEI) || + // tslint:disable-next-line:custom-no-magic-numbers + (currentFastGasPrice.lt(gasPrice) && gasPrice.minus(currentFastGasPrice).gte(currentFastGasPrice.times(3))) + ) { throw new Error('Gas price too high'); } @@ -314,6 +319,7 @@ export class MetaTransactionService { return { ethereumTransactionHash, signedEthereumTransaction, + zeroExTransactionHash, }; } public async isSignerLiveAsync(): Promise { @@ -353,7 +359,7 @@ export class MetaTransactionService { return utils.runWithTimeout(async () => { while (true) { const tx = await this._transactionEntityRepository.findOne(txEntity.refHash); - if (tx !== undefined && tx.txHash !== undefined && tx.signedTx !== undefined) { + if (!utils.isNil(tx) && !utils.isNil(tx.txHash) && !utils.isNil(tx.signedTx)) { return { ethereumTransactionHash: tx.txHash, signedEthereumTransaction: tx.signedTx }; } diff --git a/src/services/transaction_watcher_signer_service.ts b/src/services/transaction_watcher_signer_service.ts index 1444ed88d..93b559b2b 100644 --- a/src/services/transaction_watcher_signer_service.ts +++ b/src/services/transaction_watcher_signer_service.ts @@ -6,18 +6,17 @@ import { Counter, Gauge, Summary } from 'prom-client'; import { Connection, Not, Repository } from 'typeorm'; import { - ENABLE_PROMETHEUS_METRICS, - ENABLE_TRANSACTION_SIGNING, ETHEREUM_RPC_URL, META_TXN_RELAY_EXPECTED_MINED_SEC, META_TXN_RELAY_PRIVATE_KEYS, + META_TXN_SIGNING_ENABLED, } from '../config'; import { ETH_DECIMALS, GWEI_DECIMALS, + META_TXN_MIN_SIGNER_ETH_BALANCE, NUMBER_OF_BLOCKS_UNTIL_CONFIRMED, ONE_SECOND_MS, - SIGNER_ETH_BALANCE_CONSIDERED_CRITICAL, SIGNER_STATUS_DB_KEY, TX_HASH_RESPONSE_WAIT_TIME_MS, TX_WATCHER_POLLING_INTERVAL_MS, @@ -29,6 +28,7 @@ import { logger } from '../logger'; import { TransactionStates, TransactionWatcherSignerStatus } from '../types'; import { ethGasStationUtils } from '../utils/gas_station_utils'; import { Signer } from '../utils/signer'; +import { utils } from '../utils/utils'; const SIGNER_ADDRESS_LABEL = 'signer_address'; const TRANSACTION_STATUS_LABEL = 'status'; @@ -39,8 +39,8 @@ export class TransactionWatcherSignerService { private readonly _provider: SupportedProvider; private readonly _web3Wrapper: Web3Wrapper; private readonly _transactionWatcherTimer: NodeJS.Timer; - private readonly _signers: Map; - private readonly _signerBalances: Map; + private readonly _signers: Map = new Map(); + private readonly _signerBalancesEth: Map = new Map(); private readonly _availableSignerPublicAddresses: string[]; private readonly _metricsUpdateTimer: NodeJS.Timer; private readonly _signerBalancesGauge: Gauge; @@ -48,10 +48,10 @@ export class TransactionWatcherSignerService { private readonly _gasPriceSummary: Summary; public static getSortedSignersByAvailability(signerMap: Map): string[] { - return [...signerMap.entries()] + return Array.from(signerMap.entries()) .sort((a, b) => { - const aSigner = a[1]; - const bSigner = b[1]; + const [, aSigner] = a; + const [, bSigner] = b; // if the number of pending transactions is the same, we sort // the signers by their known balance. if (aSigner.count === bSigner.count) { @@ -60,7 +60,7 @@ export class TransactionWatcherSignerService { // otherwise we sort by the least amount of pending transactions. return aSigner.count - bSigner.count; }) - .map(entry => entry[0]); + .map(([address]) => address); } private static _createWeb3Provider(rpcHost: string): SupportedProvider { const providerEngine = new Web3ProviderEngine(); @@ -69,9 +69,7 @@ export class TransactionWatcherSignerService { return providerEngine; } private static _isUnsubmittedTxExpired(tx: TransactionEntity): boolean { - const now = new Date(); - const shouldBeSubmittedBy = new Date(tx.createdAt.getTime() + TX_HASH_RESPONSE_WAIT_TIME_MS); - return tx.status === TransactionStates.Unsubmitted && now > shouldBeSubmittedBy; + return tx.status === TransactionStates.Unsubmitted && Date.now() > tx.expectedAt.getTime(); } constructor(dbConnection: Connection) { this._provider = TransactionWatcherSignerService._createWeb3Provider(ETHEREUM_RPC_URL); @@ -84,6 +82,18 @@ export class TransactionWatcherSignerService { this._signers.set(signer.publicAddress, signer); return signer.publicAddress; }); + this._metricsUpdateTimer = intervalUtils.setAsyncExcludingInterval( + async () => { + await this._updateLiveSatusAsync(); + }, + TX_WATCHER_UPDATE_METRICS_INTERVAL_MS, + (err: Error) => { + logger.error({ + message: `transaction watcher failed to update metrics and heartbeat: ${JSON.stringify(err)}`, + err: err.stack, + }); + }, + ); this._transactionWatcherTimer = intervalUtils.setAsyncExcludingInterval( async () => { logger.trace('syncing transaction status'); @@ -113,23 +123,6 @@ export class TransactionWatcherSignerService { help: 'Observed gas prices by the signer in gwei', labelNames: [SIGNER_ADDRESS_LABEL], }); - if (ENABLE_PROMETHEUS_METRICS) { - this._metricsUpdateTimer = intervalUtils.setAsyncExcludingInterval( - async () => { - logger.trace('updating metrics'); - await this._updateSignerBalancesAsync(); - logger.trace('heartbeat'); - await this._updateSignerStatusAsync(); - }, - TX_WATCHER_UPDATE_METRICS_INTERVAL_MS, - (err: Error) => { - logger.error({ - message: `transaction watcher failed to update metrics and heartbeat: ${JSON.stringify(err)}`, - err: err.stack, - }); - }, - ); - } } public stop(): void { intervalUtils.clearAsyncExcludingInterval(this._transactionWatcherTimer); @@ -181,7 +174,7 @@ export class TransactionWatcherSignerService { txEntity.nonce = web3WrapperUtils.convertHexToNumber(ethereumTxnParams.nonce); txEntity.from = ethereumTxnParams.from; this._gasPriceSummary.observe( - { signer_address: txEntity.from }, + { [SIGNER_ADDRESS_LABEL]: txEntity.from }, Web3Wrapper.toUnitAmount(txEntity.gasPrice, GWEI_DECIMALS).toNumber(), ); await this._updateTxEntityAsync(txEntity); @@ -267,7 +260,10 @@ export class TransactionWatcherSignerService { }); for (const tx of transactionsToAbort) { tx.status = TransactionStates.Aborted; - this._transactionsUpdateCounter.inc({ signer_address: tx.from, status: tx.status }, 1); + this._transactionsUpdateCounter.inc( + { [SIGNER_ADDRESS_LABEL]: tx.from, [TRANSACTION_STATUS_LABEL]: tx.status }, + 1, + ); await this._transactionRepository.save(tx); } @@ -285,7 +281,7 @@ export class TransactionWatcherSignerService { for (const tx of unsignedTransactions) { if (TransactionWatcherSignerService._isUnsubmittedTxExpired(tx)) { logger.error({ - message: `found a transaction in an unsubmitted state waiting longer that ${TX_HASH_RESPONSE_WAIT_TIME_MS}ms`, + message: `found a transaction in an unsubmitted state waiting longer than ${TX_HASH_RESPONSE_WAIT_TIME_MS}ms`, refHash: tx.refHash, from: tx.from, }); @@ -314,12 +310,12 @@ export class TransactionWatcherSignerService { return signer; } private async _getNextSignerAsync(): Promise { - const sortedSigners = await this._getSortedSignerPublicAddressesByAvailabilityAsync(); + const [selectedSigner] = await this._getSortedSignerPublicAddressesByAvailabilityAsync(); // TODO(oskar) - add random choice for top signers to better distribute // the fees. - const signer = this._signers.get(sortedSigners[0]); + const signer = this._signers.get(selectedSigner); if (signer === undefined) { - throw new Error(`signer with public address: ${sortedSigners[0]} is not available`); + throw new Error(`signer with public address: ${selectedSigner} is not available`); } return signer; @@ -328,7 +324,7 @@ export class TransactionWatcherSignerService { const signerMap = new Map(); this._availableSignerPublicAddresses.forEach(signerAddress => { const count = 0; - const balance = this._signerBalances.get(signerAddress) || 0; + const balance = this._signerBalancesEth.get(signerAddress) || 0; signerMap.set(signerAddress, { count, balance }); }); // TODO(oskar) - move to query builder? @@ -383,7 +379,7 @@ export class TransactionWatcherSignerService { }); continue; } - if (tx.gasPrice !== undefined && tx.gasPrice.isGreaterThanOrEqualTo(targetGasPrice)) { + if (!utils.isNil(tx.gasPrice) && tx.gasPrice.isGreaterThanOrEqualTo(targetGasPrice)) { logger.warn({ message: 'unsticking of transaction skipped as the targetGasPrice is less than or equal to the gas price it was submitted with', @@ -454,7 +450,10 @@ export class TransactionWatcherSignerService { } } private async _updateTxEntityAsync(txEntity: TransactionEntity): Promise { - this._transactionsUpdateCounter.inc({ signer_address: txEntity.from, status: txEntity.status }, 1); + this._transactionsUpdateCounter.inc( + { [SIGNER_ADDRESS_LABEL]: txEntity.from, [TRANSACTION_STATUS_LABEL]: txEntity.status }, + 1, + ); return this._transactionRepository.save(txEntity); } private async _updateSignerBalancesAsync(): Promise { @@ -473,16 +472,23 @@ export class TransactionWatcherSignerService { private async _updateSignerBalanceAsync(signerAddress: string): Promise { const signerBalance = await this._web3Wrapper.getBalanceInWeiAsync(signerAddress); const balanceInETH = Web3Wrapper.toUnitAmount(signerBalance, ETH_DECIMALS).toNumber(); - this._signerBalancesGauge.set({ signer_address: signerAddress }, balanceInETH); - this._signerBalances.set(signerAddress, balanceInETH); + this._signerBalancesGauge.set({ [SIGNER_ADDRESS_LABEL]: signerAddress }, balanceInETH); + this._signerBalancesEth.set(signerAddress, balanceInETH); } private _isSignerLive(): boolean { // TODO: better signer liveliness checks, we just check if any address // has more than 0.1 ETH available or signing has been explicitly disabled. - return ( - [...this._signerBalances.values()].filter(val => val > SIGNER_ETH_BALANCE_CONSIDERED_CRITICAL).length > 0 && - ENABLE_TRANSACTION_SIGNING - ); + const hasAvailableBalance = + Array.from(this._signerBalancesEth.values()).filter(val => val > META_TXN_MIN_SIGNER_ETH_BALANCE).length > + 0; + const isEnabled = META_TXN_SIGNING_ENABLED; + return hasAvailableBalance && isEnabled; + } + private async _updateLiveSatusAsync(): Promise { + logger.trace('updating metrics'); + await this._updateSignerBalancesAsync(); + logger.trace('heartbeat'); + await this._updateSignerStatusAsync(); } private async _updateSignerStatusAsync(): Promise { // TODO: do we need to find the entity first, for UPDATE? @@ -493,13 +499,13 @@ export class TransactionWatcherSignerService { const statusContent: TransactionWatcherSignerStatus = { live: this._isSignerLive(), // tslint:disable-next-line:no-inferred-empty-object-type - balances: [...this._signerBalances.entries()].reduce((acc: object, signerBalance: [string, number]): Record< - string, - number - > => { - const [from, balance] = signerBalance; - return { ...acc, [from]: balance }; - }, {}), + balances: Array.from(this._signerBalancesEth.entries()).reduce( + (acc: object, signerBalance: [string, number]): Record => { + const [from, balance] = signerBalance; + return { ...acc, [from]: balance }; + }, + {}, + ), }; statusKV.value = JSON.stringify(statusContent); await this._kvRepository.save(statusKV); diff --git a/src/types.ts b/src/types.ts index caba3b7b7..899acce80 100644 --- a/src/types.ts +++ b/src/types.ts @@ -400,6 +400,7 @@ export interface CalculateMetaTransactionPriceResponse { export interface PostTransactionResponse { ethereumTransactionHash: string; signedEthereumTransaction: string; + zeroExTransactionHash: string; } export interface ZeroExTransactionWithoutDomain { diff --git a/src/utils/gas_station_utils.ts b/src/utils/gas_station_utils.ts index f31eab953..6a9b156df 100644 --- a/src/utils/gas_station_utils.ts +++ b/src/utils/gas_station_utils.ts @@ -1,22 +1,35 @@ import { BigNumber } from '@0x/utils'; -import { ETH_GAS_STATION_API_BASE_URL } from '../constants'; +import { ETH_GAS_STATION_API_BASE_URL, ONE_SECOND_MS } from '../constants'; -export const ethGasStationUtils = { - getGasPriceOrThrowAsync: async (): Promise => { +let previousGasInfo; +let lastAccessed; +const CACHE_EXPIRY_SEC = 60; + +const getGasInfoAsync = async () => { + const now = Date.now() / ONE_SECOND_MS; + if (!previousGasInfo || now - CACHE_EXPIRY_SEC > lastAccessed) { try { const res = await fetch(`${ETH_GAS_STATION_API_BASE_URL}/json/ethgasAPI.json`); - const gasInfo = await res.json(); - // Eth Gas Station result is gwei * 10 - // tslint:disable-next-line:custom-no-magic-numbers - const BASE_TEN = 10; - const gasPriceGwei = new BigNumber(gasInfo.fast / BASE_TEN); - // tslint:disable-next-line:custom-no-magic-numbers - const unit = new BigNumber(BASE_TEN).pow(9); - const gasPriceWei = unit.times(gasPriceGwei); - return gasPriceWei; + previousGasInfo = await res.json(); + lastAccessed = now; } catch (e) { throw new Error('Failed to fetch gas price from EthGasStation'); } + return previousGasInfo; + } +}; + +export const ethGasStationUtils = { + getGasPriceOrThrowAsync: async (): Promise => { + const gasInfo = await getGasInfoAsync(); + // Eth Gas Station result is gwei * 10 + // tslint:disable-next-line:custom-no-magic-numbers + const BASE_TEN = 10; + const gasPriceGwei = new BigNumber(gasInfo.fast / BASE_TEN); + // tslint:disable-next-line:custom-no-magic-numbers + const unit = new BigNumber(BASE_TEN).pow(9); + const gasPriceWei = unit.times(gasPriceGwei); + return gasPriceWei; }, }; diff --git a/src/utils/signer.ts b/src/utils/signer.ts index 11d517146..411be5a97 100644 --- a/src/utils/signer.ts +++ b/src/utils/signer.ts @@ -77,7 +77,7 @@ export class Signer { message: `attempting to sign and broadcast a meta transaction`, nonceNumber: web3WrapperUtils.convertHexToNumber(ethereumTxnParams.nonce), from: ethereumTxnParams.from, - gasPrice: ethereumTxnParams.gasPrice, + gasPrice: web3WrapperUtils.convertHexToNumber(ethereumTxnParams.gasPrice), }); const signedEthereumTransaction = await this._privateWalletSubprovider.signTransactionAsync(ethereumTxnParams); const ethereumTransactionHash = await this._contractWrappers.exchange diff --git a/src/utils/utils.ts b/src/utils/utils.ts index a7a8eedf8..91cf152f2 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -86,4 +86,9 @@ export const utils = { return result; }); }, + isNil: (value: any): boolean => { + // undefined == null => true + // undefined == undefined => true + return value == null; + }, }; diff --git a/test/meta_transaction_test.ts b/test/meta_transaction_test.ts index 698257ea2..8a698b57e 100644 --- a/test/meta_transaction_test.ts +++ b/test/meta_transaction_test.ts @@ -597,7 +597,7 @@ describe(SUITE_NAME, () => { signature, }, headers: { - '0x-api-key': config.WHITELISTED_API_KEYS_META_TXN_SUBMIT[0], + '0x-api-key': config.META_TXN_SUBMIT_WHITELISTED_API_KEYS[0], }, }); expect(response.status).to.be.eq(HttpStatus.OK); @@ -708,7 +708,7 @@ describe(SUITE_NAME, () => { signature, }, headers: { - '0x-api-key': config.WHITELISTED_API_KEYS_META_TXN_SUBMIT[0], + '0x-api-key': config.META_TXN_SUBMIT_WHITELISTED_API_KEYS[0], }, }); expect(response.status).to.be.eq(HttpStatus.OK);