eth-keyring-controller
Advanced tools
Comparing version 6.2.1 to 7.0.0
616
index.js
@@ -0,22 +1,29 @@ | ||
const { EventEmitter } = require('events'); | ||
const bip39 = require('bip39'); | ||
const ObservableStore = require('obs-store'); | ||
const encryptor = require('browser-passworder'); | ||
const { normalize: normalizeAddress } = require('eth-sig-util'); | ||
const { EventEmitter } = require('events') | ||
const log = require('loglevel') | ||
const ethUtil = require('ethereumjs-util') | ||
const SimpleKeyring = require('eth-simple-keyring'); | ||
const HdKeyring = require('@metamask/eth-hd-keyring'); | ||
const { BN } = ethUtil | ||
const bip39 = require('bip39') | ||
const ObservableStore = require('obs-store') | ||
const encryptor = require('browser-passworder') | ||
const { normalize: normalizeAddress } = require('eth-sig-util') | ||
const keyringTypes = [SimpleKeyring, HdKeyring]; | ||
const SimpleKeyring = require('eth-simple-keyring') | ||
const HdKeyring = require('eth-hd-keyring') | ||
const KEYRINGS_TYPE_MAP = { | ||
HD_KEYRING: 'HD Key Tree', | ||
SIMPLE_KEYRING: 'Simple Key Pair', | ||
}; | ||
/** | ||
* Strip the hex prefix from an address, if present | ||
* @param {string} address - The address that might be hex prefixed. | ||
* @returns {string} The address without a hex prefix. | ||
*/ | ||
function stripHexPrefix(address) { | ||
if (address.startsWith('0x')) { | ||
return address.slice(2); | ||
} | ||
return address; | ||
} | ||
const keyringTypes = [ | ||
SimpleKeyring, | ||
HdKeyring, | ||
] | ||
class KeyringController extends EventEmitter { | ||
// | ||
@@ -26,7 +33,9 @@ // PUBLIC METHODS | ||
constructor (opts) { | ||
super() | ||
const initState = opts.initState || {} | ||
this.keyringTypes = opts.keyringTypes ? keyringTypes.concat(opts.keyringTypes) : keyringTypes | ||
this.store = new ObservableStore(initState) | ||
constructor(opts) { | ||
super(); | ||
const initState = opts.initState || {}; | ||
this.keyringTypes = opts.keyringTypes | ||
? keyringTypes.concat(opts.keyringTypes) | ||
: keyringTypes; | ||
this.store = new ObservableStore(initState); | ||
this.memStore = new ObservableStore({ | ||
@@ -36,6 +45,6 @@ isUnlocked: false, | ||
keyrings: [], | ||
}) | ||
}); | ||
this.encryptor = opts.encryptor || encryptor | ||
this.keyrings = [] | ||
this.encryptor = opts.encryptor || encryptor; | ||
this.keyrings = []; | ||
} | ||
@@ -55,5 +64,5 @@ | ||
*/ | ||
fullUpdate () { | ||
this.emit('update', this.memStore.getState()) | ||
return this.memStore.getState() | ||
fullUpdate() { | ||
this.emit('update', this.memStore.getState()); | ||
return this.memStore.getState(); | ||
} | ||
@@ -73,8 +82,7 @@ | ||
*/ | ||
createNewVaultAndKeychain (password) { | ||
return this.persistAllKeyrings(password) | ||
.then(this.createFirstKeyTree.bind(this)) | ||
createNewVaultAndKeychain(password) { | ||
return this.createFirstKeyTree(password) | ||
.then(this.persistAllKeyrings.bind(this, password)) | ||
.then(this.setUnlocked.bind(this)) | ||
.then(this.fullUpdate.bind(this)) | ||
.then(this.fullUpdate.bind(this)); | ||
} | ||
@@ -94,32 +102,35 @@ | ||
*/ | ||
createNewVaultAndRestore (password, seed) { | ||
createNewVaultAndRestore(password, seed) { | ||
if (typeof password !== 'string') { | ||
return Promise.reject(new Error('Password must be text.')) | ||
return Promise.reject(new Error('Password must be text.')); | ||
} | ||
if (!bip39.validateMnemonic(seed)) { | ||
return Promise.reject(new Error('Seed phrase is invalid.')) | ||
const wordlists = Object.values(bip39.wordlists); | ||
if ( | ||
wordlists.every((wordlist) => !bip39.validateMnemonic(seed, wordlist)) | ||
) { | ||
return Promise.reject(new Error('Seed phrase is invalid.')); | ||
} | ||
this.clearKeyrings() | ||
this.clearKeyrings(); | ||
return this.persistAllKeyrings(password) | ||
.then(() => { | ||
return this.addNewKeyring('HD Key Tree', { | ||
return this.addNewKeyring(KEYRINGS_TYPE_MAP.HD_KEYRING, { | ||
mnemonic: seed, | ||
numberOfAccounts: 1, | ||
}) | ||
}); | ||
}) | ||
.then((firstKeyring) => { | ||
return firstKeyring.getAccounts() | ||
return firstKeyring.getAccounts(); | ||
}) | ||
.then(([firstAccount]) => { | ||
if (!firstAccount) { | ||
throw new Error('KeyringController - First Account not found.') | ||
throw new Error('KeyringController - First Account not found.'); | ||
} | ||
return null | ||
return null; | ||
}) | ||
.then(this.persistAllKeyrings.bind(this, password)) | ||
.then(this.setUnlocked.bind(this)) | ||
.then(this.fullUpdate.bind(this)) | ||
.then(this.fullUpdate.bind(this)); | ||
} | ||
@@ -134,11 +145,11 @@ | ||
*/ | ||
async setLocked () { | ||
async setLocked() { | ||
// set locked | ||
this.password = null | ||
this.memStore.updateState({ isUnlocked: false }) | ||
this.password = null; | ||
this.memStore.updateState({ isUnlocked: false }); | ||
// remove keyrings | ||
this.keyrings = [] | ||
await this._updateMemStoreKeyrings() | ||
this.emit('lock') | ||
return this.fullUpdate() | ||
this.keyrings = []; | ||
await this._updateMemStoreKeyrings(); | ||
this.emit('lock'); | ||
return this.fullUpdate(); | ||
} | ||
@@ -159,9 +170,8 @@ | ||
*/ | ||
submitPassword (password) { | ||
return this.unlockKeyrings(password) | ||
.then((keyrings) => { | ||
this.keyrings = keyrings | ||
this.setUnlocked() | ||
return this.fullUpdate() | ||
}) | ||
submitPassword(password) { | ||
return this.unlockKeyrings(password).then((keyrings) => { | ||
this.keyrings = keyrings; | ||
this.setUnlocked(); | ||
return this.fullUpdate(); | ||
}); | ||
} | ||
@@ -177,8 +187,8 @@ | ||
*/ | ||
async verifyPassword (password) { | ||
const encryptedVault = this.store.getState().vault | ||
async verifyPassword(password) { | ||
const encryptedVault = this.store.getState().vault; | ||
if (!encryptedVault) { | ||
throw new Error('Cannot unlock without a previous vault.') | ||
throw new Error('Cannot unlock without a previous vault.'); | ||
} | ||
await this.encryptor.decrypt(password, encryptedVault) | ||
await this.encryptor.decrypt(password, encryptedVault); | ||
} | ||
@@ -199,12 +209,18 @@ | ||
*/ | ||
addNewKeyring (type, opts) { | ||
const Keyring = this.getKeyringClassForType(type) | ||
const keyring = new Keyring(opts) | ||
return keyring.getAccounts() | ||
addNewKeyring(type, opts = {}) { | ||
const Keyring = this.getKeyringClassForType(type); | ||
const keyring = new Keyring(opts); | ||
if (!opts.mnemonic && type === KEYRINGS_TYPE_MAP.HD_KEYRING) { | ||
keyring.generateRandomMnemonic(); | ||
keyring.addAccounts(); | ||
} | ||
return keyring | ||
.getAccounts() | ||
.then((accounts) => { | ||
return this.checkForDuplicate(type, accounts) | ||
return this.checkForDuplicate(type, accounts); | ||
}) | ||
.then(() => { | ||
this.keyrings.push(keyring) | ||
return this.persistAllKeyrings() | ||
this.keyrings.push(keyring); | ||
return this.persistAllKeyrings(); | ||
}) | ||
@@ -214,4 +230,4 @@ .then(() => this._updateMemStoreKeyrings()) | ||
.then(() => { | ||
return keyring | ||
}) | ||
return keyring; | ||
}); | ||
} | ||
@@ -225,4 +241,4 @@ | ||
*/ | ||
async removeEmptyKeyrings () { | ||
const validKeyrings = [] | ||
async removeEmptyKeyrings() { | ||
const validKeyrings = []; | ||
@@ -233,9 +249,11 @@ // Since getAccounts returns a Promise | ||
await Promise.all(this.keyrings.map(async (keyring) => { | ||
const accounts = await keyring.getAccounts() | ||
if (accounts.length > 0) { | ||
validKeyrings.push(keyring) | ||
} | ||
})) | ||
this.keyrings = validKeyrings | ||
await Promise.all( | ||
this.keyrings.map(async (keyring) => { | ||
const accounts = await keyring.getAccounts(); | ||
if (accounts.length > 0) { | ||
validKeyrings.push(keyring); | ||
} | ||
}), | ||
); | ||
this.keyrings = validKeyrings; | ||
} | ||
@@ -253,23 +271,26 @@ | ||
*/ | ||
checkForDuplicate (type, newAccountArray) { | ||
return this.getAccounts() | ||
.then((accounts) => { | ||
switch (type) { | ||
case 'Simple Key Pair': { | ||
const isIncluded = Boolean( | ||
accounts.find( | ||
(key) => ( | ||
key === newAccountArray[0] || | ||
key === ethUtil.stripHexPrefix(newAccountArray[0])), | ||
), | ||
) | ||
return isIncluded | ||
? Promise.reject(new Error('The account you\'re are trying to import is a duplicate')) | ||
: Promise.resolve(newAccountArray) | ||
} | ||
default: { | ||
return Promise.resolve(newAccountArray) | ||
} | ||
checkForDuplicate(type, newAccountArray) { | ||
return this.getAccounts().then((accounts) => { | ||
switch (type) { | ||
case KEYRINGS_TYPE_MAP.SIMPLE_KEYRING: { | ||
const isIncluded = Boolean( | ||
accounts.find( | ||
(key) => | ||
key === newAccountArray[0] || | ||
key === stripHexPrefix(newAccountArray[0]), | ||
), | ||
); | ||
return isIncluded | ||
? Promise.reject( | ||
new Error( | ||
"The account you're are trying to import is a duplicate", | ||
), | ||
) | ||
: Promise.resolve(newAccountArray); | ||
} | ||
}) | ||
default: { | ||
return Promise.resolve(newAccountArray); | ||
} | ||
} | ||
}); | ||
} | ||
@@ -286,12 +307,13 @@ | ||
*/ | ||
addNewAccount (selectedKeyring) { | ||
return selectedKeyring.addAccounts(1) | ||
addNewAccount(selectedKeyring) { | ||
return selectedKeyring | ||
.addAccounts(1) | ||
.then((accounts) => { | ||
accounts.forEach((hexAccount) => { | ||
this.emit('newAccount', hexAccount) | ||
}) | ||
this.emit('newAccount', hexAccount); | ||
}); | ||
}) | ||
.then(this.persistAllKeyrings.bind(this)) | ||
.then(this._updateMemStoreKeyrings.bind(this)) | ||
.then(this.fullUpdate.bind(this)) | ||
.then(this.fullUpdate.bind(this)); | ||
} | ||
@@ -310,10 +332,9 @@ | ||
*/ | ||
exportAccount (address) { | ||
exportAccount(address) { | ||
try { | ||
return this.getKeyringForAccount(address) | ||
.then((keyring) => { | ||
return keyring.exportAccount(normalizeAddress(address)) | ||
}) | ||
return this.getKeyringForAccount(address).then((keyring) => { | ||
return keyring.exportAccount(normalizeAddress(address)); | ||
}); | ||
} catch (e) { | ||
return Promise.reject(e) | ||
return Promise.reject(e); | ||
} | ||
@@ -332,3 +353,3 @@ } | ||
*/ | ||
removeAccount (address) { | ||
removeAccount(address) { | ||
return this.getKeyringForAccount(address) | ||
@@ -338,9 +359,11 @@ .then((keyring) => { | ||
if (typeof keyring.removeAccount === 'function') { | ||
keyring.removeAccount(address) | ||
this.emit('removedAccount', address) | ||
return keyring.getAccounts() | ||
keyring.removeAccount(address); | ||
this.emit('removedAccount', address); | ||
return keyring.getAccounts(); | ||
} | ||
return Promise.reject(new Error( | ||
`Keyring ${keyring.type} doesn't support account removal operations`, | ||
)) | ||
return Promise.reject( | ||
new Error( | ||
`Keyring ${keyring.type} doesn't support account removal operations`, | ||
), | ||
); | ||
}) | ||
@@ -350,5 +373,5 @@ .then((accounts) => { | ||
if (accounts.length === 0) { | ||
return this.removeEmptyKeyrings() | ||
return this.removeEmptyKeyrings(); | ||
} | ||
return undefined | ||
return undefined; | ||
}) | ||
@@ -359,4 +382,4 @@ .then(this.persistAllKeyrings.bind(this)) | ||
.catch((e) => { | ||
return Promise.reject(e) | ||
}) | ||
return Promise.reject(e); | ||
}); | ||
} | ||
@@ -376,10 +399,9 @@ | ||
* @param {Object} opts - Signing options. | ||
* @returns {Promise<Object>} The signed transactio object. | ||
* @returns {Promise<Object>} The signed transaction object. | ||
*/ | ||
signTransaction (ethTx, _fromAddress, opts = {}) { | ||
const fromAddress = normalizeAddress(_fromAddress) | ||
return this.getKeyringForAccount(fromAddress) | ||
.then((keyring) => { | ||
return keyring.signTransaction(fromAddress, ethTx, opts) | ||
}) | ||
signTransaction(ethTx, _fromAddress, opts = {}) { | ||
const fromAddress = normalizeAddress(_fromAddress); | ||
return this.getKeyringForAccount(fromAddress).then((keyring) => { | ||
return keyring.signTransaction(fromAddress, ethTx, opts); | ||
}); | ||
} | ||
@@ -395,8 +417,7 @@ | ||
*/ | ||
signMessage (msgParams, opts = {}) { | ||
const address = normalizeAddress(msgParams.from) | ||
return this.getKeyringForAccount(address) | ||
.then((keyring) => { | ||
return keyring.signMessage(address, msgParams.data, opts) | ||
}) | ||
signMessage(msgParams, opts = {}) { | ||
const address = normalizeAddress(msgParams.from); | ||
return this.getKeyringForAccount(address).then((keyring) => { | ||
return keyring.signMessage(address, msgParams.data, opts); | ||
}); | ||
} | ||
@@ -407,3 +428,3 @@ | ||
* | ||
* Attempts to sign the provided message paramaters. | ||
* Attempts to sign the provided message parameters. | ||
* Prefixes the hash before signing per the personal sign expectation. | ||
@@ -414,8 +435,7 @@ * | ||
*/ | ||
signPersonalMessage (msgParams, opts = {}) { | ||
const address = normalizeAddress(msgParams.from) | ||
return this.getKeyringForAccount(address) | ||
.then((keyring) => { | ||
return keyring.signPersonalMessage(address, msgParams.data, opts) | ||
}) | ||
signPersonalMessage(msgParams, opts = {}) { | ||
const address = normalizeAddress(msgParams.from); | ||
return this.getKeyringForAccount(address).then((keyring) => { | ||
return keyring.signPersonalMessage(address, msgParams.data, opts); | ||
}); | ||
} | ||
@@ -431,8 +451,7 @@ | ||
*/ | ||
getEncryptionPublicKey (_address, opts = {}) { | ||
const address = normalizeAddress(_address) | ||
return this.getKeyringForAccount(address) | ||
.then((keyring) => { | ||
return keyring.getEncryptionPublicKey(address, opts) | ||
}) | ||
getEncryptionPublicKey(_address, opts = {}) { | ||
const address = normalizeAddress(_address); | ||
return this.getKeyringForAccount(address).then((keyring) => { | ||
return keyring.getEncryptionPublicKey(address, opts); | ||
}); | ||
} | ||
@@ -448,8 +467,7 @@ | ||
*/ | ||
decryptMessage (msgParams, opts = {}) { | ||
const address = normalizeAddress(msgParams.from) | ||
return this.getKeyringForAccount(address) | ||
.then((keyring) => { | ||
return keyring.decryptMessage(address, msgParams.data, opts) | ||
}) | ||
decryptMessage(msgParams, opts = {}) { | ||
const address = normalizeAddress(msgParams.from); | ||
return this.getKeyringForAccount(address).then((keyring) => { | ||
return keyring.decryptMessage(address, msgParams.data, opts); | ||
}); | ||
} | ||
@@ -464,8 +482,7 @@ | ||
*/ | ||
signTypedMessage (msgParams, opts = { version: 'V1' }) { | ||
const address = normalizeAddress(msgParams.from) | ||
return this.getKeyringForAccount(address) | ||
.then((keyring) => { | ||
return keyring.signTypedData(address, msgParams.data, opts) | ||
}) | ||
signTypedMessage(msgParams, opts = { version: 'V1' }) { | ||
const address = normalizeAddress(msgParams.from); | ||
return this.getKeyringForAccount(address).then((keyring) => { | ||
return keyring.signTypedData(address, msgParams.data, opts); | ||
}); | ||
} | ||
@@ -480,6 +497,6 @@ | ||
*/ | ||
async getAppKeyAddress (_address, origin) { | ||
const address = normalizeAddress(_address) | ||
const keyring = await this.getKeyringForAccount(address) | ||
return keyring.getAppKeyAddress(address, origin) | ||
async getAppKeyAddress(_address, origin) { | ||
const address = normalizeAddress(_address); | ||
const keyring = await this.getKeyringForAccount(address); | ||
return keyring.getAppKeyAddress(address, origin); | ||
} | ||
@@ -494,9 +511,11 @@ | ||
*/ | ||
async exportAppKeyForAddress (_address, origin) { | ||
const address = normalizeAddress(_address) | ||
const keyring = await this.getKeyringForAccount(address) | ||
async exportAppKeyForAddress(_address, origin) { | ||
const address = normalizeAddress(_address); | ||
const keyring = await this.getKeyringForAccount(address); | ||
if (!('exportAccount' in keyring)) { | ||
throw new Error(`The keyring for address ${_address} does not support exporting.`) | ||
throw new Error( | ||
`The keyring for address ${_address} does not support exporting.`, | ||
); | ||
} | ||
return keyring.exportAccount(address, { withAppKeyOrigin: origin }) | ||
return keyring.exportAccount(address, { withAppKeyOrigin: origin }); | ||
} | ||
@@ -518,18 +537,20 @@ | ||
* | ||
* @returns {Promise<void>} - A promise that resovles if the operation was successful. | ||
* @param {string} password - The keyring controller password. | ||
* @returns {Promise<void>} - A promise that resolves if the operation was successful. | ||
*/ | ||
createFirstKeyTree () { | ||
this.clearKeyrings() | ||
return this.addNewKeyring('HD Key Tree', { numberOfAccounts: 1 }) | ||
createFirstKeyTree(password) { | ||
this.password = password; | ||
this.clearKeyrings(); | ||
return this.addNewKeyring(KEYRINGS_TYPE_MAP.HD_KEYRING) | ||
.then((keyring) => { | ||
return keyring.getAccounts() | ||
return keyring.getAccounts(); | ||
}) | ||
.then(([firstAccount]) => { | ||
if (!firstAccount) { | ||
throw new Error('KeyringController - No account found on keychain.') | ||
throw new Error('KeyringController - No account found on keychain.'); | ||
} | ||
const hexAccount = normalizeAddress(firstAccount) | ||
this.emit('newVault', hexAccount) | ||
return null | ||
}) | ||
const hexAccount = normalizeAddress(firstAccount); | ||
this.emit('newVault', hexAccount); | ||
return null; | ||
}); | ||
} | ||
@@ -548,27 +569,30 @@ | ||
*/ | ||
persistAllKeyrings (password = this.password) { | ||
persistAllKeyrings(password = this.password) { | ||
if (typeof password !== 'string') { | ||
return Promise.reject(new Error( | ||
'KeyringController - password is not a string', | ||
)) | ||
return Promise.reject( | ||
new Error('KeyringController - password is not a 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], | ||
} | ||
}) | ||
})) | ||
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) | ||
return this.encryptor.encrypt(this.password, serializedKeyrings); | ||
}) | ||
.then((encryptedString) => { | ||
this.store.updateState({ vault: encryptedString }) | ||
return true | ||
}) | ||
this.store.updateState({ vault: encryptedString }); | ||
return true; | ||
}); | ||
} | ||
@@ -585,14 +609,14 @@ | ||
*/ | ||
async unlockKeyrings (password) { | ||
const encryptedVault = this.store.getState().vault | ||
async unlockKeyrings(password) { | ||
const encryptedVault = this.store.getState().vault; | ||
if (!encryptedVault) { | ||
throw new Error('Cannot unlock without a previous vault.') | ||
throw new Error('Cannot unlock without a previous vault.'); | ||
} | ||
await this.clearKeyrings() | ||
const vault = await this.encryptor.decrypt(password, encryptedVault) | ||
this.password = password | ||
await Promise.all(vault.map(this._restoreKeyring.bind(this))) | ||
await this._updateMemStoreKeyrings() | ||
return this.keyrings | ||
await this.clearKeyrings(); | ||
const vault = await this.encryptor.decrypt(password, encryptedVault); | ||
this.password = password; | ||
await Promise.all(vault.map(this._restoreKeyring.bind(this))); | ||
await this._updateMemStoreKeyrings(); | ||
return this.keyrings; | ||
} | ||
@@ -610,6 +634,6 @@ | ||
*/ | ||
async restoreKeyring (serialized) { | ||
const keyring = await this._restoreKeyring(serialized) | ||
await this._updateMemStoreKeyrings() | ||
return keyring | ||
async restoreKeyring(serialized) { | ||
const keyring = await this._restoreKeyring(serialized); | ||
await this._updateMemStoreKeyrings(); | ||
return keyring; | ||
} | ||
@@ -626,12 +650,12 @@ | ||
*/ | ||
async _restoreKeyring (serialized) { | ||
const { type, data } = serialized | ||
async _restoreKeyring(serialized) { | ||
const { type, data } = serialized; | ||
const Keyring = this.getKeyringClassForType(type) | ||
const keyring = new Keyring() | ||
await keyring.deserialize(data) | ||
const Keyring = this.getKeyringClassForType(type); | ||
const keyring = new Keyring(); | ||
await keyring.deserialize(data); | ||
// getAccounts also validates the accounts for some keyrings | ||
await keyring.getAccounts() | ||
this.keyrings.push(keyring) | ||
return keyring | ||
await keyring.getAccounts(); | ||
this.keyrings.push(keyring); | ||
return keyring; | ||
} | ||
@@ -650,4 +674,4 @@ | ||
*/ | ||
getKeyringClassForType (type) { | ||
return this.keyringTypes.find((kr) => kr.type === type) | ||
getKeyringClassForType(type) { | ||
return this.keyringTypes.find((kr) => kr.type === type); | ||
} | ||
@@ -663,4 +687,4 @@ | ||
*/ | ||
getKeyringsByType (type) { | ||
return this.keyrings.filter((keyring) => keyring.type === type) | ||
getKeyringsByType(type) { | ||
return this.keyrings.filter((keyring) => keyring.type === type); | ||
} | ||
@@ -676,11 +700,12 @@ | ||
*/ | ||
async getAccounts () { | ||
const keyrings = this.keyrings || [] | ||
const addrs = await Promise.all(keyrings.map((kr) => kr.getAccounts())) | ||
.then((keyringArrays) => { | ||
return keyringArrays.reduce((res, arr) => { | ||
return res.concat(arr) | ||
}, []) | ||
}) | ||
return addrs.map(normalizeAddress) | ||
async getAccounts() { | ||
const keyrings = this.keyrings || []; | ||
const addrs = await Promise.all( | ||
keyrings.map((kr) => kr.getAccounts()), | ||
).then((keyringArrays) => { | ||
return keyringArrays.reduce((res, arr) => { | ||
return res.concat(arr); | ||
}, []); | ||
}); | ||
return addrs.map(normalizeAddress); | ||
} | ||
@@ -697,35 +722,31 @@ | ||
*/ | ||
getKeyringForAccount (address) { | ||
const hexed = normalizeAddress(address) | ||
log.debug(`KeyringController - getKeyringForAccount: ${hexed}`) | ||
getKeyringForAccount(address) { | ||
const hexed = normalizeAddress(address); | ||
return Promise.all(this.keyrings.map((keyring) => { | ||
return Promise.all([ | ||
keyring, | ||
keyring.getAccounts(), | ||
]) | ||
})) | ||
.then((candidates) => { | ||
const winners = candidates.filter((candidate) => { | ||
const accounts = candidate[1].map(normalizeAddress) | ||
return accounts.includes(hexed) | ||
}) | ||
if (winners && winners.length > 0) { | ||
return winners[0][0] | ||
} | ||
return Promise.all( | ||
this.keyrings.map((keyring) => { | ||
return Promise.all([keyring, keyring.getAccounts()]); | ||
}), | ||
).then((candidates) => { | ||
const winners = candidates.filter((candidate) => { | ||
const accounts = candidate[1].map(normalizeAddress); | ||
return accounts.includes(hexed); | ||
}); | ||
if (winners && winners.length > 0) { | ||
return winners[0][0]; | ||
} | ||
// Adding more info to the error | ||
let errorInfo = 'Error info: ' | ||
if (!address) { | ||
errorInfo += 'The address passed in is invalid/empty; ' | ||
} | ||
if (!candidates || !candidates.length) { | ||
errorInfo += 'There are no keyrings; ' | ||
} | ||
if (!winners || !winners.length) { | ||
errorInfo += 'There are keyrings, but none match the address;' | ||
} | ||
throw new Error(`No keyring found for the requested account. ${errorInfo}`) | ||
}) | ||
// Adding more info to the error | ||
let errorInfo = ''; | ||
if (!address) { | ||
errorInfo = 'The address passed in is invalid/empty'; | ||
} else if (!candidates || !candidates.length) { | ||
errorInfo = 'There are no keyrings'; | ||
} else if (!winners || !winners.length) { | ||
errorInfo = 'There are keyrings, but none match the address'; | ||
} | ||
throw new Error( | ||
`No keyring found for the requested account. Error info: ${errorInfo}`, | ||
); | ||
}); | ||
} | ||
@@ -740,28 +761,12 @@ | ||
*/ | ||
displayForKeyring (keyring) { | ||
return keyring.getAccounts() | ||
.then((accounts) => { | ||
return { | ||
type: keyring.type, | ||
accounts: accounts.map(normalizeAddress), | ||
} | ||
}) | ||
displayForKeyring(keyring) { | ||
return keyring.getAccounts().then((accounts) => { | ||
return { | ||
type: keyring.type, | ||
accounts: accounts.map(normalizeAddress), | ||
}; | ||
}); | ||
} | ||
/** | ||
* Add Gas Buffer | ||
* | ||
* Adds a healthy buffer of gas to an initial gas estimate. | ||
* | ||
* @param {string} gas - The gas value, as a hex string. | ||
* @returns {string} The buffered gas, as a hex string. | ||
*/ | ||
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 | ||
@@ -772,19 +777,22 @@ * | ||
*/ | ||
/* eslint-disable require-await */ | ||
async clearKeyrings () { | ||
async clearKeyrings() { | ||
// clear keyrings from memory | ||
this.keyrings = [] | ||
this.keyrings = []; | ||
this.memStore.updateState({ | ||
keyrings: [], | ||
}) | ||
}); | ||
} | ||
/** | ||
* Update Memstore Keyrings | ||
* Update memStore Keyrings | ||
* | ||
* Updates the in-memory keyrings, without persisting. | ||
*/ | ||
async _updateMemStoreKeyrings () { | ||
const keyrings = await Promise.all(this.keyrings.map(this.displayForKeyring)) | ||
return this.memStore.updateState({ keyrings }) | ||
async _updateMemStoreKeyrings() { | ||
const keyrings = await Promise.all( | ||
this.keyrings.map(this.displayForKeyring), | ||
); | ||
return this.memStore.updateState({ keyrings }); | ||
} | ||
@@ -799,8 +807,26 @@ | ||
*/ | ||
setUnlocked () { | ||
this.memStore.updateState({ isUnlocked: true }) | ||
this.emit('unlock') | ||
setUnlocked() { | ||
this.memStore.updateState({ isUnlocked: true }); | ||
this.emit('unlock'); | ||
} | ||
/** | ||
* Forget hardware keyring | ||
* | ||
* Forget hardware and update memorized state. | ||
* @param {Keyring} keyring | ||
*/ | ||
forgetKeyring(keyring) { | ||
if (keyring.forgetDevice) { | ||
keyring.forgetDevice(); | ||
this.persistAllKeyrings.bind(this)(); | ||
this._updateMemStoreKeyrings.bind(this)(); | ||
} else { | ||
throw new Error( | ||
`KeyringController - keyring does not have method "forgetDevice", keyring type: ${keyring.type}`, | ||
); | ||
} | ||
} | ||
} | ||
module.exports = KeyringController | ||
module.exports = KeyringController; |
{ | ||
"name": "eth-keyring-controller", | ||
"version": "6.2.1", | ||
"version": "7.0.0", | ||
"description": "A module for managing various keyrings of Ethereum accounts, encrypting them, and using them.", | ||
"main": "index.js", | ||
"engines": { | ||
"node": ">=12.0.0" | ||
}, | ||
"scripts": { | ||
"test": "mocha --timeout 10000 --require test/helper.js test/index.js", | ||
"lint": "eslint . --ext js,json", | ||
"lint:fix": "eslint . --ext js,json --fix" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+ssh://git@github.com/MetaMask/KeyringController.git" | ||
}, | ||
"keywords": [ | ||
@@ -24,37 +11,60 @@ "ethereum", | ||
], | ||
"author": "Dan Finlay <dan@danfinlay.com>", | ||
"license": "ISC", | ||
"homepage": "https://github.com/MetaMask/KeyringController#readme", | ||
"bugs": { | ||
"url": "https://github.com/MetaMask/KeyringController/issues" | ||
}, | ||
"homepage": "https://github.com/MetaMask/KeyringController#readme", | ||
"devDependencies": { | ||
"@babel/core": "^7.8.7", | ||
"@metamask/eslint-config": "^3.2.0", | ||
"clone": "^2.1.1", | ||
"eslint": "^6.8.0", | ||
"eslint-plugin-import": "^2.20.1", | ||
"eslint-plugin-json": "^2.1.1", | ||
"eslint-plugin-mocha": "^6.0.0", | ||
"ethereumjs-wallet": "^0.6.3", | ||
"jsdom": "^11.12.0", | ||
"jsdom-global": "^3.0.2", | ||
"mocha": "^7.1.2", | ||
"polyfill-crypto.getrandomvalues": "^1.0.0", | ||
"sinon": "^7.2.7" | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/MetaMask/KeyringController.git" | ||
}, | ||
"license": "ISC", | ||
"author": "Dan Finlay <dan@danfinlay.com>", | ||
"main": "index.js", | ||
"files": [ | ||
"index.js" | ||
], | ||
"scripts": { | ||
"setup": "yarn install && yarn allow-scripts", | ||
"test": "jest", | ||
"lint:eslint": "eslint . --cache --ext js,ts", | ||
"lint:misc": "prettier '**/*.json' '**/*.md' '!CHANGELOG.md' '**/*.yml' --ignore-path .gitignore", | ||
"lint": "yarn lint:eslint && yarn lint:misc --check", | ||
"lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write" | ||
}, | ||
"dependencies": { | ||
"bip39": "^2.4.0", | ||
"bluebird": "^3.5.0", | ||
"@metamask/eth-hd-keyring": "^4.0.2", | ||
"bip39": "^3.0.4", | ||
"browser-passworder": "^2.0.3", | ||
"eth-hd-keyring": "^3.6.0", | ||
"eth-sig-util": "^3.0.1", | ||
"eth-simple-keyring": "^4.2.0", | ||
"ethereumjs-util": "^7.0.9", | ||
"loglevel": "^1.5.0", | ||
"obs-store": "^4.0.3" | ||
}, | ||
"files": [ | ||
"index.js" | ||
] | ||
"devDependencies": { | ||
"@lavamoat/allow-scripts": "^1.0.6", | ||
"@metamask/auto-changelog": "^2.3.0", | ||
"@metamask/eslint-config": "^7.0.1", | ||
"@metamask/eslint-config-jest": "^7.0.0", | ||
"@metamask/eslint-config-nodejs": "^7.0.1", | ||
"eslint": "^7.29.0", | ||
"eslint-config-prettier": "^8.3.0", | ||
"eslint-plugin-import": "^2.23.4", | ||
"eslint-plugin-jest": "^24.3.6", | ||
"eslint-plugin-node": "^11.1.0", | ||
"eslint-plugin-prettier": "^3.4.0", | ||
"ethereumjs-wallet": "^1.0.1", | ||
"jest": "^27.0.6", | ||
"prettier": "^2.3.2", | ||
"prettier-plugin-packagejson": "^2.2.11", | ||
"sinon": "^11.1.1" | ||
}, | ||
"engines": { | ||
"node": ">=12.0.0" | ||
}, | ||
"lavamoat": { | ||
"allowScripts": { | ||
"@lavamoat/preinstall-always-fail": false, | ||
"keccak": true, | ||
"secp256k1": true | ||
} | ||
} | ||
} |
@@ -1,2 +0,2 @@ | ||
# Eth Keyring Controller [](https://circleci.com/gh/MetaMask/KeyringController) | ||
# Eth Keyring Controller | ||
@@ -8,2 +8,3 @@ A module for managing groups of Ethereum accounts called "Keyrings", defined originally for MetaMask's multiple-account-type feature. | ||
The KeyringController has three main responsibilities: | ||
- Initializing & using (signing with) groups of Ethereum accounts ("keyrings"). | ||
@@ -20,4 +21,4 @@ - Keeping track of local nicknames for those individual accounts. | ||
```javascript | ||
const KeyringController = require('eth-keyring-controller') | ||
const SimpleKeyring = require('eth-simple-keyring') | ||
const KeyringController = require('eth-keyring-controller'); | ||
const SimpleKeyring = require('eth-simple-keyring'); | ||
@@ -27,26 +28,21 @@ const keyringController = new KeyringController({ | ||
initState: initState.KeyringController, // Last emitted persisted state. | ||
encryptor: { // An optional object for defining encryption schemes: | ||
// Defaults to Browser-native SubtleCrypto. | ||
encrypt (password, object) { | ||
return new Promise('encrypted!') | ||
encryptor: { | ||
// An optional object for defining encryption schemes: | ||
// Defaults to Browser-native SubtleCrypto. | ||
encrypt(password, object) { | ||
return new Promise('encrypted!'); | ||
}, | ||
decrypt (password, encryptedString) { | ||
return new Promise({ foo: 'bar' }) | ||
decrypt(password, encryptedString) { | ||
return new Promise({ foo: 'bar' }); | ||
}, | ||
}, | ||
}) | ||
}); | ||
// The KeyringController is also an event emitter: | ||
this.keyringController.on('newAccount', (address) => { | ||
console.log(`New account created: ${address}`) | ||
}) | ||
this.keyringController.on('removedAccount', handleThat) | ||
console.log(`New account created: ${address}`); | ||
}); | ||
this.keyringController.on('removedAccount', handleThat); | ||
``` | ||
## Running tests | ||
```bash | ||
yarn test | ||
``` | ||
## Methods | ||
@@ -56,1 +52,16 @@ | ||
## Contributing | ||
### Setup | ||
- Install [Node.js](https://nodejs.org) version 12 | ||
- If you are using [nvm](https://github.com/creationix/nvm#installation) (recommended) running `nvm use` will automatically choose the right node version for you. | ||
- Install [Yarn v1](https://yarnpkg.com/en/docs/install) | ||
- Run `yarn setup` to install dependencies and run any requried post-install scripts | ||
- **Warning:** Do not use the `yarn` / `yarn install` command directly. Use `yarn setup` instead. The normal install command will skip required post-install scripts, leaving your development environment in an invalid state. | ||
### Testing and Linting | ||
Run `yarn test` to run the tests once. | ||
Run `yarn lint` to run the linter, or run `yarn lint:fix` to run the linter and fix any automatically fixable issues. |
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
30197
6
5
737
64
16
1
+ Added@metamask/bip39@4.0.0(transitive)
+ Added@metamask/eth-hd-keyring@4.0.2(transitive)
+ Added@metamask/eth-sig-util@4.0.1(transitive)
+ Added@noble/hashes@1.7.1(transitive)
+ Added@types/node@11.11.6(transitive)
+ Addedbip39@3.1.0(transitive)
- Removedbluebird@^3.5.0
- Removedeth-hd-keyring@^3.6.0
- Removedethereumjs-util@^7.0.9
- Removedloglevel@^1.5.0
- Removed@types/node@22.13.5(transitive)
- Removedbip39@2.6.0(transitive)
- Removedbluebird@3.7.2(transitive)
- Removedeth-hd-keyring@3.6.0(transitive)
- Removedloglevel@1.9.2(transitive)
- Removedundici-types@6.20.0(transitive)
- Removedunorm@1.6.0(transitive)
Updatedbip39@^3.0.4