/* This file is part of web3.js. web3.js is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. web3.js is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ /** * @file index.js * @author Fabian Vogelsteller * @date 2017 */ "use strict"; var core = require('web3-core'); var helpers = require('web3-core-helpers'); var Subscriptions = require('web3-core-subscriptions').subscriptions; var Method = require('web3-core-method'); var utils = require('web3-utils'); var Net = require('web3-net'); var ENS = require('web3-eth-ens'); var Personal = require('web3-eth-personal'); var BaseContract = require('web3-eth-contract'); var Iban = require('web3-eth-iban'); var Accounts = require('web3-eth-accounts'); var abi = require('web3-eth-abi'); var getNetworkType = require('./getNetworkType.js'); var formatter = helpers.formatters; var blockCall = function (args) { return (typeof args[0] === 'string' && args[0].indexOf('0x') === 0) ? "eth_getBlockByHash" : "eth_getBlockByNumber"; }; var transactionFromBlockCall = function (args) { return (typeof args[0] === 'string' && args[0].indexOf('0x') === 0) ? 'eth_getTransactionByBlockHashAndIndex' : 'eth_getTransactionByBlockNumberAndIndex'; }; var uncleCall = function (args) { return (typeof args[0] === 'string' && args[0].indexOf('0x') === 0) ? 'eth_getUncleByBlockHashAndIndex' : 'eth_getUncleByBlockNumberAndIndex'; }; var getBlockTransactionCountCall = function (args) { return (typeof args[0] === 'string' && args[0].indexOf('0x') === 0) ? 'eth_getBlockTransactionCountByHash' : 'eth_getBlockTransactionCountByNumber'; }; var uncleCountCall = function (args) { return (typeof args[0] === 'string' && args[0].indexOf('0x') === 0) ? 'eth_getUncleCountByBlockHash' : 'eth_getUncleCountByBlockNumber'; }; var Eth = function Eth() { var _this = this; // sets _requestmanager core.packageInit(this, arguments); // overwrite package setRequestManager var setRequestManager = this.setRequestManager; this.setRequestManager = function (manager) { setRequestManager(manager); _this.net.setRequestManager(manager); _this.personal.setRequestManager(manager); _this.accounts.setRequestManager(manager); _this.Contract._requestManager = _this._requestManager; _this.Contract.currentProvider = _this._provider; return true; }; // overwrite setProvider var setProvider = this.setProvider; this.setProvider = function () { setProvider.apply(_this, arguments); _this.setRequestManager(_this._requestManager); // Set detectedAddress/lastSyncCheck back to null because the provider could be connected to a different chain now _this.ens._detectedAddress = null; _this.ens._lastSyncCheck = null; }; var handleRevert = false; var defaultAccount = null; var defaultBlock = 'latest'; var transactionBlockTimeout = 50; var transactionConfirmationBlocks = 24; var transactionPollingTimeout = 750; var blockHeaderTimeout = 10; // 10 seconds var maxListenersWarningThreshold = 100; var defaultChain, defaultHardfork, defaultCommon; Object.defineProperty(this, 'handleRevert', { get: function () { return handleRevert; }, set: function (val) { handleRevert = val; // also set on the Contract object _this.Contract.handleRevert = handleRevert; // update handleRevert methods.forEach(function (method) { method.handleRevert = handleRevert; }); }, enumerable: true }); Object.defineProperty(this, 'defaultCommon', { get: function () { return defaultCommon; }, set: function (val) { defaultCommon = val; // also set on the Contract object _this.Contract.defaultCommon = defaultCommon; // update defaultBlock methods.forEach(function (method) { method.defaultCommon = defaultCommon; }); }, enumerable: true }); Object.defineProperty(this, 'defaultHardfork', { get: function () { return defaultHardfork; }, set: function (val) { defaultHardfork = val; // also set on the Contract object _this.Contract.defaultHardfork = defaultHardfork; // update defaultBlock methods.forEach(function (method) { method.defaultHardfork = defaultHardfork; }); }, enumerable: true }); Object.defineProperty(this, 'defaultChain', { get: function () { return defaultChain; }, set: function (val) { defaultChain = val; // also set on the Contract object _this.Contract.defaultChain = defaultChain; // update defaultBlock methods.forEach(function (method) { method.defaultChain = defaultChain; }); }, enumerable: true }); Object.defineProperty(this, 'transactionPollingTimeout', { get: function () { return transactionPollingTimeout; }, set: function (val) { transactionPollingTimeout = val; // also set on the Contract object _this.Contract.transactionPollingTimeout = transactionPollingTimeout; // update defaultBlock methods.forEach(function (method) { method.transactionPollingTimeout = transactionPollingTimeout; }); }, enumerable: true }); Object.defineProperty(this, 'transactionConfirmationBlocks', { get: function () { return transactionConfirmationBlocks; }, set: function (val) { transactionConfirmationBlocks = val; // also set on the Contract object _this.Contract.transactionConfirmationBlocks = transactionConfirmationBlocks; // update defaultBlock methods.forEach(function (method) { method.transactionConfirmationBlocks = transactionConfirmationBlocks; }); }, enumerable: true }); Object.defineProperty(this, 'transactionBlockTimeout', { get: function () { return transactionBlockTimeout; }, set: function (val) { transactionBlockTimeout = val; // also set on the Contract object _this.Contract.transactionBlockTimeout = transactionBlockTimeout; // update defaultBlock methods.forEach(function (method) { method.transactionBlockTimeout = transactionBlockTimeout; }); }, enumerable: true }); Object.defineProperty(this, 'blockHeaderTimeout', { get: function () { return blockHeaderTimeout; }, set: function (val) { blockHeaderTimeout = val; // also set on the Contract object _this.Contract.blockHeaderTimeout = blockHeaderTimeout; // update defaultBlock methods.forEach(function (method) { method.blockHeaderTimeout = blockHeaderTimeout; }); }, enumerable: true }); Object.defineProperty(this, 'defaultAccount', { get: function () { return defaultAccount; }, set: function (val) { if (val) { defaultAccount = utils.toChecksumAddress(formatter.inputAddressFormatter(val)); } // also set on the Contract object _this.Contract.defaultAccount = defaultAccount; _this.personal.defaultAccount = defaultAccount; // update defaultBlock methods.forEach(function (method) { method.defaultAccount = defaultAccount; }); return val; }, enumerable: true }); Object.defineProperty(this, 'defaultBlock', { get: function () { return defaultBlock; }, set: function (val) { defaultBlock = val; // also set on the Contract object _this.Contract.defaultBlock = defaultBlock; _this.personal.defaultBlock = defaultBlock; // update defaultBlock methods.forEach(function (method) { method.defaultBlock = defaultBlock; }); return val; }, enumerable: true }); Object.defineProperty(this, 'maxListenersWarningThreshold', { get: function () { return maxListenersWarningThreshold; }, set: function (val) { if (_this.currentProvider && _this.currentProvider.setMaxListeners) { maxListenersWarningThreshold = val; _this.currentProvider.setMaxListeners(val); } }, enumerable: true }); this.clearSubscriptions = _this._requestManager.clearSubscriptions.bind(_this._requestManager); this.removeSubscriptionById = _this._requestManager.removeSubscription.bind(_this._requestManager); // add net this.net = new Net(this); // add chain detection this.net.getNetworkType = getNetworkType.bind(this); // add accounts this.accounts = new Accounts(this); // add personal this.personal = new Personal(this); this.personal.defaultAccount = this.defaultAccount; // set warnings threshold this.maxListenersWarningThreshold = maxListenersWarningThreshold; // create a proxy Contract type for this instance, as a Contract's provider // is stored as a class member rather than an instance variable. If we do // not create this proxy type, changing the provider in one instance of // web3-eth would subsequently change the provider for _all_ contract // instances! var self = this; var Contract = function Contract() { BaseContract.apply(this, arguments); // when Eth.setProvider is called, call packageInit // on all contract instances instantiated via this Eth // instances. This will update the currentProvider for // the contract instances var _this = this; var setProvider = self.setProvider; self.setProvider = function () { setProvider.apply(self, arguments); core.packageInit(_this, [self]); }; }; Contract.setProvider = function () { BaseContract.setProvider.apply(this, arguments); }; // make our proxy Contract inherit from web3-eth-contract so that it has all // the right functionality and so that instanceof and friends work properly Contract.prototype = Object.create(BaseContract.prototype); Contract.prototype.constructor = Contract; // add contract this.Contract = Contract; this.Contract.defaultAccount = this.defaultAccount; this.Contract.defaultBlock = this.defaultBlock; this.Contract.transactionBlockTimeout = this.transactionBlockTimeout; this.Contract.transactionConfirmationBlocks = this.transactionConfirmationBlocks; this.Contract.transactionPollingTimeout = this.transactionPollingTimeout; this.Contract.blockHeaderTimeout = this.blockHeaderTimeout; this.Contract.handleRevert = this.handleRevert; this.Contract._requestManager = this._requestManager; this.Contract._ethAccounts = this.accounts; this.Contract.currentProvider = this._requestManager.provider; // add IBAN this.Iban = Iban; // add ABI this.abi = abi; // add ENS this.ens = new ENS(this); var methods = [ new Method({ name: 'getNodeInfo', call: 'web3_clientVersion' }), new Method({ name: 'getProtocolVersion', call: 'eth_protocolVersion', params: 0 }), new Method({ name: 'getCoinbase', call: 'eth_coinbase', params: 0 }), new Method({ name: 'isMining', call: 'eth_mining', params: 0 }), new Method({ name: 'getHashrate', call: 'eth_hashrate', params: 0, outputFormatter: utils.hexToNumber }), new Method({ name: 'isSyncing', call: 'eth_syncing', params: 0, outputFormatter: formatter.outputSyncingFormatter }), new Method({ name: 'getGasPrice', call: 'eth_gasPrice', params: 0, outputFormatter: formatter.outputBigNumberFormatter }), new Method({ name: 'getFeeHistory', call: 'eth_feeHistory', params: 3, inputFormatter: [utils.numberToHex, formatter.inputBlockNumberFormatter, null] }), new Method({ name: 'getAccounts', call: 'eth_accounts', params: 0, outputFormatter: utils.toChecksumAddress }), new Method({ name: 'getBlockNumber', call: 'eth_blockNumber', params: 0, outputFormatter: utils.hexToNumber }), new Method({ name: 'getBalance', call: 'eth_getBalance', params: 2, inputFormatter: [formatter.inputAddressFormatter, formatter.inputDefaultBlockNumberFormatter], outputFormatter: formatter.outputBigNumberFormatter }), new Method({ name: 'getStorageAt', call: 'eth_getStorageAt', params: 3, inputFormatter: [formatter.inputAddressFormatter, utils.numberToHex, formatter.inputDefaultBlockNumberFormatter] }), new Method({ name: 'getCode', call: 'eth_getCode', params: 2, inputFormatter: [formatter.inputAddressFormatter, formatter.inputDefaultBlockNumberFormatter] }), new Method({ name: 'getBlock', call: blockCall, params: 2, inputFormatter: [formatter.inputBlockNumberFormatter, function (val) { return !!val; }], outputFormatter: formatter.outputBlockFormatter }), new Method({ name: 'getUncle', call: uncleCall, params: 2, inputFormatter: [formatter.inputBlockNumberFormatter, utils.numberToHex], outputFormatter: formatter.outputBlockFormatter, }), new Method({ name: 'getBlockTransactionCount', call: getBlockTransactionCountCall, params: 1, inputFormatter: [formatter.inputBlockNumberFormatter], outputFormatter: utils.hexToNumber }), new Method({ name: 'getBlockUncleCount', call: uncleCountCall, params: 1, inputFormatter: [formatter.inputBlockNumberFormatter], outputFormatter: utils.hexToNumber }), new Method({ name: 'getTransaction', call: 'eth_getTransactionByHash', params: 1, inputFormatter: [null], outputFormatter: formatter.outputTransactionFormatter }), new Method({ name: 'getTransactionFromBlock', call: transactionFromBlockCall, params: 2, inputFormatter: [formatter.inputBlockNumberFormatter, utils.numberToHex], outputFormatter: formatter.outputTransactionFormatter }), new Method({ name: 'getTransactionReceipt', call: 'eth_getTransactionReceipt', params: 1, inputFormatter: [null], outputFormatter: formatter.outputTransactionReceiptFormatter }), new Method({ name: 'getTransactionCount', call: 'eth_getTransactionCount', params: 2, inputFormatter: [formatter.inputAddressFormatter, formatter.inputDefaultBlockNumberFormatter], outputFormatter: utils.hexToNumber }), new Method({ name: 'sendSignedTransaction', call: 'eth_sendRawTransaction', params: 1, inputFormatter: [null], abiCoder: abi }), new Method({ name: 'signTransaction', call: 'eth_signTransaction', params: 1, inputFormatter: [formatter.inputTransactionFormatter] }), new Method({ name: 'sendTransaction', call: 'eth_sendTransaction', params: 1, inputFormatter: [formatter.inputTransactionFormatter], abiCoder: abi }), new Method({ name: 'sign', call: 'eth_sign', params: 2, inputFormatter: [formatter.inputSignFormatter, formatter.inputAddressFormatter], transformPayload: function (payload) { payload.params.reverse(); return payload; } }), new Method({ name: 'call', call: 'eth_call', params: 2, inputFormatter: [formatter.inputCallFormatter, formatter.inputDefaultBlockNumberFormatter], abiCoder: abi }), new Method({ name: 'estimateGas', call: 'eth_estimateGas', params: 1, inputFormatter: [formatter.inputCallFormatter], outputFormatter: utils.hexToNumber }), new Method({ name: 'submitWork', call: 'eth_submitWork', params: 3 }), new Method({ name: 'getWork', call: 'eth_getWork', params: 0 }), new Method({ name: 'getPastLogs', call: 'eth_getLogs', params: 1, inputFormatter: [formatter.inputLogFormatter], outputFormatter: formatter.outputLogFormatter }), new Method({ name: 'getChainId', call: 'eth_chainId', params: 0, outputFormatter: utils.hexToNumber }), new Method({ name: 'requestAccounts', call: 'eth_requestAccounts', params: 0, outputFormatter: utils.toChecksumAddress }), new Method({ name: 'getProof', call: 'eth_getProof', params: 3, inputFormatter: [formatter.inputAddressFormatter, formatter.inputStorageKeysFormatter, formatter.inputDefaultBlockNumberFormatter], outputFormatter: formatter.outputProofFormatter }), new Method({ name: 'getPendingTransactions', call: 'eth_pendingTransactions', params: 0, outputFormatter: formatter.outputTransactionFormatter }), new Method({ name: 'createAccessList', call: 'eth_createAccessList', params: 2, inputFormatter: [formatter.inputTransactionFormatter, formatter.inputDefaultBlockNumberFormatter], }), // subscriptions new Subscriptions({ name: 'subscribe', type: 'eth', subscriptions: { 'newBlockHeaders': { // TODO rename on RPC side? subscriptionName: 'newHeads', params: 0, outputFormatter: formatter.outputBlockFormatter }, 'pendingTransactions': { subscriptionName: 'newPendingTransactions', params: 0 }, 'logs': { params: 1, inputFormatter: [formatter.inputLogFormatter], outputFormatter: formatter.outputLogFormatter, // DUBLICATE, also in web3-eth-contract subscriptionHandler: function (output) { if (output.removed) { this.emit('changed', output); } else { this.emit('data', output); } if (typeof this.callback === 'function') { this.callback(null, output, this); } } }, 'syncing': { params: 0, outputFormatter: formatter.outputSyncingFormatter, subscriptionHandler: function (output) { var _this = this; // fire TRUE at start if (this._isSyncing !== true) { this._isSyncing = true; this.emit('changed', _this._isSyncing); if (typeof this.callback === 'function') { this.callback(null, _this._isSyncing, this); } setTimeout(function () { _this.emit('data', output); if (typeof _this.callback === 'function') { _this.callback(null, output, _this); } }, 0); // fire sync status } else { this.emit('data', output); if (typeof _this.callback === 'function') { this.callback(null, output, this); } // wait for some time before fireing the FALSE clearTimeout(this._isSyncingTimeout); this._isSyncingTimeout = setTimeout(function () { if (output.currentBlock > output.highestBlock - 200) { _this._isSyncing = false; _this.emit('changed', _this._isSyncing); if (typeof _this.callback === 'function') { _this.callback(null, _this._isSyncing, _this); } } }, 500); } } } } }) ]; methods.forEach(function (method) { method.attachToObject(_this); method.setRequestManager(_this._requestManager, _this.accounts); // second param is the eth.accounts module (necessary for signing transactions locally) method.defaultBlock = _this.defaultBlock; method.defaultAccount = _this.defaultAccount; method.transactionBlockTimeout = _this.transactionBlockTimeout; method.transactionConfirmationBlocks = _this.transactionConfirmationBlocks; method.transactionPollingTimeout = _this.transactionPollingTimeout; method.handleRevert = _this.handleRevert; }); }; // Adds the static givenProvider and providers property to the Eth module core.addProviders(Eth); module.exports = Eth;