aboutsummaryrefslogtreecommitdiffstats
path: root/app/scripts/metamask-controller.js
diff options
context:
space:
mode:
Diffstat (limited to 'app/scripts/metamask-controller.js')
-rw-r--r--app/scripts/metamask-controller.js289
1 files changed, 191 insertions, 98 deletions
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index a165a2e2a..b94b98eac 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -1,28 +1,54 @@
+const EventEmitter = require('events')
const extend = require('xtend')
-const EthStore = require('eth-store')
+const EthStore = require('./lib/eth-store')
const MetaMaskProvider = require('web3-provider-engine/zero.js')
const KeyringController = require('./keyring-controller')
+const NoticeController = require('./notice-controller')
const messageManager = require('./lib/message-manager')
+const TxManager = require('./transaction-manager')
const HostStore = require('./lib/remote-store.js').HostStore
const Web3 = require('web3')
const ConfigManager = require('./lib/config-manager')
const extension = require('./lib/extension')
+const autoFaucet = require('./lib/auto-faucet')
+const nodeify = require('./lib/nodeify')
+const IdStoreMigrator = require('./lib/idStore-migrator')
+const version = require('../manifest.json').version
-module.exports = class MetamaskController {
+module.exports = class MetamaskController extends EventEmitter {
constructor (opts) {
+ super()
this.state = { network: 'loading' }
this.opts = opts
- this.listeners = []
this.configManager = new ConfigManager(opts)
this.keyringController = new KeyringController({
configManager: this.configManager,
+ getNetwork: this.getStateNetwork.bind(this),
})
+ // notices
+ this.noticeController = new NoticeController({
+ configManager: this.configManager,
+ })
+ this.noticeController.updateNoticesList()
+ // to be uncommented when retrieving notices from a remote server.
+ // this.noticeController.startPolling()
this.provider = this.initializeProvider(opts)
this.ethStore = new EthStore(this.provider)
this.keyringController.setStore(this.ethStore)
this.getNetwork()
this.messageManager = messageManager
+ this.txManager = new TxManager({
+ txList: this.configManager.getTxList(),
+ txHistoryLimit: 40,
+ setTxList: this.configManager.setTxList.bind(this.configManager),
+ getSelectedAccount: this.configManager.getSelectedAccount.bind(this.configManager),
+ getGasMultiplier: this.configManager.getGasMultiplier.bind(this.configManager),
+ getNetwork: this.getStateNetwork.bind(this),
+ signTransaction: this.keyringController.signTransaction.bind(this.keyringController),
+ provider: this.provider,
+ blockTracker: this.provider,
+ })
this.publicConfigStore = this.initPublicConfigStore()
var currentFiat = this.configManager.getCurrentFiat() || 'USD'
@@ -32,22 +58,40 @@ module.exports = class MetamaskController {
this.checkTOSChange()
this.scheduleConversionInterval()
+
+ // TEMPORARY UNTIL FULL DEPRECATION:
+ this.idStoreMigrator = new IdStoreMigrator({
+ configManager: this.configManager,
+ })
+
+ this.ethStore.on('update', this.sendUpdate.bind(this))
+ this.keyringController.on('update', this.sendUpdate.bind(this))
+ this.txManager.on('update', this.sendUpdate.bind(this))
}
getState () {
- return extend(
- this.state,
- this.ethStore.getState(),
- this.configManager.getConfig(),
- this.keyringController.getState()
- )
+ return this.keyringController.getState()
+ .then((keyringControllerState) => {
+ return extend(
+ this.state,
+ this.ethStore.getState(),
+ this.configManager.getConfig(),
+ this.txManager.getState(),
+ keyringControllerState,
+ this.noticeController.getState(), {
+ lostAccounts: this.configManager.getLostAccounts(),
+ }
+ )
+ })
}
getApi () {
const keyringController = this.keyringController
+ const txManager = this.txManager
+ const noticeController = this.noticeController
return {
- getState: (cb) => { cb(null, this.getState()) },
+ getState: nodeify(this.getState.bind(this)),
setRpcTarget: this.setRpcTarget.bind(this),
setProviderType: this.setProviderType.bind(this),
useEtherscanProvider: this.useEtherscanProvider.bind(this),
@@ -57,27 +101,39 @@ module.exports = class MetamaskController {
setTOSHash: this.setTOSHash.bind(this),
checkTOSChange: this.checkTOSChange.bind(this),
setGasMultiplier: this.setGasMultiplier.bind(this),
+ markAccountsFound: this.markAccountsFound.bind(this),
// forward directly to keyringController
- placeSeedWords: keyringController.placeSeedWords.bind(keyringController),
- createNewVaultAndKeychain: keyringController.createNewVaultAndKeychain.bind(keyringController),
- createNewVaultAndRestore: keyringController.createNewVaultAndRestore.bind(keyringController),
- clearSeedWordCache: keyringController.clearSeedWordCache.bind(keyringController),
- addNewKeyring: keyringController.addNewKeyring.bind(keyringController),
- addNewAccount: keyringController.addNewAccount.bind(keyringController),
- submitPassword: keyringController.submitPassword.bind(keyringController),
- setSelectedAddress: keyringController.setSelectedAddress.bind(keyringController),
- approveTransaction: keyringController.approveTransaction.bind(keyringController),
- cancelTransaction: keyringController.cancelTransaction.bind(keyringController),
+ createNewVaultAndKeychain: nodeify(keyringController.createNewVaultAndKeychain).bind(keyringController),
+ createNewVaultAndRestore: nodeify(keyringController.createNewVaultAndRestore).bind(keyringController),
+ placeSeedWords: nodeify(keyringController.placeSeedWords).bind(keyringController),
+ clearSeedWordCache: nodeify(keyringController.clearSeedWordCache).bind(keyringController),
+ setLocked: nodeify(keyringController.setLocked).bind(keyringController),
+ submitPassword: (password, cb) => {
+ this.migrateOldVaultIfAny(password)
+ .then(keyringController.submitPassword.bind(keyringController, password))
+ .then((newState) => { cb(null, newState) })
+ .catch((reason) => { cb(reason) })
+ },
+ addNewKeyring: nodeify(keyringController.addNewKeyring).bind(keyringController),
+ addNewAccount: nodeify(keyringController.addNewAccount).bind(keyringController),
+ setSelectedAccount: nodeify(keyringController.setSelectedAccount).bind(keyringController),
+ saveAccountLabel: nodeify(keyringController.saveAccountLabel).bind(keyringController),
+ exportAccount: nodeify(keyringController.exportAccount).bind(keyringController),
+
+ // signing methods
+ approveTransaction: txManager.approveTransaction.bind(txManager),
+ cancelTransaction: txManager.cancelTransaction.bind(txManager),
signMessage: keyringController.signMessage.bind(keyringController),
cancelMessage: keyringController.cancelMessage.bind(keyringController),
- setLocked: keyringController.setLocked.bind(keyringController),
- exportAccount: keyringController.exportAccount.bind(keyringController),
- saveAccountLabel: keyringController.saveAccountLabel.bind(keyringController),
+
// coinbase
buyEth: this.buyEth.bind(this),
// shapeshift
createShapeShiftTx: this.createShapeShiftTx.bind(this),
+ // notices
+ checkNotices: noticeController.updateNoticesList.bind(noticeController),
+ markNoticeRead: noticeController.markNoticeRead.bind(noticeController),
}
}
@@ -86,23 +142,6 @@ module.exports = class MetamaskController {
}
onRpcRequest (stream, originDomain, request) {
-
- /* Commented out for Parity compliance
- * Parity does not permit additional keys, like `origin`,
- * and Infura is not currently filtering this key out.
- var payloads = Array.isArray(request) ? request : [request]
- payloads.forEach(function (payload) {
- // Append origin to rpc payload
- payload.origin = originDomain
- // Append origin to signature request
- if (payload.method === 'eth_sendTransaction') {
- payload.params[0].origin = originDomain
- } else if (payload.method === 'eth_sign') {
- payload.params.push({ origin: originDomain })
- }
- })
- */
-
// handle rpc request
this.provider.sendAsync(request, function onPayloadHandled (err, response) {
logger(err, request, response)
@@ -129,8 +168,9 @@ module.exports = class MetamaskController {
}
sendUpdate () {
- this.listeners.forEach((remote) => {
- remote.sendUpdate(this.getState())
+ this.getState()
+ .then((state) => {
+ this.emit('update', state)
})
}
@@ -138,20 +178,19 @@ module.exports = class MetamaskController {
const keyringController = this.keyringController
var providerOpts = {
+ static: {
+ eth_syncing: false,
+ web3_clientVersion: `MetaMask/v${version}`,
+ },
rpcUrl: this.configManager.getCurrentRpcAddress(),
// account mgmt
getAccounts: (cb) => {
- var selectedAddress = this.configManager.getSelectedAccount()
- var result = selectedAddress ? [selectedAddress] : []
+ var selectedAccount = this.configManager.getSelectedAccount()
+ var result = selectedAccount ? [selectedAccount] : []
cb(null, result)
},
// tx signing
- approveTransaction: this.newUnsignedTransaction.bind(this),
- signTransaction: (...args) => {
- keyringController.signTransaction(...args)
- this.sendUpdate()
- },
-
+ processTransaction: (txParams, cb) => this.newUnapprovedTransaction(txParams, cb),
// msg signing
approveMessage: this.newUnsignedMessage.bind(this),
signMessage: (...args) => {
@@ -164,7 +203,6 @@ module.exports = class MetamaskController {
var web3 = new Web3(provider)
this.web3 = web3
keyringController.web3 = web3
-
provider.on('block', this.processBlock.bind(this))
provider.on('error', this.getNetwork.bind(this))
@@ -173,34 +211,22 @@ module.exports = class MetamaskController {
initPublicConfigStore () {
// get init state
- var initPublicState = extend(
- keyringControllerToPublic(this.keyringController.getState()),
- configToPublic(this.configManager.getConfig())
- )
-
+ var initPublicState = configToPublic(this.configManager.getConfig())
var publicConfigStore = new HostStore(initPublicState)
// subscribe to changes
this.configManager.subscribe(function (state) {
storeSetFromObj(publicConfigStore, configToPublic(state))
})
- this.keyringController.on('update', () => {
- const state = this.keyringController.getState()
- storeSetFromObj(publicConfigStore, keyringControllerToPublic(state))
- this.sendUpdate()
+
+ this.keyringController.on('newAccount', (account) => {
+ autoFaucet(account)
})
- // keyringController substate
- function keyringControllerToPublic (state) {
- return {
- selectedAddress: state.selectedAddress,
- }
- }
// config substate
function configToPublic (state) {
return {
- provider: state.provider,
- selectedAddress: state.selectedAccount,
+ selectedAccount: state.selectedAccount,
}
}
// dump obj into store
@@ -213,26 +239,26 @@ module.exports = class MetamaskController {
return publicConfigStore
}
- newUnsignedTransaction (txParams, onTxDoneCb) {
- const keyringController = this.keyringController
-
- let err = this.enforceTxValidations(txParams)
- if (err) return onTxDoneCb(err)
-
- keyringController.addUnconfirmedTransaction(txParams, onTxDoneCb, (err, txData) => {
- if (err) return onTxDoneCb(err)
- this.sendUpdate()
- this.opts.showUnconfirmedTx(txParams, txData, onTxDoneCb)
+ newUnapprovedTransaction (txParams, cb) {
+ const self = this
+ self.txManager.addUnapprovedTransaction(txParams, (err, txMeta) => {
+ if (err) return cb(err)
+ self.sendUpdate()
+ self.opts.showUnapprovedTx(txMeta)
+ // listen for tx completion (success, fail)
+ self.txManager.once(`${txMeta.id}:finished`, (status) => {
+ switch (status) {
+ case 'submitted':
+ return cb(null, txMeta.hash)
+ case 'rejected':
+ return cb(new Error('MetaMask Tx Signature: User denied transaction signature.'))
+ default:
+ return cb(new Error(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(txMeta.txParams)}`))
+ }
+ })
})
}
- enforceTxValidations (txParams) {
- if (('value' in txParams) && txParams.value.indexOf('-') === 0) {
- const msg = `Invalid transaction value of ${txParams.value} not a positive number.`
- return new Error(msg)
- }
- }
-
newUnsignedMessage (msgParams, cb) {
var state = this.keyringController.getState()
if (!state.isUnlocked) {
@@ -276,7 +302,7 @@ module.exports = class MetamaskController {
setTOSHash (hash) {
try {
this.configManager.setTOSHash(hash)
- } catch (e) {
+ } catch (err) {
console.error('Error in setting terms of service hash.')
}
}
@@ -288,24 +314,25 @@ module.exports = class MetamaskController {
this.resetDisclaimer()
this.setTOSHash(global.TOS_HASH)
}
- } catch (e) {
+ } catch (err) {
console.error('Error in checking TOS change.')
}
-
}
+ // disclaimer
+
agreeToDisclaimer (cb) {
try {
- this.configManager.setConfirmed(true)
+ this.configManager.setConfirmedDisclaimer(true)
cb()
- } catch (e) {
- cb(e)
+ } catch (err) {
+ cb(err)
}
}
resetDisclaimer () {
try {
- this.configManager.setConfirmed(false)
+ this.configManager.setConfirmedDisclaimer(false)
} catch (e) {
console.error(e)
}
@@ -322,8 +349,8 @@ module.exports = class MetamaskController {
conversionDate: this.configManager.getConversionDate(),
}
cb(data)
- } catch (e) {
- cb(null, e)
+ } catch (err) {
+ cb(null, err)
}
}
@@ -360,7 +387,7 @@ module.exports = class MetamaskController {
var network = this.state.network
var url = `https://buy.coinbase.com/?code=9ec56d01-7e81-5017-930c-513daa27bb6a&amount=${amount}&address=${address}&crypto_currency=ETH`
- if (network === '2') {
+ if (network === '3') {
url = 'https://faucet.metamask.io/'
}
@@ -373,7 +400,7 @@ module.exports = class MetamaskController {
this.configManager.createShapeShiftTx(depositAddress, depositType)
}
- getNetwork(err) {
+ getNetwork (err) {
if (err) {
this.state.network = 'loading'
this.sendUpdate()
@@ -396,8 +423,74 @@ module.exports = class MetamaskController {
try {
this.configManager.setGasMultiplier(gasMultiplier)
cb()
- } catch (e) {
- cb(e)
+ } catch (err) {
+ cb(err)
+ }
+ }
+
+ getStateNetwork () {
+ return this.state.network
+ }
+
+ markAccountsFound (cb) {
+ this.configManager.setLostAccounts([])
+ this.sendUpdate()
+ cb(null, this.getState())
+ }
+
+ // Migrate Old Vault If Any
+ // @string password
+ //
+ // returns Promise()
+ //
+ // Temporary step used when logging in.
+ // Checks if old style (pre-3.0.0) Metamask Vault exists.
+ // If so, persists that vault in the new vault format
+ // with the provided password, so the other unlock steps
+ // may be completed without interruption.
+ migrateOldVaultIfAny (password) {
+
+ if (!this.checkIfShouldMigrate()) {
+ return Promise.resolve(password)
+ }
+
+ const keyringController = this.keyringController
+
+ return this.idStoreMigrator.migratedVaultForPassword(password)
+ .then(this.restoreOldVaultAccounts.bind(this))
+ .then(this.restoreOldLostAccounts.bind(this))
+ .then(keyringController.persistAllKeyrings.bind(keyringController, password))
+ .then(() => password)
+ }
+
+ checkIfShouldMigrate() {
+ return !!this.configManager.getWallet() && !this.configManager.getVault()
+ }
+
+ restoreOldVaultAccounts(migratorOutput) {
+ const { serialized } = migratorOutput
+ return this.keyringController.restoreKeyring(serialized)
+ .then(() => migratorOutput)
+ }
+
+ restoreOldLostAccounts(migratorOutput) {
+ const { lostAccounts } = migratorOutput
+ if (lostAccounts) {
+ this.configManager.setLostAccounts(lostAccounts.map(acct => acct.address))
+ return this.importLostAccounts(migratorOutput)
}
+ return Promise.resolve(migratorOutput)
+ }
+
+ // IMPORT LOST ACCOUNTS
+ // @Object with key lostAccounts: @Array accounts <{ address, privateKey }>
+ // Uses the array's private keys to create a new Simple Key Pair keychain
+ // and add it to the keyring controller.
+ importLostAccounts ({ lostAccounts }) {
+ const privKeys = lostAccounts.map(acct => acct.privateKey)
+ return this.keyringController.restoreKeyring({
+ type: 'Simple Key Pair',
+ data: privKeys,
+ })
}
}