aboutsummaryrefslogtreecommitdiffstats
path: root/app/scripts/keyring-controller.js
diff options
context:
space:
mode:
Diffstat (limited to 'app/scripts/keyring-controller.js')
-rw-r--r--app/scripts/keyring-controller.js888
1 files changed, 888 insertions, 0 deletions
diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js
new file mode 100644
index 000000000..ca4c306be
--- /dev/null
+++ b/app/scripts/keyring-controller.js
@@ -0,0 +1,888 @@
+const async = require('async')
+const ethUtil = require('ethereumjs-util')
+const EthQuery = require('eth-query')
+const bip39 = require('bip39')
+const Transaction = require('ethereumjs-tx')
+const EventEmitter = require('events').EventEmitter
+const filter = require('promise-filter')
+const normalize = require('./lib/sig-util').normalize
+const encryptor = require('./lib/encryptor')
+const messageManager = require('./lib/message-manager')
+const IdStoreMigrator = require('./lib/idStore-migrator')
+const BN = ethUtil.BN
+
+// Keyrings:
+const SimpleKeyring = require('./keyrings/simple')
+const HdKeyring = require('./keyrings/hd')
+const keyringTypes = [
+ SimpleKeyring,
+ HdKeyring,
+]
+
+const createId = require('./lib/random-id')
+
+module.exports = class KeyringController extends EventEmitter {
+
+ // PUBLIC METHODS
+ //
+ // THE FIRST SECTION OF METHODS ARE PUBLIC-FACING,
+ // MEANING THEY ARE USED BY CONSUMERS OF THIS CLASS.
+ //
+ // THEIR SURFACE AREA SHOULD BE CHANGED WITH GREAT CARE.
+
+ constructor (opts) {
+ super()
+ this.configManager = opts.configManager
+ this.ethStore = opts.ethStore
+ this.encryptor = encryptor
+ this.keyringTypes = keyringTypes
+
+ this.keyrings = []
+ this.identities = {} // Essentially a name hash
+
+ this._unconfTxCbs = {}
+ this._unconfMsgCbs = {}
+
+ this.getNetwork = opts.getNetwork
+
+ // TEMPORARY UNTIL FULL DEPRECATION:
+ this.idStoreMigrator = new IdStoreMigrator({
+ configManager: this.configManager,
+ })
+ }
+
+ // Set Store
+ //
+ // Allows setting the ethStore after the constructor.
+ // This is currently required because of the initialization order
+ // of the ethStore and this class.
+ //
+ // Eventually would be nice to be able to add this in the constructor.
+ setStore (ethStore) {
+ this.ethStore = ethStore
+ }
+
+ // Full Update
+ // returns Promise( @object state )
+ //
+ // Emits the `update` event and
+ // returns a Promise that resolves to the current state.
+ //
+ // Frequently used to end asynchronous chains in this class,
+ // indicating consumers can often either listen for updates,
+ // or accept a state-resolving promise to consume their results.
+ //
+ // Not all methods end with this, that might be a nice refactor.
+ fullUpdate () {
+ this.emit('update')
+ return Promise.resolve(this.getState())
+ }
+
+ // Get State
+ // returns @object state
+ //
+ // This method returns a hash representing the current state
+ // that the keyringController manages.
+ //
+ // It is extended in the MetamaskController along with the EthStore
+ // state, and its own state, to create the metamask state branch
+ // that is passed to the UI.
+ //
+ // This is currently a rare example of a synchronously resolving method
+ // in this class, but will need to be Promisified when we move our
+ // persistence to an async model.
+ getState () {
+ const configManager = this.configManager
+ const address = configManager.getSelectedAccount()
+ const wallet = configManager.getWallet() // old style vault
+ const vault = configManager.getVault() // new style vault
+
+ return {
+ seedWords: this.configManager.getSeedWords(),
+ isInitialized: (!!wallet || !!vault),
+ isUnlocked: Boolean(this.password),
+ isDisclaimerConfirmed: this.configManager.getConfirmedDisclaimer(), // AUDIT this.configManager.getConfirmedDisclaimer(),
+ unconfTxs: this.configManager.unconfirmedTxs(),
+ transactions: this.configManager.getTxList(),
+ unconfMsgs: messageManager.unconfirmedMsgs(),
+ messages: messageManager.getMsgList(),
+ selectedAccount: address,
+ shapeShiftTxList: this.configManager.getShapeShiftTxList(),
+ currentFiat: this.configManager.getCurrentFiat(),
+ conversionRate: this.configManager.getConversionRate(),
+ conversionDate: this.configManager.getConversionDate(),
+ keyringTypes: this.keyringTypes.map(krt => krt.type),
+ identities: this.identities,
+ }
+ }
+
+ // Create New Vault And Keychain
+ // @string password - The password to encrypt the vault with
+ //
+ // returns Promise( @object state )
+ //
+ // Destroys any old encrypted storage,
+ // creates a new encrypted store with the given password,
+ // randomly creates a new HD wallet with 1 account,
+ // faucets that account on the testnet.
+ createNewVaultAndKeychain (password) {
+ return this.persistAllKeyrings(password)
+ .then(this.createFirstKeyTree.bind(this))
+ .then(this.fullUpdate.bind(this))
+ }
+
+ // CreateNewVaultAndRestore
+ // @string password - The password to encrypt the vault with
+ // @string seed - The BIP44-compliant seed phrase.
+ //
+ // returns Promise( @object state )
+ //
+ // Destroys any old encrypted storage,
+ // creates a new encrypted store with the given password,
+ // creates a new HD wallet from the given seed with 1 account.
+ createNewVaultAndRestore (password, seed) {
+ if (typeof password !== 'string') {
+ return Promise.reject('Password must be text.')
+ }
+
+ if (!bip39.validateMnemonic(seed)) {
+ return Promise.reject('Seed phrase is invalid.')
+ }
+
+ this.clearKeyrings()
+
+ return this.persistAllKeyrings(password)
+ .then(() => {
+ return this.addNewKeyring('HD Key Tree', {
+ mnemonic: seed,
+ numberOfAccounts: 1,
+ })
+ }).then(() => {
+ const firstKeyring = this.keyrings[0]
+ return firstKeyring.getAccounts()
+ })
+ .then((accounts) => {
+ const firstAccount = accounts[0]
+ const hexAccount = normalize(firstAccount)
+ this.configManager.setSelectedAccount(hexAccount)
+ return this.setupAccounts(accounts)
+ })
+ .then(this.persistAllKeyrings.bind(this, password))
+ .then(this.fullUpdate.bind(this))
+ }
+
+ // PlaceSeedWords
+ // returns Promise( @object state )
+ //
+ // 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 () {
+ const firstKeyring = this.keyrings[0]
+ return firstKeyring.serialize()
+ .then((serialized) => {
+ const seedWords = serialized.mnemonic
+ this.configManager.setSeedWords(seedWords)
+ return this.fullUpdate()
+ })
+ }
+
+ // ClearSeedWordCache
+ //
+ // returns Promise( @string currentSelectedAccount )
+ //
+ // Removes the current vault's seed words from the UI's state tree,
+ // ensuring they are only ever available in the background process.
+ clearSeedWordCache () {
+ this.configManager.setSeedWords(null)
+ return Promise.resolve(this.configManager.getSelectedAccount())
+ }
+
+ // Set Locked
+ // returns Promise( @object state )
+ //
+ // This method deallocates all secrets, and effectively locks metamask.
+ setLocked () {
+ this.password = null
+ this.keyrings = []
+ return this.fullUpdate()
+ }
+
+ // Submit Password
+ // @string password
+ //
+ // returns Promise( @object state )
+ //
+ // Attempts to decrypt the current vault and load its keyrings
+ // into memory.
+ //
+ // Temporarily also migrates any old-style vaults first, as well.
+ // (Pre MetaMask 3.0.0)
+ submitPassword (password) {
+ return this.migrateOldVaultIfAny(password)
+ .then(() => {
+ return this.unlockKeyrings(password)
+ })
+ .then((keyrings) => {
+ this.keyrings = keyrings
+ return this.fullUpdate()
+ })
+ }
+
+ // Add New Keyring
+ // @string type
+ // @object opts
+ //
+ // returns Promise( @Keyring keyring )
+ //
+ // Adds a new Keyring of the given `type` to the vault
+ // and the current decrypted Keyrings array.
+ //
+ // All Keyring classes implement a unique `type` string,
+ // and this is used to retrieve them from the keyringTypes array.
+ addNewKeyring (type, opts) {
+ const Keyring = this.getKeyringClassForType(type)
+ const keyring = new Keyring(opts)
+ return keyring.getAccounts()
+ .then((accounts) => {
+ this.keyrings.push(keyring)
+ return this.setupAccounts(accounts)
+ })
+ .then(() => { return this.password })
+ .then(this.persistAllKeyrings.bind(this))
+ .then(() => {
+ return keyring
+ })
+ }
+
+ // Add New Account
+ // @number keyRingNum
+ //
+ // returns Promise( @object state )
+ //
+ // Calls the `addAccounts` method on the Keyring
+ // in the kryings array at index `keyringNum`,
+ // and then saves those changes.
+ addNewAccount (keyRingNum = 0) {
+ const ring = this.keyrings[keyRingNum]
+ return ring.addAccounts(1)
+ .then(this.setupAccounts.bind(this))
+ .then(this.persistAllKeyrings.bind(this))
+ .then(this.fullUpdate.bind(this))
+ }
+
+ // Set Selected Account
+ // @string address
+ //
+ // returns Promise( @string address )
+ //
+ // Sets the state's `selectedAccount` value
+ // to the specified address.
+ setSelectedAccount (address) {
+ var addr = normalize(address)
+ this.configManager.setSelectedAccount(addr)
+ return Promise.resolve(addr)
+ }
+
+ // Save Account Label
+ // @string account
+ // @string label
+ //
+ // returns Promise( @string label )
+ //
+ // Persists a nickname equal to `label` for the specified account.
+ saveAccountLabel (account, label) {
+ const address = normalize(account)
+ const configManager = this.configManager
+ configManager.setNicknameForWallet(address, label)
+ this.identities[address].name = label
+ return Promise.resolve(label)
+ }
+
+ // Export Account
+ // @string address
+ //
+ // returns Promise( @string privateKey )
+ //
+ // Requests the private key from the keyring controlling
+ // the specified address.
+ //
+ // Returns a Promise that may resolve with the private key string.
+ exportAccount (address) {
+ try {
+ return this.getKeyringForAccount(address)
+ .then((keyring) => {
+ return keyring.exportAccount(normalize(address))
+ })
+ } catch (e) {
+ return Promise.reject(e)
+ }
+ }
+
+
+ // SIGNING RELATED METHODS
+ //
+ // SIGN, SUBMIT TX, CANCEL, AND APPROVE.
+ // THIS SECTION INVOLVES THE REQUEST, STORING, AND SIGNING OF DATA
+ // WITH THE KEYS STORED IN THIS CONTROLLER.
+
+
+ // Add Unconfirmed Transaction
+ // @object txParams
+ // @function onTxDoneCb
+ // @function cb
+ //
+ // Calls back `cb` with @object txData = { txParams }
+ // Calls back `onTxDoneCb` with `true` or an `error` depending on result.
+ //
+ // Prepares the given `txParams` for final confirmation and approval.
+ // Estimates gas and other preparatory steps.
+ // Caches the requesting Dapp's callback, `onTxDoneCb`, for resolution later.
+ addUnconfirmedTransaction (txParams, onTxDoneCb, cb) {
+ const configManager = this.configManager
+
+ // create txData obj with parameters and meta data
+ var time = (new Date()).getTime()
+ var txId = createId()
+ txParams.metamaskId = txId
+ txParams.metamaskNetworkId = this.getNetwork()
+ var txData = {
+ id: txId,
+ txParams: txParams,
+ time: time,
+ status: 'unconfirmed',
+ gasMultiplier: configManager.getGasMultiplier() || 1,
+ metamaskNetworkId: this.getNetwork(),
+ }
+
+ // keep the onTxDoneCb around for after approval/denial (requires user interaction)
+ // This onTxDoneCb fires completion to the Dapp's write operation.
+ this._unconfTxCbs[txId] = onTxDoneCb
+
+ var provider = this.ethStore._query.currentProvider
+ var query = new EthQuery(provider)
+
+ // calculate metadata for tx
+ this.analyzeTxGasUsage(query, txData, this.txDidComplete.bind(this, txData, cb))
+ }
+
+ estimateTxGas (query, txData, blockGasLimitHex, cb) {
+ const txParams = txData.txParams
+ // check if gasLimit is already specified
+ txData.gasLimitSpecified = Boolean(txParams.gas)
+ // if not, fallback to block gasLimit
+ if (!txData.gasLimitSpecified) {
+ txParams.gas = blockGasLimitHex
+ }
+ // run tx, see if it will OOG
+ query.estimateGas(txParams, cb)
+ }
+
+ checkForTxGasError (txData, estimatedGasHex, cb) {
+ txData.estimatedGas = estimatedGasHex
+ // all gas used - must be an error
+ if (estimatedGasHex === txData.txParams.gas) {
+ txData.simulationFails = true
+ }
+ cb()
+ }
+
+ setTxGas (txData, blockGasLimitHex, cb) {
+ const txParams = txData.txParams
+ // if OOG, nothing more to do
+ if (txData.simulationFails) {
+ cb()
+ return
+ }
+ // if gasLimit was specified and doesnt OOG,
+ // use original specified amount
+ if (txData.gasLimitSpecified) {
+ txData.estimatedGas = txParams.gas
+ cb()
+ return
+ }
+ // if gasLimit not originally specified,
+ // try adding an additional gas buffer to our estimation for safety
+ const estimatedGasBn = new BN(ethUtil.stripHexPrefix(txData.estimatedGas), 16)
+ const blockGasLimitBn = new BN(ethUtil.stripHexPrefix(blockGasLimitHex), 16)
+ const estimationWithBuffer = new BN(this.addGasBuffer(estimatedGasBn), 16)
+ // added gas buffer is too high
+ if (estimationWithBuffer.gt(blockGasLimitBn)) {
+ txParams.gas = txData.estimatedGas
+ // added gas buffer is safe
+ } else {
+ const gasWithBufferHex = ethUtil.intToHex(estimationWithBuffer)
+ txParams.gas = gasWithBufferHex
+ }
+ cb()
+ return
+ }
+
+ txDidComplete (txData, cb, err) {
+ if (err) return cb(err)
+ const configManager = this.configManager
+ configManager.addTx(txData)
+ // signal update
+ this.emit('update')
+ // signal completion of add tx
+ cb(null, txData)
+ }
+
+ analyzeTxGasUsage (query, txData, cb) {
+ query.getBlockByNumber('latest', true, (err, block) => {
+ if (err) return cb(err)
+ async.waterfall([
+ this.estimateTxGas.bind(this, query, txData, block.gasLimit),
+ this.checkForTxGasError.bind(this, txData),
+ this.setTxGas.bind(this, txData, block.gasLimit),
+ ], cb)
+ })
+ }
+
+ // Cancel Transaction
+ // @string txId
+ // @function cb
+ //
+ // Calls back `cb` with no error if provided.
+ //
+ // Forgets any tx matching `txId`.
+ cancelTransaction (txId, cb) {
+ const configManager = this.configManager
+ var approvalCb = this._unconfTxCbs[txId] || noop
+
+ // reject tx
+ approvalCb(null, false)
+ // clean up
+ configManager.rejectTx(txId)
+ delete this._unconfTxCbs[txId]
+
+ if (cb && typeof cb === 'function') {
+ cb()
+ }
+ }
+
+ // Approve Transaction
+ // @string txId
+ // @function cb
+ //
+ // Calls back `cb` with no error always.
+ //
+ // Attempts to sign a Transaction with `txId`
+ // and submit it to the blockchain.
+ //
+ // Calls back the cached Dapp's confirmation callback, also.
+ approveTransaction (txId, cb) {
+ const configManager = this.configManager
+ var approvalCb = this._unconfTxCbs[txId] || noop
+
+ // accept tx
+ cb()
+ approvalCb(null, true)
+ // clean up
+ configManager.confirmTx(txId)
+ delete this._unconfTxCbs[txId]
+ this.emit('update')
+ }
+
+ signTransaction (txParams, cb) {
+ try {
+ const address = normalize(txParams.from)
+ return this.getKeyringForAccount(address)
+ .then((keyring) => {
+ // Handle gas pricing
+ var gasMultiplier = this.configManager.getGasMultiplier() || 1
+ var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice), 16)
+ gasPrice = gasPrice.mul(new BN(gasMultiplier * 100, 10)).div(new BN(100, 10))
+ txParams.gasPrice = ethUtil.intToHex(gasPrice.toNumber())
+
+ // normalize values
+ txParams.to = normalize(txParams.to)
+ txParams.from = normalize(txParams.from)
+ txParams.value = normalize(txParams.value)
+ txParams.data = normalize(txParams.data)
+ txParams.gasLimit = normalize(txParams.gasLimit || txParams.gas)
+ txParams.nonce = normalize(txParams.nonce)
+
+ const tx = new Transaction(txParams)
+ return keyring.signTransaction(address, tx)
+ })
+ .then((tx) => {
+ // Add the tx hash to the persisted meta-tx object
+ var txHash = ethUtil.bufferToHex(tx.hash())
+ var metaTx = this.configManager.getTx(txParams.metamaskId)
+ metaTx.hash = txHash
+ this.configManager.updateTx(metaTx)
+
+ // return raw serialized tx
+ var rawTx = ethUtil.bufferToHex(tx.serialize())
+ cb(null, rawTx)
+ })
+ } catch (e) {
+ cb(e)
+ }
+ }
+
+ // Add Unconfirmed Message
+ // @object msgParams
+ // @function cb
+ //
+ // Does not call back, only emits an `update` event.
+ //
+ // Adds the given `msgParams` and `cb` to a local cache,
+ // for displaying to a user for approval before signing or canceling.
+ addUnconfirmedMessage (msgParams, cb) {
+ // create txData obj with parameters and meta data
+ var time = (new Date()).getTime()
+ var msgId = createId()
+ var msgData = {
+ id: msgId,
+ msgParams: msgParams,
+ time: time,
+ status: 'unconfirmed',
+ }
+ messageManager.addMsg(msgData)
+ console.log('addUnconfirmedMessage:', msgData)
+
+ // keep the cb around for after approval (requires user interaction)
+ // This cb fires completion to the Dapp's write operation.
+ this._unconfMsgCbs[msgId] = cb
+
+ // signal update
+ this.emit('update')
+ return msgId
+ }
+
+ // Cancel Message
+ // @string msgId
+ // @function cb (optional)
+ //
+ // Calls back to cached `unconfMsgCb`.
+ // Calls back to `cb` if provided.
+ //
+ // Forgets any messages matching `msgId`.
+ cancelMessage (msgId, cb) {
+ var approvalCb = this._unconfMsgCbs[msgId] || noop
+
+ // reject tx
+ approvalCb(null, false)
+ // clean up
+ messageManager.rejectMsg(msgId)
+ delete this._unconfTxCbs[msgId]
+
+ if (cb && typeof cb === 'function') {
+ cb()
+ }
+ }
+
+ // Sign Message
+ // @object msgParams
+ // @function cb
+ //
+ // returns Promise(@buffer rawSig)
+ // calls back @function cb with @buffer rawSig
+ // calls back cached Dapp's @function unconfMsgCb.
+ //
+ // Attempts to sign the provided @object msgParams.
+ signMessage (msgParams, cb) {
+ try {
+ const msgId = msgParams.metamaskId
+ delete msgParams.metamaskId
+ const approvalCb = this._unconfMsgCbs[msgId] || noop
+
+ const address = normalize(msgParams.from)
+ return this.getKeyringForAccount(address)
+ .then((keyring) => {
+ return keyring.signMessage(address, msgParams.data)
+ }).then((rawSig) => {
+ cb(null, rawSig)
+ approvalCb(null, true)
+ return rawSig
+ })
+ } catch (e) {
+ cb(e)
+ }
+ }
+
+ // PRIVATE METHODS
+ //
+ // THESE METHODS ARE ONLY USED INTERNALLY TO THE KEYRING-CONTROLLER
+ // AND SO MAY BE CHANGED MORE LIBERALLY THAN THE ABOVE METHODS.
+
+ // 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) {
+ const shouldMigrate = !!this.configManager.getWallet() && !this.configManager.getVault()
+ return this.idStoreMigrator.migratedVaultForPassword(password)
+ .then((serialized) => {
+ this.password = password
+
+ if (serialized && shouldMigrate) {
+ return this.restoreKeyring(serialized)
+ .then(keyring => keyring.getAccounts())
+ .then((accounts) => {
+ this.configManager.setSelectedAccount(accounts[0])
+ return this.persistAllKeyrings()
+ })
+ } else {
+ return Promise.resolve()
+ }
+ })
+ }
+
+ // Create First Key Tree
+ // returns @Promise
+ //
+ // Clears the vault,
+ // creates a new one,
+ // creates a random new HD Keyring with 1 account,
+ // makes that account the selected account,
+ // faucets that account on testnet,
+ // puts the current seed words into the state tree.
+ createFirstKeyTree () {
+ this.clearKeyrings()
+ return this.addNewKeyring('HD Key Tree', {numberOfAccounts: 1})
+ .then(() => {
+ return this.keyrings[0].getAccounts()
+ })
+ .then((accounts) => {
+ const firstAccount = accounts[0]
+ const hexAccount = normalize(firstAccount)
+ this.configManager.setSelectedAccount(hexAccount)
+ this.emit('newAccount', hexAccount)
+ return this.setupAccounts(accounts)
+ }).then(() => {
+ return this.placeSeedWords()
+ })
+ .then(this.persistAllKeyrings.bind(this))
+ }
+
+ // Setup Accounts
+ // @array accounts
+ //
+ // returns @Promise(@object account)
+ //
+ // Initializes the provided account array
+ // Gives them numerically incremented nicknames,
+ // and adds them to the ethStore for regular balance checking.
+ setupAccounts (accounts) {
+ return this.getAccounts()
+ .then((loadedAccounts) => {
+ const arr = accounts || loadedAccounts
+ return Promise.all(arr.map((account) => {
+ return this.getBalanceAndNickname(account)
+ }))
+ })
+ }
+
+ // Get Balance And Nickname
+ // @string account
+ //
+ // returns Promise( @string label )
+ //
+ // Takes an account address and an iterator representing
+ // the current number of named accounts.
+ getBalanceAndNickname (account) {
+ if (!account) {
+ throw new Error('Problem loading account.')
+ }
+ const address = normalize(account)
+ this.ethStore.addAccount(address)
+ return this.createNickname(address)
+ }
+
+ // Create Nickname
+ // @string address
+ //
+ // returns Promise( @string label )
+ //
+ // Takes an address, and assigns it an incremented nickname, persisting it.
+ createNickname (address) {
+ const hexAddress = normalize(address)
+ var i = Object.keys(this.identities).length
+ const oldNickname = this.configManager.nicknameForWallet(address)
+ const name = oldNickname || `Account ${++i}`
+ this.identities[hexAddress] = {
+ address: hexAddress,
+ name,
+ }
+ return this.saveAccountLabel(hexAddress, name)
+ }
+
+ // Persist All Keyrings
+ // @password string
+ //
+ // returns Promise
+ //
+ // Iterates the current `keyrings` array,
+ // serializes each one into a serialized array,
+ // encrypts that array with the provided `password`,
+ // and persists that encrypted string to storage.
+ persistAllKeyrings (password = this.password) {
+ if (typeof password === 'string') {
+ this.password = password
+ }
+ return Promise.all(this.keyrings.map((keyring) => {
+ return Promise.all([keyring.type, keyring.serialize()])
+ .then((serializedKeyringArray) => {
+ // Label the output values on each serialized Keyring:
+ return {
+ type: serializedKeyringArray[0],
+ data: serializedKeyringArray[1],
+ }
+ })
+ }))
+ .then((serializedKeyrings) => {
+ return this.encryptor.encrypt(this.password, serializedKeyrings)
+ })
+ .then((encryptedString) => {
+ this.configManager.setVault(encryptedString)
+ return true
+ })
+ }
+
+ // Unlock Keyrings
+ // @string password
+ //
+ // returns Promise( @array keyrings )
+ //
+ // Attempts to unlock the persisted encrypted storage,
+ // initializing the persisted keyrings to RAM.
+ unlockKeyrings (password) {
+ const encryptedVault = this.configManager.getVault()
+ return this.encryptor.decrypt(password, encryptedVault)
+ .then((vault) => {
+ this.password = password
+ vault.forEach(this.restoreKeyring.bind(this))
+ return this.keyrings
+ })
+ }
+
+ // Restore Keyring
+ // @object serialized
+ //
+ // returns Promise( @Keyring deserialized )
+ //
+ // Attempts to initialize a new keyring from the provided
+ // serialized payload.
+ //
+ // On success, returns the resulting @Keyring instance.
+ restoreKeyring (serialized) {
+ const { type, data } = serialized
+ const Keyring = this.getKeyringClassForType(type)
+ const keyring = new Keyring()
+ return keyring.deserialize(data)
+ .then(() => {
+ return keyring.getAccounts()
+ })
+ .then((accounts) => {
+ return this.setupAccounts(accounts)
+ })
+ .then(() => {
+ this.keyrings.push(keyring)
+ return keyring
+ })
+ }
+
+ // Get Keyring Class For Type
+ // @string type
+ //
+ // Returns @class Keyring
+ //
+ // Searches the current `keyringTypes` array
+ // for a Keyring class whose unique `type` property
+ // matches the provided `type`,
+ // returning it if it exists.
+ getKeyringClassForType (type) {
+ return this.keyringTypes.find(kr => kr.type === type)
+ }
+
+ // Get Accounts
+ // returns Promise( @Array[ @string accounts ] )
+ //
+ // Returns the public addresses of all current accounts
+ // managed by all currently unlocked keyrings.
+ getAccounts () {
+ const keyrings = this.keyrings || []
+ return Promise.all(keyrings.map(kr => kr.getAccounts()))
+ .then((keyringArrays) => {
+ return keyringArrays.reduce((res, arr) => {
+ return res.concat(arr)
+ }, [])
+ })
+ }
+
+ // Get Keyring For Account
+ // @string address
+ //
+ // returns Promise(@Keyring keyring)
+ //
+ // Returns the currently initialized keyring that manages
+ // the specified `address` if one exists.
+ getKeyringForAccount (address) {
+ const hexed = normalize(address)
+
+ return Promise.all(this.keyrings.map((keyring) => {
+ return Promise.all([
+ keyring,
+ keyring.getAccounts(),
+ ])
+ }))
+ .then(filter((candidate) => {
+ const accounts = candidate[1].map(normalize)
+ return accounts.includes(hexed)
+ }))
+ .then((winners) => {
+ if (winners && winners.length > 0) {
+ return winners[0][0]
+ } else {
+ throw new Error('No keyring found for the requested account.')
+ }
+ })
+ }
+
+ // Add Gas Buffer
+ // @string gas (as hexadecimal value)
+ //
+ // returns @string bufferedGas (as hexadecimal value)
+ //
+ // Adds a healthy buffer of gas to an initial gas estimate.
+ addGasBuffer (gas) {
+ const gasBuffer = new BN('100000', 10)
+ const bnGas = new BN(ethUtil.stripHexPrefix(gas), 16)
+ const correct = bnGas.add(gasBuffer)
+ return ethUtil.addHexPrefix(correct.toString(16))
+ }
+
+ // Clear Keyrings
+ //
+ // Deallocates all currently managed keyrings and accounts.
+ // Used before initializing a new vault.
+ clearKeyrings () {
+ let accounts
+ try {
+ accounts = Object.keys(this.ethStore._currentState.accounts)
+ } catch (e) {
+ accounts = []
+ }
+ accounts.forEach((address) => {
+ this.ethStore.removeAccount(address)
+ })
+
+ this.keyrings = []
+ this.identities = {}
+ this.configManager.setSelectedAccount()
+ }
+
+}
+
+
+function noop () {}