A Metamask fork with Infura removed and default networks editable
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ciphermask/app/scripts/controllers/preferences.js

958 lines
31 KiB

import { strict as assert } from 'assert';
import { ObservableStore } from '@metamask/obs-store';
import { ethErrors } from 'eth-rpc-errors';
import { normalize as normalizeAddress } from 'eth-sig-util';
import { ethers } from 'ethers';
import log from 'loglevel';
import abiERC721 from 'human-standard-collectible-abi';
import contractsMap from '@metamask/contract-metadata';
import { LISTED_CONTRACT_ADDRESSES } from '../../../shared/constants/tokens';
import { NETWORK_TYPE_TO_ID_MAP } from '../../../shared/constants/network';
import { isPrefixedFormattedHexString } from '../../../shared/modules/network.utils';
import {
isValidHexAddress,
toChecksumHexAddress,
} from '../../../shared/modules/hexstring-utils';
import { NETWORK_EVENTS } from './network';
const ERC721_INTERFACE_ID = '0x80ac58cd';
export default class PreferencesController {
/**
*
* @typedef {Object} PreferencesController
* @param {Object} opts - Overrides the defaults for the initial state of this.store
* @property {Object} store The stored object containing a users preferences, stored in local storage
* @property {Array} store.frequentRpcList A list of custom rpcs to provide the user
* @property {Array} store.tokens The tokens the user wants display in their token lists
* @property {Object} store.accountTokens The tokens stored per account and then per network type
* @property {Object} store.assetImages Contains assets objects related to assets added
* @property {boolean} store.useBlockie The users preference for blockie identicons within the UI
Add advanced setting to enable editing nonce on confirmation screens (#7089) * Add UseNonce toggle * Get the toggle actually working and dispatching * Display nonce field on confirmation page * Remove console.log * Add placeholder * Set customNonceValue * Add nonce key/value to txParams * remove customNonceValue from component state * Use translation file and existing CSS class * Use existing TextField component * Remove console.log * Fix lint nits * Okay this sorta works? * Move nonce toggle to advanced tab * Set min to 0 * Wrap value in Number() * Add customNonceMap * Update custom nonce translation * Update styles * Reset CustomNonce * Fix lint * Get tests passing * Add customNonceValue to defaults * Fix test * Fix comments * Update tests * Use camel case * Ensure custom nonce can only be whole number * Correct font size for custom nonce input * UX improvements for custom nonce feature * Fix advanced-tab-component tests for custom nonce changes * Update title of nonce toggle in settings * Remove unused locale message * Cast custom nonce to string in confirm-transaction-base.component * Handle string conversion and invalid values for custom nonces in handler * Don't call getNonceLock in tx controller if there is a custom nonce * Set nonce details for cases where nonce is customized * Fix incorrectly use value for deciding whether to getnoncelock in approveTransaction * Default nonceLock to empty object in approveTransaction * Reapply use on nonceLock in cases where customNonceValue in approveTransaction. * Show warning message if custom nonce is higher than MetaMask's next nonce * Fix e2e test failure caused by custom nonce and 3box toggle conflict * Update nonce warning message to include the suggested nonce * Handle nextNonce comparison and update logic in lifecycle * Default nonce field to suggested nonce * Clear custom nonce on reject or confirm * Fix bug where nonces are not shown in tx list on self sent transactions * Ensure custom nonce is reset after tx is created in background * Convert customNonceValue to number in approve tranasction controller * Lint fix * Call getNextNonce after updating custom nonce
5 years ago
* @property {boolean} store.useNonceField The users preference for nonce field within the UI
* @property {Object} store.featureFlags A key-boolean map, where keys refer to features and booleans to whether the
* user wishes to see that feature.
*
* Feature flags can be set by the global function `setPreference(feature, enabled)`, and so should not expose any sensitive behavior.
* @property {Object} store.knownMethodData Contains all data methods known by the user
* @property {string} store.currentLocale The preferred language locale key
* @property {string} store.selectedAddress A hex string that matches the currently selected address in the app
*
*/
constructor(opts = {}) {
const initState = {
frequentRpcListDetail: [],
accountTokens: {},
accountHiddenTokens: {},
assetImages: {},
tokens: [],
hiddenTokens: [],
suggestedTokens: {},
useBlockie: false,
Add advanced setting to enable editing nonce on confirmation screens (#7089) * Add UseNonce toggle * Get the toggle actually working and dispatching * Display nonce field on confirmation page * Remove console.log * Add placeholder * Set customNonceValue * Add nonce key/value to txParams * remove customNonceValue from component state * Use translation file and existing CSS class * Use existing TextField component * Remove console.log * Fix lint nits * Okay this sorta works? * Move nonce toggle to advanced tab * Set min to 0 * Wrap value in Number() * Add customNonceMap * Update custom nonce translation * Update styles * Reset CustomNonce * Fix lint * Get tests passing * Add customNonceValue to defaults * Fix test * Fix comments * Update tests * Use camel case * Ensure custom nonce can only be whole number * Correct font size for custom nonce input * UX improvements for custom nonce feature * Fix advanced-tab-component tests for custom nonce changes * Update title of nonce toggle in settings * Remove unused locale message * Cast custom nonce to string in confirm-transaction-base.component * Handle string conversion and invalid values for custom nonces in handler * Don't call getNonceLock in tx controller if there is a custom nonce * Set nonce details for cases where nonce is customized * Fix incorrectly use value for deciding whether to getnoncelock in approveTransaction * Default nonceLock to empty object in approveTransaction * Reapply use on nonceLock in cases where customNonceValue in approveTransaction. * Show warning message if custom nonce is higher than MetaMask's next nonce * Fix e2e test failure caused by custom nonce and 3box toggle conflict * Update nonce warning message to include the suggested nonce * Handle nextNonce comparison and update logic in lifecycle * Default nonce field to suggested nonce * Clear custom nonce on reject or confirm * Fix bug where nonces are not shown in tx list on self sent transactions * Ensure custom nonce is reset after tx is created in background * Convert customNonceValue to number in approve tranasction controller * Lint fix * Call getNextNonce after updating custom nonce
5 years ago
useNonceField: false,
usePhishDetect: true,
dismissSeedBackUpReminder: false,
// set to true means the dynamic list from the API is being used
// set to false will be using the static list from contract-metadata
useTokenDetection: true,
// WARNING: Do not use feature flags for security-sensitive things.
// Feature flag toggling is available in the global namespace
// for convenient testing of pre-release features, and should never
// perform sensitive operations.
featureFlags: {
showIncomingTransactions: true,
},
knownMethodData: {},
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
6 years ago
firstTimeFlowType: null,
currentLocale: opts.initLangCode,
identities: {},
lostIdentities: {},
forgottenPassword: false,
preferences: {
autoLockTimeLimit: undefined,
showFiatInTestnets: false,
useNativeCurrencyAsPrimaryCurrency: true,
hideZeroBalanceTokens: false,
},
completedOnboarding: false,
// ENS decentralized website resolution
ipfsGateway: 'dweb.link',
infuraBlocked: null,
useLedgerLive: false,
...opts.initState,
};
this.network = opts.network;
this.ethersProvider = new ethers.providers.Web3Provider(opts.provider);
this.store = new ObservableStore(initState);
this.store.setMaxListeners(12);
this.openPopup = opts.openPopup;
this.migrateAddressBookState = opts.migrateAddressBookState;
this.network.on(NETWORK_EVENTS.NETWORK_DID_CHANGE, () => {
const { tokens, hiddenTokens } = this._getTokenRelatedStates();
this.ethersProvider = new ethers.providers.Web3Provider(opts.provider);
this._updateAccountTokens(tokens, this.getAssetImages(), hiddenTokens);
});
this._subscribeToInfuraAvailability();
global.setPreference = (key, value) => {
return this.setFeatureFlag(key, value);
};
}
// PUBLIC METHODS
/**
* Sets the {@code forgottenPassword} state property
* @param {boolean} forgottenPassword - whether or not the user has forgotten their password
*/
setPasswordForgotten(forgottenPassword) {
this.store.updateState({ forgottenPassword });
}
/**
* Setter for the `useBlockie` property
*
* @param {boolean} val - Whether or not the user prefers blockie indicators
*
*/
setUseBlockie(val) {
this.store.updateState({ useBlockie: val });
}
Add advanced setting to enable editing nonce on confirmation screens (#7089) * Add UseNonce toggle * Get the toggle actually working and dispatching * Display nonce field on confirmation page * Remove console.log * Add placeholder * Set customNonceValue * Add nonce key/value to txParams * remove customNonceValue from component state * Use translation file and existing CSS class * Use existing TextField component * Remove console.log * Fix lint nits * Okay this sorta works? * Move nonce toggle to advanced tab * Set min to 0 * Wrap value in Number() * Add customNonceMap * Update custom nonce translation * Update styles * Reset CustomNonce * Fix lint * Get tests passing * Add customNonceValue to defaults * Fix test * Fix comments * Update tests * Use camel case * Ensure custom nonce can only be whole number * Correct font size for custom nonce input * UX improvements for custom nonce feature * Fix advanced-tab-component tests for custom nonce changes * Update title of nonce toggle in settings * Remove unused locale message * Cast custom nonce to string in confirm-transaction-base.component * Handle string conversion and invalid values for custom nonces in handler * Don't call getNonceLock in tx controller if there is a custom nonce * Set nonce details for cases where nonce is customized * Fix incorrectly use value for deciding whether to getnoncelock in approveTransaction * Default nonceLock to empty object in approveTransaction * Reapply use on nonceLock in cases where customNonceValue in approveTransaction. * Show warning message if custom nonce is higher than MetaMask's next nonce * Fix e2e test failure caused by custom nonce and 3box toggle conflict * Update nonce warning message to include the suggested nonce * Handle nextNonce comparison and update logic in lifecycle * Default nonce field to suggested nonce * Clear custom nonce on reject or confirm * Fix bug where nonces are not shown in tx list on self sent transactions * Ensure custom nonce is reset after tx is created in background * Convert customNonceValue to number in approve tranasction controller * Lint fix * Call getNextNonce after updating custom nonce
5 years ago
/**
* Setter for the `useNonceField` property
*
* @param {boolean} val - Whether or not the user prefers to set nonce
Add advanced setting to enable editing nonce on confirmation screens (#7089) * Add UseNonce toggle * Get the toggle actually working and dispatching * Display nonce field on confirmation page * Remove console.log * Add placeholder * Set customNonceValue * Add nonce key/value to txParams * remove customNonceValue from component state * Use translation file and existing CSS class * Use existing TextField component * Remove console.log * Fix lint nits * Okay this sorta works? * Move nonce toggle to advanced tab * Set min to 0 * Wrap value in Number() * Add customNonceMap * Update custom nonce translation * Update styles * Reset CustomNonce * Fix lint * Get tests passing * Add customNonceValue to defaults * Fix test * Fix comments * Update tests * Use camel case * Ensure custom nonce can only be whole number * Correct font size for custom nonce input * UX improvements for custom nonce feature * Fix advanced-tab-component tests for custom nonce changes * Update title of nonce toggle in settings * Remove unused locale message * Cast custom nonce to string in confirm-transaction-base.component * Handle string conversion and invalid values for custom nonces in handler * Don't call getNonceLock in tx controller if there is a custom nonce * Set nonce details for cases where nonce is customized * Fix incorrectly use value for deciding whether to getnoncelock in approveTransaction * Default nonceLock to empty object in approveTransaction * Reapply use on nonceLock in cases where customNonceValue in approveTransaction. * Show warning message if custom nonce is higher than MetaMask's next nonce * Fix e2e test failure caused by custom nonce and 3box toggle conflict * Update nonce warning message to include the suggested nonce * Handle nextNonce comparison and update logic in lifecycle * Default nonce field to suggested nonce * Clear custom nonce on reject or confirm * Fix bug where nonces are not shown in tx list on self sent transactions * Ensure custom nonce is reset after tx is created in background * Convert customNonceValue to number in approve tranasction controller * Lint fix * Call getNextNonce after updating custom nonce
5 years ago
*
*/
setUseNonceField(val) {
this.store.updateState({ useNonceField: val });
Add advanced setting to enable editing nonce on confirmation screens (#7089) * Add UseNonce toggle * Get the toggle actually working and dispatching * Display nonce field on confirmation page * Remove console.log * Add placeholder * Set customNonceValue * Add nonce key/value to txParams * remove customNonceValue from component state * Use translation file and existing CSS class * Use existing TextField component * Remove console.log * Fix lint nits * Okay this sorta works? * Move nonce toggle to advanced tab * Set min to 0 * Wrap value in Number() * Add customNonceMap * Update custom nonce translation * Update styles * Reset CustomNonce * Fix lint * Get tests passing * Add customNonceValue to defaults * Fix test * Fix comments * Update tests * Use camel case * Ensure custom nonce can only be whole number * Correct font size for custom nonce input * UX improvements for custom nonce feature * Fix advanced-tab-component tests for custom nonce changes * Update title of nonce toggle in settings * Remove unused locale message * Cast custom nonce to string in confirm-transaction-base.component * Handle string conversion and invalid values for custom nonces in handler * Don't call getNonceLock in tx controller if there is a custom nonce * Set nonce details for cases where nonce is customized * Fix incorrectly use value for deciding whether to getnoncelock in approveTransaction * Default nonceLock to empty object in approveTransaction * Reapply use on nonceLock in cases where customNonceValue in approveTransaction. * Show warning message if custom nonce is higher than MetaMask's next nonce * Fix e2e test failure caused by custom nonce and 3box toggle conflict * Update nonce warning message to include the suggested nonce * Handle nextNonce comparison and update logic in lifecycle * Default nonce field to suggested nonce * Clear custom nonce on reject or confirm * Fix bug where nonces are not shown in tx list on self sent transactions * Ensure custom nonce is reset after tx is created in background * Convert customNonceValue to number in approve tranasction controller * Lint fix * Call getNextNonce after updating custom nonce
5 years ago
}
/**
* Setter for the `usePhishDetect` property
*
* @param {boolean} val - Whether or not the user prefers phishing domain protection
*
*/
setUsePhishDetect(val) {
this.store.updateState({ usePhishDetect: val });
}
/**
* Setter for the `useTokenDetection` property
*
* @param {boolean} val - Whether or not the user prefers to use the static token list or dynamic token list from the API
*
*/
setUseTokenDetection(val) {
this.store.updateState({ useTokenDetection: val });
}
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
6 years ago
/**
* Setter for the `firstTimeFlowType` property
*
* @param {string} type - Indicates the type of first time flow - create or import - the user wishes to follow
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
6 years ago
*
*/
setFirstTimeFlowType(type) {
this.store.updateState({ firstTimeFlowType: type });
}
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
6 years ago
getSuggestedTokens() {
return this.store.getState().suggestedTokens;
}
getAssetImages() {
return this.store.getState().assetImages;
}
/**
* Add new methodData to state, to avoid requesting this information again through Infura
*
* @param {string} fourBytePrefix - Four-byte method signature
* @param {string} methodData - Corresponding data method
*/
addKnownMethodData(fourBytePrefix, methodData) {
const { knownMethodData } = this.store.getState();
knownMethodData[fourBytePrefix] = methodData;
this.store.updateState({ knownMethodData });
}
/**
* wallet_watchAsset request handler.
*
* @param {Object} req - The watchAsset JSON-RPC request object.
*/
async requestWatchAsset(req) {
const { type, options } = req.params;
switch (type) {
case 'ERC20':
return await this._handleWatchAssetERC20(options);
default:
throw ethErrors.rpc.invalidParams(
`Asset of type "${type}" not supported.`,
);
}
}
/**
* Setter for the `currentLocale` property
*
* @param {string} key - he preferred language locale key
*
*/
setCurrentLocale(key) {
const textDirection = ['ar', 'dv', 'fa', 'he', 'ku'].includes(key)
? 'rtl'
: 'auto';
this.store.updateState({
currentLocale: key,
textDirection,
});
return textDirection;
}
/**
* Updates identities to only include specified addresses. Removes identities
* not included in addresses array
*
* @param {string[]} addresses - An array of hex addresses
*
*/
setAddresses(addresses) {
const oldIdentities = this.store.getState().identities;
const oldAccountTokens = this.store.getState().accountTokens;
const oldAccountHiddenTokens = this.store.getState().accountHiddenTokens;
const identities = addresses.reduce((ids, address, index) => {
const oldId = oldIdentities[address] || {};
ids[address] = { name: `Account ${index + 1}`, address, ...oldId };
return ids;
}, {});
const accountTokens = addresses.reduce((tokens, address) => {
const oldTokens = oldAccountTokens[address] || {};
tokens[address] = oldTokens;
return tokens;
}, {});
const accountHiddenTokens = addresses.reduce((hiddenTokens, address) => {
const oldHiddenTokens = oldAccountHiddenTokens[address] || {};
hiddenTokens[address] = oldHiddenTokens;
return hiddenTokens;
}, {});
this.store.updateState({ identities, accountTokens, accountHiddenTokens });
}
/**
* Removes an address from state
*
* @param {string} address - A hex address
* @returns {string} the address that was removed
*/
removeAddress(address) {
const {
identities,
accountTokens,
accountHiddenTokens,
} = this.store.getState();
if (!identities[address]) {
throw new Error(`${address} can't be deleted cause it was not found`);
}
delete identities[address];
delete accountTokens[address];
delete accountHiddenTokens[address];
this.store.updateState({ identities, accountTokens, accountHiddenTokens });
// If the selected account is no longer valid,
// select an arbitrary other account:
if (address === this.getSelectedAddress()) {
const selected = Object.keys(identities)[0];
this.setSelectedAddress(selected);
}
return address;
}
/**
* Adds addresses to the identities object without removing identities
*
* @param {string[]} addresses - An array of hex addresses
*
*/
addAddresses(addresses) {
const {
identities,
accountTokens,
accountHiddenTokens,
} = this.store.getState();
addresses.forEach((address) => {
// skip if already exists
if (identities[address]) {
return;
}
// add missing identity
const identityCount = Object.keys(identities).length;
accountTokens[address] = {};
accountHiddenTokens[address] = {};
identities[address] = { name: `Account ${identityCount + 1}`, address };
});
this.store.updateState({ identities, accountTokens, accountHiddenTokens });
}
/**
* Synchronizes identity entries with known accounts.
* Removes any unknown identities, and returns the resulting selected address.
*
* @param {Array<string>} addresses - known to the vault.
* @returns {Promise<string>} selectedAddress the selected address.
*/
syncAddresses(addresses) {
if (!Array.isArray(addresses) || addresses.length === 0) {
throw new Error('Expected non-empty array of addresses. Error #11201');
}
const { identities, lostIdentities } = this.store.getState();
const newlyLost = {};
Object.keys(identities).forEach((identity) => {
if (!addresses.includes(identity)) {
newlyLost[identity] = identities[identity];
delete identities[identity];
}
});
// Identities are no longer present.
if (Object.keys(newlyLost).length > 0) {
// store lost accounts
Object.keys(newlyLost).forEach((key) => {
lostIdentities[key] = newlyLost[key];
});
}
this.store.updateState({ identities, lostIdentities });
this.addAddresses(addresses);
7 years ago
// If the selected account is no longer valid,
// select an arbitrary other account:
let selected = this.getSelectedAddress();
if (!addresses.includes(selected)) {
selected = addresses[0];
this.setSelectedAddress(selected);
}
return selected;
}
removeSuggestedTokens() {
return new Promise((resolve) => {
this.store.updateState({ suggestedTokens: {} });
resolve({});
});
}
/**
* Setter for the `selectedAddress` property
*
* @param {string} _address - A new hex address for an account
* @returns {Promise<void>} Promise resolves with tokens
*
*/
setSelectedAddress(_address) {
const address = normalizeAddress(_address);
this._updateTokens(address);
Fix order of accounts in `eth_accounts` response (#8342) * Fix order of accounts in `eth_accounts` response The accounts returned by `eth_accounts` were in a fixed order - the order in which the keyring returned them - rather than ordered with the selected account first. The accounts returned by the `accountsChanged` event were ordered with the selected account first, but the same order wasn't used for `eth_accounts`. We needed to store additional state in order to determine the correct account order correctly on all dapps. We had only been storing the current selected account, but since we also need to determine the primary account per dapp (i.e. the last "selected" account among the accounts exposed to that dapp), that wasn't enough. A `lastSelected` property has been added to each identity in the preferences controller to keep track of the last selected time. This property is set to the current time (in milliseconds) whenever a new selection is made. The accounts returned with `accountsChanged` and by `eth_accounts` are both ordered by this property. The `updatePermittedAccounts` function was merged with the internal methods for responding to account selection, to keep things simpler. It wasn't called externally anyway, so it wasn't needed in the public API. * Remove caveat update upon change in selected account The order of accounts in the caveat isn't meaningful, so the caveat doesn't need to be updated when the accounts get re-ordered. * Emit event regardless of account order Now that we're no longer relying upon the caveat for the account order, we also have no way of knowing if a particular account selection resulted in a change in order or not. The notification is now emitted whenever an exposed account is selected - even if the order stayed the same. The inpage provider currently caches the account order, so it can be relied upon to ignore these redundant events. We were already emiting redundant `accountsChanged` events in some cases anyway.
5 years ago
const { identities, tokens } = this.store.getState();
const selectedIdentity = identities[address];
Fix order of accounts in `eth_accounts` response (#8342) * Fix order of accounts in `eth_accounts` response The accounts returned by `eth_accounts` were in a fixed order - the order in which the keyring returned them - rather than ordered with the selected account first. The accounts returned by the `accountsChanged` event were ordered with the selected account first, but the same order wasn't used for `eth_accounts`. We needed to store additional state in order to determine the correct account order correctly on all dapps. We had only been storing the current selected account, but since we also need to determine the primary account per dapp (i.e. the last "selected" account among the accounts exposed to that dapp), that wasn't enough. A `lastSelected` property has been added to each identity in the preferences controller to keep track of the last selected time. This property is set to the current time (in milliseconds) whenever a new selection is made. The accounts returned with `accountsChanged` and by `eth_accounts` are both ordered by this property. The `updatePermittedAccounts` function was merged with the internal methods for responding to account selection, to keep things simpler. It wasn't called externally anyway, so it wasn't needed in the public API. * Remove caveat update upon change in selected account The order of accounts in the caveat isn't meaningful, so the caveat doesn't need to be updated when the accounts get re-ordered. * Emit event regardless of account order Now that we're no longer relying upon the caveat for the account order, we also have no way of knowing if a particular account selection resulted in a change in order or not. The notification is now emitted whenever an exposed account is selected - even if the order stayed the same. The inpage provider currently caches the account order, so it can be relied upon to ignore these redundant events. We were already emiting redundant `accountsChanged` events in some cases anyway.
5 years ago
if (!selectedIdentity) {
throw new Error(`Identity for '${address} not found`);
Fix order of accounts in `eth_accounts` response (#8342) * Fix order of accounts in `eth_accounts` response The accounts returned by `eth_accounts` were in a fixed order - the order in which the keyring returned them - rather than ordered with the selected account first. The accounts returned by the `accountsChanged` event were ordered with the selected account first, but the same order wasn't used for `eth_accounts`. We needed to store additional state in order to determine the correct account order correctly on all dapps. We had only been storing the current selected account, but since we also need to determine the primary account per dapp (i.e. the last "selected" account among the accounts exposed to that dapp), that wasn't enough. A `lastSelected` property has been added to each identity in the preferences controller to keep track of the last selected time. This property is set to the current time (in milliseconds) whenever a new selection is made. The accounts returned with `accountsChanged` and by `eth_accounts` are both ordered by this property. The `updatePermittedAccounts` function was merged with the internal methods for responding to account selection, to keep things simpler. It wasn't called externally anyway, so it wasn't needed in the public API. * Remove caveat update upon change in selected account The order of accounts in the caveat isn't meaningful, so the caveat doesn't need to be updated when the accounts get re-ordered. * Emit event regardless of account order Now that we're no longer relying upon the caveat for the account order, we also have no way of knowing if a particular account selection resulted in a change in order or not. The notification is now emitted whenever an exposed account is selected - even if the order stayed the same. The inpage provider currently caches the account order, so it can be relied upon to ignore these redundant events. We were already emiting redundant `accountsChanged` events in some cases anyway.
5 years ago
}
selectedIdentity.lastSelected = Date.now();
this.store.updateState({ identities, selectedAddress: address });
return Promise.resolve(tokens);
}
/**
* Getter for the `selectedAddress` property
*
* @returns {string} The hex address for the currently selected account
*
*/
getSelectedAddress() {
return this.store.getState().selectedAddress;
}
/**
* Contains data about tokens users add to their account.
* @typedef {Object} AddedToken
* @property {string} address - The hex address for the token contract. Will be all lower cased and hex-prefixed.
* @property {string} symbol - The symbol of the token, usually 3 or 4 capitalized letters
* {@link https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#symbol}
* @property {boolean} decimals - The number of decimals the token uses.
* {@link https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#decimals}
*/
/**
* Adds a new token to the token array and removes it from the hiddenToken array, or updates the token if passed an address that already exists.
* Modifies the existing tokens array from the store. All objects in the tokens array array AddedToken objects.
* @see AddedToken {@link AddedToken}
*
* @param {string} rawAddress - Hex address of the token contract. May or may not be a checksum address.
* @param {string} symbol - The symbol of the token
* @param {number} decimals - The number of decimals the token uses.
* @returns {Promise<array>} Promises the new array of AddedToken objects.
*
*/
async addToken(rawAddress, symbol, decimals, image) {
const address = normalizeAddress(rawAddress);
const newEntry = { address, symbol, decimals: Number(decimals) };
const { tokens, hiddenTokens } = this.store.getState();
const assetImages = this.getAssetImages();
const updatedHiddenTokens = hiddenTokens.filter(
(tokenAddress) => tokenAddress !== rawAddress.toLowerCase(),
);
const previousEntry = tokens.find((token) => {
return token.address === address;
});
const previousIndex = tokens.indexOf(previousEntry);
newEntry.isERC721 = await this._detectIsERC721(newEntry.address);
if (previousEntry) {
tokens[previousIndex] = newEntry;
} else {
tokens.push(newEntry);
}
assetImages[address] = image;
this._updateAccountTokens(tokens, assetImages, updatedHiddenTokens);
return Promise.resolve(tokens);
}
/**
* Adds isERC721 field to token object
* (Called when a user attempts to add tokens that were previously added which do not yet had isERC721 field)
*
* @param {string} tokenAddress - The contract address of the token requiring the isERC721 field added.
* @returns {Promise<object>} The new token object with the added isERC721 field.
*
*/
async updateTokenType(tokenAddress) {
const { tokens } = this.store.getState();
const tokenIndex = tokens.findIndex((token) => {
return token.address === tokenAddress;
});
tokens[tokenIndex].isERC721 = await this._detectIsERC721(tokenAddress);
this.store.updateState({ tokens });
return Promise.resolve(tokens[tokenIndex]);
}
/**
* Removes a specified token from the tokens array and adds it to hiddenTokens array
*
* @param {string} rawAddress - Hex address of the token contract to remove.
* @returns {Promise<array>} The new array of AddedToken objects
*
*/
removeToken(rawAddress) {
const { tokens, hiddenTokens } = this.store.getState();
const assetImages = this.getAssetImages();
const updatedTokens = tokens.filter(
(token) => token.address !== rawAddress,
);
const updatedHiddenTokens = [...hiddenTokens, rawAddress.toLowerCase()];
delete assetImages[rawAddress];
this._updateAccountTokens(updatedTokens, assetImages, updatedHiddenTokens);
return Promise.resolve(updatedTokens);
}
/**
* A getter for the `tokens` property
*
* @returns {Array} The current array of AddedToken objects
*
*/
getTokens() {
return this.store.getState().tokens;
}
/**
* Sets a custom label for an account
* @param {string} account - the account to set a label for
* @param {string} label - the custom label for the account
* @returns {Promise<string>}
*/
setAccountLabel(account, label) {
if (!account) {
throw new Error(
`setAccountLabel requires a valid address, got ${String(account)}`,
);
}
const address = normalizeAddress(account);
const { identities } = this.store.getState();
identities[address] = identities[address] || {};
identities[address].name = label;
this.store.updateState({ identities });
return Promise.resolve(label);
}
/**
* updates custom RPC details
*
Update address book state upon custom RPC chainId edit (#9493) When the `chainId` for a custom RPC endpoint is edited, we now migrate the corresponding address book entries to ensure they are not orphaned. The address book entries are grouped by the `metamask.network` state, which unfortunately was sometimes the `chainId`, and sometimes the `networkId`. It was always the `networkId` for built-in Infura networks, but for custom RPC endpoints it would be set to the user-set `chainId` field, with a fallback to the `networkId` of the network. A recent change will force users to enter valid `chainId`s on all custom networks, which will be normalized to be hex-prefixed. As a result, address book contacts will now be keyed by a different string. The contact entries are now migrated when this edit takes place. There are some edge cases where two separate entries share the same set of contacts. For example, if two entries have the same `chainId`, or if they had the same `networkId` and had no `chainId` set. When the `chainId` is edited in such cases, the contacts are duplicated on both networks. This is the best we can do, as we don't have any way to know which network the contacts _should_ be on. The `typed-message-manager` unit tests have also been updated as part of this commit because the addition of `sinon.restore()` to the preferences controller tests ended up clearing a test object in-between individual tests in that file. The test object is now re-constructed before each individual test.
4 years ago
* @param {Object} newRpcDetails - Options bag.
* @param {string} newRpcDetails.rpcUrl - The RPC url to add to frequentRpcList.
* @param {string} newRpcDetails.chainId - The chainId of the selected network.
* @param {string} [newRpcDetails.ticker] - Optional ticker symbol of the selected network.
* @param {string} [newRpcDetails.nickname] - Optional nickname of the selected network.
* @param {Object} [newRpcDetails.rpcPrefs] - Optional RPC preferences, such as the block explorer URL
*
*/
async updateRpc(newRpcDetails) {
const rpcList = this.getFrequentRpcListDetail();
const index = rpcList.findIndex((element) => {
return element.rpcUrl === newRpcDetails.rpcUrl;
});
if (index > -1) {
const rpcDetail = rpcList[index];
const updatedRpc = { ...rpcDetail, ...newRpcDetails };
Update address book state upon custom RPC chainId edit (#9493) When the `chainId` for a custom RPC endpoint is edited, we now migrate the corresponding address book entries to ensure they are not orphaned. The address book entries are grouped by the `metamask.network` state, which unfortunately was sometimes the `chainId`, and sometimes the `networkId`. It was always the `networkId` for built-in Infura networks, but for custom RPC endpoints it would be set to the user-set `chainId` field, with a fallback to the `networkId` of the network. A recent change will force users to enter valid `chainId`s on all custom networks, which will be normalized to be hex-prefixed. As a result, address book contacts will now be keyed by a different string. The contact entries are now migrated when this edit takes place. There are some edge cases where two separate entries share the same set of contacts. For example, if two entries have the same `chainId`, or if they had the same `networkId` and had no `chainId` set. When the `chainId` is edited in such cases, the contacts are duplicated on both networks. This is the best we can do, as we don't have any way to know which network the contacts _should_ be on. The `typed-message-manager` unit tests have also been updated as part of this commit because the addition of `sinon.restore()` to the preferences controller tests ended up clearing a test object in-between individual tests in that file. The test object is now re-constructed before each individual test.
4 years ago
if (rpcDetail.chainId !== updatedRpc.chainId) {
// When the chainId is changed, associated address book entries should
// also be migrated. The address book entries are keyed by the `network` state,
// which for custom networks is the chainId with a fallback to the networkId
// if the chainId is not set.
let addressBookKey = rpcDetail.chainId;
Update address book state upon custom RPC chainId edit (#9493) When the `chainId` for a custom RPC endpoint is edited, we now migrate the corresponding address book entries to ensure they are not orphaned. The address book entries are grouped by the `metamask.network` state, which unfortunately was sometimes the `chainId`, and sometimes the `networkId`. It was always the `networkId` for built-in Infura networks, but for custom RPC endpoints it would be set to the user-set `chainId` field, with a fallback to the `networkId` of the network. A recent change will force users to enter valid `chainId`s on all custom networks, which will be normalized to be hex-prefixed. As a result, address book contacts will now be keyed by a different string. The contact entries are now migrated when this edit takes place. There are some edge cases where two separate entries share the same set of contacts. For example, if two entries have the same `chainId`, or if they had the same `networkId` and had no `chainId` set. When the `chainId` is edited in such cases, the contacts are duplicated on both networks. This is the best we can do, as we don't have any way to know which network the contacts _should_ be on. The `typed-message-manager` unit tests have also been updated as part of this commit because the addition of `sinon.restore()` to the preferences controller tests ended up clearing a test object in-between individual tests in that file. The test object is now re-constructed before each individual test.
4 years ago
if (!addressBookKey) {
// We need to find the networkId to determine what these addresses were keyed by
try {
addressBookKey = await this.ethersProvider.send('net_version');
assert(typeof addressBookKey === 'string');
Update address book state upon custom RPC chainId edit (#9493) When the `chainId` for a custom RPC endpoint is edited, we now migrate the corresponding address book entries to ensure they are not orphaned. The address book entries are grouped by the `metamask.network` state, which unfortunately was sometimes the `chainId`, and sometimes the `networkId`. It was always the `networkId` for built-in Infura networks, but for custom RPC endpoints it would be set to the user-set `chainId` field, with a fallback to the `networkId` of the network. A recent change will force users to enter valid `chainId`s on all custom networks, which will be normalized to be hex-prefixed. As a result, address book contacts will now be keyed by a different string. The contact entries are now migrated when this edit takes place. There are some edge cases where two separate entries share the same set of contacts. For example, if two entries have the same `chainId`, or if they had the same `networkId` and had no `chainId` set. When the `chainId` is edited in such cases, the contacts are duplicated on both networks. This is the best we can do, as we don't have any way to know which network the contacts _should_ be on. The `typed-message-manager` unit tests have also been updated as part of this commit because the addition of `sinon.restore()` to the preferences controller tests ended up clearing a test object in-between individual tests in that file. The test object is now re-constructed before each individual test.
4 years ago
} catch (error) {
log.debug(error);
log.warn(
`Failed to get networkId from ${rpcDetail.rpcUrl}; skipping address book migration`,
);
Update address book state upon custom RPC chainId edit (#9493) When the `chainId` for a custom RPC endpoint is edited, we now migrate the corresponding address book entries to ensure they are not orphaned. The address book entries are grouped by the `metamask.network` state, which unfortunately was sometimes the `chainId`, and sometimes the `networkId`. It was always the `networkId` for built-in Infura networks, but for custom RPC endpoints it would be set to the user-set `chainId` field, with a fallback to the `networkId` of the network. A recent change will force users to enter valid `chainId`s on all custom networks, which will be normalized to be hex-prefixed. As a result, address book contacts will now be keyed by a different string. The contact entries are now migrated when this edit takes place. There are some edge cases where two separate entries share the same set of contacts. For example, if two entries have the same `chainId`, or if they had the same `networkId` and had no `chainId` set. When the `chainId` is edited in such cases, the contacts are duplicated on both networks. This is the best we can do, as we don't have any way to know which network the contacts _should_ be on. The `typed-message-manager` unit tests have also been updated as part of this commit because the addition of `sinon.restore()` to the preferences controller tests ended up clearing a test object in-between individual tests in that file. The test object is now re-constructed before each individual test.
4 years ago
}
}
// There is an edge case where two separate RPC endpoints are keyed by the same
// value. In this case, the contact book entries are duplicated so that they remain
// on both networks, since we don't know which network each contact is intended for.
let duplicate = false;
const builtInProviderNetworkIds = Object.values(
NETWORK_TYPE_TO_ID_MAP,
).map((ids) => ids.networkId);
const otherRpcEntries = rpcList.filter(
(entry) => entry.rpcUrl !== newRpcDetails.rpcUrl,
);
Update address book state upon custom RPC chainId edit (#9493) When the `chainId` for a custom RPC endpoint is edited, we now migrate the corresponding address book entries to ensure they are not orphaned. The address book entries are grouped by the `metamask.network` state, which unfortunately was sometimes the `chainId`, and sometimes the `networkId`. It was always the `networkId` for built-in Infura networks, but for custom RPC endpoints it would be set to the user-set `chainId` field, with a fallback to the `networkId` of the network. A recent change will force users to enter valid `chainId`s on all custom networks, which will be normalized to be hex-prefixed. As a result, address book contacts will now be keyed by a different string. The contact entries are now migrated when this edit takes place. There are some edge cases where two separate entries share the same set of contacts. For example, if two entries have the same `chainId`, or if they had the same `networkId` and had no `chainId` set. When the `chainId` is edited in such cases, the contacts are duplicated on both networks. This is the best we can do, as we don't have any way to know which network the contacts _should_ be on. The `typed-message-manager` unit tests have also been updated as part of this commit because the addition of `sinon.restore()` to the preferences controller tests ended up clearing a test object in-between individual tests in that file. The test object is now re-constructed before each individual test.
4 years ago
if (
builtInProviderNetworkIds.includes(addressBookKey) ||
otherRpcEntries.some((entry) => entry.chainId === addressBookKey)
) {
duplicate = true;
Update address book state upon custom RPC chainId edit (#9493) When the `chainId` for a custom RPC endpoint is edited, we now migrate the corresponding address book entries to ensure they are not orphaned. The address book entries are grouped by the `metamask.network` state, which unfortunately was sometimes the `chainId`, and sometimes the `networkId`. It was always the `networkId` for built-in Infura networks, but for custom RPC endpoints it would be set to the user-set `chainId` field, with a fallback to the `networkId` of the network. A recent change will force users to enter valid `chainId`s on all custom networks, which will be normalized to be hex-prefixed. As a result, address book contacts will now be keyed by a different string. The contact entries are now migrated when this edit takes place. There are some edge cases where two separate entries share the same set of contacts. For example, if two entries have the same `chainId`, or if they had the same `networkId` and had no `chainId` set. When the `chainId` is edited in such cases, the contacts are duplicated on both networks. This is the best we can do, as we don't have any way to know which network the contacts _should_ be on. The `typed-message-manager` unit tests have also been updated as part of this commit because the addition of `sinon.restore()` to the preferences controller tests ended up clearing a test object in-between individual tests in that file. The test object is now re-constructed before each individual test.
4 years ago
}
this.migrateAddressBookState(
addressBookKey,
updatedRpc.chainId,
duplicate,
);
Update address book state upon custom RPC chainId edit (#9493) When the `chainId` for a custom RPC endpoint is edited, we now migrate the corresponding address book entries to ensure they are not orphaned. The address book entries are grouped by the `metamask.network` state, which unfortunately was sometimes the `chainId`, and sometimes the `networkId`. It was always the `networkId` for built-in Infura networks, but for custom RPC endpoints it would be set to the user-set `chainId` field, with a fallback to the `networkId` of the network. A recent change will force users to enter valid `chainId`s on all custom networks, which will be normalized to be hex-prefixed. As a result, address book contacts will now be keyed by a different string. The contact entries are now migrated when this edit takes place. There are some edge cases where two separate entries share the same set of contacts. For example, if two entries have the same `chainId`, or if they had the same `networkId` and had no `chainId` set. When the `chainId` is edited in such cases, the contacts are duplicated on both networks. This is the best we can do, as we don't have any way to know which network the contacts _should_ be on. The `typed-message-manager` unit tests have also been updated as part of this commit because the addition of `sinon.restore()` to the preferences controller tests ended up clearing a test object in-between individual tests in that file. The test object is now re-constructed before each individual test.
4 years ago
}
rpcList[index] = updatedRpc;
this.store.updateState({ frequentRpcListDetail: rpcList });
} else {
const {
rpcUrl,
chainId,
ticker,
nickname,
rpcPrefs = {},
} = newRpcDetails;
this.addToFrequentRpcList(rpcUrl, chainId, ticker, nickname, rpcPrefs);
}
}
/**
* Adds custom RPC url to state.
*
* @param {string} rpcUrl - The RPC url to add to frequentRpcList.
* @param {string} chainId - The chainId of the selected network.
* @param {string} [ticker] - Ticker symbol of the selected network.
* @param {string} [nickname] - Nickname of the selected network.
Update address book state upon custom RPC chainId edit (#9493) When the `chainId` for a custom RPC endpoint is edited, we now migrate the corresponding address book entries to ensure they are not orphaned. The address book entries are grouped by the `metamask.network` state, which unfortunately was sometimes the `chainId`, and sometimes the `networkId`. It was always the `networkId` for built-in Infura networks, but for custom RPC endpoints it would be set to the user-set `chainId` field, with a fallback to the `networkId` of the network. A recent change will force users to enter valid `chainId`s on all custom networks, which will be normalized to be hex-prefixed. As a result, address book contacts will now be keyed by a different string. The contact entries are now migrated when this edit takes place. There are some edge cases where two separate entries share the same set of contacts. For example, if two entries have the same `chainId`, or if they had the same `networkId` and had no `chainId` set. When the `chainId` is edited in such cases, the contacts are duplicated on both networks. This is the best we can do, as we don't have any way to know which network the contacts _should_ be on. The `typed-message-manager` unit tests have also been updated as part of this commit because the addition of `sinon.restore()` to the preferences controller tests ended up clearing a test object in-between individual tests in that file. The test object is now re-constructed before each individual test.
4 years ago
* @param {Object} [rpcPrefs] - Optional RPC preferences, such as the block explorer URL
*
*/
addToFrequentRpcList(
rpcUrl,
chainId,
ticker = 'ETH',
nickname = '',
rpcPrefs = {},
) {
const rpcList = this.getFrequentRpcListDetail();
const index = rpcList.findIndex((element) => {
return element.rpcUrl === rpcUrl;
});
if (index !== -1) {
rpcList.splice(index, 1);
}
if (!isPrefixedFormattedHexString(chainId)) {
throw new Error(`Invalid chainId: "${chainId}"`);
}
rpcList.push({ rpcUrl, chainId, ticker, nickname, rpcPrefs });
this.store.updateState({ frequentRpcListDetail: rpcList });
}
/**
* Removes custom RPC url from state.
*
* @param {string} url - The RPC url to remove from frequentRpcList.
* @returns {Promise<array>} Promise resolving to updated frequentRpcList.
*
*/
removeFromFrequentRpcList(url) {
const rpcList = this.getFrequentRpcListDetail();
const index = rpcList.findIndex((element) => {
return element.rpcUrl === url;
});
if (index !== -1) {
rpcList.splice(index, 1);
}
this.store.updateState({ frequentRpcListDetail: rpcList });
return Promise.resolve(rpcList);
}
/**
* Getter for the `frequentRpcListDetail` property.
*
* @returns {array<array>} An array of rpc urls.
*
*/
getFrequentRpcListDetail() {
return this.store.getState().frequentRpcListDetail;
}
7 years ago
/**
* Updates the `featureFlags` property, which is an object. One property within that object will be set to a boolean.
*
* @param {string} feature - A key that corresponds to a UI feature.
* @param {boolean} activated - Indicates whether or not the UI feature should be displayed
* @returns {Promise<object>} Promises a new object; the updated featureFlags object.
*
*/
setFeatureFlag(feature, activated) {
const currentFeatureFlags = this.store.getState().featureFlags;
7 years ago
const updatedFeatureFlags = {
...currentFeatureFlags,
[feature]: activated,
};
7 years ago
this.store.updateState({ featureFlags: updatedFeatureFlags });
7 years ago
return Promise.resolve(updatedFeatureFlags);
7 years ago
}
/**
* Updates the `preferences` property, which is an object. These are user-controlled features
* found in the settings page.
* @param {string} preference - The preference to enable or disable.
* @param {boolean} value - Indicates whether or not the preference should be enabled or disabled.
* @returns {Promise<object>} Promises a new object; the updated preferences object.
*/
setPreference(preference, value) {
const currentPreferences = this.getPreferences();
const updatedPreferences = {
...currentPreferences,
[preference]: value,
};
this.store.updateState({ preferences: updatedPreferences });
return Promise.resolve(updatedPreferences);
}
/**
* A getter for the `preferences` property
* @returns {Object} A key-boolean map of user-selected preferences.
*/
getPreferences() {
return this.store.getState().preferences;
}
/**
* Sets the completedOnboarding state to true, indicating that the user has completed the
* onboarding process.
*/
completeOnboarding() {
this.store.updateState({ completedOnboarding: true });
return Promise.resolve(true);
}
/**
* A getter for the `ipfsGateway` property
* @returns {string} The current IPFS gateway domain
*/
getIpfsGateway() {
return this.store.getState().ipfsGateway;
}
/**
* A setter for the `ipfsGateway` property
* @param {string} domain - The new IPFS gateway domain
* @returns {Promise<string>} A promise of the update IPFS gateway domain
*/
setIpfsGateway(domain) {
this.store.updateState({ ipfsGateway: domain });
return Promise.resolve(domain);
}
/**
* A setter for the `useLedgerLive` property
* @param {bool} useLedgerLive - Value for ledger live support
* @returns {Promise<string>} A promise of the update to useLedgerLive
*/
async setLedgerLivePreference(useLedgerLive) {
this.store.updateState({ useLedgerLive });
return useLedgerLive;
}
/**
* A getter for the `useLedgerLive` property
* @returns {boolean} User preference of using Ledger Live
*/
getLedgerLivePreference() {
return this.store.getState().useLedgerLive;
}
/**
* A setter for the user preference to dismiss the seed phrase backup reminder
* @param {bool} dismissBackupReminder- User preference for dismissing the back up reminder
* @returns {void}
*/
async setDismissSeedBackUpReminder(dismissSeedBackUpReminder) {
await this.store.updateState({
dismissSeedBackUpReminder,
});
}
//
// PRIVATE METHODS
//
_subscribeToInfuraAvailability() {
this.network.on(NETWORK_EVENTS.INFURA_IS_BLOCKED, () => {
this._setInfuraBlocked(true);
});
this.network.on(NETWORK_EVENTS.INFURA_IS_UNBLOCKED, () => {
this._setInfuraBlocked(false);
});
}
/**
*
* A setter for the `infuraBlocked` property
* @param {boolean} isBlocked - Bool indicating whether Infura is blocked
*
*/
_setInfuraBlocked(isBlocked) {
const { infuraBlocked } = this.store.getState();
if (infuraBlocked === isBlocked) {
return;
}
this.store.updateState({ infuraBlocked: isBlocked });
}
/**
* Updates `accountTokens`, `tokens`, `accountHiddenTokens` and `hiddenTokens` of current account and network according to it.
*
* @param {array} tokens - Array of tokens to be updated.
* @param {array} assetImages - Array of assets objects related to assets added
* @param {array} hiddenTokens - Array of tokens hidden by user
*
*/
_updateAccountTokens(tokens, assetImages, hiddenTokens) {
const {
accountTokens,
chainId,
selectedAddress,
accountHiddenTokens,
} = this._getTokenRelatedStates();
accountTokens[selectedAddress][chainId] = tokens;
accountHiddenTokens[selectedAddress][chainId] = hiddenTokens;
this.store.updateState({
accountTokens,
tokens,
assetImages,
accountHiddenTokens,
hiddenTokens,
});
}
/**
* Detects whether or not a token is ERC-721 compatible.
*
* @param {string} tokensAddress - the token contract address.
*
*/
async _detectIsERC721(tokenAddress) {
const checksumAddress = toChecksumHexAddress(tokenAddress);
// if this token is already in our contract metadata map we don't need
// to check against the contract
if (contractsMap[checksumAddress]?.erc721 === true) {
return Promise.resolve(true);
}
const tokenContract = await this._createEthersContract(
tokenAddress,
abiERC721,
this.ethersProvider,
);
return await tokenContract
.supportsInterface(ERC721_INTERFACE_ID)
.catch((error) => {
log.debug(error);
return false;
});
}
async _createEthersContract(tokenAddress, abi, ethersProvider) {
const tokenContract = await new ethers.Contract(
tokenAddress,
abi,
ethersProvider,
);
return tokenContract;
}
/**
* Updates `tokens` and `hiddenTokens` of current account and network.
*
* @param {string} selectedAddress - Account address to be updated with.
*
*/
_updateTokens(selectedAddress) {
const { tokens, hiddenTokens } = this._getTokenRelatedStates(
selectedAddress,
);
this.store.updateState({ tokens, hiddenTokens });
}
/**
* A getter for `tokens`, `accountTokens`, `hiddenTokens` and `accountHiddenTokens` related states.
*
* @param {string} [selectedAddress] - A new hex address for an account
* @returns {Object.<array, object, string, string>} States to interact with tokens in `accountTokens`
*
*/
_getTokenRelatedStates(selectedAddress) {
const { accountTokens, accountHiddenTokens } = this.store.getState();
if (!selectedAddress) {
// eslint-disable-next-line no-param-reassign
selectedAddress = this.store.getState().selectedAddress;
}
const chainId = this.network.getCurrentChainId();
if (!(selectedAddress in accountTokens)) {
accountTokens[selectedAddress] = {};
}
if (!(selectedAddress in accountHiddenTokens)) {
accountHiddenTokens[selectedAddress] = {};
}
if (!(chainId in accountTokens[selectedAddress])) {
accountTokens[selectedAddress][chainId] = [];
}
if (!(chainId in accountHiddenTokens[selectedAddress])) {
accountHiddenTokens[selectedAddress][chainId] = [];
}
const tokens = accountTokens[selectedAddress][chainId];
const hiddenTokens = accountHiddenTokens[selectedAddress][chainId];
return {
tokens,
accountTokens,
hiddenTokens,
accountHiddenTokens,
chainId,
selectedAddress,
};
}
/**
* Handle the suggestion of an ERC20 asset through `watchAsset`
* *
* @param {Object} tokenMetadata - Token metadata
*
*/
async _handleWatchAssetERC20(tokenMetadata) {
this._validateERC20AssetParams(tokenMetadata);
const address = normalizeAddress(tokenMetadata.address);
const { symbol, decimals, image } = tokenMetadata;
this._addSuggestedERC20Asset(address, symbol, decimals, image);
await this.openPopup();
const tokenAddresses = this.getTokens().filter(
(token) => token.address === address,
);
return tokenAddresses.length > 0;
}
/**
* Validates that the passed options for suggested token have all required properties.
*
* @param {Object} opts - The options object to validate
* @throws {string} Throw a custom error indicating that address, symbol and/or decimals
* doesn't fulfill requirements
*
*/
_validateERC20AssetParams({ address, symbol, decimals }) {
if (!address || !symbol || typeof decimals === 'undefined') {
throw ethErrors.rpc.invalidParams(
`Must specify address, symbol, and decimals.`,
);
}
if (typeof symbol !== 'string') {
throw ethErrors.rpc.invalidParams(`Invalid symbol: not a string.`);
}
if (!(symbol.length > 0)) {
throw ethErrors.rpc.invalidParams(
`Invalid symbol "${symbol}": shorter than a character.`,
);
}
if (!(symbol.length < 12)) {
throw ethErrors.rpc.invalidParams(
`Invalid symbol "${symbol}": longer than 11 characters.`,
);
}
const numDecimals = parseInt(decimals, 10);
if (isNaN(numDecimals) || numDecimals > 36 || numDecimals < 0) {
throw ethErrors.rpc.invalidParams(
`Invalid decimals "${decimals}": must be 0 <= 36.`,
);
}
if (!isValidHexAddress(address, { allowNonPrefixed: false })) {
throw ethErrors.rpc.invalidParams(`Invalid address "${address}".`);
}
}
_addSuggestedERC20Asset(address, symbol, decimals, image) {
const newEntry = {
address,
symbol,
decimals,
image,
unlisted: !LISTED_CONTRACT_ADDRESSES.includes(address),
};
const suggested = this.getSuggestedTokens();
suggested[address] = newEntry;
this.store.updateState({ suggestedTokens: suggested });
}
}