diff options
Diffstat (limited to 'app/scripts')
-rw-r--r-- | app/scripts/controllers/network/contract-addresses.js | 11 | ||||
-rw-r--r-- | app/scripts/controllers/preferences.js | 13 | ||||
-rw-r--r-- | app/scripts/lib/account-tracker.js | 61 | ||||
-rw-r--r-- | app/scripts/metamask-controller.js | 59 |
4 files changed, 140 insertions, 4 deletions
diff --git a/app/scripts/controllers/network/contract-addresses.js b/app/scripts/controllers/network/contract-addresses.js new file mode 100644 index 000000000..5cd7da1d0 --- /dev/null +++ b/app/scripts/controllers/network/contract-addresses.js @@ -0,0 +1,11 @@ +const SINGLE_CALL_BALANCES_ADDRESS = '0xb1f8e55c7f64d203c1400b9d8555d050f94adf39' +const SINGLE_CALL_BALANCES_ADDRESS_RINKEBY = '0x9f510b19f1ad66f0dcf6e45559fab0d6752c1db7' +const SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN = '0xb8e671734ce5c8d7dfbbea5574fa4cf39f7a54a4' +const SINGLE_CALL_BALANCES_ADDRESS_KOVAN = '0xb1d3fbb2f83aecd196f474c16ca5d9cffa0d0ffc' + +module.exports = { + SINGLE_CALL_BALANCES_ADDRESS, + SINGLE_CALL_BALANCES_ADDRESS_RINKEBY, + SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN, + SINGLE_CALL_BALANCES_ADDRESS_KOVAN, +} diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 565f4f292..584b6bc51 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -18,7 +18,9 @@ class PreferencesController { * @property {object} store.assetImages Contains assets objects related to assets added * @property {boolean} store.useBlockie The users preference for blockie identicons within the UI * @property {object} store.featureFlags A key-boolean map, where keys refer to features and booleans to whether the - * user wishes to see that feature + * user wishes to see that feature. + * + * Feature flags can be set by the global function `setPreference(feature, enabled)`, and so should not expose any sensitive behavior. * @property {object} store.knownMethodData Contains all data methods known by the user * @property {string} store.currentLocale The preferred language locale key * @property {string} store.selectedAddress A hex string that matches the currently selected address in the app @@ -33,6 +35,11 @@ class PreferencesController { tokens: [], suggestedTokens: {}, useBlockie: false, + + // WARNING: Do not use feature flags for security-sensitive things. + // Feature flag toggling is available in the global namespace + // for convenient testing of pre-release features, and should never + // perform sensitive operations. featureFlags: {}, knownMethodData: {}, currentLocale: opts.initLangCode, @@ -52,6 +59,10 @@ class PreferencesController { this.store = new ObservableStore(initState) this.openPopup = opts.openPopup this._subscribeProviderType() + + global.setPreference = (key, value) => { + return this.setFeatureFlag(key, value) + } } // PUBLIC METHODS diff --git a/app/scripts/lib/account-tracker.js b/app/scripts/lib/account-tracker.js index 2e9340018..24c5ef7ee 100644 --- a/app/scripts/lib/account-tracker.js +++ b/app/scripts/lib/account-tracker.js @@ -11,6 +11,12 @@ const EthQuery = require('eth-query') const ObservableStore = require('obs-store') const log = require('loglevel') const pify = require('pify') +const Web3 = require('web3') +const SINGLE_CALL_BALANCES_ABI = require('single-call-balance-checker-abi') + +const { bnToHex } = require('./util') +const { MAINNET_CODE, RINKEYBY_CODE, ROPSTEN_CODE, KOVAN_CODE } = require('../controllers/network/enums') +const { SINGLE_CALL_BALANCES_ADDRESS, SINGLE_CALL_BALANCES_ADDRESS_RINKEBY, SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN, SINGLE_CALL_BALANCES_ADDRESS_KOVAN } = require('../controllers/network/contract-addresses') class AccountTracker { @@ -50,6 +56,9 @@ class AccountTracker { }) // bind function for easier listener syntax this._updateForBlock = this._updateForBlock.bind(this) + this.network = opts.network + + this.web3 = new Web3(this._provider) } start () { @@ -116,7 +125,7 @@ class AccountTracker { this.store.updateState({ accounts }) // fetch balances for the accounts if there is block number ready if (!this._currentBlockNumber) return - addresses.forEach(address => this._updateAccount(address)) + this._updateAccounts() } /** @@ -161,7 +170,8 @@ class AccountTracker { } /** - * Calls this._updateAccount for each account in this.store + * balanceChecker is deployed on main eth (test)nets and requires a single call + * for all other networks, calls this._updateAccount for each account in this.store * * @returns {Promise} after all account balances updated * @@ -169,7 +179,28 @@ class AccountTracker { async _updateAccounts () { const accounts = this.store.getState().accounts const addresses = Object.keys(accounts) - await Promise.all(addresses.map(this._updateAccount.bind(this))) + const currentNetwork = parseInt(this.network.getNetworkState()) + + switch (currentNetwork) { + case MAINNET_CODE: + await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS) + break + + case RINKEYBY_CODE: + await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS_RINKEBY) + break + + case ROPSTEN_CODE: + await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS_ROPSTEN) + break + + case KOVAN_CODE: + await this._updateAccountsViaBalanceChecker(addresses, SINGLE_CALL_BALANCES_ADDRESS_KOVAN) + break + + default: + await Promise.all(addresses.map(this._updateAccount.bind(this))) + } } /** @@ -192,6 +223,30 @@ class AccountTracker { this.store.updateState({ accounts }) } + /** + * Updates current address balances from balanceChecker deployed contract instance + * @param {*} addresses + * @param {*} deployedContractAddress + */ + async _updateAccountsViaBalanceChecker (addresses, deployedContractAddress) { + const accounts = this.store.getState().accounts + this.web3.setProvider(this._provider) + const ethContract = this.web3.eth.contract(SINGLE_CALL_BALANCES_ABI).at(deployedContractAddress) + const ethBalance = ['0x0'] + + ethContract.balances(addresses, ethBalance, (error, result) => { + if (error) { + log.warn(`MetaMask - Account Tracker single call balance fetch failed`, error) + return Promise.all(addresses.map(this._updateAccount.bind(this))) + } + addresses.forEach((address, index) => { + const balance = bnToHex(result[index]) + accounts[address] = { address, balance } + }) + this.store.updateState({ accounts }) + }) + } + } module.exports = AccountTracker diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index b75f95d01..41c3e3642 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -56,6 +56,7 @@ const EthQuery = require('eth-query') const ethUtil = require('ethereumjs-util') const sigUtil = require('eth-sig-util') + module.exports = class MetamaskController extends EventEmitter { /** @@ -133,6 +134,7 @@ module.exports = class MetamaskController extends EventEmitter { this.accountTracker = new AccountTracker({ provider: this.provider, blockTracker: this.blockTracker, + network: this.networkController, }) // start and stop polling for balances based on activeControllerConnections @@ -409,6 +411,9 @@ module.exports = class MetamaskController extends EventEmitter { checkHardwareStatus: nodeify(this.checkHardwareStatus, this), unlockHardwareWalletAccount: nodeify(this.unlockHardwareWalletAccount, this), + // mobile + fetchInfoToSync: nodeify(this.fetchInfoToSync, this), + // vault management submitPassword: nodeify(this.submitPassword, this), @@ -585,6 +590,60 @@ module.exports = class MetamaskController extends EventEmitter { }) } + /** + * Collects all the information that we want to share + * with the mobile client for syncing purposes + * @returns Promise<Object> Parts of the state that we want to syncx + */ + async fetchInfoToSync () { + // Preferences + const { + accountTokens, + currentLocale, + frequentRpcList, + identities, + selectedAddress, + tokens, + } = this.preferencesController.store.getState() + + const preferences = { + accountTokens, + currentLocale, + frequentRpcList, + identities, + selectedAddress, + tokens, + } + + // Accounts + const hdKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0] + const hdAccounts = await hdKeyring.getAccounts() + const accounts = { + hd: hdAccounts.filter((item, pos) => (hdAccounts.indexOf(item) === pos)).map(address => ethUtil.toChecksumAddress(address)), + simpleKeyPair: [], + ledger: [], + trezor: [], + } + + // transactions + + let transactions = this.txController.store.getState().transactions + // delete tx for other accounts that we're not importing + transactions = transactions.filter(tx => { + const checksummedTxFrom = ethUtil.toChecksumAddress(tx.txParams.from) + return ( + accounts.hd.includes(checksummedTxFrom) + ) + }) + + return { + accounts, + preferences, + transactions, + network: this.networkController.store.getState(), + } + } + /* * Submits the user's password and attempts to unlock the vault. * Also synchronizes the preferencesController, to ensure its schema |