From ce5c62626336147e6071016459a59c494613e56e Mon Sep 17 00:00:00 2001 From: Martin Heidegger Date: Mon, 17 Jan 2022 13:08:32 +0900 Subject: [PATCH 1/9] lint: fixing identation to consistent spaces --- subproviders/etherscan.js | 68 +++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/subproviders/etherscan.js b/subproviders/etherscan.js index c6537887..d4c130df 100644 --- a/subproviders/etherscan.js +++ b/subproviders/etherscan.js @@ -38,35 +38,35 @@ function EtherscanProvider(opts) { this.times = isNaN(opts.times) ? 4 : opts.times; this.interval = isNaN(opts.interval) ? 1000 : opts.interval; this.retryFailed = typeof opts.retryFailed === 'boolean' ? opts.retryFailed : true; // not built yet - + setInterval(this.handleRequests, this.interval, this); } EtherscanProvider.prototype.handleRequests = function(self){ - if(self.requests.length == 0) return; - - //console.log('Handling the next ' + self.times + ' of ' + self.requests.length + ' requests'); - - for(var requestIndex = 0; requestIndex < self.times; requestIndex++) { - var requestItem = self.requests.shift() - - if(typeof requestItem !== 'undefined') - handlePayload(requestItem.proto, requestItem.network, requestItem.payload, requestItem.next, requestItem.end) - } + if(self.requests.length == 0) return; + + //console.log('Handling the next ' + self.times + ' of ' + self.requests.length + ' requests'); + + for(var requestIndex = 0; requestIndex < self.times; requestIndex++) { + var requestItem = self.requests.shift() + + if(typeof requestItem !== 'undefined') + handlePayload(requestItem.proto, requestItem.network, requestItem.payload, requestItem.next, requestItem.end) + } } EtherscanProvider.prototype.handleRequest = function(payload, next, end){ var requestObject = {proto: this.proto, network: this.network, payload: payload, next: next, end: end}, - self = this; - + self = this; + if(this.retryFailed) - requestObject.end = function(err, result){ - if(err === '403 - Forbidden: Access is denied.') - self.requests.push(requestObject); - else - end(err, result); - }; - + requestObject.end = function(err, result){ + if(err === '403 - Forbidden: Access is denied.') + self.requests.push(requestObject); + else + end(err, result); + }; + this.requests.push(requestObject); } @@ -142,7 +142,7 @@ function handlePayload(proto, network, payload, next, end){ tag: payloadObject.toBlock, boolean: payload.params[1] }, function(err, blockResult) { if(err) return end(err); - + for(var transaction in blockResult.transactions){ etherscanXHR(true, proto, network, 'proxy', 'eth_getTransactionReceipt', { txhash: transaction.hash }, function(err, receiptResult) { if(!err) logs.concat(receiptResult.logs); @@ -189,7 +189,7 @@ function toQueryString(params) { function etherscanXHR(useGetMethod, proto, network, module, action, params, end) { var uri = proto + '://' + network + '.etherscan.io/api?' + toQueryString({ module: module, action: action }) + '&' + toQueryString(params) - + xhr({ uri: uri, method: useGetMethod ? 'GET' : 'POST', @@ -202,19 +202,19 @@ function etherscanXHR(useGetMethod, proto, network, module, action, params, end) // console.log('[etherscan] response: ', err) if (err) return end(err) - - /*console.log('[etherscan request]' - + ' method: ' + useGetMethod - + ' proto: ' + proto - + ' network: ' + network - + ' module: ' + module - + ' action: ' + action - + ' params: ' + params - + ' return body: ' + body);*/ - + + /*console.log('[etherscan request]' + + ' method: ' + useGetMethod + + ' proto: ' + proto + + ' network: ' + network + + ' module: ' + module + + ' action: ' + action + + ' params: ' + params + + ' return body: ' + body);*/ + if(body.indexOf('403 - Forbidden: Access is denied.') > -1) - return end('403 - Forbidden: Access is denied.') - + return end('403 - Forbidden: Access is denied.') + var data try { data = JSON.parse(body) From 34a07f062c956218c0f42b20a32b43a44884e7bd Mon Sep 17 00:00:00 2001 From: Martin Heidegger Date: Mon, 17 Jan 2022 13:28:28 +0900 Subject: [PATCH 2/9] feat: adding support for api-keys --- subproviders/etherscan.js | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/subproviders/etherscan.js b/subproviders/etherscan.js index d4c130df..9958cca3 100644 --- a/subproviders/etherscan.js +++ b/subproviders/etherscan.js @@ -33,6 +33,7 @@ inherits(EtherscanProvider, Subprovider) function EtherscanProvider(opts) { opts = opts || {} this.network = opts.network || 'api' + this.apiKey = opts.apiKey || '' this.proto = (opts.https || false) ? 'https' : 'http' this.requests = []; this.times = isNaN(opts.times) ? 4 : opts.times; @@ -51,7 +52,7 @@ EtherscanProvider.prototype.handleRequests = function(self){ var requestItem = self.requests.shift() if(typeof requestItem !== 'undefined') - handlePayload(requestItem.proto, requestItem.network, requestItem.payload, requestItem.next, requestItem.end) + handlePayload(self.apiKey, requestItem.proto, requestItem.network, requestItem.payload, requestItem.next, requestItem.end) } } @@ -70,32 +71,32 @@ EtherscanProvider.prototype.handleRequest = function(payload, next, end){ this.requests.push(requestObject); } -function handlePayload(proto, network, payload, next, end){ +function handlePayload(apiKey, proto, network, payload, next, end){ switch(payload.method) { case 'eth_blockNumber': - etherscanXHR(true, proto, network, 'proxy', 'eth_blockNumber', {}, end) + etherscanXHR(apiKey, true, proto, network, 'proxy', 'eth_blockNumber', {}, end) return case 'eth_getBlockByNumber': - etherscanXHR(true, proto, network, 'proxy', 'eth_getBlockByNumber', { + etherscanXHR(apiKey, true, proto, network, 'proxy', 'eth_getBlockByNumber', { tag: payload.params[0], boolean: payload.params[1] }, end) return case 'eth_getBlockTransactionCountByNumber': - etherscanXHR(true, proto, network, 'proxy', 'eth_getBlockTransactionCountByNumber', { + etherscanXHR(apiKey, true, proto, network, 'proxy', 'eth_getBlockTransactionCountByNumber', { tag: payload.params[0] }, end) return case 'eth_getTransactionByHash': - etherscanXHR(true, proto, network, 'proxy', 'eth_getTransactionByHash', { + etherscanXHR(apiKey, true, proto, network, 'proxy', 'eth_getTransactionByHash', { txhash: payload.params[0] }, end) return case 'eth_getBalance': - etherscanXHR(true, proto, network, 'account', 'balance', { + etherscanXHR(apiKey, true, proto, network, 'account', 'balance', { address: payload.params[0], tag: payload.params[1] }, end) return @@ -116,19 +117,19 @@ function handlePayload(proto, network, payload, next, end){ params[props[i]] = payload.params[i] } - etherscanXHR(true, proto, network, 'account', 'txlist', params, end) + etherscanXHR(apiKey, true, proto, network, 'account', 'txlist', params, end) })() case 'eth_call': - etherscanXHR(true, proto, network, 'proxy', 'eth_call', payload.params[0], end) + etherscanXHR(apiKey, true, proto, network, 'proxy', 'eth_call', payload.params[0], end) return case 'eth_sendRawTransaction': - etherscanXHR(false, proto, network, 'proxy', 'eth_sendRawTransaction', { hex: payload.params[0] }, end) + etherscanXHR(apiKey, false, proto, network, 'proxy', 'eth_sendRawTransaction', { hex: payload.params[0] }, end) return case 'eth_getTransactionReceipt': - etherscanXHR(true, proto, network, 'proxy', 'eth_getTransactionReceipt', { txhash: payload.params[0] }, end) + etherscanXHR(apiKey, true, proto, network, 'proxy', 'eth_getTransactionReceipt', { txhash: payload.params[0] }, end) return // note !! this does not support topic filtering yet, it will return all block logs @@ -138,13 +139,13 @@ function handlePayload(proto, network, payload, next, end){ txProcessed = 0, logs = []; - etherscanXHR(true, proto, network, 'proxy', 'eth_getBlockByNumber', { + etherscanXHR(apiKey, true, proto, network, 'proxy', 'eth_getBlockByNumber', { tag: payloadObject.toBlock, boolean: payload.params[1] }, function(err, blockResult) { if(err) return end(err); for(var transaction in blockResult.transactions){ - etherscanXHR(true, proto, network, 'proxy', 'eth_getTransactionReceipt', { txhash: transaction.hash }, function(err, receiptResult) { + etherscanXHR(apiKey, true, proto, network, 'proxy', 'eth_getTransactionReceipt', { txhash: transaction.hash }, function(err, receiptResult) { if(!err) logs.concat(receiptResult.logs); txProcessed += 1; if(txProcessed === blockResult.transactions.length) end(null, logs) @@ -154,21 +155,21 @@ function handlePayload(proto, network, payload, next, end){ })() case 'eth_getTransactionCount': - etherscanXHR(true, proto, network, 'proxy', 'eth_getTransactionCount', { + etherscanXHR(apiKey, true, proto, network, 'proxy', 'eth_getTransactionCount', { address: payload.params[0], tag: payload.params[1] }, end) return case 'eth_getCode': - etherscanXHR(true, proto, network, 'proxy', 'eth_getCode', { + etherscanXHR(apiKey, true, proto, network, 'proxy', 'eth_getCode', { address: payload.params[0], tag: payload.params[1] }, end) return case 'eth_getStorageAt': - etherscanXHR(true, proto, network, 'proxy', 'eth_getStorageAt', { + etherscanXHR(apiKey, true, proto, network, 'proxy', 'eth_getStorageAt', { address: payload.params[0], position: payload.params[1], tag: payload.params[2] @@ -187,9 +188,9 @@ function toQueryString(params) { }).join('&') } -function etherscanXHR(useGetMethod, proto, network, module, action, params, end) { - var uri = proto + '://' + network + '.etherscan.io/api?' + toQueryString({ module: module, action: action }) + '&' + toQueryString(params) - +function etherscanXHR(apiKey, useGetMethod, proto, network, module, action, params, end) { + const qs = toQueryString({ module: module, action: action, apikey: apiKey }) + '&' + toQueryString(params) + const uri = `${proto}://${network}.etherscan.io/api?${qs}` xhr({ uri: uri, method: useGetMethod ? 'GET' : 'POST', From 75c4185fc4fe8715c055d86a8f146cac8726e4fe Mon Sep 17 00:00:00 2001 From: Martin Heidegger Date: Mon, 17 Jan 2022 13:30:55 +0900 Subject: [PATCH 3/9] feat: allowing to specify networks using common network specifiers --- subproviders/etherscan.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/subproviders/etherscan.js b/subproviders/etherscan.js index 9958cca3..e8725ac0 100644 --- a/subproviders/etherscan.js +++ b/subproviders/etherscan.js @@ -25,6 +25,7 @@ const xhr = process.browser ? require('xhr') : require('request') const inherits = require('util').inherits const Subprovider = require('./subprovider.js') +const MAINNET = 'mainnet' module.exports = EtherscanProvider @@ -32,7 +33,7 @@ inherits(EtherscanProvider, Subprovider) function EtherscanProvider(opts) { opts = opts || {} - this.network = opts.network || 'api' + this.network = opts.network || MAINNET this.apiKey = opts.apiKey || '' this.proto = (opts.https || false) ? 'https' : 'http' this.requests = []; @@ -189,8 +190,9 @@ function toQueryString(params) { } function etherscanXHR(apiKey, useGetMethod, proto, network, module, action, params, end) { + const subdomain = network === MAINNET ? 'api' : `api-${network}` const qs = toQueryString({ module: module, action: action, apikey: apiKey }) + '&' + toQueryString(params) - const uri = `${proto}://${network}.etherscan.io/api?${qs}` + const uri = `${proto}://${subdomain}.etherscan.io/api?${qs}` xhr({ uri: uri, method: useGetMethod ? 'GET' : 'POST', From 9e09aa00b8f920d06b9b02a7b0b999bf25f4cddb Mon Sep 17 00:00:00 2001 From: Martin Heidegger Date: Mon, 17 Jan 2022 13:33:18 +0900 Subject: [PATCH 4/9] fix: allowing any kind of statusCode --- subproviders/etherscan.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/subproviders/etherscan.js b/subproviders/etherscan.js index e8725ac0..6d062db1 100644 --- a/subproviders/etherscan.js +++ b/subproviders/etherscan.js @@ -206,6 +206,10 @@ function etherscanXHR(apiKey, useGetMethod, proto, network, module, action, para if (err) return end(err) + if (res.statusCode > 300) { + return end(res.statusMessage || body) + } + /*console.log('[etherscan request]' + ' method: ' + useGetMethod + ' proto: ' + proto @@ -214,10 +218,7 @@ function etherscanXHR(apiKey, useGetMethod, proto, network, module, action, para + ' action: ' + action + ' params: ' + params + ' return body: ' + body);*/ - - if(body.indexOf('403 - Forbidden: Access is denied.') > -1) - return end('403 - Forbidden: Access is denied.') - + var data try { data = JSON.parse(body) From 69615bdb26e8096a387f3abc7ac1c9a820d7e259 Mon Sep 17 00:00:00 2001 From: Martin Heidegger Date: Mon, 17 Jan 2022 13:34:10 +0900 Subject: [PATCH 5/9] fix: returning the error itself if the message isnt given. --- subproviders/etherscan.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subproviders/etherscan.js b/subproviders/etherscan.js index 6d062db1..daca89db 100644 --- a/subproviders/etherscan.js +++ b/subproviders/etherscan.js @@ -232,7 +232,7 @@ function etherscanXHR(apiKey, useGetMethod, proto, network, module, action, para // NOTE: or use id === -1? (id=1 is 'success') if ((module === 'proxy') && data.error) { // Maybe send back the code too? - return end(data.error.message) + return end(data.error.message || data.error) } // NOTE: or data.status !== 1? From d5650e287ddf1cff36b4ee9fc57fc7057a9ec029 Mon Sep 17 00:00:00 2001 From: Martin Heidegger Date: Mon, 17 Jan 2022 13:37:57 +0900 Subject: [PATCH 6/9] fix: de-referencing interval if no request is pending. --- subproviders/etherscan.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/subproviders/etherscan.js b/subproviders/etherscan.js index daca89db..669110cc 100644 --- a/subproviders/etherscan.js +++ b/subproviders/etherscan.js @@ -41,10 +41,18 @@ function EtherscanProvider(opts) { this.interval = isNaN(opts.interval) ? 1000 : opts.interval; this.retryFailed = typeof opts.retryFailed === 'boolean' ? opts.retryFailed : true; // not built yet - setInterval(this.handleRequests, this.interval, this); + this.intervalId = setInterval(this.handleRequests, this.interval, this); + unref(this.intervalId); } EtherscanProvider.prototype.handleRequests = function(self){ + self._handleRequests(self) + if(self.requests.length == 0) { + unref(self.intervalId); + } +} + +EtherscanProvider.prototype._handleRequests = function(self){ if(self.requests.length == 0) return; //console.log('Handling the next ' + self.times + ' of ' + self.requests.length + ' requests'); @@ -70,6 +78,7 @@ EtherscanProvider.prototype.handleRequest = function(payload, next, end){ }; this.requests.push(requestObject); + ref(this.intervalId); } function handlePayload(apiKey, proto, network, payload, next, end){ @@ -243,3 +252,12 @@ function etherscanXHR(apiKey, useGetMethod, proto, network, module, action, para end(null, data.result) }) } + +function unref (timeout) { + if (timeout.unref) timeout.unref(); +} + +function ref (timeout) { + if (timeout.ref) timeout.ref(); +} + From e8d91c7eec51b6bbf5fb59c337239ae6c8c510f3 Mon Sep 17 00:00:00 2001 From: Martin Heidegger Date: Mon, 17 Jan 2022 13:40:09 +0900 Subject: [PATCH 7/9] fix: only use defined properties during transaction listening. --- subproviders/etherscan.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/subproviders/etherscan.js b/subproviders/etherscan.js index 669110cc..8876de7e 100644 --- a/subproviders/etherscan.js +++ b/subproviders/etherscan.js @@ -124,7 +124,10 @@ function handlePayload(apiKey, proto, network, payload, next, end){ const params = {} for (let i = 0, l = Math.min(payload.params.length, props.length); i < l; i++) { - params[props[i]] = payload.params[i] + const value = payload.params[i] + if (value !== undefined) { + params[props[i]] = value + } } etherscanXHR(apiKey, true, proto, network, 'account', 'txlist', params, end) From cb61e5b6bf4d5acfc971bfb1580a2e1dd19d85c5 Mon Sep 17 00:00:00 2001 From: Martin Heidegger Date: Mon, 17 Jan 2022 13:41:50 +0900 Subject: [PATCH 8/9] fix: making sure that callbacks are always called with error instances --- subproviders/etherscan.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/subproviders/etherscan.js b/subproviders/etherscan.js index 8876de7e..75ce2f5c 100644 --- a/subproviders/etherscan.js +++ b/subproviders/etherscan.js @@ -66,6 +66,7 @@ EtherscanProvider.prototype._handleRequests = function(self){ } EtherscanProvider.prototype.handleRequest = function(payload, next, end){ + end = normalizeCallback(end) var requestObject = {proto: this.proto, network: this.network, payload: payload, next: next, end: end}, self = this; @@ -264,3 +265,16 @@ function ref (timeout) { if (timeout.ref) timeout.ref(); } +function normalizeError (err) { + if (err instanceof Error) return err + + return new Error("" + err) +} + +function normalizeCallback (cb) { + return function (err, result) { + if (err) err = normalizeError(err) + + cb(err, result) + } +} From 40105e816292b7acf467ebce5795fe1ebd84253f Mon Sep 17 00:00:00 2001 From: Martin Heidegger Date: Mon, 17 Jan 2022 13:43:42 +0900 Subject: [PATCH 9/9] feat: adding support for gas estimate through etherscan --- subproviders/etherscan.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/subproviders/etherscan.js b/subproviders/etherscan.js index 75ce2f5c..33ee994b 100644 --- a/subproviders/etherscan.js +++ b/subproviders/etherscan.js @@ -83,11 +83,21 @@ EtherscanProvider.prototype.handleRequest = function(payload, next, end){ } function handlePayload(apiKey, proto, network, payload, next, end){ + const params0 = payload.params[0] switch(payload.method) { case 'eth_blockNumber': etherscanXHR(apiKey, true, proto, network, 'proxy', 'eth_blockNumber', {}, end) return + case 'eth_estimateGas': + etherscanXHR(apiKey, true, proto, network, 'proxy', 'eth_estimateGas', pickNonNull({ + to: params0.to, + value: params0.value, + gasPrice: params0.gasPrice, + gas: params0.gas + }), end) + return + case 'eth_getBlockByNumber': etherscanXHR(apiKey, true, proto, network, 'proxy', 'eth_getBlockByNumber', { tag: payload.params[0], @@ -265,6 +275,17 @@ function ref (timeout) { if (timeout.ref) timeout.ref(); } +function pickNonNull (obj) { + const defined = {} + for (let key in obj) { + if (obj[key] != null) { + defined[key] = obj[key] + } + } + + return defined +} + function normalizeError (err) { if (err instanceof Error) return err