'use strict'; var Provider = require('./provider.js'); var utils = (function() { var convert = require('ethers-utils/convert.js'); return { defineProperty: require('ethers-utils/properties.js').defineProperty, hexlify: convert.hexlify, }; })(); function getTransactionString(transaction) { var result = []; for (var key in transaction) { if (transaction[key] == null) { continue; } result.push(key + '=' + utils.hexlify(transaction[key])); } return result.join('&'); } function EtherscanProvider(testnet, apiKey) { Provider.call(this, testnet); utils.defineProperty(this, 'apiKey', apiKey || null); } Provider.inherits(EtherscanProvider); utils.defineProperty(EtherscanProvider.prototype, '_call', function() { }); utils.defineProperty(EtherscanProvider.prototype, '_callProxy', function() { }); function getResult(result) { // getLogs has weird success responses if (result.status == 0 && result.message === 'No records found') { return result.result; } if (result.status != 1 || result.message != 'OK') { var error = new Error('invalid response'); error.result = JSON.stringify(result); throw error; } return result.result; } function getJsonResult(result) { if (result.jsonrpc != '2.0') { var error = new Error('invalid response'); error.result = JSON.stringify(result); throw error; } if (result.error) { var error = new Error(result.error.message || 'unknown error'); if (result.error.code) { error.code = result.error.code; } if (result.error.data) { error.data = result.error.data; } throw error; } return result.result; } function checkLogTag(blockTag) { if (blockTag === 'pending') { throw new Error('pending not supported'); } if (blockTag === 'latest') { return blockTag; } return parseInt(blockTag.substring(2), 16); } utils.defineProperty(EtherscanProvider.prototype, 'perform', function(method, params) { if (!params) { params = {}; } var url = this.testnet ? 'https://testnet.etherscan.io': 'https://api.etherscan.io'; var apiKey = ''; if (this.apiKey) { apiKey += '&apikey=' + this.apiKey; } switch (method) { case 'getBlockNumber': url += '/api?module=proxy&action=eth_blockNumber' + apiKey; return Provider.fetchJSON(url, null, getJsonResult); case 'getGasPrice': url += '/api?module=proxy&action=eth_gasPrice' + apiKey; return Provider.fetchJSON(url, null, getJsonResult); case 'getBalance': // Returns base-10 result url += '/api?module=account&action=balance&address=' + params.address; url += '&tag=' + params.blockTag + apiKey; return Provider.fetchJSON(url, null, getResult); case 'getTransactionCount': url += '/api?module=proxy&action=eth_getTransactionCount&address=' + params.address; url += '&tag=' + params.blockTag + apiKey; return Provider.fetchJSON(url, null, getJsonResult); case 'getCode': url += '/api?module=proxy&action=eth_getCode&address=' + params.address; url += '&tag=' + params.blockTag + apiKey; return Provider.fetchJSON(url, null, getJsonResult); case 'getStorageAt': url += '/api?module=proxy&action=eth_getStorageAt&address=' + params.address; url += '&position=' + params.position; url += '&tag=' + params.blockTag + apiKey; return Provider.fetchJSON(url, null, getJsonResult); case 'sendTransaction': url += '/api?module=proxy&action=eth_sendRawTransaction&hex=' + params.signedTransaction; url += apiKey; return Provider.fetchJSON(url, null, getJsonResult); case 'getBlock': if (params.blockTag) { url += '/api?module=proxy&action=eth_getBlockByNumber&tag=' + params.blockTag; url += '&boolean=false'; url += apiKey; return Provider.fetchJSON(url, null, getJsonResult); } throw new Error('getBlock by blockHash not implmeneted'); case 'getTransaction': url += '/api?module=proxy&action=eth_getTransactionByHash&txhash=' + params.transactionHash; url += apiKey; return Provider.fetchJSON(url, null, getJsonResult); case 'getTransactionReceipt': url += '/api?module=proxy&action=eth_getTransactionReceipt&txhash=' + params.transactionHash; url += apiKey; return Provider.fetchJSON(url, null, getJsonResult); case 'call': var transaction = getTransactionString(params.transaction); if (transaction) { transaction = '&' + transaction; } url += '/api?module=proxy&action=eth_call' + transaction; url += apiKey; return Provider.fetchJSON(url, null, getJsonResult); case 'estimateGas': var transaction = getTransactionString(params.transaction); if (transaction) { transaction = '&' + transaction; } url += '/api?module=proxy&action=eth_estimateGas&' + transaction; url += apiKey; return Provider.fetchJSON(url, null, getJsonResult); case 'getLogs': url += '/api?module=logs&action=getLogs'; try { if (params.filter.fromBlock) { url += '&fromBlock=' + checkLogTag(params.filter.fromBlock); } if (params.filter.toBlock) { url += '&toBlock=' + checkLogTag(params.filter.toBlock); } if (params.filter.address) { url += '&address=' + params.filter.address; } // @TODO: We can handle slightly more complicated logs using the logs API if (params.filter.topics && params.filter.topics.length > 0) { if (params.filter.topics.length > 1) { throw new Error('unsupported topic format'); } var topic0 = params.filter.topics[0]; if (typeof(topic0) !== 'string' || topic0.length !== 66) { throw new Error('unsupported topic0 format'); } url += '&topic0=' + topic0; } } catch (error) { return Promise.reject(error); } url += apiKey; return Provider.fetchJSON(url, null, getResult); default: break; } return Promise.reject(new Error('not implemented - ' + method)); }); module.exports = EtherscanProvider;;