'use strict'; var inherits = require('inherits'); var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; var networks = require('./networks.json'); var utils = (function() { var convert = require('ethers-utils/convert'); return { defineProperty: require('ethers-utils/properties').defineProperty, getAddress: require('ethers-utils/address').getAddress, getContractAddress: require('ethers-utils/contract-address').getContractAddress, bigNumberify: require('ethers-utils/bignumber').bigNumberify, arrayify: convert.arrayify, hexlify: convert.hexlify, isHexString: convert.isHexString, concat: convert.concat, stripZeros: convert.stripZeros, namehash: require('ethers-utils/namehash'), toUtf8String: require('ethers-utils/utf8').toUtf8String, RLP: require('ethers-utils/rlp'), } })(); function copyObject(obj) { var result = {}; for (var key in obj) { result[key] = obj[key]; } return result; } function check(format, object) { var result = {}; for (var key in format) { try { var value = format[key](object[key]); if (value !== undefined) { result[key] = value; } } catch (error) { error.checkKey = key; error.checkValue = object[key]; throw error; } } return result; } function allowNull(check, nullValue) { return (function(value) { if (value == null) { return nullValue; } return check(value); }); } function allowFalsish(check, replaceValue) { return (function(value) { if (!value) { return replaceValue; } return check(value); }); } function arrayOf(check) { return (function(array) { if (!Array.isArray(array)) { throw new Error('not an array'); } var result = []; array.forEach(function(value) { result.push(check(value)); }); return result; }); } function checkHash(hash) { if (!utils.isHexString(hash) || hash.length !== 66) { throw new Error('invalid hash - ' + hash); } return hash; } function checkNumber(number) { return utils.bigNumberify(number).toNumber(); } function checkBoolean(value) { if (typeof(value) === 'boolean') { return value; } if (typeof(value) === 'string') { if (value === 'true') { return true; } if (value === 'false') { return false; } } throw new Error('invaid boolean - ' + value); } function checkUint256(uint256) { if (!utils.isHexString(uint256)) { throw new Error('invalid uint256'); } while (uint256.length < 66) { uint256 = '0x0' + uint256.substring(2); } return uint256; } function checkString(string) { if (typeof(string) !== 'string') { throw new Error('invalid string'); } return string; } function checkBlockTag(blockTag) { if (blockTag == null) { return 'latest'; } if (blockTag === 'earliest') { return '0x0'; } if (blockTag === 'latest' || blockTag === 'pending') { return blockTag; } if (typeof(blockTag) === 'number') { return utils.hexlify(blockTag); } if (utils.isHexString(blockTag)) { return blockTag; } throw new Error('invalid blockTag'); } var formatBlock = { hash: checkHash, parentHash: checkHash, number: checkNumber, timestamp: checkNumber, nonce: allowNull(utils.hexlify), difficulty: allowNull(checkNumber), gasLimit: utils.bigNumberify, gasUsed: utils.bigNumberify, miner: utils.getAddress, extraData: utils.hexlify, //transactions: allowNull(arrayOf(checkTransaction)), transactions: allowNull(arrayOf(checkHash)), //transactionRoot: checkHash, //stateRoot: checkHash, //sha3Uncles: checkHash, //logsBloom: utils.hexlify, }; function checkBlock(block) { if (block.author != null && block.miner == null) { block.miner = block.author; } return check(formatBlock, block); } var formatTransaction = { hash: checkHash, blockHash: allowNull(checkHash, null), blockNumber: allowNull(checkNumber, null), transactionIndex: allowNull(checkNumber, null), from: utils.getAddress, gasPrice: utils.bigNumberify, gasLimit: utils.bigNumberify, to: allowNull(utils.getAddress, null), value: utils.bigNumberify, nonce: checkNumber, data: utils.hexlify, r: allowNull(checkUint256), s: allowNull(checkUint256), v: allowNull(checkNumber), creates: allowNull(utils.getAddress, null), raw: allowNull(utils.hexlify), }; function checkTransaction(transaction) { // Rename gas to gasLimit if (transaction.gas != null && transaction.gasLimit == null) { transaction.gasLimit = transaction.gas; } // Some clients (TestRPC) do strange things like return 0x0 for the // 0 address; correct this to be a real address if (transaction.to && utils.bigNumberify(transaction.to).isZero()) { transaction.to = '0x0000000000000000000000000000000000000000'; } // Rename input to data if (transaction.input != null && transaction.data == null) { transaction.data = transaction.input; } // If to and creates are empty, populate the creates from the transaction if (transaction.to == null && transaction.creates == null) { transaction.creates = utils.getContractAddress(transaction); } if (!transaction.raw) { // Very loose providers (e.g. TestRPC) don't provide a signature or raw if (transaction.v && transaction.r && transaction.s) { var raw = [ utils.stripZeros(utils.hexlify(transaction.nonce)), utils.stripZeros(utils.hexlify(transaction.gasPrice)), utils.stripZeros(utils.hexlify(transaction.gasLimit)), (transaction.to || "0x"), utils.stripZeros(utils.hexlify(transaction.value || '0x')), utils.hexlify(transaction.data || '0x'), utils.stripZeros(utils.hexlify(transaction.v || '0x')), utils.stripZeros(utils.hexlify(transaction.r)), utils.stripZeros(utils.hexlify(transaction.s)), ]; transaction.raw = utils.RLP.encode(raw); } } var result = check(formatTransaction, transaction); var networkId = transaction.networkId; if (utils.isHexString(networkId)) { networkId = utils.bigNumberify(networkId).toNumber(); } if (typeof(networkId) !== 'number' && result.v != null) { networkId = (result.v - 35) / 2; if (networkId < 0) { networkId = 0; } networkId = parseInt(networkId); } if (typeof(networkId) !== 'number') { networkId = 0; } result.networkId = networkId; // 0x0000... should actually be null if (result.blockHash && result.blockHash.replace(/0/g, '') === 'x') { result.blockHash = null; } return result; } var formatTransactionRequest = { from: allowNull(utils.getAddress), nonce: allowNull(checkNumber), gasLimit: allowNull(utils.bigNumberify), gasPrice: allowNull(utils.bigNumberify), to: allowNull(utils.getAddress), value: allowNull(utils.bigNumberify), data: allowNull(utils.hexlify), }; function checkTransactionRequest(transaction) { return check(formatTransactionRequest, transaction); } var formatTransactionReceiptLog = { transactionLogIndex: allowNull(checkNumber), transactionIndex: checkNumber, blockNumber: checkNumber, transactionHash: checkHash, address: utils.getAddress, // @TODO: Next major release remove this type: allowNull(checkString), topics: arrayOf(checkHash), data: utils.hexlify, logIndex: checkNumber, blockHash: checkHash, }; function checkTransactionReceiptLog(log) { return check(formatTransactionReceiptLog, log); } var formatTransactionReceipt = { contractAddress: allowNull(utils.getAddress, null), transactionIndex: checkNumber, root: allowNull(checkHash), gasUsed: utils.bigNumberify, logsBloom: utils.hexlify, blockHash: checkHash, transactionHash: checkHash, logs: arrayOf(checkTransactionReceiptLog), blockNumber: checkNumber, cumulativeGasUsed: utils.bigNumberify, status: allowNull(checkNumber) }; function checkTransactionReceipt(transactionReceipt) { var status = transactionReceipt.status; var root = transactionReceipt.root; var result = check(formatTransactionReceipt, transactionReceipt); result.logs.forEach(function(entry, index) { if (entry.transactionLogIndex == null) { entry.transactionLogIndex = index; } // @TODO: Remove this is next major version if (entry.type == null) { entry.type = 'mined'; } }); if (transactionReceipt.status != null) { result.byzantium = true; } return result; } function checkTopics(topics) { if (Array.isArray(topics)) { topics.forEach(function(topic) { checkTopics(topic); }); } else if (topics != null) { checkHash(topics); } return topics; } var formatFilter = { fromBlock: allowNull(checkBlockTag, undefined), toBlock: allowNull(checkBlockTag, undefined), address: allowNull(utils.getAddress, undefined), topics: allowNull(checkTopics, undefined), }; function checkFilter(filter) { return check(formatFilter, filter); } var formatLog = { blockNumber: allowNull(checkNumber), blockHash: allowNull(checkHash), transactionIndex: checkNumber, removed: allowNull(checkBoolean), address: utils.getAddress, data: allowFalsish(utils.hexlify, '0x'), topics: arrayOf(checkHash), transactionHash: checkHash, logIndex: checkNumber, } function checkLog(log) { return check(formatLog, log); } function Provider(network) { if (!(this instanceof Provider)) { throw new Error('missing new'); } network = Provider._legacyConstructor(network, arguments.length, arguments[0], arguments[1]); // Check the ensAddress (if any) var ensAddress = null; if (network.ensAddress) { ensAddress = utils.getAddress(network.ensAddress); } // Setup our network properties utils.defineProperty(this, 'chainId', network.chainId); utils.defineProperty(this, 'ensAddress', ensAddress); utils.defineProperty(this, 'name', network.name); // @TODO: Remove in the next major release utils.defineProperty(this, 'testnet', (network.name !== 'homestead')); var events = {}; utils.defineProperty(this, '_events', events); var self = this; var lastBlockNumber = null; var balances = {}; function doPoll() { self.getBlockNumber().then(function(blockNumber) { // If the block hasn't changed, meh. if (blockNumber === lastBlockNumber) { return; } if (lastBlockNumber === null) { lastBlockNumber = blockNumber - 1; } // Notify all listener for each block that has passed for (var i = lastBlockNumber + 1; i <= blockNumber; i++) { self.emit('block', i); } // Sweep balances and remove addresses we no longer have events for var newBalances = {}; // Find all transaction hashes we are waiting on Object.keys(events).forEach(function(eventName) { var event = parseEventString(eventName); if (event.type === 'transaction') { self.getTransaction(event.hash).then(function(transaction) { if (!transaction || !transaction.blockNumber) { return; } self.emit(event.hash, transaction); }); } else if (event.type === 'address') { if (balances[event.address]) { newBalances[event.address] = balances[event.address]; } self.getBalance(event.address, 'latest').then(function(balance) { var lastBalance = balances[event.address]; if (lastBalance && balance.eq(lastBalance)) { return; } balances[event.address] = balance; self.emit(event.address, balance); }); } else if (event.type === 'topic') { self.getLogs({ fromBlock: lastBlockNumber + 1, toBlock: blockNumber, topics: event.topic }).then(function(logs) { if (logs.length === 0) { return; } logs.forEach(function(log) { self.emit(event.topic, log); }); }); } }); lastBlockNumber = blockNumber; balances = newBalances; }); self.doPoll(); } utils.defineProperty(this, 'resetEventsBlock', function(blockNumber) { lastBlockNumber = blockNumber; self.doPoll(); }); var poller = null; Object.defineProperty(this, 'polling', { get: function() { return (poller != null); }, set: function(value) { setTimeout(function() { if (value && !poller) { poller = setInterval(doPoll, 4000); } else if (!value && poller) { clearInterval(poller); poller = null; } }, 0); } }); } function inheritable(parent) { return function(child) { inherits(child, parent); utils.defineProperty(child, 'inherits', inheritable(child)); } } utils.defineProperty(Provider, 'inherits', inheritable(Provider)); /* function(child) { inherits(child, Provider); child.inherits = function(grandchild) { inherits(grandchild, child) } }); */ utils.defineProperty(Provider, '_legacyConstructor', function(network, length, arg0, arg1) { // Legacy parameters Provider(testnet:boolean, chainId:Number) if (typeof(arg0) === 'boolean' || length === 2) { var testnet = !!arg0; var chainId = arg1; // true => testnet, false => mainnet network = networks[testnet ? 'ropsten': 'homestead']; // Overriding chain ID if (length === 2 && chainId != null) { network = { chainId: chainId, ensAddress: network.ensAddress, name: network.name }; } } else if (typeof(network) === 'string') { network = networks[network]; if (!network) { throw new Error('unknown network'); } } else if (network == null) { network = networks['homestead']; } if (typeof(network.chainId) !== 'number') { throw new Error('invalid chainId'); } return network; }); // @TODO: Remove in next major version (use networks instead) utils.defineProperty(Provider, 'chainId', { homestead: 1, morden: 2, ropsten: 3, rinkeby: 4, kovan: 42 }); //utils.defineProperty(Provider, 'isProvider', function(object) { // return (object instanceof Provider); //}); utils.defineProperty(Provider, 'networks', networks); utils.defineProperty(Provider, 'fetchJSON', function(url, json, processFunc) { return new Promise(function(resolve, reject) { var request = new XMLHttpRequest(); if (json) { request.open('POST', url, true); request.setRequestHeader('Content-Type','application/json'); } else { request.open('GET', url, true); } request.onreadystatechange = function() { if (request.readyState !== 4) { return; } try { var result = JSON.parse(request.responseText); } catch (error) { var jsonError = new Error('invalid json response'); jsonError.orginialError = error; jsonError.responseText = request.responseText; reject(jsonError); return; } if (processFunc) { try { result = processFunc(result); } catch (error) { error.url = url; error.body = json; error.responseText = request.responseText; reject(error); return; } } if (request.status != 200) { var error = new Error('invalid response - ' + request.status); error.statusCode = request.statusCode; reject(error); return; } resolve(result); }; request.onerror = function(error) { reject(error); } try { if (json) { request.send(json); } else { request.send(); } } catch (error) { var connectionError = new Error('connection error'); connectionError.error = error; reject(connectionError); } }); }); utils.defineProperty(Provider.prototype, 'waitForTransaction', function(transactionHash, timeout) { var self = this; return new Promise(function(resolve, reject) { var timer = null; function complete(transaction) { if (timer) { clearTimeout(timer); } resolve(transaction); } self.once(transactionHash, complete); if (typeof(timeout) === 'number' && timeout > 0) { timer = setTimeout(function() { self.removeListener(transactionHash, complete); reject(new Error('timeout')); }, timeout); } }); }); utils.defineProperty(Provider.prototype, 'getBlockNumber', function() { try { return this.perform('getBlockNumber').then(function(result) { var value = parseInt(result); if (value != result) { throw new Error('invalid response - getBlockNumber'); } return value; }); } catch (error) { return Promise.reject(error); } }); utils.defineProperty(Provider.prototype, 'getGasPrice', function() { try { return this.perform('getGasPrice').then(function(result) { return utils.bigNumberify(result); }); } catch (error) { return Promise.reject(error); } }); utils.defineProperty(Provider.prototype, 'getBalance', function(addressOrName, blockTag) { var self = this; return this.resolveName(addressOrName).then(function(address) { var params = { address: address, blockTag: checkBlockTag(blockTag) }; return self.perform('getBalance', params).then(function(result) { return utils.bigNumberify(result); }); }); }); utils.defineProperty(Provider.prototype, 'getTransactionCount', function(addressOrName, blockTag) { var self = this; return this.resolveName(addressOrName).then(function(address) { var params = { address: address, blockTag: checkBlockTag(blockTag) }; return self.perform('getTransactionCount', params).then(function(result) { var value = parseInt(result); if (value != result) { throw new Error('invalid response - getTransactionCount'); } return value; }); }); }); utils.defineProperty(Provider.prototype, 'getCode', function(addressOrName, blockTag) { var self = this; return this.resolveName(addressOrName).then(function(address) { var params = {address: address, blockTag: checkBlockTag(blockTag)}; return self.perform('getCode', params).then(function(result) { return utils.hexlify(result); }); }); }); utils.defineProperty(Provider.prototype, 'getStorageAt', function(addressOrName, position, blockTag) { var self = this; return this.resolveName(addressOrName).then(function(address) { var params = { address: address, blockTag: checkBlockTag(blockTag), position: utils.hexlify(position), }; return self.perform('getStorageAt', params).then(function(result) { return utils.hexlify(result); }); }); }); utils.defineProperty(Provider.prototype, 'sendTransaction', function(signedTransaction) { try { var params = {signedTransaction: utils.hexlify(signedTransaction)}; return this.perform('sendTransaction', params).then(function(result) { result = utils.hexlify(result); if (result.length !== 66) { throw new Error('invalid response - sendTransaction'); } return result; }); } catch (error) { return Promise.reject(error); } }); utils.defineProperty(Provider.prototype, 'call', function(transaction) { var self = this; return this._resolveNames(transaction, [ 'to', 'from' ]).then(function(transaction) { var params = { transaction: checkTransactionRequest(transaction) }; return self.perform('call', params).then(function(result) { return utils.hexlify(result); }); }); }); utils.defineProperty(Provider.prototype, 'estimateGas', function(transaction) { var self = this; return this._resolveNames(transaction, [ 'to', 'from' ]).then(function(transaction) { var params = {transaction: checkTransactionRequest(transaction)}; return self.perform('estimateGas', params).then(function(result) { return utils.bigNumberify(result); }); }); }); utils.defineProperty(Provider.prototype, 'getBlock', function(blockHashOrBlockTag) { try { var blockHash = utils.hexlify(blockHashOrBlockTag); if (blockHash.length === 66) { return this.perform('getBlock', {blockHash: blockHash}).then(function(block) { if (block == null) { return null; } return checkBlock(block); }); } } catch (error) { console.log('DEBUG', error); } try { var blockTag = checkBlockTag(blockHashOrBlockTag); return this.perform('getBlock', {blockTag: blockTag}).then(function(block) { if (block == null) { return null; } return checkBlock(block); }); } catch (error) { console.log('DEBUG', error); } return Promise.reject(new Error('invalid block hash or block tag')); }); utils.defineProperty(Provider.prototype, 'getTransaction', function(transactionHash) { try { var params = { transactionHash: checkHash(transactionHash) }; return this.perform('getTransaction', params).then(function(result) { if (result != null) { result = checkTransaction(result); } return result; }); } catch (error) { return Promise.reject(error); } }); utils.defineProperty(Provider.prototype, 'getTransactionReceipt', function(transactionHash) { try { var params = { transactionHash: checkHash(transactionHash) }; return this.perform('getTransactionReceipt', params).then(function(result) { if (result != null) { result = checkTransactionReceipt(result); } return result; }); } catch (error) { return Promise.reject(error); } }); utils.defineProperty(Provider.prototype, 'getLogs', function(filter) { var self = this; return this._resolveNames(filter, ['address']).then(function(filter) { var params = { filter: checkFilter(filter) }; return self.perform('getLogs', params).then(function(result) { return arrayOf(checkLog)(result); }); }); }); utils.defineProperty(Provider.prototype, 'getEtherPrice', function() { try { return this.perform('getEtherPrice', {}).then(function(result) { // @TODO: Check valid float return result; }); } catch (error) { return Promise.reject(error); } }); utils.defineProperty(Provider.prototype, '_resolveNames', function(object, keys) { var promises = []; var result = copyObject(object); keys.forEach(function(key) { if (result[key] === undefined) { return; } promises.push(this.resolveName(result[key]).then(function(address) { result[key] = address; })); }, this); return Promise.all(promises).then(function() { return result; }); }); utils.defineProperty(Provider.prototype, '_getResolver', function(name) { var nodeHash = utils.namehash(name); // keccak256('resolver(bytes32)') var data = '0x0178b8bf' + nodeHash.substring(2); var transaction = { to: this.ensAddress, data: data }; // Get the resolver from the blockchain return this.call(transaction).then(function(data) { // extract the address from the data if (data.length != 66) { return null; } return utils.getAddress('0x' + data.substring(26)); }); }); utils.defineProperty(Provider.prototype, 'resolveName', function(name) { // If it is already an address, nothing to resolve try { return Promise.resolve(utils.getAddress(name)); } catch (error) { } var self = this; var nodeHash = utils.namehash(name); // Get the addr from the resovler return this._getResolver(name).then(function(resolverAddress) { // keccak256('addr(bytes32)') var data = '0x3b3b57de' + nodeHash.substring(2); var transaction = { to: resolverAddress, data: data }; return self.call(transaction); // extract the address from the data }).then(function(data) { if (data.length != 66) { return null; } var address = utils.getAddress('0x' + data.substring(26)); if (address === '0x0000000000000000000000000000000000000000') { return null; } return address; }); }); utils.defineProperty(Provider.prototype, 'lookupAddress', function(address) { address = utils.getAddress(address); var name = address.substring(2) + '.addr.reverse' var nodehash = utils.namehash(name); var self = this; return this._getResolver(name).then(function(resolverAddress) { if (!resolverAddress) { return null; } // keccak('name(bytes32)') var data = '0x691f3431' + nodehash.substring(2); var transaction = { to: resolverAddress, data: data }; return self.call(transaction); }).then(function(data) { // Strip off the "0x" data = data.substring(2); // Strip off the dynamic string pointer (0x20) if (data.length < 64) { return null; } data = data.substring(64); if (data.length < 64) { return null; } var length = utils.bigNumberify('0x' + data.substring(0, 64)).toNumber(); data = data.substring(64); if (2 * length > data.length) { return null; } var name = utils.toUtf8String('0x' + data.substring(0, 2 * length)); // Make sure the reverse record matches the foward record return self.resolveName(name).then(function(addr) { if (addr != address) { return null; } return name; }); }); }); utils.defineProperty(Provider.prototype, 'doPoll', function() { }); utils.defineProperty(Provider.prototype, 'perform', function(method, params) { return Promise.reject(new Error('not implemented - ' + method)); }); function recurse(object, convertFunc) { if (Array.isArray(object)) { var result = []; object.forEach(function(object) { result.push(recurse(object, convertFunc)); }); return result; } return convertFunc(object); } function getEventString(object) { try { return 'address:' + utils.getAddress(object); } catch (error) { } if (object === 'block') { return 'block'; } else if (object === 'pending') { return 'pending'; } else if (utils.isHexString(object)) { if (object.length === 66) { return 'tx:' + object; } } else if (Array.isArray(object)) { object = recurse(object, function(object) { if (object == null) { object = '0x'; } return object; }); try { return 'topic:' + utils.RLP.encode(object); } catch (error) { console.log(error); } } throw new Error('invalid event - ' + object); } function parseEventString(string) { if (string.substring(0, 3) === 'tx:') { return {type: 'transaction', hash: string.substring(3)}; } else if (string === 'block') { return {type: 'block'}; } else if (string === 'pending') { return {type: 'pending'}; } else if (string.substring(0, 8) === 'address:') { return {type: 'address', address: string.substring(8)}; } else if (string.substring(0, 6) === 'topic:') { try { var object = utils.RLP.decode(string.substring(6)); object = recurse(object, function(object) { if (object === '0x') { object = null; } return object; }); return {type: 'topic', topic: object}; } catch (error) { console.log(error); } } throw new Error('invalid event string'); } utils.defineProperty(Provider.prototype, '_startPending', function() { console.log('WARNING: this provider does not support pending events'); }); utils.defineProperty(Provider.prototype, '_stopPending', function() { }); utils.defineProperty(Provider.prototype, 'on', function(eventName, listener) { var key = getEventString(eventName); if (!this._events[key]) { this._events[key] = []; } this._events[key].push({eventName: eventName, listener: listener, type: 'on'}); if (key === 'pending') { this._startPending(); } this.polling = true; }); utils.defineProperty(Provider.prototype, 'once', function(eventName, listener) { var key = getEventString(eventName); if (!this._events[key]) { this._events[key] = []; } this._events[key].push({eventName: eventName, listener: listener, type: 'once'}); if (key === 'pending') { this._startPending(); } this.polling = true; }); utils.defineProperty(Provider.prototype, 'emit', function(eventName) { var key = getEventString(eventName); var args = Array.prototype.slice.call(arguments, 1); var listeners = this._events[key]; if (!listeners) { return; } for (var i = 0; i < listeners.length; i++) { var listener = listeners[i]; if (listener.type === 'once') { listeners.splice(i, 1); i--; } try { listener.listener.apply(this, args); } catch (error) { console.log('Event Listener Error: ' + error.message); } } if (listeners.length === 0) { delete this._events[key]; if (key === 'pending') { this._stopPending(); } } if (this.listenerCount() === 0) { this.polling = false; } }); utils.defineProperty(Provider.prototype, 'listenerCount', function(eventName) { if (!eventName) { var result = 0; for (var key in this._events) { result += this._events[key].length; } return result; } var listeners = this._events[getEventString(eventName)]; if (!listeners) { return 0; } return listeners.length; }); utils.defineProperty(Provider.prototype, 'listeners', function(eventName) { var listeners = this._events[getEventString(eventName)]; if (!listeners) { return 0; } var result = []; for (var i = 0; i < listeners.length; i++) { result.push(listeners[i].listener); } return result; }); utils.defineProperty(Provider.prototype, 'removeAllListeners', function(eventName) { delete this._events[getEventString(eventName)]; if (this.listenerCount() === 0) { this.polling = false; } }); utils.defineProperty(Provider.prototype, 'removeListener', function(eventName, listener) { var listeners = this._events[getEventString(eventName)]; if (!listeners) { return 0; } for (var i = 0; i < listeners.length; i++) { if (listeners[i].listener === listener) { listeners.splice(i, 1); return; } } if (this.listenerCount() === 0) { this.polling = false; } }); utils.defineProperty(Provider, '_formatters', { checkTransactionResponse: checkTransaction }); module.exports = Provider;