|
|
@ -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 EventEmitter = require('events') |
|
|
|
const extend = require('xtend') |
|
|
|
const extend = require('xtend') |
|
|
|
const pump = require('pump') |
|
|
|
const pump = require('pump') |
|
|
@ -41,7 +47,11 @@ const seedPhraseVerifier = require('./lib/seed-phrase-verifier') |
|
|
|
|
|
|
|
|
|
|
|
module.exports = class MetamaskController extends EventEmitter { |
|
|
|
module.exports = class MetamaskController extends EventEmitter { |
|
|
|
|
|
|
|
|
|
|
|
constructor (opts) { |
|
|
|
/** |
|
|
|
|
|
|
|
* @constructor |
|
|
|
|
|
|
|
* @param {Object} opts
|
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
constructor (opts) { |
|
|
|
super() |
|
|
|
super() |
|
|
|
|
|
|
|
|
|
|
|
this.defaultMaxListeners = 20 |
|
|
|
this.defaultMaxListeners = 20 |
|
|
@ -223,10 +233,9 @@ module.exports = class MetamaskController extends EventEmitter { |
|
|
|
this.infuraController.store.subscribe(sendUpdate) |
|
|
|
this.infuraController.store.subscribe(sendUpdate) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
/** |
|
|
|
// Constructor helpers
|
|
|
|
* Constructor helper: initialize a provider. |
|
|
|
//
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
|
|
initializeProvider () { |
|
|
|
initializeProvider () { |
|
|
|
const providerOpts = { |
|
|
|
const providerOpts = { |
|
|
|
static: { |
|
|
|
static: { |
|
|
@ -257,6 +266,9 @@ module.exports = class MetamaskController extends EventEmitter { |
|
|
|
return providerProxy |
|
|
|
return providerProxy |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Constructor helper: initialize a public config store. |
|
|
|
|
|
|
|
*/ |
|
|
|
initPublicConfigStore () { |
|
|
|
initPublicConfigStore () { |
|
|
|
// get init state
|
|
|
|
// get init state
|
|
|
|
const publicConfigStore = new ObservableStore() |
|
|
|
const publicConfigStore = new ObservableStore() |
|
|
@ -278,10 +290,15 @@ module.exports = class MetamaskController extends EventEmitter { |
|
|
|
return publicConfigStore |
|
|
|
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 () { |
|
|
|
getState () { |
|
|
|
const wallet = this.configManager.getWallet() |
|
|
|
const wallet = this.configManager.getWallet() |
|
|
|
const vault = this.keyringController.store.getState().vault |
|
|
|
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 () { |
|
|
|
getApi () { |
|
|
|
const keyringController = this.keyringController |
|
|
|
const keyringController = this.keyringController |
|
|
|
const preferencesController = this.preferencesController |
|
|
|
const preferencesController = this.preferencesController |
|
|
@ -400,127 +418,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
|
|
|
|
// VAULT / KEYRING RELATED METHODS
|
|
|
|
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
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Creates a new Vault(?) and create a new keychain(?) |
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* A vault is ... |
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* A keychain is ... |
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param {} password |
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* @returns {} vault |
|
|
|
|
|
|
|
*/ |
|
|
|
async createNewVaultAndKeychain (password) { |
|
|
|
async createNewVaultAndKeychain (password) { |
|
|
|
const release = await this.createVaultMutex.acquire() |
|
|
|
const release = await this.createVaultMutex.acquire() |
|
|
|
let vault |
|
|
|
let vault |
|
|
@ -544,6 +459,11 @@ module.exports = class MetamaskController extends EventEmitter { |
|
|
|
return vault |
|
|
|
return vault |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Create a new Vault and restore an existent keychain |
|
|
|
|
|
|
|
* @param {} password |
|
|
|
|
|
|
|
* @param {} seed |
|
|
|
|
|
|
|
*/ |
|
|
|
async createNewVaultAndRestore (password, seed) { |
|
|
|
async createNewVaultAndRestore (password, seed) { |
|
|
|
const release = await this.createVaultMutex.acquire() |
|
|
|
const release = await this.createVaultMutex.acquire() |
|
|
|
try { |
|
|
|
try { |
|
|
@ -557,16 +477,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) { |
|
|
|
selectFirstIdentity (vault) { |
|
|
|
const { identities } = vault |
|
|
|
const { identities } = vault |
|
|
|
const address = Object.keys(identities)[0] |
|
|
|
const address = Object.keys(identities)[0] |
|
|
|
this.preferencesController.setSelectedAddress(address) |
|
|
|
this.preferencesController.setSelectedAddress(address) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// ?
|
|
|
|
// Opinionated Keyring Management
|
|
|
|
// Opinionated Keyring Management
|
|
|
|
//
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Adds a new account to ...
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* @returns {} keyState |
|
|
|
|
|
|
|
*/ |
|
|
|
async addNewAccount () { |
|
|
|
async addNewAccount () { |
|
|
|
const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0] |
|
|
|
const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0] |
|
|
|
if (!primaryKeyring) { |
|
|
|
if (!primaryKeyring) { |
|
|
@ -588,10 +520,12 @@ module.exports = class MetamaskController extends EventEmitter { |
|
|
|
return keyState |
|
|
|
return keyState |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Adds the current vault's seed words to the UI's state tree.
|
|
|
|
/** |
|
|
|
//
|
|
|
|
* 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.
|
|
|
|
* Used when creating a first vault, to allow confirmation. |
|
|
|
|
|
|
|
* Also used when revealing the seed words in the confirmation view. |
|
|
|
|
|
|
|
*/ |
|
|
|
placeSeedWords (cb) { |
|
|
|
placeSeedWords (cb) { |
|
|
|
|
|
|
|
|
|
|
|
this.verifySeedPhrase() |
|
|
|
this.verifySeedPhrase() |
|
|
@ -604,10 +538,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.
|
|
|
|
* Verifies the validity of the current vault's seed phrase. |
|
|
|
//
|
|
|
|
*
|
|
|
|
// Called when the first account is created and on unlocking the vault.
|
|
|
|
* 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 () { |
|
|
|
async verifySeedPhrase () { |
|
|
|
|
|
|
|
|
|
|
|
const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0] |
|
|
|
const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0] |
|
|
@ -632,22 +569,33 @@ module.exports = class MetamaskController extends EventEmitter { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ClearSeedWordCache
|
|
|
|
/** |
|
|
|
//
|
|
|
|
* Remove the primary account seed phrase from the UI's state tree. |
|
|
|
// Removes the primary account's seed words from the UI's state tree,
|
|
|
|
*
|
|
|
|
// ensuring they are only ever available in the background process.
|
|
|
|
* The seed phrase remains available in the background process. |
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
*/ |
|
|
|
clearSeedWordCache (cb) { |
|
|
|
clearSeedWordCache (cb) { |
|
|
|
this.configManager.setSeedWords(null) |
|
|
|
this.configManager.setSeedWords(null) |
|
|
|
cb(null, this.preferencesController.getSelectedAddress()) |
|
|
|
cb(null, this.preferencesController.getSelectedAddress()) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* ? |
|
|
|
|
|
|
|
*/ |
|
|
|
resetAccount (cb) { |
|
|
|
resetAccount (cb) { |
|
|
|
const selectedAddress = this.preferencesController.getSelectedAddress() |
|
|
|
const selectedAddress = this.preferencesController.getSelectedAddress() |
|
|
|
this.txController.wipeTransactions(selectedAddress) |
|
|
|
this.txController.wipeTransactions(selectedAddress) |
|
|
|
cb(null, selectedAddress) |
|
|
|
cb(null, selectedAddress) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Imports an account ... ? |
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* @param {} strategy |
|
|
|
|
|
|
|
* @param {} args |
|
|
|
|
|
|
|
* @param {} cb |
|
|
|
|
|
|
|
*/ |
|
|
|
importAccountWithStrategy (strategy, args, cb) { |
|
|
|
importAccountWithStrategy (strategy, args, cb) { |
|
|
|
accountImporter.importAccount(strategy, args) |
|
|
|
accountImporter.importAccount(strategy, args) |
|
|
|
.then((privateKey) => { |
|
|
|
.then((privateKey) => { |
|
|
@ -659,11 +607,150 @@ module.exports = class MetamaskController extends EventEmitter { |
|
|
|
.catch((reason) => { cb(reason) }) |
|
|
|
.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) { |
|
|
|
async retryTransaction (txId, cb) { |
|
|
|
await this.txController.retryTransaction(txId) |
|
|
|
await this.txController.retryTransaction(txId) |
|
|
@ -730,25 +817,6 @@ 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) { |
|
|
|
cancelMessage (msgId, cb) { |
|
|
|
const messageManager = this.messageManager |
|
|
|
const messageManager = this.messageManager |
|
|
|
messageManager.rejectMsg(msgId) |
|
|
|
messageManager.rejectMsg(msgId) |
|
|
@ -757,59 +825,6 @@ module.exports = class MetamaskController extends EventEmitter { |
|
|
|
} |
|
|
|
} |
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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) { |
|
|
|
cancelPersonalMessage (msgId, cb) { |
|
|
|
const messageManager = this.personalMessageManager |
|
|
|
const messageManager = this.personalMessageManager |
|
|
|
messageManager.rejectMsg(msgId) |
|
|
|
messageManager.rejectMsg(msgId) |
|
|
@ -844,36 +859,130 @@ module.exports = class MetamaskController extends EventEmitter { |
|
|
|
cb() |
|
|
|
cb() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
restoreOldVaultAccounts (migratorOutput) { |
|
|
|
//=============================================================================
|
|
|
|
const { serialized } = migratorOutput |
|
|
|
// SETUP
|
|
|
|
return this.keyringController.restoreKeyring(serialized) |
|
|
|
//=============================================================================
|
|
|
|
.then(() => migratorOutput) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
restoreOldLostAccounts (migratorOutput) { |
|
|
|
setupUntrustedCommunication (connectionStream, originDomain) { |
|
|
|
const { lostAccounts } = migratorOutput |
|
|
|
// Check if new connection is blacklisted
|
|
|
|
if (lostAccounts) { |
|
|
|
if (this.blacklistController.checkForPhishing(originDomain)) { |
|
|
|
this.configManager.setLostAccounts(lostAccounts.map(acct => acct.address)) |
|
|
|
log.debug('MetaMask - sending phishing warning for', originDomain) |
|
|
|
return this.importLostAccounts(migratorOutput) |
|
|
|
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
|
|
|
|
setupTrustedCommunication (connectionStream, originDomain) { |
|
|
|
// @Object with key lostAccounts: @Array accounts <{ address, privateKey }>
|
|
|
|
// setup multiplexing
|
|
|
|
// Uses the array's private keys to create a new Simple Key Pair keychain
|
|
|
|
const mux = setupMultiplex(connectionStream) |
|
|
|
// and add it to the keyring controller.
|
|
|
|
// connect features
|
|
|
|
importLostAccounts ({ lostAccounts }) { |
|
|
|
this.setupControllerConnection(mux.createStream('controller')) |
|
|
|
const privKeys = lostAccounts.map(acct => acct.privateKey) |
|
|
|
this.setupProviderConnection(mux.createStream('provider'), originDomain) |
|
|
|
return this.keyringController.restoreKeyring({ |
|
|
|
} |
|
|
|
type: 'Simple Key Pair', |
|
|
|
|
|
|
|
data: privKeys, |
|
|
|
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) { |
|
|
|
// config
|
|
|
|
// 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
|
|
|
|
// Log blocks
|
|
|
|
|
|
|
|
|
|
|
|