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.js946
1 files changed, 528 insertions, 418 deletions
diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js
index fb3091143..4be00a5a5 100644
--- a/app/scripts/keyring-controller.js
+++ b/app/scripts/keyring-controller.js
@@ -1,18 +1,12 @@
-const async = require('async')
-const EventEmitter = require('events').EventEmitter
-const encryptor = require('./lib/encryptor')
-const messageManager = require('./lib/message-manager')
const ethUtil = require('ethereumjs-util')
-const ethBinToOps = require('eth-bin-to-ops')
-const EthQuery = require('eth-query')
-const BN = ethUtil.BN
-const Transaction = require('ethereumjs-tx')
-const createId = require('web3-provider-engine/util/random-id')
-const autoFaucet = require('./lib/auto-faucet')
const bip39 = require('bip39')
+const EventEmitter = require('events').EventEmitter
+const filter = require('promise-filter')
+const encryptor = require('browser-passworder')
-// TEMPORARY UNTIL FULL DEPRECATION:
-const IdStoreMigrator = require('./lib/idStore-migrator')
+const normalize = require('./lib/sig-util').normalize
+const messageManager = require('./lib/message-manager')
+const BN = ethUtil.BN
// Keyrings:
const SimpleKeyring = require('./keyrings/simple')
@@ -22,390 +16,322 @@ const keyringTypes = [
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.web3 = opts.web3
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.network = opts.network
+ 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
}
- getState() {
+ // 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
+ const keyrings = this.keyrings
- return {
- seedWords: this.configManager.getSeedWords(),
- isInitialized: (!!wallet || !!vault),
- isUnlocked: !!this.key,
- isConfirmed: true, // AUDIT this.configManager.getConfirmed(),
- unconfTxs: this.configManager.unconfirmedTxs(),
- transactions: this.configManager.getTxList(),
- unconfMsgs: messageManager.unconfirmedMsgs(),
- messages: messageManager.getMsgList(),
- selectedAddress: address,
- 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,
- }
- }
-
- setStore(ethStore) {
- this.ethStore = ethStore
- }
-
- createNewVaultAndKeychain(password, entropy, cb) {
- this.createNewVault(password, entropy, (err) => {
- if (err) return cb(err)
- this.createFirstKeyTree(password, cb)
+ return Promise.all(keyrings.map(this.displayForKeyring))
+ .then((displayKeyrings) => {
+ return {
+ seedWords: this.configManager.getSeedWords(),
+ isInitialized: (!!wallet || !!vault),
+ isUnlocked: Boolean(this.password),
+ isDisclaimerConfirmed: this.configManager.getConfirmedDisclaimer(),
+ 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,
+ keyrings: displayKeyrings,
+ }
})
}
- createNewVaultAndRestore(password, seed, cb) {
+ // 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 cb('Password must be text.')
+ return Promise.reject('Password must be text.')
}
if (!bip39.validateMnemonic(seed)) {
- return cb('Seed phrase is invalid.')
+ return Promise.reject('Seed phrase is invalid.')
}
this.clearKeyrings()
- this.createNewVault(password, '', (err) => {
- if (err) return cb(err)
- this.addNewKeyring('HD Key Tree', {
+ return this.persistAllKeyrings(password)
+ .then(() => {
+ return this.addNewKeyring('HD Key Tree', {
mnemonic: seed,
- n: 1,
- }, (err) => {
- if (err) return cb(err)
- const firstKeyring = this.keyrings[0]
- const accounts = firstKeyring.getAccounts()
- const firstAccount = accounts[0]
- const hexAccount = normalize(firstAccount)
- this.configManager.setSelectedAccount(hexAccount)
- this.setupAccounts(accounts)
-
- this.emit('update')
- cb(null, this.getState())
+ numberOfAccounts: 1,
})
- })
- }
-
- migrateAndGetKey(password) {
- let key
- const shouldMigrate = !!this.configManager.getWallet() && !this.configManager.getVault()
-
- return this.loadKey(password)
- .then((derivedKey) => {
- key = derivedKey
- this.key = key
- return this.idStoreMigrator.oldSeedForPassword(password)
- })
- .then((serialized) => {
- if (serialized && shouldMigrate) {
- const keyring = this.restoreKeyring(serialized)
- this.keyrings.push(keyring)
- this.configManager.setSelectedAccount(keyring.getAccounts()[0])
- }
- return key
- })
- }
-
- createNewVault(password, entropy, cb) {
- const configManager = this.configManager
- const salt = this.encryptor.generateSalt()
- configManager.setSalt(salt)
-
- return this.migrateAndGetKey(password)
- .then(() => {
- return this.persistAllKeyrings()
- })
- .then(() => {
- cb(null)
- })
- .catch((err) => {
- cb(err)
- })
- }
-
- createFirstKeyTree(password, cb) {
- this.clearKeyrings()
- this.addNewKeyring('HD Key Tree', {n: 1}, (err) => {
+ }).then(() => {
const firstKeyring = this.keyrings[0]
- const accounts = firstKeyring.getAccounts()
+ return firstKeyring.getAccounts()
+ })
+ .then((accounts) => {
const firstAccount = accounts[0]
const hexAccount = normalize(firstAccount)
- const seedWords = firstKeyring.serialize().mnemonic
- this.configManager.setSelectedAccount(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)
- autoFaucet(hexAccount)
- this.setupAccounts(accounts)
- this.persistAllKeyrings()
- .then(() => {
- cb(err, this.getState())
- })
- .catch((reason) => {
- cb(reason)
- })
+ return this.fullUpdate()
})
}
- placeSeedWords (cb) {
- const firstKeyring = this.keyrings[0]
- const seedWords = firstKeyring.serialize().mnemonic
- this.configManager.setSeedWords(seedWords)
+ // 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())
}
- submitPassword(password, cb) {
- this.migrateAndGetKey(password)
- .then((key) => {
- return this.unlockKeyrings(key)
- })
+ // 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.unlockKeyrings(password)
.then((keyrings) => {
this.keyrings = keyrings
- this.setupAccounts()
- this.emit('update')
- cb(null, this.getState())
- })
- .catch((err) => {
- console.error(err)
- cb(err)
+ return this.fullUpdate()
})
}
- loadKey(password) {
- const salt = this.configManager.getSalt() || this.encryptor.generateSalt()
- return this.encryptor.keyFromPassword(password + salt)
- .then((key) => {
- this.key = key
- this.configManager.setSalt(salt)
- return key
- })
- }
-
- addNewKeyring(type, opts, cb) {
+ // 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)
- const accounts = keyring.getAccounts()
-
- this.keyrings.push(keyring)
- this.setupAccounts(accounts)
- this.persistAllKeyrings()
- .then(() => {
- cb(null, this.getState())
- })
- .catch((reason) => {
- cb(reason)
+ return keyring.getAccounts()
+ .then((accounts) => {
+ this.keyrings.push(keyring)
+ return this.setupAccounts(accounts)
})
- }
-
- addNewAccount(keyRingNum = 0, cb) {
- const ring = this.keyrings[keyRingNum]
- const accounts = ring.addAccounts(1)
- this.setupAccounts(accounts)
- this.persistAllKeyrings()
+ .then(() => { return this.password })
+ .then(this.persistAllKeyrings.bind(this))
.then(() => {
- cb(null, this.getState())
- })
- .catch((reason) => {
- cb(reason)
- })
- }
-
- setupAccounts(accounts) {
- var arr = accounts || this.getAccounts()
- arr.forEach((account) => {
- this.loadBalanceAndNickname(account)
+ return keyring
})
}
- // Takes an account address and an iterator representing
- // the current number of named accounts.
- loadBalanceAndNickname(account) {
- const address = normalize(account)
- this.ethStore.addAccount(address)
- this.createNickname(address)
- }
-
- 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)
+ // 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 this.fullUpdate()
}
- saveAccountLabel (account, label, cb) {
+ // 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
- if (cb) {
- cb(null, label)
- } else {
- return 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)
}
}
- persistAllKeyrings() {
- const serialized = this.keyrings.map((k) => {
- return {
- type: k.type,
- data: k.serialize(),
- }
- })
- return this.encryptor.encryptWithKey(this.key, serialized)
- .then((encryptedString) => {
- this.configManager.setVault(encryptedString)
- return true
- })
- }
+ // SIGNING METHODS
+ //
+ // This method signs tx and returns a promise for
+ // TX Manager to update the state after signing
- unlockKeyrings(key) {
- const encryptedVault = this.configManager.getVault()
- return this.encryptor.decryptWithKey(key, encryptedVault)
- .then((vault) => {
- vault.forEach(this.restoreKeyring.bind(this))
- return this.keyrings
+ signTransaction (ethTx, _fromAddress) {
+ const fromAddress = normalize(_fromAddress)
+ return this.getKeyringForAccount(fromAddress)
+ .then((keyring) => {
+ return keyring.signTransaction(fromAddress, ethTx)
})
}
-
- restoreKeyring(serialized) {
- const { type, data } = serialized
- const Keyring = this.getKeyringClassForType(type)
- const keyring = new Keyring()
- keyring.deserialize(data)
-
- const accounts = keyring.getAccounts()
- this.setupAccounts(accounts)
-
- this.keyrings.push(keyring)
- return keyring
- }
-
- getKeyringClassForType(type) {
- const Keyring = this.keyringTypes.reduce((res, kr) => {
- if (kr.type() === type) {
- return kr
- } else {
- return res
- }
- })
- return Keyring
- }
-
- getAccounts() {
- const keyrings = this.keyrings || []
- return keyrings.map(kr => kr.getAccounts())
- .reduce((res, arr) => {
- return res.concat(arr)
- }, [])
- }
-
- setSelectedAddress(address, cb) {
- var addr = normalize(address)
- this.configManager.setSelectedAccount(addr)
- cb(null, addr)
- }
-
- addUnconfirmedTransaction(txParams, onTxDoneCb, cb) {
- var self = this
- 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.network
- var txData = {
- id: txId,
- txParams: txParams,
- time: time,
- status: 'unconfirmed',
- gasMultiplier: configManager.getGasMultiplier() || 1,
- }
-
-
- // 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
- async.parallel([
- analyzeForDelegateCall,
- estimateGas,
- ], didComplete)
-
- // perform static analyis on the target contract code
- function analyzeForDelegateCall(cb){
- if (txParams.to) {
- query.getCode(txParams.to, function (err, result) {
- if (err) return cb(err)
- var code = ethUtil.toBuffer(result)
- if (code !== '0x') {
- var ops = ethBinToOps(code)
- var containsDelegateCall = ops.some((op) => op.name === 'DELEGATECALL')
- txData.containsDelegateCall = containsDelegateCall
- cb()
- } else {
- cb()
- }
- })
- } else {
- cb()
- }
- }
-
- function estimateGas(cb){
- query.estimateGas(txParams, function(err, result){
- if (err) return cb(err)
- txData.estimatedGas = self.addGasBuffer(result)
- cb()
- })
- }
-
- function didComplete (err) {
- if (err) return cb(err)
- configManager.addTx(txData)
- // signal update
- self.emit('update')
- // signal completion of add tx
- cb(null, txData)
- }
- }
-
- addUnconfirmedMessage(msgParams, cb) {
+ // 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()
@@ -427,125 +353,313 @@ module.exports = class KeyringController extends EventEmitter {
return msgId
}
- 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')
- }
-
- cancelTransaction(txId, cb) {
- const configManager = this.configManager
- var approvalCb = this._unconfTxCbs[txId] || noop
+ // 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
- configManager.rejectTx(txId)
- delete this._unconfTxCbs[txId]
+ messageManager.rejectMsg(msgId)
+ delete this._unconfTxCbs[msgId]
if (cb && typeof cb === 'function') {
cb()
}
}
- signTransaction(txParams, 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 address = normalize(txParams.from)
- const keyring = this.getKeyringForAccount(address)
-
- // 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)
-
- let tx = new Transaction(txParams)
- tx = keyring.signTransaction(address, 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)
- }
- }
+ const msgId = msgParams.metamaskId
+ delete msgParams.metamaskId
+ const approvalCb = this._unconfMsgCbs[msgId] || noop
- signMessage(msgParams, cb) {
- try {
- const keyring = this.getKeyringForAccount(msgParams.from)
const address = normalize(msgParams.from)
- const rawSig = keyring.signMessage(address, msgParams.data)
- cb(null, rawSig)
+ return this.getKeyringForAccount(address)
+ .then((keyring) => {
+ return keyring.signMessage(address, msgParams.data)
+ }).then((rawSig) => {
+ cb(null, rawSig)
+ approvalCb(null, true)
+ messageManager.confirmMsg(msgId)
+ return rawSig
+ })
} catch (e) {
cb(e)
}
}
- getKeyringForAccount(address) {
- const hexed = normalize(address)
- return this.keyrings.find((ring) => {
- return ring.getAccounts()
- .map(normalize)
- .includes(hexed)
+ // PRIVATE METHODS
+ //
+ // THESE METHODS ARE ONLY USED INTERNALLY TO THE KEYRING-CONTROLLER
+ // AND SO MAY BE CHANGED MORE LIBERALLY THAN THE ABOVE METHODS.
+
+ // 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)
+ }))
})
}
- cancelMessage(msgId, cb) {
- if (cb && typeof cb === 'function') {
- cb()
+ // 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)
}
- setLocked(cb) {
- this.key = null
- this.keyrings = []
- cb()
+ // 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
+ })
}
- exportAccount(address, cb) {
- try {
- const keyring = this.getKeyringForAccount(address)
- const privateKey = keyring.exportAccount(normalize(address))
- cb(null, privateKey)
- } catch (e) {
- cb(e)
+ // 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()
+ if (!encryptedVault) {
+ throw new Error('Cannot unlock without a previous vault.')
}
+
+ return this.encryptor.decrypt(password, encryptedVault)
+ .then((vault) => {
+ this.password = password
+ vault.forEach(this.restoreKeyring.bind(this))
+ return this.keyrings
+ })
}
- addGasBuffer(gasHex) {
- var gas = new BN(gasHex, 16)
- var buffer = new BN('100000', 10)
- var result = gas.add(buffer)
- return normalize(result.toString(16))
+ // 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
+ })
}
- clearSeedWordCache(cb) {
- this.configManager.setSeedWords(null)
- cb(null, this.configManager.getSelectedAccount())
+ // 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)
+ }, [])
+ })
}
- clearKeyrings() {
+ // 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.')
+ }
+ })
+ }
+
+ // Display For Keyring
+ // @Keyring keyring
+ //
+ // returns Promise( @Object { type:String, accounts:Array } )
+ //
+ // Is used for adding the current keyrings to the state object.
+ displayForKeyring (keyring) {
+ return keyring.getAccounts()
+ .then((accounts) => {
+ return {
+ type: keyring.type,
+ accounts: accounts,
+ }
+ })
+ }
+
+ // 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)
@@ -563,9 +677,5 @@ module.exports = class KeyringController extends EventEmitter {
}
-function normalize(address) {
- if (!address) return
- return ethUtil.addHexPrefix(address.toLowerCase())
-}
function noop () {}