1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-27 21:00:13 +01:00
metamask-extension/app/scripts/metamask-controller.js

1770 lines
62 KiB
JavaScript
Raw Normal View History

2018-03-15 23:27:10 +01:00
/**
* @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 pify = require('pify')
2017-01-25 04:47:00 +01:00
const ObservableStore = require('obs-store')
const ComposableObservableStore = require('./lib/ComposableObservableStore')
const createDnodeRemoteGetter = require('./lib/createDnodeRemoteGetter')
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 createSubscriptionManager = require('eth-json-rpc-filters/subscriptionManager')
const createOriginMiddleware = require('./lib/createOriginMiddleware')
const createLoggerMiddleware = require('./lib/createLoggerMiddleware')
const providerAsMiddleware = require('eth-json-rpc-middleware/providerAsMiddleware')
const {setupMultiplex} = require('./lib/stream-utils.js')
2017-09-22 23:38:40 +02:00
const KeyringController = require('eth-keyring-controller')
const NetworkController = require('./controllers/network')
2017-02-27 19:39:48 +01:00
const PreferencesController = require('./controllers/preferences')
const AppStateController = require('./controllers/app-state')
const InfuraController = require('./controllers/infura')
const CachedBalancesController = require('./controllers/cached-balances')
const OnboardingController = require('./controllers/onboarding')
const RecentBlocksController = require('./controllers/recent-blocks')
const MessageManager = require('./lib/message-manager')
const PersonalMessageManager = require('./lib/personal-message-manager')
2017-09-29 18:24:08 +02:00
const TypedMessageManager = require('./lib/typed-message-manager')
2017-05-16 19:27:41 +02:00
const TransactionController = require('./controllers/transactions')
const BalancesController = require('./controllers/computed-balances')
const TokenRatesController = require('./controllers/token-rates')
2018-06-27 22:29:24 +02:00
const DetectTokensController = require('./controllers/detect-tokens')
2018-09-27 20:19:09 +02:00
const ProviderApprovalController = require('./controllers/provider-approval')
const nodeify = require('./lib/nodeify')
const accountImporter = require('./account-import-strategies')
const getBuyEthUrl = require('./lib/buy-eth-url')
const {Mutex} = require('await-semaphore')
const {version} = require('../manifest.json')
const {BN} = require('ethereumjs-util')
const GWEI_BN = new BN('1000000000')
const percentile = require('percentile')
2018-03-03 00:32:57 +01:00
const seedPhraseVerifier = require('./lib/seed-phrase-verifier')
const log = require('loglevel')
2018-06-13 07:32:13 +02:00
const TrezorKeyring = require('eth-trezor-keyring')
2018-08-12 07:34:01 +02:00
const LedgerBridgeKeyring = require('eth-ledger-bridge-keyring')
2018-11-26 19:27:49 +01:00
const HW_WALLETS_KEYRINGS = [TrezorKeyring.type, LedgerBridgeKeyring.type]
const EthQuery = require('eth-query')
2018-09-10 23:11:57 +02:00
const ethUtil = require('ethereumjs-util')
const sigUtil = require('eth-sig-util')
const {
AddressBookController,
CurrencyRateController,
ShapeShiftController,
PhishingController,
} = require('gaba')
const backEndMetaMetricsEvent = require('./lib/backend-metametrics')
2019-02-25 20:10:13 +01:00
module.exports = class MetamaskController extends EventEmitter {
2018-03-15 23:27:10 +01:00
/**
* @constructor
* @param {Object} opts
2018-03-15 23:27:10 +01:00
*/
constructor (opts) {
super()
2018-01-25 21:28:11 +01:00
this.defaultMaxListeners = 20
this.sendUpdate = debounce(this.privateSendUpdate.bind(this), 200)
2016-06-25 02:00:35 +02:00
this.opts = opts
2017-04-27 06:05:45 +02:00
const initState = opts.initState || {}
this.recordFirstTimeInfo(initState)
2017-01-12 04:04:19 +01:00
// this keeps track of how many "controllerStream" connections are open
// the only thing that uses controller connections are open metamask UI instances
this.activeControllerConnections = 0
// platform-specific api
this.platform = opts.platform
2017-01-12 04:04:19 +01:00
// observable state store
this.store = new ComposableObservableStore(initState)
// lock to ensure only one vault created at once
this.createVaultMutex = new Mutex()
2017-02-03 05:59:47 +01:00
// network store
this.networkController = new NetworkController(initState.NetworkController)
// preferences controller
this.preferencesController = new PreferencesController({
initState: initState.PreferencesController,
initLangCode: opts.initLangCode,
2018-09-27 20:19:09 +02:00
openPopup: opts.openPopup,
network: this.networkController,
})
// app-state controller
this.appStateController = new AppStateController({
preferencesStore: this.preferencesController.store,
onInactiveTimeout: () => this.setLocked(),
})
// currency controller
this.currencyRateController = new CurrencyRateController(undefined, initState.CurrencyController)
// infura controller
this.infuraController = new InfuraController({
initState: initState.InfuraController,
})
this.infuraController.scheduleInfuraNetworkCheck()
this.phishingController = new PhishingController()
// rpc provider
2018-08-17 18:56:07 +02:00
this.initializeProvider()
this.provider = this.networkController.getProviderAndBlockTracker().provider
this.blockTracker = this.networkController.getProviderAndBlockTracker().blockTracker
// token exchange rate tracker
this.tokenRatesController = new TokenRatesController({
currency: this.currencyRateController,
preferences: this.preferencesController.store,
})
this.recentBlocksController = new RecentBlocksController({
blockTracker: this.blockTracker,
provider: this.provider,
networkController: this.networkController,
})
// account tracker watches balances, nonces, and any code at their address.
2017-09-22 23:16:19 +02:00
this.accountTracker = new AccountTracker({
provider: this.provider,
blockTracker: this.blockTracker,
network: this.networkController,
})
// start and stop polling for balances based on activeControllerConnections
this.on('controllerConnectionChanged', (activeControllerConnections) => {
if (activeControllerConnections > 0) {
this.accountTracker.start()
} else {
this.accountTracker.stop()
}
})
this.cachedBalancesController = new CachedBalancesController({
accountTracker: this.accountTracker,
getNetwork: this.networkController.getNetworkState.bind(this.networkController),
initState: initState.CachedBalancesController,
})
this.onboardingController = new OnboardingController({
initState: initState.OnboardingController,
})
// ensure accountTracker updates balances after network change
this.networkController.on('networkDidChange', () => {
this.accountTracker._updateAccounts()
})
2017-01-12 04:04:19 +01:00
// key mgmt
2018-08-12 07:34:01 +02:00
const additionalKeyrings = [TrezorKeyring, LedgerBridgeKeyring]
this.keyringController = new KeyringController({
2018-06-10 09:52:32 +02:00
keyringTypes: additionalKeyrings,
2017-01-28 22:12:12 +01:00
initState: initState.KeyringController,
getNetwork: this.networkController.getNetworkState.bind(this.networkController),
encryptor: opts.encryptor || undefined,
2016-06-25 01:13:27 +02:00
})
this.keyringController.memStore.subscribe((s) => this._onKeyringControllerUpdate(s))
2018-07-20 01:46:46 +02:00
// detect tokens controller
this.detectTokensController = new DetectTokensController({
preferences: this.preferencesController,
network: this.networkController,
keyringMemStore: this.keyringController.memStore,
})
this.addressBookController = new AddressBookController(undefined, initState.AddressBookController)
// tx mgmt
2017-05-16 19:27:41 +02:00
this.txController = new TransactionController({
initState: initState.TransactionController || initState.TransactionManager,
networkStore: this.networkController.networkStore,
2017-02-03 06:09:17 +01:00
preferencesStore: this.preferencesController.store,
2016-12-16 19:33:36 +01:00
txHistoryLimit: 40,
getNetwork: this.networkController.getNetworkState.bind(this),
signTransaction: this.keyringController.signTransaction.bind(this.keyringController),
2016-12-16 19:33:36 +01:00
provider: this.provider,
blockTracker: this.blockTracker,
getGasPrice: this.getGasPrice.bind(this),
2016-12-16 19:33:36 +01:00
})
2018-09-10 02:33:09 +02:00
this.txController.on('newUnapprovedTx', () => opts.showUnapprovedTx())
this.txController.on(`tx:status-update`, async (txId, status) => {
2018-07-20 19:58:26 +02:00
if (status === 'confirmed' || status === 'failed') {
2018-07-20 13:20:40 +02:00
const txMeta = this.txController.txStateManager.getTx(txId)
this.platform.showTransactionNotification(txMeta)
const { txReceipt } = txMeta
const participateInMetaMetrics = this.preferencesController.getParticipateInMetaMetrics()
if (txReceipt && txReceipt.status === '0x0' && participateInMetaMetrics) {
const metamaskState = await this.getState()
backEndMetaMetricsEvent(metamaskState, {
customVariables: {
errorMessage: txMeta.simulationFails.reason,
},
eventOpts: {
category: 'backend',
action: 'Transactions',
name: 'On Chain Failure',
},
})
}
2018-07-20 13:20:40 +02:00
}
})
2017-09-13 00:06:19 +02:00
// computed balances (accounting for pending transactions)
this.balancesController = new BalancesController({
accountTracker: this.accountTracker,
2017-09-13 00:06:19 +02:00
txController: this.txController,
blockTracker: this.blockTracker,
2017-09-13 00:06:19 +02:00
})
this.networkController.on('networkDidChange', () => {
this.balancesController.updateAllBalances()
this.setCurrentCurrency(this.currencyRateController.state.currentCurrency, function () {})
})
this.balancesController.updateAllBalances()
2017-09-13 00:06:19 +02:00
this.shapeshiftController = new ShapeShiftController(undefined, initState.ShapeShiftController)
this.networkController.lookupNetwork()
this.messageManager = new MessageManager()
this.personalMessageManager = new PersonalMessageManager()
2018-09-10 23:11:57 +02:00
this.typedMessageManager = new TypedMessageManager({ networkController: this.networkController })
// ensure isClientOpenAndUnlocked is updated when memState updates
this.on('update', (memState) => {
this.isClientOpenAndUnlocked = memState.isUnlocked && this._isClientOpen
})
2018-09-27 20:19:09 +02:00
this.providerApprovalController = new ProviderApprovalController({
closePopup: opts.closePopup,
2018-10-29 22:28:59 +01:00
keyringController: this.keyringController,
2018-09-27 20:19:09 +02:00
openPopup: opts.openPopup,
2018-10-10 20:52:26 +02:00
preferencesController: this.preferencesController,
2018-09-27 20:19:09 +02:00
})
this.store.updateStructure({
AppStateController: this.appStateController.store,
TransactionController: this.txController.store,
KeyringController: this.keyringController.store,
PreferencesController: this.preferencesController.store,
AddressBookController: this.addressBookController,
CurrencyController: this.currencyRateController,
ShapeShiftController: this.shapeshiftController,
NetworkController: this.networkController.store,
InfuraController: this.infuraController.store,
CachedBalancesController: this.cachedBalancesController.store,
OnboardingController: this.onboardingController.store,
})
2018-01-31 19:49:58 +01:00
this.memStore = new ComposableObservableStore(null, {
AppStateController: this.appStateController.store,
NetworkController: this.networkController.store,
AccountTracker: this.accountTracker.store,
TxController: this.txController.memStore,
BalancesController: this.balancesController.store,
CachedBalancesController: this.cachedBalancesController.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,
CurrencyController: this.currencyRateController,
ShapeshiftController: this.shapeshiftController,
InfuraController: this.infuraController.store,
2018-09-27 20:19:09 +02:00
ProviderApprovalController: this.providerApprovalController.store,
OnboardingController: this.onboardingController.store,
})
this.memStore.subscribe(this.sendUpdate.bind(this))
}
2018-03-15 23:27:10 +01:00
/**
* Constructor helper: initialize a provider.
*/
initializeProvider () {
const providerOpts = {
static: {
eth_syncing: false,
web3_clientVersion: `MetaMask/v${version}`,
},
2018-09-25 21:14:37 +02:00
version,
// account mgmt
2018-09-27 20:19:09 +02:00
getAccounts: async ({ origin }) => {
// Expose no accounts if this origin has not been approved, preventing
// account-requring RPC methods from completing successfully
2018-11-06 20:13:27 +01:00
const exposeAccounts = this.providerApprovalController.shouldExposeAccounts(origin)
if (origin !== 'MetaMask' && !exposeAccounts) { return [] }
const isUnlocked = this.keyringController.memStore.getState().isUnlocked
const selectedAddress = this.preferencesController.getSelectedAddress()
// only show address if account is unlocked
if (isUnlocked && selectedAddress) {
2018-08-17 18:56:07 +02:00
return [selectedAddress]
} else {
return []
}
},
// tx signing
2018-08-17 18:56:07 +02:00
processTransaction: this.newUnapprovedTransaction.bind(this),
// msg signing
processEthSignMessage: this.newUnsignedMessage.bind(this),
processTypedMessage: this.newUnsignedTypedMessage.bind(this),
processTypedMessageV3: this.newUnsignedTypedMessage.bind(this),
processPersonalMessage: this.newUnsignedPersonalMessage.bind(this),
getPendingNonce: this.getPendingNonce.bind(this),
}
const providerProxy = this.networkController.initializeProvider(providerOpts)
return providerProxy
}
2018-03-15 23:27:10 +01:00
/**
* Constructor helper: initialize a public config store.
2018-04-19 02:54:50 +02:00
* This store is used to make some config info available to Dapps synchronously.
2018-03-15 23:27:10 +01:00
*/
createPublicConfigStore ({ checkIsEnabled }) {
// subset of state for metamask inpage provider
const publicConfigStore = new ObservableStore()
// setup memStore subscription hooks
this.on('update', updatePublicConfigStore)
updatePublicConfigStore(this.getState())
publicConfigStore.destroy = () => {
this.removeEventListener && this.removeEventListener('update', updatePublicConfigStore)
}
function updatePublicConfigStore (memState) {
const publicState = selectPublicState(memState)
publicConfigStore.putState(publicState)
}
function selectPublicState ({ isUnlocked, selectedAddress, network, completedOnboarding }) {
const isEnabled = checkIsEnabled()
const isReady = isUnlocked && isEnabled
const result = {
isUnlocked,
isEnabled,
selectedAddress: isReady ? selectedAddress : undefined,
networkVersion: network,
onboardingcomplete: completedOnboarding,
}
2017-01-27 06:19:09 +01:00
return result
}
return publicConfigStore
}
//=============================================================================
// EXPOSED TO THE UI SUBSYSTEM
//=============================================================================
2017-01-27 06:19:09 +01:00
2018-03-15 23:27:10 +01:00
/**
* The metamask-state of the various controllers, made available to the UI
*
* @returns {Object} status
2018-03-15 23:27:10 +01:00
*/
getState () {
const vault = this.keyringController.store.getState().vault
const isInitialized = !!vault
2017-09-13 23:20:19 +02:00
return {
...{ isInitialized },
...this.memStore.getFlatState(),
}
}
2018-03-15 23:27:10 +01:00
/**
2018-04-20 18:26:24 +02:00
* 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.
*
2018-04-20 18:26:24 +02:00
* @returns {Object} Object containing API functions.
2018-03-15 23:27:10 +01:00
*/
getApi () {
const keyringController = this.keyringController
const preferencesController = this.preferencesController
2017-05-16 19:27:41 +02:00
const txController = this.txController
2017-09-30 01:09:38 +02:00
const networkController = this.networkController
2018-09-27 20:19:09 +02:00
const providerApprovalController = this.providerApprovalController
const onboardingController = this.onboardingController
return {
// etc
2017-04-27 06:05:45 +02:00
getState: (cb) => cb(null, this.getState()),
setCurrentCurrency: this.setCurrentCurrency.bind(this),
setUseBlockie: this.setUseBlockie.bind(this),
Metametrics (#6171) * Add metametrics provider and util. * Add backend api and state for participating in metametrics. * Add frontend action for participating in metametrics. * Add metametrics opt-in screen. * Add metametrics events to first time flow. * Add metametrics events for route changes * Add metametrics events for send and confirm screens * Add metametrics events to dropdowns, transactions, log in and out, settings, sig requests and main screen * Ensures each log in is measured as a new visit by metametrics. * Ensure metametrics is called with an empty string for dimensions params if specified * Adds opt in metametrics modal after unlock for existing users * Adds settings page toggle for opting in and out of MetaMetrics * Switch metametrics dimensions to page level scope * Lint, test and translation fixes for metametrics. * Update design for metametrics opt-in screen * Complete responsive styling of metametrics-opt-in modal * Use new chart image on metrics opt in screens * Incorporate the metametrics opt-in screen into the new onboarding flow * Update e2e tests to accomodate metametrics changes * Mock out metametrics network requests in integration tests * Fix tx-list integration test to support metametrics provider. * Send number of tokens and accounts data with every metametrics event. * Update metametrics event descriptor schema and add new events. * Fix import tos bug and send gas button bug due to metametrics changes. * Various small fixes on the metametrics branch. * Add origin custom variable type to metametrics.util * Fix names of onboarding complete actions (metametrics). * Fix names of Metrics Options actions (metametrics). * Clean up code related to metametrics. * Fix bad merge conflict resolution and improve promise handling in sendMetaMetrics event and confrim tx base * Don't send a second metrics event if user has gone back during first time flow. * Collect metametrics on going back from onboarding create/import. * Add missing custom variable constants for metametrics * Fix metametrics provider * Make height of opt-in modal responsive. * Adjust text content for opt-in modal. * Update metametrics event names and clean up code in opt-in-modal * Put phishing warning step next to last in onboarding flow * Link terms of service on create and import screens of first time flow * Add subtext to options on the onboarding select action screen. * Fix styling of bullet points on end of onboarding screen. * Combine phishing warning and congratulations screens. * Fix placement of users if unlocking after an incomplete onboarding import flow. * Fix capitalization in opt-in screen * Fix last onboarding screen translations * Add link to 'Learn More' on the last screen of onboarding * Code clean up: metametrics branch * Update e2e tests for phishing warning step removal * e2e tests passing on metametrics branch * Different tracking urls for metametrics on development and prod
2019-03-05 16:45:01 +01:00
setParticipateInMetaMetrics: this.setParticipateInMetaMetrics.bind(this),
setMetaMetricsSendCount: this.setMetaMetricsSendCount.bind(this),
setFirstTimeFlowType: this.setFirstTimeFlowType.bind(this),
2018-03-16 01:29:45 +01:00
setCurrentLocale: this.setCurrentLocale.bind(this),
2017-04-27 06:05:45 +02:00
markAccountsFound: this.markAccountsFound.bind(this),
markPasswordForgotten: this.markPasswordForgotten.bind(this),
unMarkPasswordForgotten: this.unMarkPasswordForgotten.bind(this),
getGasPrice: (cb) => cb(null, this.getGasPrice()),
2017-09-30 01:09:38 +02:00
// coinbase
buyEth: this.buyEth.bind(this),
2016-08-18 19:40:35 +02:00
// shapeshift
2016-08-19 00:20:26 +02:00
createShapeShiftTx: this.createShapeShiftTx.bind(this),
// primary HD keyring management
addNewAccount: nodeify(this.addNewAccount, this),
verifySeedPhrase: nodeify(this.verifySeedPhrase, this),
resetAccount: nodeify(this.resetAccount, this),
2018-07-11 06:20:40 +02:00
removeAccount: nodeify(this.removeAccount, this),
importAccountWithStrategy: nodeify(this.importAccountWithStrategy, this),
// hardware wallets
2018-06-10 09:52:32 +02:00
connectHardware: nodeify(this.connectHardware, this),
forgetDevice: nodeify(this.forgetDevice, this),
checkHardwareStatus: nodeify(this.checkHardwareStatus, this),
unlockHardwareWalletAccount: nodeify(this.unlockHardwareWalletAccount, this),
2018-06-10 09:52:32 +02:00
2019-02-25 20:10:13 +01:00
// mobile
fetchInfoToSync: nodeify(this.fetchInfoToSync, this),
// vault management
submitPassword: nodeify(this.submitPassword, this),
2017-09-30 01:09:38 +02:00
// network management
setProviderType: nodeify(networkController.setProviderType, networkController),
setCustomRpc: nodeify(this.setCustomRpc, this),
updateAndSetCustomRpc: nodeify(this.updateAndSetCustomRpc, this),
delCustomRpc: nodeify(this.delCustomRpc, this),
2017-09-30 01:09:38 +02:00
// PreferencesController
2017-07-13 00:06:49 +02:00
setSelectedAddress: nodeify(preferencesController.setSelectedAddress, preferencesController),
addToken: nodeify(preferencesController.addToken, preferencesController),
removeToken: nodeify(preferencesController.removeToken, preferencesController),
2018-08-04 01:24:12 +02:00
removeSuggestedTokens: nodeify(preferencesController.removeSuggestedTokens, preferencesController),
2017-07-13 00:06:49 +02:00
setCurrentAccountTab: nodeify(preferencesController.setCurrentAccountTab, preferencesController),
setAccountLabel: nodeify(preferencesController.setAccountLabel, preferencesController),
2017-11-14 17:04:55 +01:00
setFeatureFlag: nodeify(preferencesController.setFeatureFlag, preferencesController),
setPreference: nodeify(preferencesController.setPreference, preferencesController),
completeOnboarding: nodeify(preferencesController.completeOnboarding, preferencesController),
addKnownMethodData: nodeify(preferencesController.addKnownMethodData, preferencesController),
unsetMigratedPrivacyMode: nodeify(preferencesController.unsetMigratedPrivacyMode, preferencesController),
// BlacklistController
whitelistPhishingDomain: this.whitelistPhishingDomain.bind(this),
// AddressController
setAddressBook: nodeify(this.addressBookController.set, this.addressBookController),
Address book send plus contact list (#6914) * Style Send Header * Move Send to-row to send view and restyle * Add "Recents" group to select recipient view * Rename SendToRow to AddRecipient * Basic UI and Layout * New ENSInput component * wip - fuzzy search for input * small refactor * Add Dialog * contact list initial * initial error on invalid address * clean up edit * Click to open modal * Create AddToAddressBookModal component * Modal styling and layout * modal i18n * Add to Addressbook * ens wip * ens wip * ENS Resolution * Reset input * Send to explicit address * Happy Path Complete * Add back error checking * Reset send-to when emptying input * Add back warning object * Fix linter * Fix unit test #1 - fix import paths * Remove dead tests * One more to go * Fix all unit tests * add unit test for reducers and actions * test rendering AddRecipient * Add tests for dialog boxes in AddRecipient * Add test for validating * Fix linter * Fix e2e tests * Token send e2e fix * Style View Contact * Style edit-contact * Fix e2e * Fix from-import-beta-ui e2e spec * Make section header say "add recipient” by default * Auto-focus add recipient input * Update placeholder text * Update input title font size * Auto advance to next step if user paste a valid address * Ellipsify address when recipient is selected * Fix app header background color on desktop * Give each form row a margin of 16px * Use .container/.component naming pattern for ens-input * Auto-focus on input when add to addressbook modal is opened; Save on Enter * Fix and add unit test * Fix selectors name in e2e tests * Correct e2e test token amount for address-book-send changes * Adds e2e test for editing a transaction * Delete test/integration/lib/send-new-ui.js * Add tests for amount max button and high value error on send screen to test/e2e/metamask-ui.spec.js * lint and revert to address as object keys * add chainId based on current network to address book entry * fix test * only display contacts for the current network * Improve ENS message when not found on current network * Add error to indicate when network does not support ENS * bump gaba * address book, resolve comments * Move contact-list to its own component * De-duplicate getaddressbook selector and refactor name selection logic in contact-list-tab/ * Use contact-list component in contact-list-tab.component (i.e. in settings) * Improve/fix settings headers for popup and browser views * Lint fixes related to address book updates * Add 'My accounts' page to settings address book * Update add new contact button in settings to match floating circular design * Improve styles of view contact page * Improve styles and labels of the add-contact.component * Further lint fixes related to address book updates * Update unit tests as per address book updates * Ensure that contact list groups are sorted alphabetically * Refactor settings component to use a container for connection to redux; allow display of addressbook name in settings header * Decouple ens-input.component from send context * Add ens resolution to add contact screen in settings * Switching networks when an ens address is shown on send form removes the ens address. * Resolve send screen search for ensAddress to matching address book entry if it exists * Show resolved ens icon and address if exists (settings: add-contact.component) * Make the displayed and copied address in view-contact.component the checksummed address * Default alias state prop in AddToAddressBookModal to empty string * Use keyCode to detect enter key in AddToAddressBookModal * Ensure add-contact component properly updates after QR code detection * Fix display of all recents after clicking 'Load More' in contact list * Fix send screen contact searching after network switching * Code cleanup related to address book changes * Update unit tests for address book changes * Update ENS name not found on network message * Add ens registration error message * Cancel on edit mode takes user back to view screen * Adds support for memo to settings contact list view and edit screens * Modify designs of edit and view contact in popup environment * Update settings content list UX to show split columns in fullscreen and proper internal navigation * Correct background address book API usages in UI
2019-07-31 21:56:44 +02:00
removeFromAddressBook: this.addressBookController.delete.bind(this.addressBookController),
// AppStateController
setLastActiveTime: nodeify(this.appStateController.setLastActiveTime, this.appStateController),
// KeyringController
2018-10-29 21:55:13 +01:00
setLocked: nodeify(this.setLocked, this),
createNewVaultAndKeychain: nodeify(this.createNewVaultAndKeychain, this),
createNewVaultAndRestore: nodeify(this.createNewVaultAndRestore, this),
2017-07-13 00:06:49 +02:00
addNewKeyring: nodeify(keyringController.addNewKeyring, keyringController),
exportAccount: nodeify(keyringController.exportAccount, keyringController),
2017-05-16 19:27:41 +02:00
// txController
cancelTransaction: nodeify(txController.cancelTransaction, txController),
updateTransaction: nodeify(txController.updateTransaction, txController),
2017-07-13 00:06:49 +02:00
updateAndApproveTransaction: nodeify(txController.updateAndApproveTransaction, txController),
2017-12-07 05:20:15 +01:00
retryTransaction: nodeify(this.retryTransaction, this),
2018-09-09 19:07:23 +02:00
createCancelTransaction: nodeify(this.createCancelTransaction, this),
createSpeedUpTransaction: nodeify(this.createSpeedUpTransaction, this),
getFilteredTxList: nodeify(txController.getFilteredTxList, txController),
isNonceTaken: nodeify(txController.isNonceTaken, txController),
estimateGas: nodeify(this.estimateGas, this),
// messageManager
2017-07-13 00:06:49 +02:00
signMessage: nodeify(this.signMessage, this),
2017-04-27 06:05:45 +02:00
cancelMessage: this.cancelMessage.bind(this),
2017-02-23 01:23:13 +01:00
// personalMessageManager
2017-07-13 00:06:49 +02:00
signPersonalMessage: nodeify(this.signPersonalMessage, this),
2017-04-27 06:05:45 +02:00
cancelPersonalMessage: this.cancelPersonalMessage.bind(this),
2017-09-29 18:24:08 +02:00
// personalMessageManager
signTypedMessage: nodeify(this.signTypedMessage, this),
cancelTypedMessage: this.cancelTypedMessage.bind(this),
// provider approval
approveProviderRequestByOrigin: providerApprovalController.approveProviderRequestByOrigin.bind(providerApprovalController),
rejectProviderRequestByOrigin: providerApprovalController.rejectProviderRequestByOrigin.bind(providerApprovalController),
forceApproveProviderRequestByOrigin: providerApprovalController.forceApproveProviderRequestByOrigin.bind(providerApprovalController),
2018-09-27 20:19:09 +02:00
clearApprovedOrigins: providerApprovalController.clearApprovedOrigins.bind(providerApprovalController),
// onboarding controller
setSeedPhraseBackedUp: nodeify(onboardingController.setSeedPhraseBackedUp, onboardingController),
}
}
2018-01-09 00:41:57 +01:00
//=============================================================================
// VAULT / KEYRING RELATED METHODS
//=============================================================================
2018-03-15 23:27:10 +01:00
/**
2018-04-19 02:54:50 +02:00
* Creates a new Vault and create a new keychain.
*
2018-04-19 02:54:50 +02:00
* A vault, or KeyringController, is a controller that contains
* many different account strategies, currently called Keyrings.
* Creating it new means wiping all previous keyrings.
*
2018-04-19 02:54:50 +02:00
* A keychain, or keyring, controls many accounts with a single backup and signing strategy.
* For example, a mnemonic phrase can generate many accounts, and is a keyring.
*
2018-04-19 02:54:50 +02:00
* @param {string} password
*
2018-04-20 18:26:24 +02:00
* @returns {Object} vault
2018-03-15 23:27:10 +01:00
*/
2017-11-20 22:27:29 +01:00
async createNewVaultAndKeychain (password) {
const releaseLock = await this.createVaultMutex.acquire()
try {
let vault
const accounts = await this.keyringController.getAccounts()
if (accounts.length > 0) {
vault = await this.keyringController.fullUpdate()
} else {
2017-11-29 00:35:20 +01:00
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
2017-11-20 22:27:29 +01:00
}
}
2018-03-15 23:27:10 +01:00
/**
2018-04-19 02:54:50 +02:00
* Create a new Vault and restore an existent keyring.
2018-03-15 23:27:10 +01:00
* @param {} password
* @param {} seed
*/
2017-11-20 22:27:29 +01:00
async createNewVaultAndRestore (password, seed) {
const releaseLock = await this.createVaultMutex.acquire()
try {
let accounts, lastBalance
const keyringController = this.keyringController
// clear known identities
this.preferencesController.setAddresses([])
// create new vault
const vault = await keyringController.createNewVaultAndRestore(password, seed)
const ethQuery = new EthQuery(this.provider)
accounts = await keyringController.getAccounts()
lastBalance = await this.getBalance(accounts[accounts.length - 1], ethQuery)
const primaryKeyring = keyringController.getKeyringsByType('HD Key Tree')[0]
if (!primaryKeyring) {
throw new Error('MetamaskController - No HD Key Tree found')
}
// seek out the first zero balance
while (lastBalance !== '0x0') {
await keyringController.addNewAccount(primaryKeyring)
accounts = await keyringController.getAccounts()
lastBalance = await this.getBalance(accounts[accounts.length - 1], ethQuery)
}
// set new identities
this.preferencesController.setAddresses(accounts)
this.selectFirstIdentity()
releaseLock()
return vault
} catch (err) {
releaseLock()
throw err
}
}
/**
* Get an account balance from the AccountTracker or request it directly from the network.
* @param {string} address - The account address
* @param {EthQuery} ethQuery - The EthQuery instance to use when asking the network
*/
getBalance (address, ethQuery) {
return new Promise((resolve, reject) => {
const cached = this.accountTracker.store.getState().accounts[address]
if (cached && cached.balance) {
resolve(cached.balance)
} else {
ethQuery.getBalance(address, (error, balance) => {
if (error) {
reject(error)
log.error(error)
} else {
resolve(balance || '0x0')
}
})
}
})
}
2019-02-25 20:10:13 +01:00
/**
* Collects all the information that we want to share
* with the mobile client for syncing purposes
* @returns Promise<Object> Parts of the state that we want to syncx
*/
async fetchInfoToSync () {
2019-02-25 20:10:13 +01:00
// Preferences
const {
accountTokens,
currentLocale,
frequentRpcList,
identities,
selectedAddress,
tokens,
} = this.preferencesController.store.getState()
const preferences = {
accountTokens,
currentLocale,
frequentRpcList,
identities,
selectedAddress,
tokens,
}
// Accounts
const hdKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0]
const hdAccounts = await hdKeyring.getAccounts()
const accounts = {
hd: hdAccounts.filter((item, pos) => (hdAccounts.indexOf(item) === pos)).map(address => ethUtil.toChecksumAddress(address)),
simpleKeyPair: [],
ledger: [],
trezor: [],
}
// transactions
let transactions = this.txController.store.getState().transactions
// delete tx for other accounts that we're not importing
transactions = transactions.filter(tx => {
const checksummedTxFrom = ethUtil.toChecksumAddress(tx.txParams.from)
return (
accounts.hd.includes(checksummedTxFrom)
)
})
return {
accounts,
preferences,
transactions,
network: this.networkController.store.getState(),
}
}
/*
* 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<object>} - The keyringController update.
*/
async submitPassword (password) {
await this.keyringController.submitPassword(password)
const accounts = await this.keyringController.getAccounts()
// verify keyrings
const nonSimpleKeyrings = this.keyringController.keyrings.filter(keyring => keyring.type !== 'Simple Key Pair')
2018-06-05 21:20:24 +02:00
if (nonSimpleKeyrings.length > 1 && this.diagnostics) {
await this.diagnostics.reportMultipleKeyrings(nonSimpleKeyrings)
}
await this.preferencesController.syncAddresses(accounts)
2018-10-05 19:32:28 +02:00
await this.balancesController.updateAllBalances()
await this.txController.pendingTxTracker.updatePendingTxs()
return this.keyringController.fullUpdate()
}
2018-04-19 02:54:50 +02:00
/**
* @type Identity
* @property {string} name - The account nickname.
* @property {string} address - The account's ethereum address, in lower case.
* @property {boolean} mayBeFauceting - Whether this account is currently
* receiving funds from our automatic Ropsten faucet.
*/
2018-03-15 23:27:10 +01:00
/**
* Sets the first address in the state to the selected address
2018-03-15 23:27:10 +01:00
*/
selectFirstIdentity () {
const { identities } = this.preferencesController.store.getState()
const address = Object.keys(identities)[0]
this.preferencesController.setSelectedAddress(address)
}
2018-06-10 09:52:32 +02:00
//
// Hardware
//
2018-08-14 01:29:43 +02:00
async getKeyringForDevice (deviceName, hdPath = null) {
let keyringName = null
switch (deviceName) {
case 'trezor':
keyringName = TrezorKeyring.type
break
case 'ledger':
2018-08-12 07:34:01 +02:00
keyringName = LedgerBridgeKeyring.type
break
default:
2018-08-14 09:42:23 +02:00
throw new Error('MetamaskController:getKeyringForDevice - Unknown device')
}
let keyring = await this.keyringController.getKeyringsByType(keyringName)[0]
if (!keyring) {
keyring = await this.keyringController.addNewKeyring(keyringName)
}
2018-08-14 09:42:23 +02:00
if (hdPath && keyring.setHdPath) {
keyring.setHdPath(hdPath)
2018-08-14 01:29:43 +02:00
}
2018-08-14 07:26:18 +02:00
keyring.network = this.networkController.getProviderConfig().type
return keyring
}
2018-06-10 09:52:32 +02:00
/**
* Fetch account list from a trezor device.
*
* @returns [] accounts
*/
2018-08-14 01:29:43 +02:00
async connectHardware (deviceName, page, hdPath) {
const keyring = await this.getKeyringForDevice(deviceName, hdPath)
let accounts = []
switch (page) {
case -1:
accounts = await keyring.getPreviousPage()
break
case 1:
accounts = await keyring.getNextPage()
break
default:
accounts = await keyring.getFirstPage()
2018-06-13 08:09:25 +02:00
}
// Merge with existing accounts
// and make sure addresses are not repeated
2018-08-11 08:35:20 +02:00
const oldAccounts = await this.keyringController.getAccounts()
const accountsToTrack = [...new Set(oldAccounts.concat(accounts.map(a => a.address.toLowerCase())))]
this.accountTracker.syncWithAddresses(accountsToTrack)
return accounts
2018-06-10 09:52:32 +02:00
}
/**
* Check if the device is unlocked
*
* @returns {Promise<boolean>}
*/
2018-08-14 01:29:43 +02:00
async checkHardwareStatus (deviceName, hdPath) {
const keyring = await this.getKeyringForDevice(deviceName, hdPath)
return keyring.isUnlocked()
}
/**
* Clear
*
* @returns {Promise<boolean>}
*/
async forgetDevice (deviceName) {
const keyring = await this.getKeyringForDevice(deviceName)
keyring.forgetDevice()
return true
}
2018-06-10 09:52:32 +02:00
/**
* Imports an account from a trezor device.
*
* @returns {} keyState
*/
2018-08-14 01:29:43 +02:00
async unlockHardwareWalletAccount (index, deviceName, hdPath) {
const keyring = await this.getKeyringForDevice(deviceName, hdPath)
2018-06-10 09:52:32 +02:00
2018-06-11 01:02:54 +02:00
keyring.setAccountToUnlock(index)
const oldAccounts = await this.keyringController.getAccounts()
const keyState = await this.keyringController.addNewAccount(keyring)
const newAccounts = await this.keyringController.getAccounts()
2018-06-10 09:52:32 +02:00
this.preferencesController.setAddresses(newAccounts)
newAccounts.forEach(address => {
if (!oldAccounts.includes(address)) {
2018-08-21 06:04:30 +02:00
// Set the account label to Trezor 1 / Ledger 1, etc
this.preferencesController.setAccountLabel(address, `${deviceName[0].toUpperCase()}${deviceName.slice(1)} ${parseInt(index, 10) + 1}`)
// Select the account
2018-06-10 09:52:32 +02:00
this.preferencesController.setSelectedAddress(address)
}
})
const { identities } = this.preferencesController.store.getState()
return { ...keyState, identities }
}
2018-06-13 08:09:25 +02:00
2018-06-10 09:52:32 +02:00
2018-04-19 02:54:50 +02:00
//
// Account Management
//
2018-03-15 23:27:10 +01:00
/**
2018-04-19 02:54:50 +02:00
* Adds a new account to the default (first) HD seed phrase Keyring.
*
2018-03-15 23:27:10 +01:00
* @returns {} keyState
*/
async addNewAccount () {
const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0]
if (!primaryKeyring) {
throw new Error('MetamaskController - No HD Key Tree found')
}
const keyringController = this.keyringController
const oldAccounts = await keyringController.getAccounts()
const keyState = await keyringController.addNewAccount(primaryKeyring)
const newAccounts = await keyringController.getAccounts()
await this.verifySeedPhrase()
this.preferencesController.setAddresses(newAccounts)
newAccounts.forEach((address) => {
if (!oldAccounts.includes(address)) {
this.preferencesController.setSelectedAddress(address)
}
})
const {identities} = this.preferencesController.store.getState()
return {...keyState, identities}
}
2018-03-15 23:27:10 +01:00
/**
* Verifies the validity of the current vault's seed phrase.
*
* Validity: seed phrase restores the accounts belonging to the current vault.
2018-03-15 23:27:10 +01:00
*
* Called when the first account is created and on unlocking the vault.
2018-04-19 02:54:50 +02:00
*
* @returns {Promise<string>} Seed phrase to be confirmed by the user.
2018-03-15 23:27:10 +01:00
*/
async verifySeedPhrase () {
2018-03-03 22:08:10 +01:00
const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0]
if (!primaryKeyring) {
throw new Error('MetamaskController - No HD Key Tree found')
}
const serialized = await primaryKeyring.serialize()
const seedWords = serialized.mnemonic
const accounts = await primaryKeyring.getAccounts()
if (accounts.length < 1) {
throw new Error('MetamaskController - No accounts found')
}
try {
await seedPhraseVerifier.verifyAccounts(accounts, seedWords)
return seedWords
} catch (err) {
log.error(err.message)
throw err
}
}
2018-03-15 23:27:10 +01:00
/**
2018-04-19 02:54:50 +02:00
* Clears the transaction history, to allow users to force-reset their nonces.
* Mostly used in development environments, when networks are restarted with
* the same network ID.
*
* @returns Promise<string> The current selected address.
2018-03-15 23:27:10 +01:00
*/
2018-04-19 02:54:50 +02:00
async resetAccount () {
const selectedAddress = this.preferencesController.getSelectedAddress()
this.txController.wipeTransactions(selectedAddress)
this.networkController.resetConnection()
return selectedAddress
2018-01-31 09:33:15 +01:00
}
2018-07-11 06:20:40 +02:00
/**
2018-07-12 02:01:44 +02:00
* Removes an account from state / storage.
2018-07-11 06:20:40 +02:00
*
* @param {string[]} address A hex address
*
*/
async removeAccount (address) {
2018-07-12 02:01:44 +02:00
// Remove account from the preferences controller
2018-07-11 06:20:40 +02:00
this.preferencesController.removeAddress(address)
2018-07-12 02:01:44 +02:00
// Remove account from the account tracker controller
2018-08-21 06:04:07 +02:00
this.accountTracker.removeAccount([address])
2018-07-12 02:01:44 +02:00
// Remove account from the keyring
await this.keyringController.removeAccount(address)
2018-07-11 06:20:40 +02:00
return address
}
2018-03-15 23:27:10 +01:00
/**
2018-04-19 02:54:50 +02:00
* Imports an account with the specified import strategy.
* These are defined in app/scripts/account-import-strategies
* Each strategy represents a different way of serializing an Ethereum key pair.
*
2018-04-20 18:26:24 +02:00
* @param {string} strategy - A unique identifier for an account import strategy.
2018-04-19 02:54:50 +02:00
* @param {any} args - The data required by that strategy to import an account.
* @param {Function} cb - A callback function called with a state update on success.
2018-03-15 23:27:10 +01:00
*/
async importAccountWithStrategy (strategy, args) {
const privateKey = await accountImporter.importAccount(strategy, args)
const keyring = await this.keyringController.addNewKeyring('Simple Key Pair', [ privateKey ])
const accounts = await keyring.getAccounts()
// update accounts in preferences controller
2018-05-29 08:14:38 +02:00
const allAccounts = await this.keyringController.getAccounts()
this.preferencesController.setAddresses(allAccounts)
// set new account as selected
await this.preferencesController.setSelectedAddress(accounts[0])
}
// ---------------------------------------------------------------------------
2018-04-19 02:54:50 +02:00
// Identity Management (signature operations)
2018-08-17 18:56:07 +02:00
/**
* Called when a Dapp suggests a new tx to be signed.
* this wrapper needs to exist so we can provide a reference to
* "newUnapprovedTransaction" before "txController" is instantiated
*
* @param {Object} msgParams - The params passed to eth_sign.
* @param {Object} req - (optional) the original request, containing the origin
*/
async newUnapprovedTransaction (txParams, req) {
return await this.txController.newUnapprovedTransaction(txParams, req)
}
2018-04-19 02:54:50 +02:00
// eth_sign methods:
2018-03-15 23:27:10 +01:00
/**
2018-04-19 02:54:50 +02:00
* Called when a Dapp uses the eth_sign method, to request user approval.
* eth_sign is a pure signature of arbitrary data. It is on a deprecation
* path, since this data can be a transaction, or can leak private key
* information.
*
* @param {Object} msgParams - The params passed to eth_sign.
* @param {Function} cb = The callback function called with the signature.
2018-03-15 23:27:10 +01:00
*/
2018-08-17 18:56:07 +02:00
newUnsignedMessage (msgParams, req) {
const promise = this.messageManager.addUnapprovedMessageAsync(msgParams, req)
2018-04-19 02:54:50 +02:00
this.sendUpdate()
this.opts.showUnconfirmedMessage()
2018-08-17 18:56:07 +02:00
return promise
2018-04-19 02:54:50 +02:00
}
/**
* Signifies user intent to complete an eth_sign method.
*
* @param {Object} msgParams The params passed to eth_call.
2018-04-20 18:26:24 +02:00
* @returns {Promise<Object>} Full state update.
2018-04-19 02:54:50 +02:00
*/
signMessage (msgParams) {
log.info('MetaMaskController - signMessage')
const msgId = msgParams.metamaskId
// sets the status op the message to 'approved'
// and removes the metamaskId for signing
return this.messageManager.approveMessage(msgParams)
.then((cleanMsgParams) => {
// signs the message
return this.keyringController.signMessage(cleanMsgParams)
})
.then((rawSig) => {
// tells the listener that the message has been signed
// and can be returned to the dapp
this.messageManager.setMsgStatusSigned(msgId, rawSig)
return this.getState()
})
}
2018-04-20 18:26:24 +02:00
/**
2018-04-19 02:54:50 +02:00
* Used to cancel a message submitted via eth_sign.
*
2018-04-20 18:26:24 +02:00
* @param {string} msgId - The id of the message to cancel.
2018-04-19 02:54:50 +02:00
*/
cancelMessage (msgId, cb) {
const messageManager = this.messageManager
messageManager.rejectMsg(msgId)
if (cb && typeof cb === 'function') {
cb(null, this.getState())
}
}
// personal_sign methods:
2018-04-20 18:26:24 +02:00
/**
2018-04-19 02:54:50 +02:00
* Called when a dapp uses the personal_sign method.
* This is identical to the Geth eth_sign method, and may eventually replace
* eth_sign.
*
* We currently define our eth_sign and personal_sign mostly for legacy Dapps.
*
2018-04-19 02:54:50 +02:00
* @param {Object} msgParams - The params of the message to sign & return to the Dapp.
* @param {Function} cb - The callback function called with the signature.
* Passed back to the requesting Dapp.
2018-03-15 23:27:10 +01:00
*/
2018-08-17 18:56:07 +02:00
async newUnsignedPersonalMessage (msgParams, req) {
const promise = this.personalMessageManager.addUnapprovedMessageAsync(msgParams, req)
this.sendUpdate()
this.opts.showUnconfirmedMessage()
2018-08-17 18:56:07 +02:00
return promise
}
2018-03-15 23:27:10 +01:00
/**
2018-04-19 02:54:50 +02:00
* Signifies a user's approval to sign a personal_sign message in queue.
* Triggers signing, and the callback function from newUnsignedPersonalMessage.
*
* @param {Object} msgParams - The params of the message to sign & return to the Dapp.
2018-04-23 23:47:11 +02:00
* @returns {Promise<Object>} - A full state update.
2018-03-15 23:27:10 +01:00
*/
signPersonalMessage (msgParams) {
log.info('MetaMaskController - signPersonalMessage')
const msgId = msgParams.metamaskId
// sets the status op the message to 'approved'
// and removes the metamaskId for signing
return this.personalMessageManager.approveMessage(msgParams)
.then((cleanMsgParams) => {
// signs the message
return this.keyringController.signPersonalMessage(cleanMsgParams)
})
.then((rawSig) => {
// tells the listener that the message has been signed
// and can be returned to the dapp
this.personalMessageManager.setMsgStatusSigned(msgId, rawSig)
return this.getState()
})
}
2018-04-20 18:26:24 +02:00
/**
2018-04-19 02:54:50 +02:00
* Used to cancel a personal_sign type message.
2018-04-20 18:26:24 +02:00
* @param {string} msgId - The ID of the message to cancel.
2018-04-19 02:54:50 +02:00
* @param {Function} cb - The callback function called with a full state update.
*/
cancelPersonalMessage (msgId, cb) {
const messageManager = this.personalMessageManager
messageManager.rejectMsg(msgId)
if (cb && typeof cb === 'function') {
cb(null, this.getState())
}
}
// eth_signTypedData methods
2018-04-20 18:26:24 +02:00
/**
2018-04-19 02:54:50 +02:00
* Called when a dapp uses the eth_signTypedData method, per EIP 712.
*
* @param {Object} msgParams - The params passed to eth_signTypedData.
* @param {Function} cb - The callback function, called with the signature.
*/
2018-10-25 05:24:42 +02:00
newUnsignedTypedMessage (msgParams, req, version) {
const promise = this.typedMessageManager.addUnapprovedMessageAsync(msgParams, req, version)
2018-08-17 18:56:07 +02:00
this.sendUpdate()
this.opts.showUnconfirmedMessage()
return promise
2018-04-19 02:54:50 +02:00
}
2018-03-15 23:27:10 +01:00
/**
2018-04-19 02:54:50 +02:00
* The method for a user approving a call to eth_signTypedData, per EIP 712.
* Triggers the callback in newUnsignedTypedMessage.
*
* @param {Object} msgParams - The params passed to eth_signTypedData.
* @returns {Object} Full state update.
2018-03-15 23:27:10 +01:00
*/
2018-09-10 23:11:57 +02:00
async signTypedMessage (msgParams) {
log.info('MetaMaskController - eth_signTypedData')
2017-09-29 18:24:08 +02:00
const msgId = msgParams.metamaskId
2018-09-10 23:11:57 +02:00
const version = msgParams.version
try {
const cleanMsgParams = await this.typedMessageManager.approveMessage(msgParams)
const address = sigUtil.normalize(cleanMsgParams.from)
const keyring = await this.keyringController.getKeyringForAccount(address)
let signature
2018-11-26 19:27:49 +01:00
// HW Wallet keyrings don't expose private keys
// so we need to handle it separately
if (!HW_WALLETS_KEYRINGS.includes(keyring.type)) {
2018-11-20 18:48:43 +01:00
const wallet = keyring._getWalletForAccount(address)
const privKey = ethUtil.toBuffer(wallet.getPrivateKey())
switch (version) {
case 'V1':
signature = sigUtil.signTypedDataLegacy(privKey, { data: cleanMsgParams.data })
break
case 'V3':
signature = sigUtil.signTypedData(privKey, { data: JSON.parse(cleanMsgParams.data) })
break
}
} else {
2018-11-20 18:57:58 +01:00
signature = await keyring.signTypedData(address, cleanMsgParams.data)
2018-09-10 23:11:57 +02:00
}
this.typedMessageManager.setMsgStatusSigned(msgId, signature)
return this.getState()
} catch (error) {
log.info('MetaMaskController - eth_signTypedData failed.', error)
this.typedMessageManager.errorMessage(msgId, error)
}
2017-09-29 18:24:08 +02:00
}
2018-04-20 18:26:24 +02:00
/**
2018-04-19 02:54:50 +02:00
* Used to cancel a eth_signTypedData type message.
2018-04-20 18:26:24 +02:00
* @param {string} msgId - The ID of the message to cancel.
2018-04-19 02:54:50 +02:00
* @param {Function} cb - The callback function called with a full state update.
*/
cancelTypedMessage (msgId, cb) {
const messageManager = this.typedMessageManager
messageManager.rejectMsg(msgId)
if (cb && typeof cb === 'function') {
cb(null, this.getState())
}
}
// ---------------------------------------------------------------------------
2018-04-19 02:54:50 +02:00
// MetaMask Version 3 Migration Account Restauration Methods
2018-03-15 23:27:10 +01:00
/**
2018-04-19 02:54:50 +02:00
* A legacy method (probably dead code) that was used when we swapped out our
* key management library that we depended on.
*
* Described in:
* https://medium.com/metamask/metamask-3-migration-guide-914b79533cdd
*
2018-04-19 02:54:50 +02:00
* @deprecated
2018-03-15 23:27:10 +01:00
* @param {} migratorOutput
*/
2017-04-27 06:05:45 +02:00
restoreOldVaultAccounts (migratorOutput) {
2017-01-28 04:35:03 +01:00
const { serialized } = migratorOutput
return this.keyringController.restoreKeyring(serialized)
.then(() => migratorOutput)
2017-01-28 04:35:03 +01:00
}
2018-04-20 18:26:24 +02:00
/**
2018-04-19 02:54:50 +02:00
* A legacy method used to record user confirmation that they understand
* that some of their accounts have been recovered but should be backed up.
* This function no longer does anything and will be removed.
2018-04-19 02:54:50 +02:00
*
* @deprecated
* @param {Function} cb - A callback function called with a full state update.
*/
markAccountsFound (cb) {
// TODO Remove me
2018-04-19 02:54:50 +02:00
cb(null, this.getState())
}
2018-04-20 18:26:24 +02:00
/**
* An account object
* @typedef Account
* @property string privateKey - The private key of the account.
*/
//=============================================================================
// END (VAULT / KEYRING RELATED METHODS)
//=============================================================================
2018-04-20 18:26:24 +02:00
/**
2018-04-19 02:54:50 +02:00
* Allows a user to try to speed up a transaction by retrying it
* with higher gas.
*
2018-04-20 18:26:24 +02:00
* @param {string} txId - The ID of the transaction to speed up.
2018-04-19 02:54:50 +02:00
*/
async retryTransaction (txId, gasPrice) {
2018-12-09 21:48:06 +01:00
await this.txController.retryTransaction(txId, gasPrice)
2017-12-07 05:20:15 +01:00
const state = await this.getState()
return state
}
2018-09-09 19:07:23 +02:00
/**
* Allows a user to attempt to cancel a previously submitted transaction by creating a new
* transaction.
* @param {number} originalTxId - the id of the txMeta that you want to attempt to cancel
* @param {string=} customGasPrice - the hex value to use for the cancel transaction
* @returns {object} MetaMask state
*/
async createCancelTransaction (originalTxId, customGasPrice) {
2018-12-09 21:48:06 +01:00
try {
await this.txController.createCancelTransaction(originalTxId, customGasPrice)
const state = await this.getState()
return state
} catch (error) {
throw error
}
2017-12-07 05:20:15 +01:00
}
async createSpeedUpTransaction (originalTxId, customGasPrice) {
await this.txController.createSpeedUpTransaction(originalTxId, customGasPrice)
const state = await this.getState()
return state
}
estimateGas (estimateGasParams) {
return new Promise((resolve, reject) => {
return this.txController.txGasUtil.query.estimateGas(estimateGasParams, (err, res) => {
if (err) {
return reject(err)
}
return resolve(res)
})
})
}
//=============================================================================
// PASSWORD MANAGEMENT
//=============================================================================
2018-04-20 18:26:24 +02:00
/**
2018-04-19 02:54:50 +02:00
* Allows a user to begin the seed phrase recovery process.
* @param {Function} cb - A callback function called when complete.
*/
2018-07-03 00:49:33 +02:00
markPasswordForgotten (cb) {
this.preferencesController.setPasswordForgotten(true)
this.sendUpdate()
cb()
}
2018-04-20 18:26:24 +02:00
/**
2018-04-19 02:54:50 +02:00
* Allows a user to end the seed phrase recovery process.
* @param {Function} cb - A callback function called when complete.
*/
2018-07-03 00:49:33 +02:00
unMarkPasswordForgotten (cb) {
this.preferencesController.setPasswordForgotten(false)
this.sendUpdate()
cb()
}
//=============================================================================
// SETUP
//=============================================================================
2017-01-28 04:35:03 +01:00
2018-04-20 18:26:24 +02:00
/**
2018-04-19 02:54:50 +02:00
* Used to create a multiplexed stream for connecting to an untrusted context
* like a Dapp or other extension.
* @param {*} connectionStream - The Duplex stream to connect to.
2018-04-20 18:26:24 +02:00
* @param {string} originDomain - The domain requesting the stream, which
2018-04-19 02:54:50 +02:00
* may trigger a blacklist reload.
*/
setupUntrustedCommunication (connectionStream, originDomain) {
// Check if new connection is blacklisted
if (this.phishingController.test(originDomain)) {
log.debug('MetaMask - sending phishing warning for', originDomain)
this.sendPhishingWarning(connectionStream, originDomain)
return
}
// setup multiplexing
const mux = setupMultiplex(connectionStream)
// connect features
const publicApi = this.setupPublicApi(mux.createStream('publicApi'), originDomain)
this.setupProviderConnection(mux.createStream('provider'), originDomain, publicApi)
this.setupPublicConfig(mux.createStream('publicConfig'), originDomain)
}
2018-04-20 18:26:24 +02:00
/**
2018-04-19 02:54:50 +02:00
* Used to create a multiplexed stream for connecting to a trusted context,
* like our own user interfaces, which have the provider APIs, but also
* receive the exported API from this controller, which includes trusted
* functions, like the ability to approve transactions or sign messages.
*
* @param {*} connectionStream - The duplex stream to connect to.
2018-04-20 18:26:24 +02:00
* @param {string} originDomain - The domain requesting the connection,
2018-04-19 02:54:50 +02:00
* used in logging and error reporting.
*/
setupTrustedCommunication (connectionStream, originDomain) {
// setup multiplexing
const mux = setupMultiplex(connectionStream)
// connect features
this.setupControllerConnection(mux.createStream('controller'))
this.setupProviderConnection(mux.createStream('provider'), originDomain)
}
2018-04-20 18:26:24 +02:00
/**
2018-04-19 02:54:50 +02:00
* Called when we detect a suspicious domain. Requests the browser redirects
* to our anti-phishing page.
*
* @private
* @param {*} connectionStream - The duplex stream to the per-page script,
* for sending the reload attempt to.
2018-04-20 18:26:24 +02:00
* @param {string} hostname - The URL that triggered the suspicion.
2018-04-19 02:54:50 +02:00
*/
sendPhishingWarning (connectionStream, hostname) {
const mux = setupMultiplex(connectionStream)
const phishingStream = mux.createStream('phishing')
phishingStream.write({ hostname })
}
2018-04-20 18:26:24 +02:00
/**
2018-04-19 02:54:50 +02:00
* A method for providing our API over a stream using Dnode.
* @param {*} outStream - The stream to provide our API over.
*/
setupControllerConnection (outStream) {
const api = this.getApi()
const dnode = Dnode(api)
// report new active controller connection
this.activeControllerConnections++
this.emit('controllerConnectionChanged', this.activeControllerConnections)
// connect dnode api to remote connection
pump(
outStream,
dnode,
outStream,
(err) => {
// report new active controller connection
this.activeControllerConnections--
this.emit('controllerConnectionChanged', this.activeControllerConnections)
// report any error
if (err) log.error(err)
}
)
dnode.on('remote', (remote) => {
// push updates to popup
2018-09-10 02:33:09 +02:00
const sendUpdate = (update) => remote.sendUpdate(update)
this.on('update', sendUpdate)
2018-09-10 02:33:09 +02:00
// remove update listener once the connection ends
dnode.on('end', () => this.removeListener('update', sendUpdate))
2017-01-28 04:35:03 +01:00
})
}
2018-04-20 18:26:24 +02:00
/**
2018-04-19 02:54:50 +02:00
* A method for serving our ethereum provider over a given stream.
* @param {*} outStream - The stream to provide over.
2018-04-20 18:26:24 +02:00
* @param {string} origin - The URI of the requesting resource.
2018-04-19 02:54:50 +02:00
*/
setupProviderConnection (outStream, origin, publicApi) {
const getSiteMetadata = publicApi && publicApi.getSiteMetadata
const engine = this.setupProviderEngine(origin, getSiteMetadata)
// setup connection
const providerStream = createEngineStream({ engine })
pump(
outStream,
providerStream,
outStream,
(err) => {
// cleanup filter polyfill middleware
engine._middleware.forEach((mid) => {
if (mid.destroy && typeof mid.destroy === 'function') {
mid.destroy()
}
})
if (err) log.error(err)
}
)
}
/**
* A method for creating a provider that is safely restricted for the requesting domain.
**/
setupProviderEngine (origin, getSiteMetadata) {
// setup json rpc engine stack
const engine = new RpcEngine()
const provider = this.provider
const blockTracker = this.blockTracker
// create filter polyfill middleware
const filterMiddleware = createFilterMiddleware({ provider, blockTracker })
// create subscription polyfill middleware
const subscriptionManager = createSubscriptionManager({ provider, blockTracker })
subscriptionManager.events.on('notification', (message) => engine.emit('notification', message))
// metadata
engine.push(createOriginMiddleware({ origin }))
engine.push(createLoggerMiddleware({ origin }))
// filter and subscription polyfills
engine.push(filterMiddleware)
engine.push(subscriptionManager.middleware)
// watch asset
engine.push(this.preferencesController.requestWatchAsset.bind(this.preferencesController))
// requestAccounts
engine.push(this.providerApprovalController.createMiddleware({
origin,
getSiteMetadata,
}))
// forward to metamask primary provider
engine.push(providerAsMiddleware(provider))
return engine
}
2018-04-20 18:26:24 +02:00
/**
2018-04-19 02:54:50 +02:00
* A method for providing our public config info over a stream.
* This includes info we like to be synchronous if possible, like
* the current selected account, and network ID.
*
* Since synchronous methods have been deprecated in web3,
* this is a good candidate for deprecation.
*
* @param {*} outStream - The stream to provide public config over.
*/
setupPublicConfig (outStream, originDomain) {
const configStore = this.createPublicConfigStore({
// check the providerApprovalController's approvedOrigins
checkIsEnabled: () => this.providerApprovalController.shouldExposeAccounts(originDomain),
})
const configStream = asStream(configStore)
pump(
2018-09-10 02:33:09 +02:00
configStream,
outStream,
(err) => {
configStore.destroy()
2018-09-10 02:33:09 +02:00
configStream.destroy()
if (err) log.error(err)
}
)
}
/**
* A method for providing our public api over a stream.
* This includes a method for setting site metadata like title and image
*
* @param {*} outStream - The stream to provide the api over.
*/
setupPublicApi (outStream) {
const dnode = Dnode()
// connect dnode api to remote connection
pump(
outStream,
dnode,
outStream,
(err) => {
// report any error
if (err) log.error(err)
}
)
const getRemote = createDnodeRemoteGetter(dnode)
const publicApi = {
// wrap with an await remote
getSiteMetadata: async () => {
const remote = await getRemote()
return await pify(remote.getSiteMetadata)()
},
}
return publicApi
}
2018-04-20 18:26:24 +02:00
/**
* Handle a KeyringController update
* @param {object} state the KC state
* @return {Promise<void>}
* @private
*/
async _onKeyringControllerUpdate (state) {
const {isUnlocked, keyrings} = state
const addresses = keyrings.reduce((acc, {accounts}) => acc.concat(accounts), [])
if (!addresses.length) {
return
}
// Ensure preferences + identities controller know about all addresses
this.preferencesController.addAddresses(addresses)
this.accountTracker.syncWithAddresses(addresses)
const wasLocked = !isUnlocked
if (wasLocked) {
const oldSelectedAddress = this.preferencesController.getSelectedAddress()
if (!addresses.includes(oldSelectedAddress)) {
const address = addresses[0]
await this.preferencesController.setSelectedAddress(address)
}
}
}
2018-04-20 18:26:24 +02:00
/**
2018-04-19 02:54:50 +02:00
* A method for emitting the full MetaMask state to all registered listeners.
* @private
*/
privateSendUpdate () {
this.emit('update', this.getState())
}
2018-04-20 18:26:24 +02:00
/**
2018-04-19 02:54:50 +02:00
* A method for estimating a good gas price at recent prices.
* Returns the lowest price that would have been included in
* 50% of recent blocks.
*
2018-04-20 18:26:24 +02:00
* @returns {string} A hex representation of the suggested wei gas price.
2018-04-19 02:54:50 +02:00
*/
getGasPrice () {
const { recentBlocksController } = this
const { recentBlocks } = recentBlocksController.store.getState()
// Return 1 gwei if no blocks have been observed:
if (recentBlocks.length === 0) {
return '0x' + GWEI_BN.toString(16)
}
const lowestPrices = recentBlocks.map((block) => {
if (!block.gasPrices || block.gasPrices.length < 1) {
return GWEI_BN
}
return block.gasPrices
.map(hexPrefix => hexPrefix.substr(2))
.map(hex => new BN(hex, 16))
.sort((a, b) => {
return a.gt(b) ? 1 : -1
})[0]
})
.map(number => number.div(GWEI_BN).toNumber())
const percentileNum = percentile(65, lowestPrices)
const percentileNumBn = new BN(percentileNum)
return '0x' + percentileNumBn.mul(GWEI_BN).toString(16)
}
/**
* Returns the nonce that will be associated with a transaction once approved
* @param address {string} - The hex string address for the transaction
* @returns Promise<number>
*/
async getPendingNonce (address) {
const { nonceDetails, releaseLock} = await this.txController.nonceTracker.getNonceLock(address)
const pendingNonce = nonceDetails.params.highestSuggested
releaseLock()
return pendingNonce
}
//=============================================================================
// CONFIG
//=============================================================================
2017-01-28 04:35:03 +01:00
// Log blocks
2018-04-20 18:26:24 +02:00
/**
2018-04-19 02:54:50 +02:00
* A method for setting the user's preferred display currency.
2018-04-20 18:26:24 +02:00
* @param {string} currencyCode - The code of the preferred currency.
2018-04-19 02:54:50 +02:00
* @param {Function} cb - A callback function returning currency info.
*/
setCurrentCurrency (currencyCode, cb) {
const { ticker } = this.networkController.getNetworkConfig()
try {
const currencyState = {
nativeCurrency: ticker,
currentCurrency: currencyCode,
}
this.currencyRateController.update(currencyState)
this.currencyRateController.configure(currencyState)
cb(null, this.currencyRateController.state)
} catch (err) {
cb(err)
}
}
2018-04-20 18:26:24 +02:00
/**
2018-04-19 02:54:50 +02:00
* A method for forwarding the user to the easiest way to obtain ether,
* or the network "gas" currency, for the current selected network.
*
2018-04-20 18:26:24 +02:00
* @param {string} address - The address to fund.
* @param {string} amount - The amount of ether desired, as a base 10 string.
2018-04-19 02:54:50 +02:00
*/
buyEth (address, amount) {
if (!amount) amount = '5'
const network = this.networkController.getNetworkState()
const url = getBuyEthUrl({ network, address, amount })
if (url) this.platform.openWindow({ url })
}
2018-04-20 18:26:24 +02:00
/**
2018-04-19 02:54:50 +02:00
* A method for triggering a shapeshift currency transfer.
2018-04-20 18:26:24 +02:00
* @param {string} depositAddress - The address to deposit to.
2018-04-19 02:54:50 +02:00
* @property {string} depositType - An abbreviation of the type of crypto currency to be deposited.
*/
2016-08-19 00:20:26 +02:00
createShapeShiftTx (depositAddress, depositType) {
this.shapeshiftController.createTransaction(depositAddress, depositType)
2016-08-18 19:40:35 +02:00
}
2017-09-30 01:09:38 +02:00
// network
/**
* A method for selecting a custom URL for an ethereum RPC provider and updating it
* @param {string} rpcUrl - A URL for a valid Ethereum RPC API.
* @param {number} chainId - The chainId of the selected network.
* @param {string} ticker - The ticker symbol of the selected network.
* @param {string} nickname - Optional nickname of the selected network.
* @returns {Promise<String>} - The RPC Target URL confirmed.
*/
New settings custom rpc form (#6490) * Add networks tab to settings, with header. * Adds network list to settings network tab. * Adds form to settings networks tab and connects it to network list. * Network tab: form adding and editing working * Settings network form properly handles input errors * Add translations for settings network form * Clean up styles of settings network tab. * Add popup-view styles and behaviour to settings network tab. * Fix save button on settings network form * Adds 'Add Network' button and addMode to settings networks tab * Lint fix for settings networks tab addition * Fix navigation in settings networks tab. * Editing an rpcurl in networks tab does not create new network, just changes rpc of old * Fix layout of settings tabs other than network * Networks dropdown 'Custom Rpc' item links to networks tab in settings. * Update settings sidebar networks subheader. * Make networks tab buttons width consistent with input widths in extension view. * Fix settings screen subheader height in popup view * Fix height of add networks button in popup view * Add optional label to chainId and symbol form labels in networks setting tab * Style fixes for networks tab headers * Add ability to customize block explorer used by custom rpc * Stylistic improvements+fixes to custom rpc form. * Hide cancel button. * Highlight and show network form of provider by default. * Standardize network subheader name to 'Networks' * Update e2e tests for new settings network form * Update unit tests for new rpcPrefs prop * Extract blockexplorer url construction into method. * Fix broken styles on non-network tabs in popup mode * Fix block explorer url links for cases when provider in state has not been updated. * Fix vertical spacing of network form * Don't allow click of save button on network form if nothing has changed * Ensure add network button is shown in popup view * Lint fix for networks tab * Fix block explorer url preference setting. * Fix e2e tests for custom blockexplorer in account details modal changes. * Update integration test states to include frequentRpcList property * Fix some capitalizations in en/messages.json * Remove some console.logs added during custom rpc form work * Fix external account link text and url for modal and dropdown. * Documentation, url validation, proptype required additions and lint fixes on network tab and form.
2019-05-09 19:27:14 +02:00
async updateAndSetCustomRpc (rpcUrl, chainId, ticker = 'ETH', nickname, rpcPrefs) {
await this.preferencesController.updateRpc({ rpcUrl, chainId, ticker, nickname, rpcPrefs })
this.networkController.setRpcTarget(rpcUrl, chainId, ticker, nickname, rpcPrefs)
return rpcUrl
}
2018-04-20 18:26:24 +02:00
/**
2018-04-19 02:54:50 +02:00
* A method for selecting a custom URL for an ethereum RPC provider.
2018-04-20 18:26:24 +02:00
* @param {string} rpcTarget - A URL for a valid Ethereum RPC API.
* @param {number} chainId - The chainId of the selected network.
* @param {string} ticker - The ticker symbol of the selected network.
* @param {string} nickname - Optional nickname of the selected network.
2018-04-19 02:54:50 +02:00
* @returns {Promise<String>} - The RPC Target URL confirmed.
*/
New settings custom rpc form (#6490) * Add networks tab to settings, with header. * Adds network list to settings network tab. * Adds form to settings networks tab and connects it to network list. * Network tab: form adding and editing working * Settings network form properly handles input errors * Add translations for settings network form * Clean up styles of settings network tab. * Add popup-view styles and behaviour to settings network tab. * Fix save button on settings network form * Adds 'Add Network' button and addMode to settings networks tab * Lint fix for settings networks tab addition * Fix navigation in settings networks tab. * Editing an rpcurl in networks tab does not create new network, just changes rpc of old * Fix layout of settings tabs other than network * Networks dropdown 'Custom Rpc' item links to networks tab in settings. * Update settings sidebar networks subheader. * Make networks tab buttons width consistent with input widths in extension view. * Fix settings screen subheader height in popup view * Fix height of add networks button in popup view * Add optional label to chainId and symbol form labels in networks setting tab * Style fixes for networks tab headers * Add ability to customize block explorer used by custom rpc * Stylistic improvements+fixes to custom rpc form. * Hide cancel button. * Highlight and show network form of provider by default. * Standardize network subheader name to 'Networks' * Update e2e tests for new settings network form * Update unit tests for new rpcPrefs prop * Extract blockexplorer url construction into method. * Fix broken styles on non-network tabs in popup mode * Fix block explorer url links for cases when provider in state has not been updated. * Fix vertical spacing of network form * Don't allow click of save button on network form if nothing has changed * Ensure add network button is shown in popup view * Lint fix for networks tab * Fix block explorer url preference setting. * Fix e2e tests for custom blockexplorer in account details modal changes. * Update integration test states to include frequentRpcList property * Fix some capitalizations in en/messages.json * Remove some console.logs added during custom rpc form work * Fix external account link text and url for modal and dropdown. * Documentation, url validation, proptype required additions and lint fixes on network tab and form.
2019-05-09 19:27:14 +02:00
async setCustomRpc (rpcTarget, chainId, ticker = 'ETH', nickname = '', rpcPrefs = {}) {
const frequentRpcListDetail = this.preferencesController.getFrequentRpcListDetail()
const rpcSettings = frequentRpcListDetail.find((rpc) => rpcTarget === rpc.rpcUrl)
if (rpcSettings) {
New settings custom rpc form (#6490) * Add networks tab to settings, with header. * Adds network list to settings network tab. * Adds form to settings networks tab and connects it to network list. * Network tab: form adding and editing working * Settings network form properly handles input errors * Add translations for settings network form * Clean up styles of settings network tab. * Add popup-view styles and behaviour to settings network tab. * Fix save button on settings network form * Adds 'Add Network' button and addMode to settings networks tab * Lint fix for settings networks tab addition * Fix navigation in settings networks tab. * Editing an rpcurl in networks tab does not create new network, just changes rpc of old * Fix layout of settings tabs other than network * Networks dropdown 'Custom Rpc' item links to networks tab in settings. * Update settings sidebar networks subheader. * Make networks tab buttons width consistent with input widths in extension view. * Fix settings screen subheader height in popup view * Fix height of add networks button in popup view * Add optional label to chainId and symbol form labels in networks setting tab * Style fixes for networks tab headers * Add ability to customize block explorer used by custom rpc * Stylistic improvements+fixes to custom rpc form. * Hide cancel button. * Highlight and show network form of provider by default. * Standardize network subheader name to 'Networks' * Update e2e tests for new settings network form * Update unit tests for new rpcPrefs prop * Extract blockexplorer url construction into method. * Fix broken styles on non-network tabs in popup mode * Fix block explorer url links for cases when provider in state has not been updated. * Fix vertical spacing of network form * Don't allow click of save button on network form if nothing has changed * Ensure add network button is shown in popup view * Lint fix for networks tab * Fix block explorer url preference setting. * Fix e2e tests for custom blockexplorer in account details modal changes. * Update integration test states to include frequentRpcList property * Fix some capitalizations in en/messages.json * Remove some console.logs added during custom rpc form work * Fix external account link text and url for modal and dropdown. * Documentation, url validation, proptype required additions and lint fixes on network tab and form.
2019-05-09 19:27:14 +02:00
this.networkController.setRpcTarget(rpcSettings.rpcUrl, rpcSettings.chainId, rpcSettings.ticker, rpcSettings.nickname, rpcPrefs)
} else {
New settings custom rpc form (#6490) * Add networks tab to settings, with header. * Adds network list to settings network tab. * Adds form to settings networks tab and connects it to network list. * Network tab: form adding and editing working * Settings network form properly handles input errors * Add translations for settings network form * Clean up styles of settings network tab. * Add popup-view styles and behaviour to settings network tab. * Fix save button on settings network form * Adds 'Add Network' button and addMode to settings networks tab * Lint fix for settings networks tab addition * Fix navigation in settings networks tab. * Editing an rpcurl in networks tab does not create new network, just changes rpc of old * Fix layout of settings tabs other than network * Networks dropdown 'Custom Rpc' item links to networks tab in settings. * Update settings sidebar networks subheader. * Make networks tab buttons width consistent with input widths in extension view. * Fix settings screen subheader height in popup view * Fix height of add networks button in popup view * Add optional label to chainId and symbol form labels in networks setting tab * Style fixes for networks tab headers * Add ability to customize block explorer used by custom rpc * Stylistic improvements+fixes to custom rpc form. * Hide cancel button. * Highlight and show network form of provider by default. * Standardize network subheader name to 'Networks' * Update e2e tests for new settings network form * Update unit tests for new rpcPrefs prop * Extract blockexplorer url construction into method. * Fix broken styles on non-network tabs in popup mode * Fix block explorer url links for cases when provider in state has not been updated. * Fix vertical spacing of network form * Don't allow click of save button on network form if nothing has changed * Ensure add network button is shown in popup view * Lint fix for networks tab * Fix block explorer url preference setting. * Fix e2e tests for custom blockexplorer in account details modal changes. * Update integration test states to include frequentRpcList property * Fix some capitalizations in en/messages.json * Remove some console.logs added during custom rpc form work * Fix external account link text and url for modal and dropdown. * Documentation, url validation, proptype required additions and lint fixes on network tab and form.
2019-05-09 19:27:14 +02:00
this.networkController.setRpcTarget(rpcTarget, chainId, ticker, nickname, rpcPrefs)
await this.preferencesController.addToFrequentRpcList(rpcTarget, chainId, ticker, nickname, rpcPrefs)
}
2017-09-30 01:09:38 +02:00
return rpcTarget
}
2017-09-30 01:09:38 +02:00
/**
* A method for deleting a selected custom URL.
* @param {string} rpcTarget - A RPC URL to delete.
*/
async delCustomRpc (rpcTarget) {
await this.preferencesController.removeFromFrequentRpcList(rpcTarget)
}
2018-04-20 18:26:24 +02:00
/**
2018-04-19 02:54:50 +02:00
* Sets whether or not to use the blockie identicon format.
2018-04-20 18:26:24 +02:00
* @param {boolean} val - True for bockie, false for jazzicon.
2018-04-19 02:54:50 +02:00
* @param {Function} cb - A callback function called when complete.
*/
2017-11-25 23:57:54 +01:00
setUseBlockie (val, cb) {
try {
this.preferencesController.setUseBlockie(val)
cb(null)
} catch (err) {
cb(err)
}
}
Metametrics (#6171) * Add metametrics provider and util. * Add backend api and state for participating in metametrics. * Add frontend action for participating in metametrics. * Add metametrics opt-in screen. * Add metametrics events to first time flow. * Add metametrics events for route changes * Add metametrics events for send and confirm screens * Add metametrics events to dropdowns, transactions, log in and out, settings, sig requests and main screen * Ensures each log in is measured as a new visit by metametrics. * Ensure metametrics is called with an empty string for dimensions params if specified * Adds opt in metametrics modal after unlock for existing users * Adds settings page toggle for opting in and out of MetaMetrics * Switch metametrics dimensions to page level scope * Lint, test and translation fixes for metametrics. * Update design for metametrics opt-in screen * Complete responsive styling of metametrics-opt-in modal * Use new chart image on metrics opt in screens * Incorporate the metametrics opt-in screen into the new onboarding flow * Update e2e tests to accomodate metametrics changes * Mock out metametrics network requests in integration tests * Fix tx-list integration test to support metametrics provider. * Send number of tokens and accounts data with every metametrics event. * Update metametrics event descriptor schema and add new events. * Fix import tos bug and send gas button bug due to metametrics changes. * Various small fixes on the metametrics branch. * Add origin custom variable type to metametrics.util * Fix names of onboarding complete actions (metametrics). * Fix names of Metrics Options actions (metametrics). * Clean up code related to metametrics. * Fix bad merge conflict resolution and improve promise handling in sendMetaMetrics event and confrim tx base * Don't send a second metrics event if user has gone back during first time flow. * Collect metametrics on going back from onboarding create/import. * Add missing custom variable constants for metametrics * Fix metametrics provider * Make height of opt-in modal responsive. * Adjust text content for opt-in modal. * Update metametrics event names and clean up code in opt-in-modal * Put phishing warning step next to last in onboarding flow * Link terms of service on create and import screens of first time flow * Add subtext to options on the onboarding select action screen. * Fix styling of bullet points on end of onboarding screen. * Combine phishing warning and congratulations screens. * Fix placement of users if unlocking after an incomplete onboarding import flow. * Fix capitalization in opt-in screen * Fix last onboarding screen translations * Add link to 'Learn More' on the last screen of onboarding * Code clean up: metametrics branch * Update e2e tests for phishing warning step removal * e2e tests passing on metametrics branch * Different tracking urls for metametrics on development and prod
2019-03-05 16:45:01 +01:00
/**
* Sets whether or not the user will have usage data tracked with MetaMetrics
* @param {boolean} bool - True for users that wish to opt-in, false for users that wish to remain out.
* @param {Function} cb - A callback function called when complete.
*/
setParticipateInMetaMetrics (bool, cb) {
try {
const metaMetricsId = this.preferencesController.setParticipateInMetaMetrics(bool)
cb(null, metaMetricsId)
} catch (err) {
cb(err)
}
}
setMetaMetricsSendCount (val, cb) {
try {
this.preferencesController.setMetaMetricsSendCount(val)
cb(null)
} catch (err) {
cb(err)
}
}
/**
* Sets the type of first time flow the user wishes to follow: create or import
* @param {String} type - Indicates the type of first time flow the user wishes to follow
* @param {Function} cb - A callback function called when complete.
*/
setFirstTimeFlowType (type, cb) {
try {
this.preferencesController.setFirstTimeFlowType(type)
cb(null)
} catch (err) {
cb(err)
}
}
2018-04-20 18:26:24 +02:00
/**
2018-04-19 02:54:50 +02:00
* A method for setting a user's current locale, affecting the language rendered.
2018-04-20 18:26:24 +02:00
* @param {string} key - Locale identifier.
2018-04-19 02:54:50 +02:00
* @param {Function} cb - A callback function called when complete.
*/
2018-03-16 01:29:45 +01:00
setCurrentLocale (key, cb) {
try {
this.preferencesController.setCurrentLocale(key)
cb(null)
} catch (err) {
cb(err)
}
}
2018-04-20 18:26:24 +02:00
/**
2018-04-19 02:54:50 +02:00
* A method for initializing storage the first time.
* @param {Object} initState - The default state to initialize with.
* @private
*/
recordFirstTimeInfo (initState) {
if (!('firstTimeInfo' in initState)) {
initState.firstTimeInfo = {
version,
date: Date.now(),
}
}
}
// TODO: Replace isClientOpen methods with `controllerConnectionChanged` events.
2018-04-20 18:26:24 +02:00
/**
2018-04-19 02:54:50 +02:00
* A method for recording whether the MetaMask user interface is open or not.
* @private
2018-04-20 18:26:24 +02:00
* @param {boolean} open
2018-04-19 02:54:50 +02:00
*/
set isClientOpen (open) {
this._isClientOpen = open
this.isClientOpenAndUnlocked = this.getState().isUnlocked && open
2018-07-21 01:58:03 +02:00
this.detectTokensController.isOpen = open
}
2018-04-20 18:26:24 +02:00
/**
2018-07-31 02:44:22 +02:00
* A method for activating the retrieval of price data,
* which should only be fetched when the UI is visible.
2018-04-19 02:54:50 +02:00
* @private
2018-04-20 18:26:24 +02:00
* @param {boolean} active - True if price data should be getting fetched.
2018-04-19 02:54:50 +02:00
*/
set isClientOpenAndUnlocked (active) {
this.tokenRatesController.isActive = active
}
2018-09-10 23:11:57 +02:00
/**
2018-09-10 23:11:57 +02:00
* Creates RPC engine middleware for processing eth_signTypedData requests
*
* @param {Object} req - request object
* @param {Object} res - response object
* @param {Function} - next
* @param {Function} - end
*/
/**
* Adds a domain to the PhishingController whitelist
* @param {string} hostname the domain to whitelist
*/
whitelistPhishingDomain (hostname) {
return this.phishingController.bypass(hostname)
}
2018-10-29 21:55:13 +01:00
2018-10-29 22:28:59 +01:00
/**
* Locks MetaMask
*/
setLocked () {
2018-10-29 21:55:13 +01:00
return this.keyringController.setLocked()
}
}