const EventEmitter = require('events').EventEmitter const inherits = require('util').inherits const async = require('async') const clone = require('clone') const EthQuery = require('eth-query') module.exports = EthereumStore inherits(EthereumStore, EventEmitter) function EthereumStore(engine) { const self = this EventEmitter.call(self) self._currentState = { accounts: {}, transactions: {}, } self._query = new EthQuery(engine) engine.on('block', self._updateForBlock.bind(self)) } // // public // EthereumStore.prototype.getState = function(){ const self = this return clone(self._currentState) } EthereumStore.prototype.addAccount = function(address){ const self = this self._currentState.accounts[address] = {} self._didUpdate() if (!self.currentBlockNumber) return self._updateAccount(self.currentBlockNumber, address, noop) } EthereumStore.prototype.removeAccount = function(address){ const self = this delete self._currentState.accounts[address] self._didUpdate() } EthereumStore.prototype.addTransaction = function(txHash){ const self = this self._currentState.transactions[txHash] = {} self._didUpdate() if (!self.currentBlockNumber) return self._updateTransaction(self.currentBlockNumber, txHash, noop) } EthereumStore.prototype.removeTransaction = function(address){ const self = this delete self._currentState.transactions[address] self._didUpdate() } // // private // EthereumStore.prototype._didUpdate = function() { const self = this var state = self.getState() self.emit('update', state) } EthereumStore.prototype._updateForBlock = function(block) { const self = this var blockNumber = '0x'+block.number.toString('hex') self.currentBlockNumber = blockNumber async.parallel([ self._updateAccounts.bind(self), self._updateTransactions.bind(self, blockNumber), ], function(err){ if (err) return console.error(err) self.emit('block', self.getState()) }) } EthereumStore.prototype._updateAccounts = function(cb) { const self = this var accountsState = self._currentState.accounts var addresses = Object.keys(accountsState) async.each(addresses, self._updateAccount.bind(self), cb) } EthereumStore.prototype._updateAccount = function(address, cb) { const self = this var accountsState = self._currentState.accounts self._query.getAccount(address, function(err, result){ if (err) return cb(err) result.address = address // only populate if the entry is still present if (accountsState[address]) { accountsState[address] = result self._didUpdate() } cb(null, result) }) } EthereumStore.prototype.getAccount = function(address, cb){ const block = 'latest' async.parallel({ balance: this._query.getBalance.bind(this, address, block), nonce: this._query.getNonce.bind(this, address, block), code: this._query.getCode.bind(this, address, block), }, cb) } EthereumStore.prototype._updateTransactions = function(block, cb) { const self = this var transactionsState = self._currentState.transactions var txHashes = Object.keys(transactionsState) async.each(txHashes, self._updateTransaction.bind(self, block), cb) } EthereumStore.prototype._updateTransaction = function(block, txHash, cb) { const self = this // would use the block here to determine how many confirmations the tx has var transactionsState = self._currentState.transactions self._query.getTransaction(txHash, function(err, result){ if (err) return cb(err) // only populate if the entry is still present if (transactionsState[txHash]) { transactionsState[txHash] = result self._didUpdate() } cb(null, result) }) } function valuesFor(obj){ return Object.keys(obj).map(function(key){ return obj[key] }) } function noop(){}