├── .gitignore ├── README.md ├── index.js ├── package.json └── test ├── index.js └── infura.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Eth Gas Price Suggestor 2 | 3 | A module for getting the current average gas price of ethereum transactions. 4 | 5 | ## Installation 6 | 7 | `npm install eth-gas-price-suggestor -S` 8 | 9 | ## Example Usage 10 | 11 | ```javascript 12 | const Eth = require('ethjs'); 13 | const provider = new Eth.HttpProvider('https://mainnet.infura.io'); 14 | const BlockTracker = require('eth-block-tracker') 15 | const Suggestor = require('eth-gas-price-suggestor') 16 | 17 | const blockTracker = new BlockTracker({ provider }) 18 | blockTracker.start() 19 | 20 | const suggestor = new Suggestor({ 21 | blockTracker, 22 | historyLength: 10, // number of blocks to average, default 20. 23 | defaultPrice: 20000000000, // In case of network error. Default 20 gwei. 24 | }) 25 | 26 | setInterval(async function() { 27 | try { 28 | const suggested = await suggestor.currentAverage() 29 | console.log('CURRENT SUGGESTION in GWEI: ' + Eth.fromWei(suggested, 'gwei')) 30 | } catch (e) { 31 | console.log('failed: ', e) 32 | } 33 | }, 10000) 34 | 35 | ``` 36 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const BN = require('bn.js') 2 | const Eth = require('ethjs'); 3 | 4 | class Suggestor { 5 | 6 | constructor (opts = {}) { 7 | this.blockTracker = opts.blockTracker 8 | if (!this.blockTracker) { 9 | throw new Error('gas suggestor requires a block tracker.') 10 | } 11 | 12 | this.historyLength = opts.historyLength || 20 13 | this.defaultPrice = opts.defaultPrice || 20000000000 14 | 15 | this.query = this.blockTracker._query 16 | this.recentPriceAverages = [] 17 | this.firstPriceQuery = this.fetchFirstGasPrice() 18 | this.trackBlocks() 19 | } 20 | 21 | trackBlocks() { 22 | this.blockTracker.on('block', block => this.processBlock(block)) 23 | } 24 | 25 | processBlock (newBlock) { 26 | if (newBlock.transactions.length === 0) { 27 | return 28 | } 29 | 30 | const gasPriceSum = newBlock.transactions 31 | .map(tx => Eth.toBN(tx.gasPrice)) 32 | .reduce((result, gasPrice) => { 33 | return result.add(gasPrice) 34 | }, new BN(0)) 35 | 36 | const average = gasPriceSum.divn(newBlock.transactions.length) 37 | this.recentPriceAverages.push(Math.round(average.toNumber())) 38 | 39 | if (this.recentPriceAverages.length > this.historyLength) { 40 | this.recentPriceAverages.shift() 41 | } 42 | } 43 | 44 | fetchFirstGasPrice() { 45 | return new Promise((resolve, reject) => { 46 | 47 | this.query.gasPrice((err, gasPriceBn) => { 48 | if (err) { 49 | console.warn('Failed to retrieve gas price, defaulting.', err) 50 | this.fillHistoryWith(this.defaultPrice) 51 | } else { 52 | const gasPrice = parseInt(gasPriceBn.toString(10)) 53 | this.fillHistoryWith(gasPrice) 54 | } 55 | return resolve(this.currentAverage()) 56 | }) 57 | }) 58 | } 59 | 60 | fillHistoryWith (value) { 61 | this.recentPriceAverages.push(value) 62 | } 63 | 64 | async currentAverage() { 65 | if (this.recentPriceAverages.length === 0) { 66 | return this.firstPriceQuery 67 | } 68 | 69 | const sum = this.recentPriceAverages.reduce((result, value) => { 70 | return result + value 71 | }, 0) 72 | const result = sum / this.recentPriceAverages.length 73 | return result 74 | } 75 | 76 | } 77 | 78 | module.exports = Suggestor 79 | 80 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eth-gas-price-suggestor", 3 | "version": "1.0.2", 4 | "description": "A module for suggesting a reasonable successful ethereum gas price.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node test" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+ssh://git@github.com/flyswatter/eth-gas-price-suggestor.git" 12 | }, 13 | "keywords": [ 14 | "ethereum", 15 | "gas", 16 | "price" 17 | ], 18 | "author": "Dan Finlay ", 19 | "license": "ISC", 20 | "bugs": { 21 | "url": "https://github.com/flyswatter/eth-gas-price-suggestor/issues" 22 | }, 23 | "homepage": "https://github.com/flyswatter/eth-gas-price-suggestor#readme", 24 | "devDependencies": { 25 | "eth-block-tracker": "^2.0.1", 26 | "ethereumjs-testrpc": "^3.0.5", 27 | "ethjs-query": "^0.2.6", 28 | "tape": "^4.6.3" 29 | }, 30 | "dependencies": { 31 | "bn.js": "^4.11.7", 32 | "ethjs": "^0.2.7" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const test = require('tape') 2 | const BN = require('bn.js') 3 | const TestRPC = require('ethereumjs-testrpc') 4 | const Eth = require('ethjs-query') 5 | const BlockTracker = require('eth-block-tracker') 6 | const DEFAULT_PRICE_BN = new BN(20000000000) 7 | const DEFAULT_PRICE = DEFAULT_PRICE_BN.toString() 8 | 9 | const Suggestor = require('../') 10 | 11 | test('should initialize and advise current eth_gasPrice', async function (t) { 12 | const provider = TestRPC.provider({ locked: false }) 13 | const blockTracker = new BlockTracker({ provider }) 14 | blockTracker.start({ fromBlock: '0x00' }) 15 | const suggestor = new Suggestor({ blockTracker }) 16 | let suggested 17 | try { 18 | suggested = await suggestor.currentAverage() 19 | } catch (e) { 20 | t.error(e, 'should not fail') 21 | } 22 | t.equal(String(suggested), DEFAULT_PRICE, 'Default testrpc gas price.') 23 | blockTracker.stop() 24 | t.end() 25 | }) 26 | 27 | test('should increase with a new tx added', async function (t) { 28 | t.plan(1) 29 | const provider = TestRPC.provider({ locked: false }) 30 | const eth = new Eth(provider) 31 | const doublePrice = DEFAULT_PRICE_BN.muln(2).toString() 32 | let suggested, blockTracker 33 | 34 | try { 35 | 36 | const accounts = await eth.accounts() 37 | const account = accounts[0] 38 | 39 | blockTracker = new BlockTracker({ provider }) 40 | blockTracker.start({ fromBlock: '0x00' }) 41 | const historyLength = 2 42 | const suggestor = new Suggestor({ blockTracker, historyLength }) 43 | 44 | let txHash = await eth.sendTransaction({ 45 | from: account, 46 | to: account, 47 | value: '1', 48 | gasPrice: doublePrice, 49 | data: null, 50 | }) 51 | 52 | let tx 53 | while (!tx) { 54 | tx = await eth.getTransactionByHash(txHash) 55 | } 56 | 57 | suggested = await suggestor.currentAverage() 58 | } catch (e) { 59 | t.error(e, 'should not fail') 60 | blockTracker.stop() 61 | t.end() 62 | } 63 | 64 | t.equal(String(suggested), doublePrice, 'Doubled testrpc gas price.') 65 | blockTracker.stop() 66 | t.end() 67 | }) 68 | 69 | -------------------------------------------------------------------------------- /test/infura.js: -------------------------------------------------------------------------------- 1 | const test = require('tape') 2 | const BN = require('bn.js') 3 | const TestRPC = require('ethereumjs-testrpc') 4 | const BlockTracker = require('eth-block-tracker') 5 | const DEFAULT_PRICE_BN = new BN(20000000000) 6 | const DEFAULT_PRICE = DEFAULT_PRICE_BN.toString() 7 | 8 | const Eth = require('ethjs'); 9 | const provider = new Eth.HttpProvider('https://mainnet.infura.io'); 10 | 11 | const Suggestor = require('../') 12 | 13 | const blockTracker = new BlockTracker({ provider }) 14 | blockTracker.start() 15 | 16 | const suggestor = new Suggestor({ blockTracker }) 17 | 18 | setInterval(async function() { 19 | try { 20 | const suggested = await suggestor.currentAverage() 21 | console.log('CURRENT SUGGESTION in GWEI: ' + Eth.fromWei(suggested, 'gwei')) 22 | } catch (e) { 23 | console.log('failed: ', e) 24 | } 25 | }, 10000) 26 | 27 | 28 | --------------------------------------------------------------------------------