The MetaMask Controller——The central metamask controller. Aggregates other controllers and exports an api.
The core functionality of MetaMask all lives in what we call . Our goal for this file is for it to eventually be its own javascript module that can be imported into any JS-compatible context, allowing it to fully manage an app's relationship to Ethereum.
When callingnew MetaMask(opts), many platform-specific options are configured. The keys onopts are as follows:
在调用new MetaMask(opts)时,配置了许多特定于平台的选项。如下:
initState: The last emitted state, used for restoring persistent state between sessions.最后发出的状态,用于在会话之间恢复持久状态
platform: Theplatform object defines a variety of platform-specific functions, including opening the confirmation view, and opening web sites.平台对象定义了各种特定于平台的功能,包括打开确认视图和打开web站点。
encryptor - An object that provides access to the desired encryption methods.提供对所需加密方法的访问的对象
Encryptor
An object that provides two simple methods, which can encrypt in any format you prefer. This parameter is optional, and will default to the browser-native WebCrypto API.
encrypt(password, object) - returns a Promise of a string that is ready for storage.返回一个可用于存储的字符串的Promise
decrypt(password, encryptedString) - Accepts the encrypted output ofencrypt and returns a Promise of a restoredobject as it was encrypted.接受加密的输出encryptedString,并返回解密的对象的promise。
Platform Options
Theplatform object has a variety of options:
reload (function) - Will be called when MetaMask would like to reload its own context.将在MetaMask想要重新加载其自己的上下文时调用
openWindow ({ url }) - Will be called when MetaMask would like to open a web page. It will be passed a singleoptions object with aurl key, with a string value.将在MetaMask想要打开web页面时调用。它将传递一个带有url键和字符串值的单一选项对象
getVersion() - Should return the current MetaMask version, as described in the currentCHANGELOG.md orapp/manifest.json.应该返回当前MetaMask的版本,即在当前的CHANGELOG.md或app/manifest.json中所述的版本号
///metamask-controller.js
/** * @file The central metamask controller. Aggregates other controllers and exports an api. * @copyright Copyright (c) 2018 MetaMask * @license MIT */const EventEmitter = require('events')const pump = require('pump')const Dnode = require('dnode')const ObservableStore = require('obs-store')const ComposableObservableStore = require('./lib/ComposableObservableStore')//一个可观察存储,它可以根据配置组成子存储的平面结构const asStream = require('obs-store/lib/asStream')const AccountTracker = require('./lib/account-tracker')const RpcEngine = require('json-rpc-engine')const debounce = require('debounce')const createEngineStream = require('json-rpc-middleware-stream/engineStream')const createFilterMiddleware = require('eth-json-rpc-filters')const createOriginMiddleware = require('./lib/createOriginMiddleware')const createLoggerMiddleware = require('./lib/createLoggerMiddleware')const createProviderMiddleware = require('./lib/createProviderMiddleware')const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex //other controllerconst KeyringController = require('eth-keyring-controller')const NetworkController = require('./controllers/network')const PreferencesController = require('./controllers/preferences')const CurrencyController = require('./controllers/currency')const NoticeController = require('./notice-controller')const ShapeShiftController = require('./controllers/shapeshift')const AddressBookController = require('./controllers/address-book')const InfuraController = require('./controllers/infura')const BlacklistController = require('./controllers/blacklist')const RecentBlocksController = require('./controllers/recent-blocks') const TransactionController = require('./controllers/transactions') const BalancesController = require('./controllers/computed-balances') const TokenRatesController = require('./controllers/token-rates') const DetectTokensController = require('./controllers/detect-tokens') const MessageManager = require('./lib/message-manager')const PersonalMessageManager = require('./lib/personal-message-manager')const TypedMessageManager = require('./lib/typed-message-manager')const ConfigManager = require('./lib/config-manager') const nodeify = require('./lib/nodeify')const accountImporter = require('./account-import-strategies')const getBuyEthUrl = require('./lib/buy-eth-url')const Mutex = require('await-semaphore').Mutexconst version = require('../manifest.json').versionconst BN = require('ethereumjs-util').BNconst GWEI_BN = new BN('1000000000')const percentile = require('percentile')const seedPhraseVerifier = require('./lib/seed-phrase-verifier')const cleanErrorStack = require('./lib/cleanErrorStack')const log = require('loglevel')const TrezorKeyring = require('eth-trezor-keyring')const LedgerBridgeKeyring = require('eth-ledger-bridge-keyring') // MetamaskController集成了一些其他的controller并释放APImodule.exports = class MetamaskController extends EventEmitter { /** * @constructor * @param {Object} opts */ constructor (opts) { //配置所有需要的controller并获得相应的信息,得到交易信息、store信息等并按上面格式存储... super() this.defaultMaxListeners = 20 this.sendUpdate = debounce(this.privateSendUpdate.bind(this), 200) this.opts = opts const initState = opts.initState || {} this.recordFirstTimeInfo(initState) // platform-specific api this.platform = opts.platform // observable state store this.store = new ComposableObservableStore(initState) // lock to ensure only one vault created at once,互斥信号量,用于保证一次只有一个vault创建 this.createVaultMutex = new Mutex() // network store this.networkController = new NetworkController(initState.NetworkController) // config manager this.configManager = new ConfigManager({ store: this.store, }) // preferences controller this.preferencesController = new PreferencesController({ initState: initState.PreferencesController, initLangCode: opts.initLangCode, network: this.networkController, }) // currency controller this.currencyController = new CurrencyController({ initState: initState.CurrencyController, }) this.currencyController.updateConversionRate() this.currencyController.scheduleConversionInterval() // infura controller this.infuraController = new InfuraController({ initState: initState.InfuraController, }) this.infuraController.scheduleInfuraNetworkCheck() this.blacklistController = new BlacklistController() this.blacklistController.scheduleUpdates() // rpc provider this.provider = this.initializeProvider() this.blockTracker = this.provider._blockTracker // token exchange rate tracker this.tokenRatesController = new TokenRatesController({ preferences: this.preferencesController.store, }) this.recentBlocksController = new RecentBlocksController({ blockTracker: this.blockTracker, provider: this.provider, }) // account tracker watches balances, nonces, and any code at their address. this.accountTracker = new AccountTracker({ provider: this.provider, blockTracker: this.blockTracker, }) // key mgmt const additionalKeyrings = [TrezorKeyring, LedgerBridgeKeyring] this.keyringController = new KeyringController({ keyringTypes: additionalKeyrings, initState: initState.KeyringController, getNetwork: this.networkController.getNetworkState.bind(this.networkController), encryptor: opts.encryptor || undefined, }) // If only one account exists, make sure it is selected. this.keyringController.memStore.subscribe((state) => { const addresses = state.keyrings.reduce((res, keyring) => { return res.concat(keyring.accounts) }, []) if (addresses.length === 1) { const address = addresses[0] this.preferencesController.setSelectedAddress(address) } // ensure preferences + identities controller know about all addresses this.preferencesController.addAddresses(addresses) this.accountTracker.syncWithAddresses(addresses) }) // detect tokens controller this.detectTokensController = new DetectTokensController({ preferences: this.preferencesController, network: this.networkController, keyringMemStore: this.keyringController.memStore, }) // address book controller this.addressBookController = new AddressBookController({ initState: initState.AddressBookController, preferencesStore: this.preferencesController.store, }) // tx mgmt this.txController = new TransactionController({ initState: initState.TransactionController || initState.TransactionManager, networkStore: this.networkController.networkStore, preferencesStore: this.preferencesController.store, txHistoryLimit: 40, getNetwork: this.networkController.getNetworkState.bind(this), signTransaction: this.keyringController.signTransaction.bind(this.keyringController), provider: this.provider, blockTracker: this.blockTracker, getGasPrice: this.getGasPrice.bind(this), }) this.txController.on('newUnapprovedTx', opts.showUnapprovedTx.bind(opts)) this.txController.on(`tx:status-update`, (txId, status) => { if (status === 'confirmed' || status === 'failed') { const txMeta = this.txController.txStateManager.getTx(txId) this.platform.showTransactionNotification(txMeta) } }) // computed balances (accounting for pending transactions) this.balancesController = new BalancesController({ accountTracker: this.accountTracker, txController: this.txController, blockTracker: this.blockTracker, }) this.networkController.on('networkDidChange', () => { this.balancesController.updateAllBalances() }) this.balancesController.updateAllBalances() // notices this.noticeController = new NoticeController({ initState: initState.NoticeController, version, firstVersion: initState.firstTimeInfo.version, }) this.shapeshiftController = new ShapeShiftController({ initState: initState.ShapeShiftController, }) this.networkController.lookupNetwork() this.messageManager = new MessageManager() this.personalMessageManager = new PersonalMessageManager() this.typedMessageManager = new TypedMessageManager() this.publicConfigStore = this.initPublicConfigStore() this.store.updateStructure({ TransactionController: this.txController.store, KeyringController: this.keyringController.store, PreferencesController: this.preferencesController.store, AddressBookController: this.addressBookController.store, CurrencyController: this.currencyController.store, NoticeController: this.noticeController.store, ShapeShiftController: this.shapeshiftController.store, NetworkController: this.networkController.store, InfuraController: this.infuraController.store, }) this.memStore = new ComposableObservableStore(null, { NetworkController: this.networkController.store, AccountTracker: this.accountTracker.store, TxController: this.txController.memStore, BalancesController: this.balancesController.store, TokenRatesController: this.tokenRatesController.store, MessageManager: this.messageManager.memStore, PersonalMessageManager: this.personalMessageManager.memStore, TypesMessageManager: this.typedMessageManager.memStore, KeyringController: this.keyringController.memStore, PreferencesController: this.preferencesController.store, RecentBlocksController: this.recentBlocksController.store, AddressBookController: this.addressBookController.store, CurrencyController: this.currencyController.store, NoticeController: this.noticeController.memStore, ShapeshiftController: this.shapeshiftController.store, InfuraController: this.infuraController.store, }) this.memStore.subscribe(this.sendUpdate.bind(this)) } /** * Constructor helper: initialize a provider. */ initializeProvider () { //初始化provider const providerOpts = { static: { eth_syncing: false, web3_clientVersion: `MetaMask/v${version}`, eth_sendTransaction: (payload, next, end) => { const origin = payload.origin const txParams = payload.params[0] nodeify(this.txController.newUnapprovedTransaction, this.txController)(txParams, { origin }, end) }, }, // account mgmt getAccounts: (cb) => { 得到账户信息,是否上锁 const isUnlocked = this.keyringController.memStore.getState().isUnlocked const result = [] const selectedAddress = this.preferencesController.getSelectedAddress() // only show address if account is unlocked if (isUnlocked && selectedAddress) { //没上锁时才显示address result.push(selectedAddress) } cb(null, result) }, // tx signing ,没有签名的信息 // old style msg signing processMessage: this.newUnsignedMessage.bind(this), // personal_sign msg signing processPersonalMessage: this.newUnsignedPersonalMessage.bind(this), processTypedMessage: this.newUnsignedTypedMessage.bind(this), } const providerProxy = this.networkController.initializeProvider(providerOpts) return providerProxy } /** * Constructor helper: initialize a public config store. * This store is used to make some config info available to Dapps synchronously.这个存储用于同步地为Dapps提供一些配置信息 */ initPublicConfigStore () { // get init state const publicConfigStore = new ObservableStore() // memStore -> transform -> publicConfigStore this.on('update', (memState) => { this.isClientOpenAndUnlocked = memState.isUnlocked && this._isClientOpen const publicState = selectPublicState(memState) publicConfigStore.putState(publicState) }) function selectPublicState (memState) { const result = { selectedAddress: memState.isUnlocked ? memState.selectedAddress : undefined, networkVersion: memState.network, } return result } return publicConfigStore }//=============================================================================// EXPOSED TO THE UI SUBSYSTEM 提供两个暴露给UI的接口//============================================================================= /** * The metamask-state of the various controllers, made available to the UI * * @returns {Object} status */ getState () { //得到控制器的状态 const wallet = this.configManager.getWallet() const vault = this.keyringController.store.getState().vault const isInitialized = (!!wallet || !!vault) return { ...{ isInitialized }, ...this.memStore.getFlatState(), ...this.configManager.getConfig(), ...{ lostAccounts: this.configManager.getLostAccounts(), seedWords: this.configManager.getSeedWords(), forgottenPassword: this.configManager.getPasswordForgotten(), }, } } /** * Returns an Object containing API Callback Functions. * These functions are the interface for the UI. * The API object can be transmitted over a stream with dnode. * * @returns {Object} Object containing API functions. */ getApi () { //提供了UI上进行操作所要调用的函数的接口,比如在图形化界面上有一个addToken的按钮,那么就会调用下面的addToken api const keyringController = this.keyringController const preferencesController = this.preferencesController const txController = this.txController const noticeController = this.noticeController const addressBookController = this.addressBookController const networkController = this.networkController return { // etc getState: (cb) => cb(null, this.getState()), setCurrentCurrency: this.setCurrentCurrency.bind(this), setUseBlockie: this.setUseBlockie.bind(this), setCurrentLocale: this.setCurrentLocale.bind(this), markAccountsFound: this.markAccountsFound.bind(this), markPasswordForgotten: this.markPasswordForgotten.bind(this), unMarkPasswordForgotten: this.unMarkPasswordForgotten.bind(this), getGasPrice: (cb) => cb(null, this.getGasPrice()), // coinbase buyEth: this.buyEth.bind(this), // shapeshift createShapeShiftTx: this.createShapeShiftTx.bind(this), // primary HD keyring management addNewAccount: nodeify(this.addNewAccount, this), placeSeedWords: this.placeSeedWords.bind(this), verifySeedPhrase: nodeify(this.verifySeedPhrase, this), clearSeedWordCache: this.clearSeedWordCache.bind(this), resetAccount: nodeify(this.resetAccount, this), removeAccount: nodeify(this.removeAccount, this), importAccountWithStrategy: nodeify(this.importAccountWithStrategy, this), // hardware wallets connectHardware: nodeify(this.connectHardware, this), forgetDevice: nodeify(this.forgetDevice, this), checkHardwareStatus: nodeify(this.checkHardwareStatus, this), unlockHardwareWalletAccount: nodeify(this.unlockHardwareWalletAccount, this), // vault management submitPassword: nodeify(this.submitPassword, this), // network management setProviderType: nodeify(networkController.setProviderType, networkController), setCustomRpc: nodeify(this.setCustomRpc, this), // PreferencesController setSelectedAddress: nodeify(preferencesController.setSelectedAddress, preferencesController), addToken: nodeify(preferencesController.addToken, preferencesController), removeToken: nodeify(preferencesController.removeToken, preferencesController), setCurrentAccountTab: nodeify(preferencesController.setCurrentAccountTab, preferencesController), setAccountLabel: nodeify(preferencesController.setAccountLabel, preferencesController), setFeatureFlag: nodeify(preferencesController.setFeatureFlag, preferencesController), // AddressController setAddressBook: nodeify(addressBookController.setAddressBook, addressBookController), // KeyringController setLocked: nodeify(keyringController.setLocked, keyringController), createNewVaultAndKeychain: nodeify(this.createNewVaultAndKeychain, this), createNewVaultAndRestore: nodeify(this.createNewVaultAndRestore, this), addNewKeyring: nodeify(keyringController.addNewKeyring, keyringController), exportAccount: nodeify(keyringController.exportAccount, keyringController), // txController cancelTransaction: nodeify(txController.cancelTransaction, txController), updateTransaction: nodeify(txController.updateTransaction, txController), updateAndApproveTransaction: nodeify(txController.updateAndApproveTransaction, txController), retryTransaction: nodeify(this.retryTransaction, this), getFilteredTxList: nodeify(txController.getFilteredTxList, txController), isNonceTaken: nodeify(txController.isNonceTaken, txController), estimateGas: nodeify(this.estimateGas, this), // messageManager signMessage: nodeify(this.signMessage, this), cancelMessage: this.cancelMessage.bind(this), // personalMessageManager signPersonalMessage: nodeify(this.signPersonalMessage, this), cancelPersonalMessage: this.cancelPersonalMessage.bind(this), // personalMessageManager signTypedMessage: nodeify(this.signTypedMessage, this), cancelTypedMessage: this.cancelTypedMessage.bind(this), // notices checkNotices: noticeController.updateNoticesList.bind(noticeController), markNoticeRead: noticeController.markNoticeRead.bind(noticeController), } }//=============================================================================// VAULT / KEYRING RELATED METHODS//============================================================================= /** * Creates a new Vault and create a new keychain. * * A vault, or KeyringController, is a controller that contains 控制器包含了很多不同的账户策略,该控制器称为Keyrings * many different account strategies, currently called Keyrings. * Creating it new means wiping all previous keyrings. 创建新的控制器即要擦除以前所有的keyrings * * A keychain, or keyring, controls many accounts with a single backup and signing strategy. keychain或keyring使用一个简单的恢复和签名策略来控制账户 * For example, a mnemonic phrase can generate many accounts, and is a keyring. 一个mnemonic能够控制所有在metamask上生成的所有账户,这就是一个keyring * * @param {string} password * * @returns {Object} vault */ async createNewVaultAndKeychain (password) { const releaseLock = await this.createVaultMutex.acquire()//上锁 try { let vault const accounts = await this.keyringController.getAccounts() //得到旧的keyringController中的所有账户 if (accounts.length > 0) {//要有账户才有创建新keyringController的意义 vault = await this.keyringController.fullUpdate() //更新 } else {//否则就第一次创建 vault = await this.keyringController.createNewVaultAndKeychain(password) const accounts = await this.keyringController.getAccounts() this.preferencesController.setAddresses(accounts) this.selectFirstIdentity() } releaseLock()//解锁 return vault } catch (err) { releaseLock() throw err } } /** * Create a new Vault and restore an existent keyring. * @param {} password * @param {} seed */ async createNewVaultAndRestore (password, seed) { const releaseLock = await this.createVaultMutex.acquire() //上锁 try { // clear known identities this.preferencesController.setAddresses([]) // create new vault const vault = await this.keyringController.createNewVaultAndRestore(password, seed) // set new identities const accounts = await this.keyringController.getAccounts() this.preferencesController.setAddresses(accounts) this.selectFirstIdentity() releaseLock()//解锁 return vault } catch (err) { releaseLock() throw err } } /* * Submits the user's password and attempts to unlock the vault. * Also synchronizes the preferencesController, to ensure its schema * is up to date with known accounts once the vault is decrypted. * * @param {string} password - The user's password * @returns {Promise