Skip to content
This repository has been archived by the owner on Sep 28, 2022. It is now read-only.

Commit

Permalink
Merge pull request #87 from makerdao/gas-price
Browse files Browse the repository at this point in the history
Gas prices from EthGasStation
  • Loading branch information
ethanbennett authored Mar 22, 2019
2 parents 525742c + 4b90434 commit 987d790
Show file tree
Hide file tree
Showing 13 changed files with 414 additions and 206 deletions.
2 changes: 1 addition & 1 deletion src/config/ConfigFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const serviceRoles = [
'cdp',
'conversion',
'exchange',
'gasEstimator',
'gas',
'log',
'price',
'proxy',
Expand Down
6 changes: 3 additions & 3 deletions src/config/DefaultServiceProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import DSProxyService from '../eth/DSProxyService';
import EthereumCdpService from '../eth/EthereumCdpService';
import EthereumTokenService from '../eth/EthereumTokenService';
import EventService from '../utils/events/EventService';
import GasEstimatorService from '../eth/GasEstimatorService';
import GasService from '../eth/GasService';
import NonceService from '../eth/NonceService';
import NullEventService from '../utils/events/NullEventService';
import NullLogger from '../utils/loggers/NullLogger';
Expand All @@ -30,7 +30,7 @@ export const resolver = {
conversion: 'TokenConversionService',
event: 'EventService',
// exchange: intentionally omitted
gasEstimator: 'GasEstimatorService',
gas: 'GasService',
log: 'ConsoleLogger',
nonce: 'NonceService',
price: 'PriceService',
Expand Down Expand Up @@ -70,7 +70,7 @@ export default class DefaultServiceProvider extends ServiceProvider {
EthereumCdpService,
EthereumTokenService,
EventService,
GasEstimatorService,
GasService,
NonceService,
NullEventService,
NullLogger,
Expand Down
10 changes: 2 additions & 8 deletions src/eth/EthereumTokenService.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,7 @@ import ERC20TokenAbi from '../../contracts/abis/ERC20.json';

export default class EthereumTokenService extends PrivateService {
constructor(name = 'token') {
super(name, [
'smartContract',
'web3',
'log',
'gasEstimator',
'transactionManager'
]);
super(name, ['smartContract', 'web3', 'log', 'gas', 'transactionManager']);
this._tokens = tokens;
this._addedTokens = {};
}
Expand Down Expand Up @@ -51,7 +45,7 @@ export default class EthereumTokenService extends PrivateService {
if (symbol === tokens.ETH) {
return new EtherToken(
this.get('web3'),
this.get('gasEstimator'),
this.get('gas'),
this.get('transactionManager')
);
}
Expand Down
90 changes: 0 additions & 90 deletions src/eth/GasEstimatorService.js

This file was deleted.

159 changes: 159 additions & 0 deletions src/eth/GasService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { PublicService } from '@makerdao/services-core';
import { map } from 'lodash';
import fetch from 'isomorphic-fetch';

export default class GasService extends PublicService {
constructor(name = 'gas') {
super(name, ['web3', 'log']);
this._fallback = 4000000;
this._multiplier = 1.55;
this._transactionSpeed = 'fast';
}

initialize(settings) {
this._gasStationDataPromise = this.fetchGasStationData();

if (settings) {
this._parseConfig(settings.limit, 'limit');
this._parseConfig(settings.price, 'price');
}
}

_parseConfig(settings = 'default', label) {
return settings === 'default' || typeof settings === 'object'
? this._setProperties(settings, label)
: (this[label] = settings);
}

_setProperties(settings, label) {
if (settings === 'default') return;

return map(settings, (value, key) => {
if (key === 'disable') {
this[
'disable' + label.charAt(0).toUpperCase() + label.slice(1)
] = value;
} else {
this[key] = value;
}
});
}

async fetchGasStationData() {
try {
const response = await fetch(
'https://ethgasstation.info/json/ethgasAPI.json'
);
return response.json();
} catch (err) {
console.error('Error fetching gas data; disabling preset gas price');
this.disablePrice = true;
}
}

async getGasPrice(txSpeed) {
if (this.price) return this.price;
const speedSetting = txSpeed ? txSpeed : this.transactionSpeed;
const gasStationData = await this._gasStationDataPromise;

return gasStationData[speedSetting];
}

async getWaitTime(txSpeed) {
const speedSetting = txSpeed ? txSpeed : this.transactionSpeed;
const gasStationData = await this._gasStationDataPromise;

return gasStationData[`${speedSetting}Wait`];
}

async estimateGasLimit(transaction) {
if (this.limit) return this.limit;
if (this.disableLimit) return this.fallback;

let web3Data = [];
try {
web3Data = await Promise.all([
this.get('web3').getBlock('latest'),
this.get('web3').estimateGas(transaction)
]);
} catch (err) {
return this.fallback;
}

const blockLimit = web3Data[0].gasLimit;
const estimate = web3Data[1];

if (!this.multiplier && !this.absolute) {
return Math.min(this.absolute, blockLimit);
} else if (!this.absolute) {
return Math.min(parseInt(estimate * this.multiplier), blockLimit);
} else {
return Math.min(
parseInt(estimate * this.multiplier),
this.absolute,
blockLimit
);
}
}

get multiplier() {
return this._multiplier;
}

set multiplier(number) {
if (number <= 0) {
throw new Error('Gas limit multiplier must be greater than 0');
}

this._multiplier = number;
}

get absolute() {
return this._absolute;
}

set absolute(number) {
if (number <= 0) {
throw new Error('Absolute gas limit must be greater than 0');
}

this._absolute = number;
}

get fallback() {
return this._fallback;
}

set fallback(number) {
if (number <= 0) {
throw new Error('Fallback gas limit must be greater than 0');
}

this._fallback = number;
}

get transactionSpeed() {
return this._transactionSpeed;
}

set transactionSpeed(speed) {
const validKeys = ['average', 'fast', 'fastest', 'safeLow'];
if (!validKeys.includes(speed)) {
throw new Error(`Invalid transaction speed -- options are ${validKeys}`);
}

this._transactionSpeed = speed;
}

removeMultiplier() {
this._multiplier = null;
}

removeAbsolute() {
this._absolute = null;
}

removeFallback() {
this._fallback = null;
}
}
11 changes: 8 additions & 3 deletions src/eth/TransactionManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const log = debug('dai:TransactionManager');

export default class TransactionManager extends PublicService {
constructor(name = 'transactionManager') {
super(name, ['web3', 'log', 'nonce', 'proxy', 'gasEstimator']);
super(name, ['web3', 'log', 'nonce', 'proxy', 'gas']);
this._newTxListeners = [];
this._tracker = new Tracker();
}
Expand Down Expand Up @@ -130,7 +130,7 @@ export default class TransactionManager extends PublicService {
}

_createTransactionObject(tx, { businessObject, metadata, promise } = {}) {
const txo = new TransactionObject(tx, this.get('web3'), this.get('nonce'), {
const txo = new TransactionObject(tx, this, {
businessObject,
metadata
});
Expand Down Expand Up @@ -164,6 +164,11 @@ export default class TransactionManager extends PublicService {
);
}

if (!this.get('gas').disablePrice) {
let txSpeed = options.transactionSpeed;
options.gasPrice = await this.get('gas').getGasPrice(txSpeed);
}

return {
...options,
...this.get('web3').transactionSettings(),
Expand Down Expand Up @@ -193,7 +198,7 @@ export default class TransactionManager extends PublicService {
...transaction
};

return this.get('gasEstimator').estimateGasLimit(transaction);
return this.get('gas').estimateGasLimit(transaction);
}
}

Expand Down
19 changes: 13 additions & 6 deletions src/eth/TransactionObject.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@ const log = debug('dai:TransactionObject');
export default class TransactionObject extends TransactionLifeCycle {
constructor(
transaction,
web3Service,
nonceService,
transactionManager,
{ businessObject, metadata } = {}
) {
super(businessObject);
this._transaction = transaction;
this._web3Service = web3Service;
this._nonceService = nonceService;
this._web3Service = transactionManager.get('web3');
this._nonceService = transactionManager.get('nonce');
this._timeStampSubmitted = new Date();
this.metadata = metadata || {};
this._confirmedBlockCount = this._web3Service.confirmedBlockCount();
Expand Down Expand Up @@ -131,13 +130,21 @@ export default class TransactionObject extends TransactionLifeCycle {
let tx;
const startTime = new Date();
log(`waiting for transaction ${this.hash.substring(8)}... to mine`);
for (let i = 0; i < 240; i++) {
// 20 minutes max
for (let i = 0; i < 24; i++) {
// 2 minutes max
tx = await this._web3Service.getTransaction(this.hash);
if ((tx || {}).blockHash) break;
log('not mined yet');
await promiseWait(5000);
}

if (tx && !tx.blockHash) {
throw new Error(
'This transaction is taking longer than it should. Check its status on etherscan or try again. Tx hash:',
this.hash
);
}

const elapsed = (new Date() - startTime) / 1000;
log(`mined ${this.hash.substring(8)}... done in ${elapsed}s`);
return tx;
Expand Down
Loading

0 comments on commit 987d790

Please sign in to comment.