aboutsummaryrefslogtreecommitdiffstats
path: root/app/scripts/metamask-controller.js
diff options
context:
space:
mode:
authorDan <danjm.com@gmail.com>2018-03-22 08:58:58 +0800
committerDan <danjm.com@gmail.com>2018-03-22 08:58:58 +0800
commitbe845fd190ee71d2bb6e641a5c23c453668268e3 (patch)
tree28d5267d8c149b0e323351a9ebfa222de9d33bdc /app/scripts/metamask-controller.js
parentd24a0590d363dbe88d383c8faec8eb28809242f0 (diff)
parent04b1f8428b487663b276a541d739631954fe0be1 (diff)
downloadtangerine-wallet-browser-be845fd190ee71d2bb6e641a5c23c453668268e3.tar
tangerine-wallet-browser-be845fd190ee71d2bb6e641a5c23c453668268e3.tar.gz
tangerine-wallet-browser-be845fd190ee71d2bb6e641a5c23c453668268e3.tar.bz2
tangerine-wallet-browser-be845fd190ee71d2bb6e641a5c23c453668268e3.tar.lz
tangerine-wallet-browser-be845fd190ee71d2bb6e641a5c23c453668268e3.tar.xz
tangerine-wallet-browser-be845fd190ee71d2bb6e641a5c23c453668268e3.tar.zst
tangerine-wallet-browser-be845fd190ee71d2bb6e641a5c23c453668268e3.zip
Merge branch 'master' into i18n-translator-redux
Diffstat (limited to 'app/scripts/metamask-controller.js')
-rw-r--r--app/scripts/metamask-controller.js599
1 files changed, 354 insertions, 245 deletions
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index 4ff08e029..bd092cea0 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -1,3 +1,9 @@
+/**
+ * @file The central metamask controller. Aggregates other controllers and exports an api.
+ * @copyright Copyright (c) 2018 MetaMask
+ * @license MIT
+ */
+
const EventEmitter = require('events')
const extend = require('xtend')
const pump = require('pump')
@@ -41,7 +47,11 @@ const seedPhraseVerifier = require('./lib/seed-phrase-verifier')
module.exports = class MetamaskController extends EventEmitter {
- constructor (opts) {
+ /**
+ * @constructor
+ * @param {Object} opts
+ */
+ constructor (opts) {
super()
this.defaultMaxListeners = 20
@@ -223,10 +233,9 @@ module.exports = class MetamaskController extends EventEmitter {
this.infuraController.store.subscribe(sendUpdate)
}
- //
- // Constructor helpers
- //
-
+ /**
+ * Constructor helper: initialize a provider.
+ */
initializeProvider () {
const providerOpts = {
static: {
@@ -257,6 +266,9 @@ module.exports = class MetamaskController extends EventEmitter {
return providerProxy
}
+ /**
+ * Constructor helper: initialize a public config store.
+ */
initPublicConfigStore () {
// get init state
const publicConfigStore = new ObservableStore()
@@ -278,10 +290,15 @@ module.exports = class MetamaskController extends EventEmitter {
return publicConfigStore
}
- //
- // State Management
- //
+//=============================================================================
+// EXPOSED TO THE UI SUBSYSTEM
+//=============================================================================
+ /**
+ * The metamask-state of the various controllers, made available to the UI
+ *
+ * @returns {Object} status
+ */
getState () {
const wallet = this.configManager.getWallet()
const vault = this.keyringController.store.getState().vault
@@ -316,10 +333,11 @@ module.exports = class MetamaskController extends EventEmitter {
)
}
- //
- // Remote Features
- //
-
+ /**
+ * Returns an api-object which is consumed by the UI
+ *
+ * @returns {Object}
+ */
getApi () {
const keyringController = this.keyringController
const preferencesController = this.preferencesController
@@ -401,127 +419,24 @@ module.exports = class MetamaskController extends EventEmitter {
}
}
- setupUntrustedCommunication (connectionStream, originDomain) {
- // Check if new connection is blacklisted
- if (this.blacklistController.checkForPhishing(originDomain)) {
- log.debug('MetaMask - sending phishing warning for', originDomain)
- this.sendPhishingWarning(connectionStream, originDomain)
- return
- }
-
- // setup multiplexing
- const mux = setupMultiplex(connectionStream)
- // connect features
- this.setupProviderConnection(mux.createStream('provider'), originDomain)
- this.setupPublicConfig(mux.createStream('publicConfig'))
- }
-
- setupTrustedCommunication (connectionStream, originDomain) {
- // setup multiplexing
- const mux = setupMultiplex(connectionStream)
- // connect features
- this.setupControllerConnection(mux.createStream('controller'))
- this.setupProviderConnection(mux.createStream('provider'), originDomain)
- }
-
- sendPhishingWarning (connectionStream, hostname) {
- const mux = setupMultiplex(connectionStream)
- const phishingStream = mux.createStream('phishing')
- phishingStream.write({ hostname })
- }
-
- setupControllerConnection (outStream) {
- const api = this.getApi()
- const dnode = Dnode(api)
- pump(
- outStream,
- dnode,
- outStream,
- (err) => {
- if (err) log.error(err)
- }
- )
- dnode.on('remote', (remote) => {
- // push updates to popup
- const sendUpdate = remote.sendUpdate.bind(remote)
- this.on('update', sendUpdate)
- })
- }
-
- setupProviderConnection (outStream, origin) {
- // setup json rpc engine stack
- const engine = new RpcEngine()
-
- // create filter polyfill middleware
- const filterMiddleware = createFilterMiddleware({
- provider: this.provider,
- blockTracker: this.provider._blockTracker,
- })
-
- engine.push(createOriginMiddleware({ origin }))
- engine.push(createLoggerMiddleware({ origin }))
- engine.push(filterMiddleware)
- engine.push(createProviderMiddleware({ provider: this.provider }))
-
- // setup connection
- const providerStream = createEngineStream({ engine })
- pump(
- outStream,
- providerStream,
- outStream,
- (err) => {
- // cleanup filter polyfill middleware
- filterMiddleware.destroy()
- if (err) log.error(err)
- }
- )
- }
-
- setupPublicConfig (outStream) {
- pump(
- asStream(this.publicConfigStore),
- outStream,
- (err) => {
- if (err) log.error(err)
- }
- )
- }
- privateSendUpdate () {
- this.emit('update', this.getState())
- }
- getGasPrice () {
- const { recentBlocksController } = this
- const { recentBlocks } = recentBlocksController.store.getState()
-
- // Return 1 gwei if no blocks have been observed:
- if (recentBlocks.length === 0) {
- return '0x' + GWEI_BN.toString(16)
- }
-
- const lowestPrices = recentBlocks.map((block) => {
- if (!block.gasPrices || block.gasPrices.length < 1) {
- return GWEI_BN
- }
- return block.gasPrices
- .map(hexPrefix => hexPrefix.substr(2))
- .map(hex => new BN(hex, 16))
- .sort((a, b) => {
- return a.gt(b) ? 1 : -1
- })[0]
- })
- .map(number => number.div(GWEI_BN).toNumber())
-
- const percentileNum = percentile(50, lowestPrices)
- const percentileNumBn = new BN(percentileNum)
- return '0x' + percentileNumBn.mul(GWEI_BN).toString(16)
- }
-
- //
- // Vault Management
- //
+//=============================================================================
+// VAULT / KEYRING RELATED METHODS
+//=============================================================================
+ /**
+ * Creates a new Vault(?) and create a new keychain(?)
+ *
+ * A vault is ...
+ *
+ * A keychain is ...
+ *
+ *
+ * @param {} password
+ *
+ * @returns {} vault
+ */
async createNewVaultAndKeychain (password) {
const release = await this.createVaultMutex.acquire()
let vault
@@ -545,6 +460,11 @@ module.exports = class MetamaskController extends EventEmitter {
return vault
}
+ /**
+ * Create a new Vault and restore an existent keychain
+ * @param {} password
+ * @param {} seed
+ */
async createNewVaultAndRestore (password, seed) {
const release = await this.createVaultMutex.acquire()
try {
@@ -558,16 +478,28 @@ module.exports = class MetamaskController extends EventEmitter {
}
}
+ /**
+ * Retrieves the first Identiy from the passed Vault and selects the related address
+ *
+ * An Identity is ...
+ *
+ * @param {} vault
+ */
selectFirstIdentity (vault) {
const { identities } = vault
const address = Object.keys(identities)[0]
this.preferencesController.setSelectedAddress(address)
}
- //
+ // ?
// Opinionated Keyring Management
//
+ /**
+ * Adds a new account to ...
+ *
+ * @returns {} keyState
+ */
async addNewAccount () {
const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0]
if (!primaryKeyring) {
@@ -589,10 +521,12 @@ module.exports = class MetamaskController extends EventEmitter {
return keyState
}
- // Adds the current vault's seed words to the UI's state tree.
- //
- // Used when creating a first vault, to allow confirmation.
- // Also used when revealing the seed words in the confirmation view.
+ /**
+ * Adds the current vault's seed words to the UI's state tree.
+ *
+ * Used when creating a first vault, to allow confirmation.
+ * Also used when revealing the seed words in the confirmation view.
+ */
placeSeedWords (cb) {
this.verifySeedPhrase()
@@ -605,10 +539,13 @@ module.exports = class MetamaskController extends EventEmitter {
})
}
- // Verifies the current vault's seed words if they can restore the
- // accounts belonging to the current vault.
- //
- // Called when the first account is created and on unlocking the vault.
+ /**
+ * Verifies the validity of the current vault's seed phrase.
+ *
+ * Validity: seed phrase restores the accounts belonging to the current vault.
+ *
+ * Called when the first account is created and on unlocking the vault.
+ */
async verifySeedPhrase () {
const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0]
@@ -633,22 +570,33 @@ module.exports = class MetamaskController extends EventEmitter {
}
}
- // ClearSeedWordCache
- //
- // Removes the primary account's seed words from the UI's state tree,
- // ensuring they are only ever available in the background process.
+ /**
+ * Remove the primary account seed phrase from the UI's state tree.
+ *
+ * The seed phrase remains available in the background process.
+ *
+ */
clearSeedWordCache (cb) {
this.configManager.setSeedWords(null)
cb(null, this.preferencesController.getSelectedAddress())
}
-
+
+ /**
+ * ?
+ */
resetAccount (cb) {
const selectedAddress = this.preferencesController.getSelectedAddress()
this.txController.wipeTransactions(selectedAddress)
cb(null, selectedAddress)
}
-
+ /**
+ * Imports an account ... ?
+ *
+ * @param {} strategy
+ * @param {} args
+ * @param {} cb
+ */
importAccountWithStrategy (strategy, args, cb) {
accountImporter.importAccount(strategy, args)
.then((privateKey) => {
@@ -660,11 +608,150 @@ module.exports = class MetamaskController extends EventEmitter {
.catch((reason) => { cb(reason) })
}
+ // ---------------------------------------------------------------------------
+ // Identity Management (sign)
- //
- // Identity Management
- //
- //
+ /**
+ * @param {} msgParams
+ * @param {} cb
+ */
+ signMessage (msgParams, cb) {
+ log.info('MetaMaskController - signMessage')
+ const msgId = msgParams.metamaskId
+
+ // sets the status op the message to 'approved'
+ // and removes the metamaskId for signing
+ return this.messageManager.approveMessage(msgParams)
+ .then((cleanMsgParams) => {
+ // signs the message
+ return this.keyringController.signMessage(cleanMsgParams)
+ })
+ .then((rawSig) => {
+ // tells the listener that the message has been signed
+ // and can be returned to the dapp
+ this.messageManager.setMsgStatusSigned(msgId, rawSig)
+ return this.getState()
+ })
+ }
+
+ // Prefixed Style Message Signing Methods:
+
+ /**
+ *
+ * @param {} msgParams
+ * @param {} cb
+ */
+ approvePersonalMessage (msgParams, cb) {
+ const msgId = this.personalMessageManager.addUnapprovedMessage(msgParams)
+ this.sendUpdate()
+ this.opts.showUnconfirmedMessage()
+ this.personalMessageManager.once(`${msgId}:finished`, (data) => {
+ switch (data.status) {
+ case 'signed':
+ return cb(null, data.rawSig)
+ case 'rejected':
+ return cb(new Error('MetaMask Message Signature: User denied transaction signature.'))
+ default:
+ return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
+ }
+ })
+ }
+
+ /**
+ * @param {} msgParams
+ */
+ signPersonalMessage (msgParams) {
+ log.info('MetaMaskController - signPersonalMessage')
+ const msgId = msgParams.metamaskId
+ // sets the status op the message to 'approved'
+ // and removes the metamaskId for signing
+ return this.personalMessageManager.approveMessage(msgParams)
+ .then((cleanMsgParams) => {
+ // signs the message
+ return this.keyringController.signPersonalMessage(cleanMsgParams)
+ })
+ .then((rawSig) => {
+ // tells the listener that the message has been signed
+ // and can be returned to the dapp
+ this.personalMessageManager.setMsgStatusSigned(msgId, rawSig)
+ return this.getState()
+ })
+ }
+
+ /**
+ * @param {} msgParams
+ */
+ signTypedMessage (msgParams) {
+ log.info('MetaMaskController - signTypedMessage')
+ const msgId = msgParams.metamaskId
+ // sets the status op the message to 'approved'
+ // and removes the metamaskId for signing
+ return this.typedMessageManager.approveMessage(msgParams)
+ .then((cleanMsgParams) => {
+ // signs the message
+ return this.keyringController.signTypedMessage(cleanMsgParams)
+ })
+ .then((rawSig) => {
+ // tells the listener that the message has been signed
+ // and can be returned to the dapp
+ this.typedMessageManager.setMsgStatusSigned(msgId, rawSig)
+ return this.getState()
+ })
+ }
+
+ // ---------------------------------------------------------------------------
+ // Account Restauration
+
+ /**
+ * ?
+ *
+ * @param {} migratorOutput
+ */
+ restoreOldVaultAccounts (migratorOutput) {
+ const { serialized } = migratorOutput
+ return this.keyringController.restoreKeyring(serialized)
+ .then(() => migratorOutput)
+ }
+
+ /**
+ * ?
+ *
+ * @param {} 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
+ *
+ * @param {Object} {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,
+ })
+ }
+
+//=============================================================================
+// END (VAULT / KEYRING RELATED METHODS)
+//=============================================================================
+
+//
+
+//=============================================================================
+// MESSAGES
+//=============================================================================
async retryTransaction (txId, cb) {
await this.txController.retryTransaction(txId)
@@ -731,85 +818,13 @@ module.exports = class MetamaskController extends EventEmitter {
})
}
- signMessage (msgParams, cb) {
- log.info('MetaMaskController - signMessage')
- const msgId = msgParams.metamaskId
-
- // sets the status op the message to 'approved'
- // and removes the metamaskId for signing
- return this.messageManager.approveMessage(msgParams)
- .then((cleanMsgParams) => {
- // signs the message
- return this.keyringController.signMessage(cleanMsgParams)
- })
- .then((rawSig) => {
- // tells the listener that the message has been signed
- // and can be returned to the dapp
- this.messageManager.setMsgStatusSigned(msgId, rawSig)
- return this.getState()
- })
- }
-
cancelMessage (msgId, cb) {
const messageManager = this.messageManager
messageManager.rejectMsg(msgId)
if (cb && typeof cb === 'function') {
cb(null, this.getState())
}
- }
-
- // Prefixed Style Message Signing Methods:
- approvePersonalMessage (msgParams, cb) {
- const msgId = this.personalMessageManager.addUnapprovedMessage(msgParams)
- this.sendUpdate()
- this.opts.showUnconfirmedMessage()
- this.personalMessageManager.once(`${msgId}:finished`, (data) => {
- switch (data.status) {
- case 'signed':
- return cb(null, data.rawSig)
- case 'rejected':
- return cb(new Error('MetaMask Message Signature: User denied transaction signature.'))
- default:
- return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
- }
- })
- }
-
- signPersonalMessage (msgParams) {
- log.info('MetaMaskController - signPersonalMessage')
- const msgId = msgParams.metamaskId
- // sets the status op the message to 'approved'
- // and removes the metamaskId for signing
- return this.personalMessageManager.approveMessage(msgParams)
- .then((cleanMsgParams) => {
- // signs the message
- return this.keyringController.signPersonalMessage(cleanMsgParams)
- })
- .then((rawSig) => {
- // tells the listener that the message has been signed
- // and can be returned to the dapp
- this.personalMessageManager.setMsgStatusSigned(msgId, rawSig)
- return this.getState()
- })
- }
-
- signTypedMessage (msgParams) {
- log.info('MetaMaskController - signTypedMessage')
- const msgId = msgParams.metamaskId
- // sets the status op the message to 'approved'
- // and removes the metamaskId for signing
- return this.typedMessageManager.approveMessage(msgParams)
- .then((cleanMsgParams) => {
- // signs the message
- return this.keyringController.signTypedMessage(cleanMsgParams)
- })
- .then((rawSig) => {
- // tells the listener that the message has been signed
- // and can be returned to the dapp
- this.typedMessageManager.setMsgStatusSigned(msgId, rawSig)
- return this.getState()
- })
- }
+ }
cancelPersonalMessage (msgId, cb) {
const messageManager = this.personalMessageManager
@@ -845,36 +860,130 @@ module.exports = class MetamaskController extends EventEmitter {
cb()
}
- restoreOldVaultAccounts (migratorOutput) {
- const { serialized } = migratorOutput
- return this.keyringController.restoreKeyring(serialized)
- .then(() => migratorOutput)
- }
+//=============================================================================
+// SETUP
+//=============================================================================
- restoreOldLostAccounts (migratorOutput) {
- const { lostAccounts } = migratorOutput
- if (lostAccounts) {
- this.configManager.setLostAccounts(lostAccounts.map(acct => acct.address))
- return this.importLostAccounts(migratorOutput)
+ setupUntrustedCommunication (connectionStream, originDomain) {
+ // Check if new connection is blacklisted
+ if (this.blacklistController.checkForPhishing(originDomain)) {
+ log.debug('MetaMask - sending phishing warning for', originDomain)
+ this.sendPhishingWarning(connectionStream, originDomain)
+ return
}
- return Promise.resolve(migratorOutput)
+
+ // setup multiplexing
+ const mux = setupMultiplex(connectionStream)
+ // connect features
+ this.setupProviderConnection(mux.createStream('provider'), originDomain)
+ this.setupPublicConfig(mux.createStream('publicConfig'))
}
- // 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,
+ setupTrustedCommunication (connectionStream, originDomain) {
+ // setup multiplexing
+ const mux = setupMultiplex(connectionStream)
+ // connect features
+ this.setupControllerConnection(mux.createStream('controller'))
+ this.setupProviderConnection(mux.createStream('provider'), originDomain)
+ }
+
+ sendPhishingWarning (connectionStream, hostname) {
+ const mux = setupMultiplex(connectionStream)
+ const phishingStream = mux.createStream('phishing')
+ phishingStream.write({ hostname })
+ }
+
+ setupControllerConnection (outStream) {
+ const api = this.getApi()
+ const dnode = Dnode(api)
+ pump(
+ outStream,
+ dnode,
+ outStream,
+ (err) => {
+ if (err) log.error(err)
+ }
+ )
+ dnode.on('remote', (remote) => {
+ // push updates to popup
+ const sendUpdate = remote.sendUpdate.bind(remote)
+ this.on('update', sendUpdate)
})
}
- //
- // config
- //
+ setupProviderConnection (outStream, origin) {
+ // setup json rpc engine stack
+ const engine = new RpcEngine()
+
+ // create filter polyfill middleware
+ const filterMiddleware = createFilterMiddleware({
+ provider: this.provider,
+ blockTracker: this.provider._blockTracker,
+ })
+
+ engine.push(createOriginMiddleware({ origin }))
+ engine.push(createLoggerMiddleware({ origin }))
+ engine.push(filterMiddleware)
+ engine.push(createProviderMiddleware({ provider: this.provider }))
+
+ // setup connection
+ const providerStream = createEngineStream({ engine })
+ pump(
+ outStream,
+ providerStream,
+ outStream,
+ (err) => {
+ // cleanup filter polyfill middleware
+ filterMiddleware.destroy()
+ if (err) log.error(err)
+ }
+ )
+ }
+
+ setupPublicConfig (outStream) {
+ pump(
+ asStream(this.publicConfigStore),
+ outStream,
+ (err) => {
+ if (err) log.error(err)
+ }
+ )
+ }
+
+ privateSendUpdate () {
+ this.emit('update', this.getState())
+ }
+
+ getGasPrice () {
+ const { recentBlocksController } = this
+ const { recentBlocks } = recentBlocksController.store.getState()
+
+ // Return 1 gwei if no blocks have been observed:
+ if (recentBlocks.length === 0) {
+ return '0x' + GWEI_BN.toString(16)
+ }
+
+ const lowestPrices = recentBlocks.map((block) => {
+ if (!block.gasPrices || block.gasPrices.length < 1) {
+ return GWEI_BN
+ }
+ return block.gasPrices
+ .map(hexPrefix => hexPrefix.substr(2))
+ .map(hex => new BN(hex, 16))
+ .sort((a, b) => {
+ return a.gt(b) ? 1 : -1
+ })[0]
+ })
+ .map(number => number.div(GWEI_BN).toNumber())
+
+ const percentileNum = percentile(50, lowestPrices)
+ const percentileNumBn = new BN(percentileNum)
+ return '0x' + percentileNumBn.mul(GWEI_BN).toString(16)
+ }
+
+//=============================================================================
+// CONFIG
+//=============================================================================
// Log blocks