mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Merge branch 'develop' of github.com:MetaMask/metamask-extension into minimal
This commit is contained in:
commit
27e9cb7f88
21
app/_locales/en/messages.json
generated
21
app/_locales/en/messages.json
generated
@ -815,7 +815,7 @@
|
||||
"message": "Contract deployment"
|
||||
},
|
||||
"contractDescription": {
|
||||
"message": "To protect yourself against scammers, take a moment to verify contract details."
|
||||
"message": "To protect yourself against scammers, take a moment to verify third-party details."
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Contract interaction"
|
||||
@ -830,10 +830,10 @@
|
||||
"message": "Contract requesting signature"
|
||||
},
|
||||
"contractRequestingSpendingCap": {
|
||||
"message": "Contract requesting spending cap"
|
||||
"message": "Third party requesting spending cap"
|
||||
},
|
||||
"contractTitle": {
|
||||
"message": "Contract details"
|
||||
"message": "Third-party details"
|
||||
},
|
||||
"contractToken": {
|
||||
"message": "Token contract"
|
||||
@ -1808,14 +1808,14 @@
|
||||
"message": "Your initial transaction was confirmed by the network. Click OK to go back."
|
||||
},
|
||||
"inputLogicEmptyState": {
|
||||
"message": "Only enter a number that you're comfortable with the contract spending now or in the future. You can always increase the spending cap later."
|
||||
"message": "Only enter a number that you're comfortable with the third party spending now or in the future. You can always increase the spending cap later."
|
||||
},
|
||||
"inputLogicEqualOrSmallerNumber": {
|
||||
"message": "This allows the contract to spend $1 from your current balance.",
|
||||
"message": "This allows the third party to spend $1 from your current balance.",
|
||||
"description": "$1 is the current token balance in the account and the name of the current token"
|
||||
},
|
||||
"inputLogicHigherNumber": {
|
||||
"message": "This allows the contract to spend all your token balance until it reaches the cap or you revoke the spending cap. If this is not intended, consider setting a lower spending cap."
|
||||
"message": "This allows the third party to spend all your token balance until it reaches the cap or you revoke the spending cap. If this is not intended, consider setting a lower spending cap."
|
||||
},
|
||||
"insightsFromSnap": {
|
||||
"message": "Insights from $1",
|
||||
@ -2486,6 +2486,9 @@
|
||||
"message": "OpenSea is the first provider for this feature. More providers coming soon!",
|
||||
"description": "Description of a notification in the 'See What's New' popup. Describes Opensea Security Provider feature."
|
||||
},
|
||||
"notifications18Title": {
|
||||
"message": "Stay safe with security alerts"
|
||||
},
|
||||
"notifications19ActionText": {
|
||||
"message": "Enable NFT autodetection"
|
||||
},
|
||||
@ -3328,7 +3331,7 @@
|
||||
"description": "$1 is a token symbol"
|
||||
},
|
||||
"revokeSpendingCapTooltipText": {
|
||||
"message": "This contract will be unable to spend any more of your current or future tokens."
|
||||
"message": "This third party will be unable to spend any more of your current or future tokens."
|
||||
},
|
||||
"rpcUrl": {
|
||||
"message": "New RPC URL"
|
||||
@ -4660,7 +4663,7 @@
|
||||
"message": "Username"
|
||||
},
|
||||
"verifyContractDetails": {
|
||||
"message": "Verify contract details"
|
||||
"message": "Verify third-party details"
|
||||
},
|
||||
"verifyThisTokenDecimalOn": {
|
||||
"message": "Token decimal can be found on $1",
|
||||
@ -4746,7 +4749,7 @@
|
||||
"message": "Warning"
|
||||
},
|
||||
"warningTooltipText": {
|
||||
"message": "$1 The contract could spend your entire token balance without further notice or consent. Protect yourself by customizing a lower spending cap.",
|
||||
"message": "$1 The third party could spend your entire token balance without further notice or consent. Protect yourself by customizing a lower spending cap.",
|
||||
"description": "$1 is a warning icon with text 'Be careful' in 'warning' colour"
|
||||
},
|
||||
"weak": {
|
||||
|
@ -727,7 +727,6 @@ export function setupController(
|
||||
}
|
||||
|
||||
function getUnapprovedTransactionCount() {
|
||||
const unapprovedTxCount = controller.txController.getUnapprovedTxCount();
|
||||
const { unapprovedDecryptMsgCount } = controller.decryptMessageManager;
|
||||
const { unapprovedEncryptionPublicKeyMsgCount } =
|
||||
controller.encryptionPublicKeyManager;
|
||||
@ -736,7 +735,6 @@ export function setupController(
|
||||
const waitingForUnlockCount =
|
||||
controller.appStateController.waitingForUnlock.length;
|
||||
return (
|
||||
unapprovedTxCount +
|
||||
unapprovedDecryptMsgCount +
|
||||
unapprovedEncryptionPublicKeyMsgCount +
|
||||
pendingApprovalCount +
|
||||
|
@ -13,7 +13,7 @@ import { convertHexToDecimal } from '@metamask/controller-utils';
|
||||
import { NETWORK_TYPES } from '../../../shared/constants/network';
|
||||
import { toChecksumHexAddress } from '../../../shared/modules/hexstring-utils';
|
||||
import DetectTokensController from './detect-tokens';
|
||||
import NetworkController, { NetworkControllerEventTypes } from './network';
|
||||
import { NetworkController, NetworkControllerEventType } from './network';
|
||||
import PreferencesController from './preferences';
|
||||
|
||||
describe('DetectTokensController', function () {
|
||||
@ -248,7 +248,7 @@ describe('DetectTokensController', function () {
|
||||
),
|
||||
onNetworkStateChange: (cb) =>
|
||||
networkControllerMessenger.subscribe(
|
||||
NetworkControllerEventTypes.NetworkDidChange,
|
||||
NetworkControllerEventType.NetworkDidChange,
|
||||
() => {
|
||||
const networkState = network.store.getState();
|
||||
const modifiedNetworkState = {
|
||||
|
@ -1 +0,0 @@
|
||||
export { default, NetworkControllerEventTypes } from './network-controller';
|
1
app/scripts/controllers/network/index.ts
Normal file
1
app/scripts/controllers/network/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './network-controller';
|
@ -1,679 +0,0 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import EventEmitter from 'events';
|
||||
import { ComposedStore, ObservableStore } from '@metamask/obs-store';
|
||||
import log from 'loglevel';
|
||||
import {
|
||||
createSwappableProxy,
|
||||
createEventEmitterProxy,
|
||||
} from '@metamask/swappable-obj-proxy';
|
||||
import EthQuery from 'eth-query';
|
||||
// ControllerMessenger is referred to in the JSDocs
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { ControllerMessenger } from '@metamask/base-controller';
|
||||
import { v4 as random } from 'uuid';
|
||||
import { hasProperty, isPlainObject } from '@metamask/utils';
|
||||
import { errorCodes } from 'eth-rpc-errors';
|
||||
import {
|
||||
INFURA_PROVIDER_TYPES,
|
||||
BUILT_IN_NETWORKS,
|
||||
INFURA_BLOCKED_KEY,
|
||||
TEST_NETWORK_TICKER_MAP,
|
||||
CHAIN_IDS,
|
||||
NETWORK_TYPES,
|
||||
NetworkStatus,
|
||||
} from '../../../../shared/constants/network';
|
||||
import {
|
||||
isPrefixedFormattedHexString,
|
||||
isSafeChainId,
|
||||
} from '../../../../shared/modules/network.utils';
|
||||
import { MetaMetricsEventCategory } from '../../../../shared/constants/metametrics';
|
||||
import { createNetworkClient } from './create-network-client';
|
||||
|
||||
/**
|
||||
* @typedef {object} NetworkConfiguration
|
||||
* @property {string} rpcUrl - RPC target URL.
|
||||
* @property {string} chainId - Network ID as per EIP-155
|
||||
* @property {string} ticker - Currency ticker.
|
||||
* @property {object} [rpcPrefs] - Personalized preferences.
|
||||
* @property {string} [nickname] - Personalized network name.
|
||||
*/
|
||||
|
||||
function buildDefaultProviderConfigState() {
|
||||
if (process.env.IN_TEST) {
|
||||
return {
|
||||
type: NETWORK_TYPES.RPC,
|
||||
rpcUrl: 'http://localhost:8545',
|
||||
chainId: '0x539',
|
||||
nickname: 'Localhost 8545',
|
||||
ticker: 'ETH',
|
||||
};
|
||||
} else if (
|
||||
process.env.METAMASK_DEBUG ||
|
||||
process.env.METAMASK_ENV === 'test'
|
||||
) {
|
||||
return {
|
||||
type: NETWORK_TYPES.GOERLI,
|
||||
chainId: CHAIN_IDS.GOERLI,
|
||||
ticker: TEST_NETWORK_TICKER_MAP.GOERLI,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: NETWORK_TYPES.MAINNET,
|
||||
chainId: CHAIN_IDS.MAINNET,
|
||||
ticker: 'ETH',
|
||||
};
|
||||
}
|
||||
|
||||
function buildDefaultNetworkIdState() {
|
||||
return null;
|
||||
}
|
||||
|
||||
function buildDefaultNetworkStatusState() {
|
||||
return NetworkStatus.Unknown;
|
||||
}
|
||||
|
||||
function buildDefaultNetworkDetailsState() {
|
||||
return {
|
||||
EIPS: {
|
||||
1559: undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function buildDefaultNetworkConfigurationsState() {
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the controller.
|
||||
*/
|
||||
const name = 'NetworkController';
|
||||
|
||||
/**
|
||||
* The set of event types that this controller can publish via its messenger.
|
||||
*/
|
||||
export const NetworkControllerEventTypes = {
|
||||
/**
|
||||
* Fired after the current network is changed.
|
||||
*/
|
||||
NetworkDidChange: `${name}:networkDidChange`,
|
||||
/**
|
||||
* Fired when there is a request to change the current network, but no state
|
||||
* changes have occurred yet.
|
||||
*/
|
||||
NetworkWillChange: `${name}:networkWillChange`,
|
||||
/**
|
||||
* Fired after the network is changed to an Infura network, but when Infura
|
||||
* returns an error denying support for the user's location.
|
||||
*/
|
||||
InfuraIsBlocked: `${name}:infuraIsBlocked`,
|
||||
/**
|
||||
* Fired after the network is changed to an Infura network and Infura does not
|
||||
* return an error denying support for the user's location, or after the
|
||||
* network is changed to a custom network.
|
||||
*/
|
||||
InfuraIsUnblocked: `${name}:infuraIsUnblocked`,
|
||||
};
|
||||
|
||||
export default class NetworkController extends EventEmitter {
|
||||
/**
|
||||
* Construct a NetworkController.
|
||||
*
|
||||
* @param {object} options - Options for this controller.
|
||||
* @param {ControllerMessenger} options.messenger - The controller messenger.
|
||||
* @param {object} [options.state] - Initial controller state.
|
||||
* @param {string} [options.infuraProjectId] - The Infura project ID.
|
||||
* @param {string} [options.trackMetaMetricsEvent] - A method to forward events to the MetaMetricsController
|
||||
*/
|
||||
constructor({
|
||||
messenger,
|
||||
state = {},
|
||||
infuraProjectId,
|
||||
trackMetaMetricsEvent,
|
||||
} = {}) {
|
||||
super();
|
||||
|
||||
this.messenger = messenger;
|
||||
|
||||
// create stores
|
||||
this.providerStore = new ObservableStore(
|
||||
state.provider || buildDefaultProviderConfigState(),
|
||||
);
|
||||
this.previousProviderStore = new ObservableStore(
|
||||
this.providerStore.getState(),
|
||||
);
|
||||
this.networkIdStore = new ObservableStore(buildDefaultNetworkIdState());
|
||||
this.networkStatusStore = new ObservableStore(
|
||||
buildDefaultNetworkStatusState(),
|
||||
);
|
||||
// We need to keep track of a few details about the current network.
|
||||
// Ideally we'd merge this.networkStatusStore with this new store, but doing
|
||||
// so will require a decent sized refactor of how we're accessing network
|
||||
// state. Currently this is only used for detecting EIP-1559 support but can
|
||||
// be extended to track other network details.
|
||||
this.networkDetails = new ObservableStore(
|
||||
state.networkDetails || buildDefaultNetworkDetailsState(),
|
||||
);
|
||||
|
||||
this.networkConfigurationsStore = new ObservableStore(
|
||||
state.networkConfigurations || buildDefaultNetworkConfigurationsState(),
|
||||
);
|
||||
|
||||
this.store = new ComposedStore({
|
||||
provider: this.providerStore,
|
||||
previousProviderStore: this.previousProviderStore,
|
||||
networkId: this.networkIdStore,
|
||||
networkStatus: this.networkStatusStore,
|
||||
networkDetails: this.networkDetails,
|
||||
networkConfigurations: this.networkConfigurationsStore,
|
||||
});
|
||||
|
||||
// provider and block tracker
|
||||
this._provider = null;
|
||||
this._blockTracker = null;
|
||||
|
||||
// provider and block tracker proxies - because the network changes
|
||||
this._providerProxy = null;
|
||||
this._blockTrackerProxy = null;
|
||||
|
||||
if (!infuraProjectId || typeof infuraProjectId !== 'string') {
|
||||
throw new Error('Invalid Infura project ID');
|
||||
}
|
||||
this._infuraProjectId = infuraProjectId;
|
||||
|
||||
this._trackMetaMetricsEvent = trackMetaMetricsEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the network controller, stopping any ongoing polling.
|
||||
*
|
||||
* In-progress requests will not be aborted.
|
||||
*/
|
||||
async destroy() {
|
||||
await this._blockTracker?.destroy();
|
||||
}
|
||||
|
||||
async initializeProvider() {
|
||||
const { type, rpcUrl, chainId } = this.providerStore.getState();
|
||||
this._configureProvider({ type, rpcUrl, chainId });
|
||||
await this.lookupNetwork();
|
||||
}
|
||||
|
||||
// return the proxies so the references will always be good
|
||||
getProviderAndBlockTracker() {
|
||||
const provider = this._providerProxy;
|
||||
const blockTracker = this._blockTrackerProxy;
|
||||
return { provider, blockTracker };
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the network supports EIP-1559 by checking whether the
|
||||
* latest block has a `baseFeePerGas` property, then updates state
|
||||
* appropriately.
|
||||
*
|
||||
* @returns {Promise<boolean>} A promise that resolves to true if the network
|
||||
* supports EIP-1559 and false otherwise.
|
||||
*/
|
||||
async getEIP1559Compatibility() {
|
||||
const { EIPS } = this.networkDetails.getState();
|
||||
// NOTE: This isn't necessary anymore because the block cache middleware
|
||||
// already prevents duplicate requests from taking place
|
||||
if (EIPS[1559] !== undefined) {
|
||||
return EIPS[1559];
|
||||
}
|
||||
const supportsEIP1559 = await this._determineEIP1559Compatibility();
|
||||
this.networkDetails.updateState({
|
||||
EIPS: {
|
||||
...this.networkDetails.getState().EIPS,
|
||||
1559: supportsEIP1559,
|
||||
},
|
||||
});
|
||||
return supportsEIP1559;
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures information about the currently selected network — namely,
|
||||
* the network ID and whether the network supports EIP-1559 — and then uses
|
||||
* the results of these requests to determine the status of the network.
|
||||
*/
|
||||
async lookupNetwork() {
|
||||
const { chainId, type } = this.providerStore.getState();
|
||||
let networkChanged = false;
|
||||
let networkId;
|
||||
let supportsEIP1559;
|
||||
let networkStatus;
|
||||
|
||||
if (!this._provider) {
|
||||
log.warn(
|
||||
'NetworkController - lookupNetwork aborted due to missing provider',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!chainId) {
|
||||
log.warn(
|
||||
'NetworkController - lookupNetwork aborted due to missing chainId',
|
||||
);
|
||||
this._resetNetworkId();
|
||||
this._resetNetworkStatus();
|
||||
this._resetNetworkDetails();
|
||||
return;
|
||||
}
|
||||
|
||||
const isInfura = INFURA_PROVIDER_TYPES.includes(type);
|
||||
|
||||
const listener = () => {
|
||||
networkChanged = true;
|
||||
this.messenger.unsubscribe(
|
||||
NetworkControllerEventTypes.NetworkDidChange,
|
||||
listener,
|
||||
);
|
||||
};
|
||||
this.messenger.subscribe(
|
||||
NetworkControllerEventTypes.NetworkDidChange,
|
||||
listener,
|
||||
);
|
||||
|
||||
try {
|
||||
const results = await Promise.all([
|
||||
this._getNetworkId(),
|
||||
this._determineEIP1559Compatibility(),
|
||||
]);
|
||||
networkId = results[0];
|
||||
supportsEIP1559 = results[1];
|
||||
networkStatus = NetworkStatus.Available;
|
||||
} catch (error) {
|
||||
if (hasProperty(error, 'code')) {
|
||||
let responseBody;
|
||||
try {
|
||||
responseBody = JSON.parse(error.message);
|
||||
} catch {
|
||||
// error.message must not be JSON
|
||||
}
|
||||
|
||||
if (
|
||||
isPlainObject(responseBody) &&
|
||||
responseBody.error === INFURA_BLOCKED_KEY
|
||||
) {
|
||||
networkStatus = NetworkStatus.Blocked;
|
||||
} else if (error.code === errorCodes.rpc.internal) {
|
||||
networkStatus = NetworkStatus.Unknown;
|
||||
} else {
|
||||
networkStatus = NetworkStatus.Unavailable;
|
||||
}
|
||||
} else {
|
||||
log.warn(
|
||||
'NetworkController - could not determine network status',
|
||||
error,
|
||||
);
|
||||
networkStatus = NetworkStatus.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
if (networkChanged) {
|
||||
// If the network has changed, then `lookupNetwork` either has been or is
|
||||
// in the process of being called, so we don't need to go further.
|
||||
return;
|
||||
}
|
||||
this.messenger.unsubscribe(
|
||||
NetworkControllerEventTypes.NetworkDidChange,
|
||||
listener,
|
||||
);
|
||||
|
||||
this.networkStatusStore.putState(networkStatus);
|
||||
|
||||
if (networkStatus === NetworkStatus.Available) {
|
||||
this.networkIdStore.putState(networkId);
|
||||
this.networkDetails.updateState({
|
||||
EIPS: {
|
||||
...this.networkDetails.getState().EIPS,
|
||||
1559: supportsEIP1559,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
this._resetNetworkId();
|
||||
this._resetNetworkDetails();
|
||||
}
|
||||
|
||||
if (isInfura) {
|
||||
if (networkStatus === NetworkStatus.Available) {
|
||||
this.messenger.publish(NetworkControllerEventTypes.InfuraIsUnblocked);
|
||||
} else if (networkStatus === NetworkStatus.Blocked) {
|
||||
this.messenger.publish(NetworkControllerEventTypes.InfuraIsBlocked);
|
||||
}
|
||||
} else {
|
||||
// Always publish infuraIsUnblocked regardless of network status to
|
||||
// prevent consumers from being stuck in a blocked state if they were
|
||||
// previously connected to an Infura network that was blocked
|
||||
this.messenger.publish(NetworkControllerEventTypes.InfuraIsUnblocked);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A method for setting the currently selected network provider by networkConfigurationId.
|
||||
*
|
||||
* @param {string} networkConfigurationId - the universal unique identifier that corresponds to the network configuration to set as active.
|
||||
* @returns {string} The rpcUrl of the network that was just set as active
|
||||
*/
|
||||
setActiveNetwork(networkConfigurationId) {
|
||||
const targetNetwork =
|
||||
this.networkConfigurationsStore.getState()[networkConfigurationId];
|
||||
|
||||
if (!targetNetwork) {
|
||||
throw new Error(
|
||||
`networkConfigurationId ${networkConfigurationId} does not match a configured networkConfiguration`,
|
||||
);
|
||||
}
|
||||
|
||||
this._setProviderConfig({
|
||||
type: NETWORK_TYPES.RPC,
|
||||
...targetNetwork,
|
||||
});
|
||||
|
||||
return targetNetwork.rpcUrl;
|
||||
}
|
||||
|
||||
setProviderType(type) {
|
||||
assert.notStrictEqual(
|
||||
type,
|
||||
NETWORK_TYPES.RPC,
|
||||
`NetworkController - cannot call "setProviderType" with type "${NETWORK_TYPES.RPC}". Use "setActiveNetwork"`,
|
||||
);
|
||||
assert.ok(
|
||||
INFURA_PROVIDER_TYPES.includes(type),
|
||||
`Unknown Infura provider type "${type}".`,
|
||||
);
|
||||
const { chainId, ticker, blockExplorerUrl } = BUILT_IN_NETWORKS[type];
|
||||
this._setProviderConfig({
|
||||
type,
|
||||
rpcUrl: '',
|
||||
chainId,
|
||||
ticker: ticker ?? 'ETH',
|
||||
nickname: '',
|
||||
rpcPrefs: { blockExplorerUrl },
|
||||
});
|
||||
}
|
||||
|
||||
resetConnection() {
|
||||
this._setProviderConfig(this.providerStore.getState());
|
||||
}
|
||||
|
||||
rollbackToPreviousProvider() {
|
||||
const config = this.previousProviderStore.getState();
|
||||
this.providerStore.putState(config);
|
||||
this._switchNetwork(config);
|
||||
}
|
||||
|
||||
//
|
||||
// Private
|
||||
//
|
||||
|
||||
/**
|
||||
* Method to return the latest block for the current network
|
||||
*
|
||||
* @returns {object} Block header
|
||||
*/
|
||||
_getLatestBlock() {
|
||||
const { provider } = this.getProviderAndBlockTracker();
|
||||
const ethQuery = new EthQuery(provider);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
ethQuery.sendAsync(
|
||||
{ method: 'eth_getBlockByNumber', params: ['latest', false] },
|
||||
(error, result) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the network ID for the current selected network
|
||||
*
|
||||
* @returns {string} The network ID for the current network.
|
||||
*/
|
||||
async _getNetworkId() {
|
||||
const { provider } = this.getProviderAndBlockTracker();
|
||||
const ethQuery = new EthQuery(provider);
|
||||
|
||||
return await new Promise((resolve, reject) => {
|
||||
ethQuery.sendAsync({ method: 'net_version' }, (error, result) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the stored network ID.
|
||||
*/
|
||||
_resetNetworkId() {
|
||||
this.networkIdStore.putState(buildDefaultNetworkIdState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets network status to the default ("unknown").
|
||||
*/
|
||||
_resetNetworkStatus() {
|
||||
this.networkStatusStore.putState(buildDefaultNetworkStatusState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears details previously stored for the network.
|
||||
*/
|
||||
_resetNetworkDetails() {
|
||||
this.networkDetails.putState(buildDefaultNetworkDetailsState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the provider config and switches the network.
|
||||
*
|
||||
* @param config
|
||||
*/
|
||||
_setProviderConfig(config) {
|
||||
this.previousProviderStore.putState(this.providerStore.getState());
|
||||
this.providerStore.putState(config);
|
||||
this._switchNetwork(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the latest block from the currently selected network; if the
|
||||
* block has a `baseFeePerGas` property, then we know that the network
|
||||
* supports EIP-1559; otherwise it doesn't.
|
||||
*
|
||||
* @returns {Promise<boolean>} A promise that resolves to true if the network
|
||||
* supports EIP-1559 and false otherwise.
|
||||
*/
|
||||
async _determineEIP1559Compatibility() {
|
||||
const latestBlock = await this._getLatestBlock();
|
||||
return latestBlock && latestBlock.baseFeePerGas !== undefined;
|
||||
}
|
||||
|
||||
_switchNetwork(opts) {
|
||||
this.messenger.publish(NetworkControllerEventTypes.NetworkWillChange);
|
||||
this._resetNetworkId();
|
||||
this._resetNetworkStatus();
|
||||
this._resetNetworkDetails();
|
||||
this._configureProvider(opts);
|
||||
this.messenger.publish(NetworkControllerEventTypes.NetworkDidChange);
|
||||
this.lookupNetwork();
|
||||
}
|
||||
|
||||
_configureProvider({ type, rpcUrl, chainId }) {
|
||||
// infura type-based endpoints
|
||||
const isInfura = INFURA_PROVIDER_TYPES.includes(type);
|
||||
if (isInfura) {
|
||||
this._configureInfuraProvider({
|
||||
type,
|
||||
infuraProjectId: this._infuraProjectId,
|
||||
});
|
||||
// url-based rpc endpoints
|
||||
} else if (type === NETWORK_TYPES.RPC) {
|
||||
this._configureStandardProvider(rpcUrl, chainId);
|
||||
} else {
|
||||
throw new Error(
|
||||
`NetworkController - _configureProvider - unknown type "${type}"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_configureInfuraProvider({ type, infuraProjectId }) {
|
||||
log.info('NetworkController - configureInfuraProvider', type);
|
||||
const { provider, blockTracker } = createNetworkClient({
|
||||
network: type,
|
||||
infuraProjectId,
|
||||
type: 'infura',
|
||||
});
|
||||
this._setProviderAndBlockTracker({ provider, blockTracker });
|
||||
}
|
||||
|
||||
_configureStandardProvider(rpcUrl, chainId) {
|
||||
log.info('NetworkController - configureStandardProvider', rpcUrl);
|
||||
const { provider, blockTracker } = createNetworkClient({
|
||||
chainId,
|
||||
rpcUrl,
|
||||
type: 'custom',
|
||||
});
|
||||
this._setProviderAndBlockTracker({ provider, blockTracker });
|
||||
}
|
||||
|
||||
_setProviderAndBlockTracker({ provider, blockTracker }) {
|
||||
// update or initialize proxies
|
||||
if (this._providerProxy) {
|
||||
this._providerProxy.setTarget(provider);
|
||||
} else {
|
||||
this._providerProxy = createSwappableProxy(provider);
|
||||
}
|
||||
if (this._blockTrackerProxy) {
|
||||
this._blockTrackerProxy.setTarget(blockTracker);
|
||||
} else {
|
||||
this._blockTrackerProxy = createEventEmitterProxy(blockTracker, {
|
||||
eventFilter: 'skipInternal',
|
||||
});
|
||||
}
|
||||
// set new provider and blockTracker
|
||||
this._provider = provider;
|
||||
this._blockTracker = blockTracker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Network Configuration management functions
|
||||
*/
|
||||
|
||||
/**
|
||||
* Adds a network configuration if the rpcUrl is not already present on an
|
||||
* existing network configuration. Otherwise updates the entry with the matching rpcUrl.
|
||||
*
|
||||
* @param {NetworkConfiguration} networkConfiguration - The network configuration to add or, if rpcUrl matches an existing entry, to modify.
|
||||
* @param {object} options
|
||||
* @param {boolean} options.setActive - An option to set the newly added networkConfiguration as the active provider.
|
||||
* @param {string} options.referrer - The site from which the call originated, or 'metamask' for internal calls - used for event metrics.
|
||||
* @param {string} options.source - Where the upsertNetwork event originated (i.e. from a dapp or from the network form)- used for event metrics.
|
||||
* @returns {string} id for the added or updated network configuration
|
||||
*/
|
||||
upsertNetworkConfiguration(
|
||||
{ rpcUrl, chainId, ticker, nickname, rpcPrefs },
|
||||
{ setActive = false, referrer, source },
|
||||
) {
|
||||
assert.ok(
|
||||
isPrefixedFormattedHexString(chainId),
|
||||
`Invalid chain ID "${chainId}": invalid hex string.`,
|
||||
);
|
||||
assert.ok(
|
||||
isSafeChainId(parseInt(chainId, 16)),
|
||||
`Invalid chain ID "${chainId}": numerical value greater than max safe value.`,
|
||||
);
|
||||
|
||||
if (!rpcUrl) {
|
||||
throw new Error(
|
||||
'An rpcUrl is required to add or update network configuration',
|
||||
);
|
||||
}
|
||||
|
||||
if (!referrer || !source) {
|
||||
throw new Error(
|
||||
'referrer and source are required arguments for adding or updating a network configuration',
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line no-new
|
||||
new URL(rpcUrl);
|
||||
} catch (e) {
|
||||
if (e.message.includes('Invalid URL')) {
|
||||
throw new Error('rpcUrl must be a valid URL');
|
||||
}
|
||||
}
|
||||
|
||||
if (!ticker) {
|
||||
throw new Error(
|
||||
'A ticker is required to add or update networkConfiguration',
|
||||
);
|
||||
}
|
||||
|
||||
const networkConfigurations = this.networkConfigurationsStore.getState();
|
||||
const newNetworkConfiguration = {
|
||||
rpcUrl,
|
||||
chainId,
|
||||
ticker,
|
||||
nickname,
|
||||
rpcPrefs,
|
||||
};
|
||||
|
||||
const oldNetworkConfigurationId = Object.values(networkConfigurations).find(
|
||||
(networkConfiguration) =>
|
||||
networkConfiguration.rpcUrl?.toLowerCase() === rpcUrl?.toLowerCase(),
|
||||
)?.id;
|
||||
|
||||
const newNetworkConfigurationId = oldNetworkConfigurationId || random();
|
||||
this.networkConfigurationsStore.putState({
|
||||
...networkConfigurations,
|
||||
[newNetworkConfigurationId]: {
|
||||
...newNetworkConfiguration,
|
||||
id: newNetworkConfigurationId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!oldNetworkConfigurationId) {
|
||||
this._trackMetaMetricsEvent({
|
||||
event: 'Custom Network Added',
|
||||
category: MetaMetricsEventCategory.Network,
|
||||
referrer: {
|
||||
url: referrer,
|
||||
},
|
||||
properties: {
|
||||
chain_id: chainId,
|
||||
symbol: ticker,
|
||||
source,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (setActive) {
|
||||
this.setActiveNetwork(newNetworkConfigurationId);
|
||||
}
|
||||
|
||||
return newNetworkConfigurationId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes network configuration from state.
|
||||
*
|
||||
* @param {string} networkConfigurationId - the unique id for the network configuration to remove.
|
||||
*/
|
||||
removeNetworkConfiguration(networkConfigurationId) {
|
||||
const networkConfigurations = {
|
||||
...this.networkConfigurationsStore.getState(),
|
||||
};
|
||||
delete networkConfigurations[networkConfigurationId];
|
||||
this.networkConfigurationsStore.putState(networkConfigurations);
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ import sinon from 'sinon';
|
||||
import { ControllerMessenger } from '@metamask/base-controller';
|
||||
import { BUILT_IN_NETWORKS } from '../../../../shared/constants/network';
|
||||
import { MetaMetricsNetworkEventSource } from '../../../../shared/constants/metametrics';
|
||||
import NetworkController from './network-controller';
|
||||
import { NetworkController } from './network-controller';
|
||||
|
||||
jest.mock('uuid', () => {
|
||||
const actual = jest.requireActual('uuid');
|
||||
@ -1100,7 +1100,7 @@ describe('NetworkController', () => {
|
||||
});
|
||||
|
||||
describe('when the request for the latest block responds with null', () => {
|
||||
it('stores null as whether the network supports EIP-1559', async () => {
|
||||
it('persists false to state as whether the network supports EIP-1559', async () => {
|
||||
await withController(
|
||||
{
|
||||
state: {
|
||||
@ -1118,13 +1118,13 @@ describe('NetworkController', () => {
|
||||
await controller.getEIP1559Compatibility();
|
||||
|
||||
expect(controller.store.getState().networkDetails.EIPS[1559]).toBe(
|
||||
null,
|
||||
false,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('returns null', async () => {
|
||||
it('returns false', async () => {
|
||||
await withController(async ({ controller, network }) => {
|
||||
network.mockEssentialRpcCalls({
|
||||
latestBlock: null,
|
||||
@ -1133,7 +1133,7 @@ describe('NetworkController', () => {
|
||||
|
||||
const supportsEIP1559 = await controller.getEIP1559Compatibility();
|
||||
|
||||
expect(supportsEIP1559).toBe(null);
|
||||
expect(supportsEIP1559).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
1171
app/scripts/controllers/network/network-controller.ts
Normal file
1171
app/scripts/controllers/network/network-controller.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -4,7 +4,7 @@ import { ControllerMessenger } from '@metamask/base-controller';
|
||||
import { TokenListController } from '@metamask/assets-controllers';
|
||||
import { CHAIN_IDS } from '../../../shared/constants/network';
|
||||
import PreferencesController from './preferences';
|
||||
import NetworkController from './network';
|
||||
import { NetworkController } from './network';
|
||||
|
||||
describe('preferences controller', function () {
|
||||
let preferencesController;
|
||||
|
@ -52,7 +52,10 @@ import {
|
||||
determineTransactionType,
|
||||
isEIP1559Transaction,
|
||||
} from '../../../../shared/modules/transaction.utils';
|
||||
import { ORIGIN_METAMASK } from '../../../../shared/constants/app';
|
||||
import {
|
||||
ORIGIN_METAMASK,
|
||||
MESSAGE_TYPE,
|
||||
} from '../../../../shared/constants/app';
|
||||
import {
|
||||
calcGasTotal,
|
||||
getSwapsTokensReceivedFromTxMeta,
|
||||
@ -156,6 +159,7 @@ export default class TransactionController extends EventEmitter {
|
||||
this.getAccountType = opts.getAccountType;
|
||||
this.getTokenStandardAndDetails = opts.getTokenStandardAndDetails;
|
||||
this.securityProviderRequest = opts.securityProviderRequest;
|
||||
this.messagingSystem = opts.messenger;
|
||||
|
||||
this.memStore = new ObservableStore({});
|
||||
|
||||
@ -798,6 +802,7 @@ export default class TransactionController extends EventEmitter {
|
||||
this.txStateManager.getTransactionWithActionId(actionId);
|
||||
if (existingTxMeta) {
|
||||
this.emit('newUnapprovedTx', existingTxMeta);
|
||||
this._requestApproval(existingTxMeta);
|
||||
existingTxMeta = await this.addTransactionGasDefaults(existingTxMeta);
|
||||
return existingTxMeta;
|
||||
}
|
||||
@ -870,6 +875,7 @@ export default class TransactionController extends EventEmitter {
|
||||
|
||||
this.addTransaction(txMeta);
|
||||
this.emit('newUnapprovedTx', txMeta);
|
||||
this._requestApproval(txMeta);
|
||||
|
||||
txMeta = await this.addTransactionGasDefaults(txMeta);
|
||||
|
||||
@ -1355,6 +1361,7 @@ export default class TransactionController extends EventEmitter {
|
||||
try {
|
||||
// approve
|
||||
this.txStateManager.setTxStatusApproved(txId);
|
||||
this._acceptApproval(txMeta);
|
||||
// get next nonce
|
||||
const fromAddress = txMeta.txParams.from;
|
||||
// wait for a nonce
|
||||
@ -1734,6 +1741,7 @@ export default class TransactionController extends EventEmitter {
|
||||
async cancelTransaction(txId, actionId) {
|
||||
const txMeta = this.txStateManager.getTransaction(txId);
|
||||
this.txStateManager.setTxStatusRejected(txId);
|
||||
this._rejectApproval(txMeta);
|
||||
this._trackTransactionMetricsEvent(
|
||||
txMeta,
|
||||
TransactionMetaMetricsEvent.rejected,
|
||||
@ -2596,4 +2604,54 @@ export default class TransactionController extends EventEmitter {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
_requestApproval(txMeta) {
|
||||
const id = this._getApprovalId(txMeta);
|
||||
const { origin } = txMeta;
|
||||
const type = MESSAGE_TYPE.TRANSACTION;
|
||||
const requestData = { txId: txMeta.id };
|
||||
|
||||
this.messagingSystem
|
||||
.call(
|
||||
'ApprovalController:addRequest',
|
||||
{
|
||||
id,
|
||||
origin,
|
||||
type,
|
||||
requestData,
|
||||
},
|
||||
true,
|
||||
)
|
||||
.catch(() => {
|
||||
// Intentionally ignored as promise not currently used
|
||||
});
|
||||
}
|
||||
|
||||
_acceptApproval(txMeta) {
|
||||
const id = this._getApprovalId(txMeta);
|
||||
|
||||
try {
|
||||
this.messagingSystem.call('ApprovalController:acceptRequest', id);
|
||||
} catch (error) {
|
||||
log.error('Failed to accept transaction approval request', error);
|
||||
}
|
||||
}
|
||||
|
||||
_rejectApproval(txMeta) {
|
||||
const id = this._getApprovalId(txMeta);
|
||||
|
||||
try {
|
||||
this.messagingSystem.call(
|
||||
'ApprovalController:rejectRequest',
|
||||
id,
|
||||
new Error('Rejected'),
|
||||
);
|
||||
} catch (error) {
|
||||
log.error('Failed to reject transaction approval request', error);
|
||||
}
|
||||
}
|
||||
|
||||
_getApprovalId(txMeta) {
|
||||
return String(txMeta.id);
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,10 @@ import {
|
||||
GasRecommendations,
|
||||
} from '../../../../shared/constants/gas';
|
||||
import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller';
|
||||
import { ORIGIN_METAMASK } from '../../../../shared/constants/app';
|
||||
import {
|
||||
MESSAGE_TYPE,
|
||||
ORIGIN_METAMASK,
|
||||
} from '../../../../shared/constants/app';
|
||||
import { NetworkStatus } from '../../../../shared/constants/network';
|
||||
import { TRANSACTION_ENVELOPE_TYPE_NAMES } from '../../../../shared/lib/transactions-controller-utils';
|
||||
import TransactionController from '.';
|
||||
@ -52,7 +55,8 @@ describe('Transaction Controller', function () {
|
||||
fromAccount,
|
||||
fragmentExists,
|
||||
networkStatusStore,
|
||||
getCurrentChainId;
|
||||
getCurrentChainId,
|
||||
messengerMock;
|
||||
|
||||
beforeEach(function () {
|
||||
fragmentExists = false;
|
||||
@ -76,6 +80,7 @@ describe('Transaction Controller', function () {
|
||||
blockTrackerStub.getLatestBlock = noop;
|
||||
|
||||
getCurrentChainId = sinon.stub().callsFake(() => currentChainId);
|
||||
messengerMock = { call: sinon.stub().returns(Promise.resolve()) };
|
||||
|
||||
txController = new TransactionController({
|
||||
provider,
|
||||
@ -108,6 +113,7 @@ describe('Transaction Controller', function () {
|
||||
getAccountType: () => 'MetaMask',
|
||||
getDeviceModel: () => 'N/A',
|
||||
securityProviderRequest: () => undefined,
|
||||
messenger: messengerMock,
|
||||
});
|
||||
txController.nonceTracker.getNonceLock = () =>
|
||||
Promise.resolve({ nextNonce: 0, releaseLock: noop });
|
||||
@ -489,6 +495,67 @@ describe('Transaction Controller', function () {
|
||||
{ message: 'MetaMask is having trouble connecting to the network' },
|
||||
);
|
||||
});
|
||||
|
||||
it('should create an approval request', async function () {
|
||||
const txMeta = await txController.addUnapprovedTransaction(
|
||||
undefined,
|
||||
{
|
||||
from: selectedAddress,
|
||||
to: recipientAddress,
|
||||
},
|
||||
ORIGIN_METAMASK,
|
||||
);
|
||||
|
||||
assert.equal(messengerMock.call.callCount, 1);
|
||||
assert.deepEqual(messengerMock.call.getCall(0).args, [
|
||||
'ApprovalController:addRequest',
|
||||
{
|
||||
id: String(txMeta.id),
|
||||
origin: ORIGIN_METAMASK,
|
||||
requestData: { txId: txMeta.id },
|
||||
type: MESSAGE_TYPE.TRANSACTION,
|
||||
},
|
||||
true, // Show popup
|
||||
]);
|
||||
});
|
||||
|
||||
it('should still create an approval request when called twice with same actionId', async function () {
|
||||
await txController.addUnapprovedTransaction(
|
||||
undefined,
|
||||
{
|
||||
from: selectedAddress,
|
||||
to: recipientAddress,
|
||||
},
|
||||
ORIGIN_METAMASK,
|
||||
undefined,
|
||||
undefined,
|
||||
'12345',
|
||||
);
|
||||
|
||||
const secondTxMeta = await txController.addUnapprovedTransaction(
|
||||
undefined,
|
||||
{
|
||||
from: selectedAddress,
|
||||
to: recipientAddress,
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
'12345',
|
||||
);
|
||||
|
||||
assert.equal(messengerMock.call.callCount, 2);
|
||||
assert.deepEqual(messengerMock.call.getCall(1).args, [
|
||||
'ApprovalController:addRequest',
|
||||
{
|
||||
id: String(secondTxMeta.id),
|
||||
origin: ORIGIN_METAMASK,
|
||||
requestData: { txId: secondTxMeta.id },
|
||||
type: MESSAGE_TYPE.TRANSACTION,
|
||||
},
|
||||
true, // Show popup
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#createCancelTransaction', function () {
|
||||
@ -997,9 +1064,11 @@ describe('Transaction Controller', function () {
|
||||
});
|
||||
|
||||
describe('#approveTransaction', function () {
|
||||
it('does not overwrite set values', async function () {
|
||||
const originalValue = '0x01';
|
||||
const txMeta = {
|
||||
let originalValue, txMeta, signStub, pubStub;
|
||||
|
||||
beforeEach(function () {
|
||||
originalValue = '0x01';
|
||||
txMeta = {
|
||||
id: '1',
|
||||
status: TransactionStatus.unapproved,
|
||||
metamaskNetworkId: currentNetworkId,
|
||||
@ -1019,17 +1088,22 @@ describe('Transaction Controller', function () {
|
||||
providerResultStub.eth_gasPrice = wrongValue;
|
||||
providerResultStub.eth_estimateGas = '0x5209';
|
||||
|
||||
const signStub = sinon
|
||||
signStub = sinon
|
||||
.stub(txController, 'signTransaction')
|
||||
.callsFake(() => Promise.resolve());
|
||||
|
||||
const pubStub = sinon
|
||||
.stub(txController, 'publishTransaction')
|
||||
.callsFake(() => {
|
||||
txController.setTxHash('1', originalValue);
|
||||
txController.txStateManager.setTxStatusSubmitted('1');
|
||||
});
|
||||
pubStub = sinon.stub(txController, 'publishTransaction').callsFake(() => {
|
||||
txController.setTxHash('1', originalValue);
|
||||
txController.txStateManager.setTxStatusSubmitted('1');
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
signStub.restore();
|
||||
pubStub.restore();
|
||||
});
|
||||
|
||||
it('does not overwrite set values', async function () {
|
||||
await txController.approveTransaction(txMeta.id);
|
||||
const result = txController.txStateManager.getTransaction(txMeta.id);
|
||||
const params = result.txParams;
|
||||
@ -1042,8 +1116,21 @@ describe('Transaction Controller', function () {
|
||||
TransactionStatus.submitted,
|
||||
'should have reached the submitted status.',
|
||||
);
|
||||
signStub.restore();
|
||||
pubStub.restore();
|
||||
});
|
||||
|
||||
it('should accept the approval request', async function () {
|
||||
await txController.approveTransaction(txMeta.id);
|
||||
|
||||
assert.equal(messengerMock.call.callCount, 1);
|
||||
assert.deepEqual(messengerMock.call.getCall(0).args, [
|
||||
'ApprovalController:acceptRequest',
|
||||
txMeta.id,
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not throw if accepting approval request throws', async function () {
|
||||
messengerMock.call.throws();
|
||||
await txController.approveTransaction(txMeta.id);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1108,7 +1195,7 @@ describe('Transaction Controller', function () {
|
||||
});
|
||||
|
||||
describe('#cancelTransaction', function () {
|
||||
it('should emit a status change to rejected', function (done) {
|
||||
beforeEach(function () {
|
||||
txController.txStateManager._addTransactionsToState([
|
||||
{
|
||||
id: 0,
|
||||
@ -1181,7 +1268,9 @@ describe('Transaction Controller', function () {
|
||||
history: [{}],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should emit a status change to rejected', function (done) {
|
||||
txController.once('tx:status-update', (txId, status) => {
|
||||
try {
|
||||
assert.equal(
|
||||
@ -1198,6 +1287,22 @@ describe('Transaction Controller', function () {
|
||||
|
||||
txController.cancelTransaction(0);
|
||||
});
|
||||
|
||||
it('should reject the approval request', function () {
|
||||
txController.cancelTransaction(0);
|
||||
|
||||
assert.equal(messengerMock.call.callCount, 1);
|
||||
assert.deepEqual(messengerMock.call.getCall(0).args, [
|
||||
'ApprovalController:rejectRequest',
|
||||
'0',
|
||||
new Error('Rejected'),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not throw if rejecting approval request throws', async function () {
|
||||
messengerMock.call.throws();
|
||||
txController.cancelTransaction(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#createSpeedUpTransaction', function () {
|
||||
|
@ -143,8 +143,9 @@ import createTabIdMiddleware from './lib/createTabIdMiddleware';
|
||||
import createOnboardingMiddleware from './lib/createOnboardingMiddleware';
|
||||
import { setupMultiplex } from './lib/stream-utils';
|
||||
import EnsController from './controllers/ens';
|
||||
import NetworkController, {
|
||||
NetworkControllerEventTypes,
|
||||
import {
|
||||
NetworkController,
|
||||
NetworkControllerEventType,
|
||||
} from './controllers/network';
|
||||
import PreferencesController from './controllers/preferences';
|
||||
import AppStateController from './controllers/app-state';
|
||||
@ -263,7 +264,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
|
||||
const networkControllerMessenger = this.controllerMessenger.getRestricted({
|
||||
name: 'NetworkController',
|
||||
allowedEvents: Object.values(NetworkControllerEventTypes),
|
||||
allowedEvents: Object.values(NetworkControllerEventType),
|
||||
});
|
||||
this.networkController = new NetworkController({
|
||||
messenger: networkControllerMessenger,
|
||||
@ -310,11 +311,11 @@ export default class MetamaskController extends EventEmitter {
|
||||
initLangCode: opts.initLangCode,
|
||||
onInfuraIsBlocked: networkControllerMessenger.subscribe.bind(
|
||||
networkControllerMessenger,
|
||||
NetworkControllerEventTypes.InfuraIsBlocked,
|
||||
NetworkControllerEventType.InfuraIsBlocked,
|
||||
),
|
||||
onInfuraIsUnblocked: networkControllerMessenger.subscribe.bind(
|
||||
networkControllerMessenger,
|
||||
NetworkControllerEventTypes.InfuraIsUnblocked,
|
||||
NetworkControllerEventType.InfuraIsUnblocked,
|
||||
),
|
||||
tokenListController: this.tokenListController,
|
||||
provider: this.provider,
|
||||
@ -452,7 +453,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
preferencesStore: this.preferencesController.store,
|
||||
onNetworkDidChange: networkControllerMessenger.subscribe.bind(
|
||||
networkControllerMessenger,
|
||||
NetworkControllerEventTypes.NetworkDidChange,
|
||||
NetworkControllerEventType.NetworkDidChange,
|
||||
),
|
||||
getNetworkIdentifier: () => {
|
||||
const { type, rpcUrl } =
|
||||
@ -491,7 +492,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
// onNetworkDidChange
|
||||
onNetworkStateChange: networkControllerMessenger.subscribe.bind(
|
||||
networkControllerMessenger,
|
||||
NetworkControllerEventTypes.NetworkDidChange,
|
||||
NetworkControllerEventType.NetworkDidChange,
|
||||
),
|
||||
getCurrentNetworkEIP1559Compatibility:
|
||||
this.networkController.getEIP1559Compatibility.bind(
|
||||
@ -609,7 +610,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.networkController.store.getState().provider.chainId,
|
||||
onNetworkDidChange: networkControllerMessenger.subscribe.bind(
|
||||
networkControllerMessenger,
|
||||
NetworkControllerEventTypes.NetworkDidChange,
|
||||
NetworkControllerEventType.NetworkDidChange,
|
||||
),
|
||||
});
|
||||
|
||||
@ -621,7 +622,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
blockTracker: this.blockTracker,
|
||||
onNetworkDidChange: networkControllerMessenger.subscribe.bind(
|
||||
networkControllerMessenger,
|
||||
NetworkControllerEventTypes.NetworkDidChange,
|
||||
NetworkControllerEventType.NetworkDidChange,
|
||||
),
|
||||
getCurrentChainId: () =>
|
||||
this.networkController.store.getState().provider.chainId,
|
||||
@ -1007,8 +1008,15 @@ export default class MetamaskController extends EventEmitter {
|
||||
getDeviceModel: this.getDeviceModel.bind(this),
|
||||
getTokenStandardAndDetails: this.getTokenStandardAndDetails.bind(this),
|
||||
securityProviderRequest: this.securityProviderRequest.bind(this),
|
||||
messenger: this.controllerMessenger.getRestricted({
|
||||
name: 'TransactionController',
|
||||
allowedActions: [
|
||||
`${this.approvalController.name}:addRequest`,
|
||||
`${this.approvalController.name}:acceptRequest`,
|
||||
`${this.approvalController.name}:rejectRequest`,
|
||||
],
|
||||
}),
|
||||
});
|
||||
this.txController.on('newUnapprovedTx', () => opts.showUserConfirmation());
|
||||
|
||||
this.txController.on(`tx:status-update`, async (txId, status) => {
|
||||
if (
|
||||
@ -1099,7 +1107,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
});
|
||||
|
||||
networkControllerMessenger.subscribe(
|
||||
NetworkControllerEventTypes.NetworkDidChange,
|
||||
NetworkControllerEventType.NetworkDidChange,
|
||||
async () => {
|
||||
const { ticker } = this.networkController.store.getState().provider;
|
||||
try {
|
||||
@ -1145,7 +1153,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
networkController: this.networkController,
|
||||
onNetworkDidChange: networkControllerMessenger.subscribe.bind(
|
||||
networkControllerMessenger,
|
||||
NetworkControllerEventTypes.NetworkDidChange,
|
||||
NetworkControllerEventType.NetworkDidChange,
|
||||
),
|
||||
provider: this.provider,
|
||||
getProviderConfig: () => this.networkController.store.getState().provider,
|
||||
@ -1188,7 +1196,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
|
||||
// ensure accountTracker updates balances after network change
|
||||
networkControllerMessenger.subscribe(
|
||||
NetworkControllerEventTypes.NetworkDidChange,
|
||||
NetworkControllerEventType.NetworkDidChange,
|
||||
() => {
|
||||
this.accountTracker._updateAccounts();
|
||||
},
|
||||
@ -1196,7 +1204,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
|
||||
// clear unapproved transactions and messages when the network will change
|
||||
networkControllerMessenger.subscribe(
|
||||
NetworkControllerEventTypes.NetworkWillChange,
|
||||
NetworkControllerEventType.NetworkWillChange,
|
||||
() => {
|
||||
this.txController.txStateManager.clearUnapprovedTxs();
|
||||
this.encryptionPublicKeyManager.clearUnapproved();
|
||||
|
254
app/scripts/migrations/084.test.js
Normal file
254
app/scripts/migrations/084.test.js
Normal file
@ -0,0 +1,254 @@
|
||||
import { v4 } from 'uuid';
|
||||
import { migrate, version } from './084';
|
||||
|
||||
jest.mock('uuid', () => {
|
||||
const actual = jest.requireActual('uuid');
|
||||
|
||||
return {
|
||||
...actual,
|
||||
v4: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('migration #84', () => {
|
||||
beforeEach(() => {
|
||||
v4.mockImplementationOnce(() => 'network-configuration-id-1')
|
||||
.mockImplementationOnce(() => 'network-configuration-id-2')
|
||||
.mockImplementationOnce(() => 'network-configuration-id-3')
|
||||
.mockImplementationOnce(() => 'network-configuration-id-4');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
it('should update the version metadata', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 83,
|
||||
},
|
||||
data: {},
|
||||
};
|
||||
|
||||
const newStorage = await migrate(oldStorage);
|
||||
expect(newStorage.meta).toStrictEqual({
|
||||
version,
|
||||
});
|
||||
});
|
||||
|
||||
it('should use the key of the networkConfigurations object to set the id of each network configuration', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version,
|
||||
},
|
||||
data: {
|
||||
NetworkController: {
|
||||
networkConfigurations: {
|
||||
'network-configuration-id-1': {
|
||||
chainId: '0x539',
|
||||
nickname: 'Localhost 8545',
|
||||
rpcPrefs: {},
|
||||
rpcUrl: 'http://localhost:8545',
|
||||
ticker: 'ETH',
|
||||
},
|
||||
'network-configuration-id-2': {
|
||||
chainId: '0xa4b1',
|
||||
nickname: 'Arbitrum One',
|
||||
rpcPrefs: {
|
||||
blockExplorerUrl: 'https://explorer.arbitrum.io',
|
||||
},
|
||||
rpcUrl:
|
||||
'https://arbitrum-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748',
|
||||
ticker: 'ETH',
|
||||
},
|
||||
'network-configuration-id-3': {
|
||||
chainId: '0x4e454152',
|
||||
nickname: 'Aurora Mainnet',
|
||||
rpcPrefs: {
|
||||
blockExplorerUrl: 'https://aurorascan.dev/',
|
||||
},
|
||||
rpcUrl:
|
||||
'https://aurora-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748',
|
||||
ticker: 'Aurora ETH',
|
||||
},
|
||||
'network-configuration-id-4': {
|
||||
chainId: '0x38',
|
||||
nickname:
|
||||
'BNB Smart Chain (previously Binance Smart Chain Mainnet)',
|
||||
rpcPrefs: {
|
||||
blockExplorerUrl: 'https://bscscan.com/',
|
||||
},
|
||||
rpcUrl: 'https://bsc-dataseed.binance.org/',
|
||||
ticker: 'BNB',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const newStorage = await migrate(oldStorage);
|
||||
|
||||
const expectedNewStorage = {
|
||||
meta: {
|
||||
version,
|
||||
},
|
||||
data: {
|
||||
NetworkController: {
|
||||
networkConfigurations: {
|
||||
'network-configuration-id-1': {
|
||||
chainId: '0x539',
|
||||
nickname: 'Localhost 8545',
|
||||
rpcPrefs: {},
|
||||
rpcUrl: 'http://localhost:8545',
|
||||
ticker: 'ETH',
|
||||
id: 'network-configuration-id-1',
|
||||
},
|
||||
'network-configuration-id-2': {
|
||||
chainId: '0xa4b1',
|
||||
nickname: 'Arbitrum One',
|
||||
rpcPrefs: {
|
||||
blockExplorerUrl: 'https://explorer.arbitrum.io',
|
||||
},
|
||||
rpcUrl:
|
||||
'https://arbitrum-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748',
|
||||
ticker: 'ETH',
|
||||
id: 'network-configuration-id-2',
|
||||
},
|
||||
'network-configuration-id-3': {
|
||||
chainId: '0x4e454152',
|
||||
nickname: 'Aurora Mainnet',
|
||||
rpcPrefs: {
|
||||
blockExplorerUrl: 'https://aurorascan.dev/',
|
||||
},
|
||||
rpcUrl:
|
||||
'https://aurora-mainnet.infura.io/v3/373266a93aab4acda48f89d4fe77c748',
|
||||
ticker: 'Aurora ETH',
|
||||
id: 'network-configuration-id-3',
|
||||
},
|
||||
'network-configuration-id-4': {
|
||||
chainId: '0x38',
|
||||
nickname:
|
||||
'BNB Smart Chain (previously Binance Smart Chain Mainnet)',
|
||||
rpcPrefs: {
|
||||
blockExplorerUrl: 'https://bscscan.com/',
|
||||
},
|
||||
rpcUrl: 'https://bsc-dataseed.binance.org/',
|
||||
ticker: 'BNB',
|
||||
id: 'network-configuration-id-4',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(newStorage).toStrictEqual(expectedNewStorage);
|
||||
});
|
||||
|
||||
it('should not modify state if state.NetworkController is undefined', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version,
|
||||
},
|
||||
data: {
|
||||
testProperty: 'testValue',
|
||||
},
|
||||
};
|
||||
|
||||
const newStorage = await migrate(oldStorage);
|
||||
|
||||
const expectedNewStorage = {
|
||||
meta: {
|
||||
version,
|
||||
},
|
||||
data: {
|
||||
testProperty: 'testValue',
|
||||
},
|
||||
};
|
||||
expect(newStorage).toStrictEqual(expectedNewStorage);
|
||||
});
|
||||
|
||||
it('should not modify state if state.NetworkController is not an object', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version,
|
||||
},
|
||||
data: {
|
||||
NetworkController: false,
|
||||
testProperty: 'testValue',
|
||||
},
|
||||
};
|
||||
|
||||
const newStorage = await migrate(oldStorage);
|
||||
|
||||
const expectedNewStorage = {
|
||||
meta: {
|
||||
version,
|
||||
},
|
||||
data: {
|
||||
NetworkController: false,
|
||||
testProperty: 'testValue',
|
||||
},
|
||||
};
|
||||
expect(newStorage).toStrictEqual(expectedNewStorage);
|
||||
});
|
||||
|
||||
it('should not modify state if state.NetworkController.networkConfigurations is undefined', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version,
|
||||
},
|
||||
data: {
|
||||
NetworkController: {
|
||||
testNetworkControllerProperty: 'testNetworkControllerValue',
|
||||
networkConfigurations: undefined,
|
||||
},
|
||||
testProperty: 'testValue',
|
||||
},
|
||||
};
|
||||
|
||||
const newStorage = await migrate(oldStorage);
|
||||
|
||||
const expectedNewStorage = {
|
||||
meta: {
|
||||
version,
|
||||
},
|
||||
data: {
|
||||
NetworkController: {
|
||||
testNetworkControllerProperty: 'testNetworkControllerValue',
|
||||
networkConfigurations: undefined,
|
||||
},
|
||||
testProperty: 'testValue',
|
||||
},
|
||||
};
|
||||
expect(newStorage).toStrictEqual(expectedNewStorage);
|
||||
});
|
||||
|
||||
it('should not modify state if state.NetworkController.networkConfigurations is an empty object', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version,
|
||||
},
|
||||
data: {
|
||||
NetworkController: {
|
||||
testNetworkControllerProperty: 'testNetworkControllerValue',
|
||||
networkConfigurations: {},
|
||||
},
|
||||
testProperty: 'testValue',
|
||||
},
|
||||
};
|
||||
|
||||
const newStorage = await migrate(oldStorage);
|
||||
|
||||
const expectedNewStorage = {
|
||||
meta: {
|
||||
version,
|
||||
},
|
||||
data: {
|
||||
NetworkController: {
|
||||
testNetworkControllerProperty: 'testNetworkControllerValue',
|
||||
networkConfigurations: {},
|
||||
},
|
||||
testProperty: 'testValue',
|
||||
},
|
||||
};
|
||||
expect(newStorage).toStrictEqual(expectedNewStorage);
|
||||
});
|
||||
});
|
58
app/scripts/migrations/084.ts
Normal file
58
app/scripts/migrations/084.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { isObject } from '@metamask/utils';
|
||||
|
||||
export const version = 84;
|
||||
|
||||
/**
|
||||
* Ensure that each networkConfigurations object in state.NetworkController.networkConfigurations has an
|
||||
* `id` property which matches the key pointing that object
|
||||
*
|
||||
* @param originalVersionedData - Versioned MetaMask extension state, exactly what we persist to dist.
|
||||
* @param originalVersionedData.meta - State metadata.
|
||||
* @param originalVersionedData.meta.version - The current state version.
|
||||
* @param originalVersionedData.data - The persisted MetaMask state, keyed by controller.
|
||||
* @returns Updated versioned MetaMask extension state.
|
||||
*/
|
||||
export async function migrate(originalVersionedData: {
|
||||
meta: { version: number };
|
||||
data: Record<string, unknown>;
|
||||
}) {
|
||||
const versionedData = cloneDeep(originalVersionedData);
|
||||
versionedData.meta.version = version;
|
||||
versionedData.data = transformState(versionedData.data);
|
||||
return versionedData;
|
||||
}
|
||||
|
||||
function transformState(state: Record<string, unknown>) {
|
||||
if (!isObject(state.NetworkController)) {
|
||||
return state;
|
||||
}
|
||||
const { NetworkController } = state;
|
||||
|
||||
if (!isObject(NetworkController.networkConfigurations)) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const { networkConfigurations } = NetworkController;
|
||||
|
||||
const newNetworkConfigurations: Record<string, Record<string, unknown>> = {};
|
||||
|
||||
for (const networkConfigurationId of Object.keys(networkConfigurations)) {
|
||||
const networkConfiguration = networkConfigurations[networkConfigurationId];
|
||||
if (!isObject(networkConfiguration)) {
|
||||
return state;
|
||||
}
|
||||
newNetworkConfigurations[networkConfigurationId] = {
|
||||
...networkConfiguration,
|
||||
id: networkConfigurationId,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
NetworkController: {
|
||||
...NetworkController,
|
||||
networkConfigurations: newNetworkConfigurations,
|
||||
},
|
||||
};
|
||||
}
|
@ -87,6 +87,7 @@ import m080 from './080';
|
||||
import * as m081 from './081';
|
||||
import * as m082 from './082';
|
||||
import * as m083 from './083';
|
||||
import * as m084 from './084';
|
||||
|
||||
const migrations = [
|
||||
m002,
|
||||
@ -171,6 +172,7 @@ const migrations = [
|
||||
m081,
|
||||
m082,
|
||||
m083,
|
||||
m084,
|
||||
];
|
||||
|
||||
export default migrations;
|
||||
|
@ -51,7 +51,7 @@ module.exports = {
|
||||
'<rootDir>/ui/**/*.test.(js|ts|tsx)',
|
||||
'<rootDir>/development/fitness-functions/**/*.test.(js|ts|tsx)',
|
||||
],
|
||||
testTimeout: 2500,
|
||||
testTimeout: 5500,
|
||||
// We have to specify the environment we are running in, which is jsdom. The
|
||||
// default is 'node'. This can be modified *per file* using a comment at the
|
||||
// head of the file. So it may be worthwhile to switch to 'node' in any
|
||||
|
@ -1193,7 +1193,7 @@
|
||||
"setInterval": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/gas-fee-controller>@metamask/base-controller": true,
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/gas-fee-controller>@metamask/controller-utils": true,
|
||||
"eth-query": true,
|
||||
"ethereumjs-util": true,
|
||||
@ -1201,11 +1201,6 @@
|
||||
"uuid": true
|
||||
}
|
||||
},
|
||||
"@metamask/gas-fee-controller>@metamask/base-controller": {
|
||||
"packages": {
|
||||
"immer": true
|
||||
}
|
||||
},
|
||||
"@metamask/gas-fee-controller>@metamask/controller-utils": {
|
||||
"globals": {
|
||||
"console.error": true,
|
||||
|
@ -1265,7 +1265,7 @@
|
||||
"setInterval": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/gas-fee-controller>@metamask/base-controller": true,
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/gas-fee-controller>@metamask/controller-utils": true,
|
||||
"eth-query": true,
|
||||
"ethereumjs-util": true,
|
||||
@ -1273,11 +1273,6 @@
|
||||
"uuid": true
|
||||
}
|
||||
},
|
||||
"@metamask/gas-fee-controller>@metamask/base-controller": {
|
||||
"packages": {
|
||||
"immer": true
|
||||
}
|
||||
},
|
||||
"@metamask/gas-fee-controller>@metamask/controller-utils": {
|
||||
"globals": {
|
||||
"console.error": true,
|
||||
|
@ -1265,7 +1265,7 @@
|
||||
"setInterval": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/gas-fee-controller>@metamask/base-controller": true,
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/gas-fee-controller>@metamask/controller-utils": true,
|
||||
"eth-query": true,
|
||||
"ethereumjs-util": true,
|
||||
@ -1273,11 +1273,6 @@
|
||||
"uuid": true
|
||||
}
|
||||
},
|
||||
"@metamask/gas-fee-controller>@metamask/base-controller": {
|
||||
"packages": {
|
||||
"immer": true
|
||||
}
|
||||
},
|
||||
"@metamask/gas-fee-controller>@metamask/controller-utils": {
|
||||
"globals": {
|
||||
"console.error": true,
|
||||
|
@ -1193,7 +1193,7 @@
|
||||
"setInterval": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/gas-fee-controller>@metamask/base-controller": true,
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/gas-fee-controller>@metamask/controller-utils": true,
|
||||
"eth-query": true,
|
||||
"ethereumjs-util": true,
|
||||
@ -1201,11 +1201,6 @@
|
||||
"uuid": true
|
||||
}
|
||||
},
|
||||
"@metamask/gas-fee-controller>@metamask/base-controller": {
|
||||
"packages": {
|
||||
"immer": true
|
||||
}
|
||||
},
|
||||
"@metamask/gas-fee-controller>@metamask/controller-utils": {
|
||||
"globals": {
|
||||
"console.error": true,
|
||||
|
@ -242,14 +242,14 @@
|
||||
"@metamask/eth-ledger-bridge-keyring": "^0.13.0",
|
||||
"@metamask/eth-token-tracker": "^4.0.0",
|
||||
"@metamask/etherscan-link": "^2.2.0",
|
||||
"@metamask/gas-fee-controller": "^1.0.0",
|
||||
"@metamask/gas-fee-controller": "^3.0.0",
|
||||
"@metamask/jazzicon": "^2.0.0",
|
||||
"@metamask/key-tree": "^7.0.0",
|
||||
"@metamask/logo": "^3.1.1",
|
||||
"@metamask/message-manager": "^2.1.0",
|
||||
"@metamask/metamask-eth-abis": "^3.0.0",
|
||||
"@metamask/notification-controller": "^1.0.0",
|
||||
"@metamask/obs-store": "^8.0.0",
|
||||
"@metamask/obs-store": "^8.1.0",
|
||||
"@metamask/permission-controller": "^3.1.0",
|
||||
"@metamask/phishing-controller": "^2.0.0",
|
||||
"@metamask/post-message-stream": "^6.0.0",
|
||||
|
@ -53,6 +53,7 @@ export const MESSAGE_TYPE = {
|
||||
PERSONAL_SIGN: 'personal_sign',
|
||||
SEND_METADATA: 'metamask_sendDomainMetadata',
|
||||
SWITCH_ETHEREUM_CHAIN: 'wallet_switchEthereumChain',
|
||||
TRANSACTION: 'transaction',
|
||||
WALLET_REQUEST_PERMISSIONS: 'wallet_requestPermissions',
|
||||
WATCH_ASSET: 'wallet_watchAsset',
|
||||
WATCH_ASSET_LEGACY: 'metamask_watchAsset',
|
||||
|
@ -238,7 +238,7 @@ export const INFURA_PROVIDER_TYPES = [
|
||||
NETWORK_TYPES.MAINNET,
|
||||
NETWORK_TYPES.GOERLI,
|
||||
NETWORK_TYPES.SEPOLIA,
|
||||
];
|
||||
] as const;
|
||||
|
||||
export const TEST_CHAINS = [
|
||||
CHAIN_IDS.GOERLI,
|
||||
|
@ -1535,7 +1535,18 @@
|
||||
"origin": "tmashuang.github.io"
|
||||
}
|
||||
],
|
||||
"desktopEnabled": false
|
||||
"desktopEnabled": false,
|
||||
"pendingApprovals": {
|
||||
"testApprovalId": {
|
||||
"id": "testApprovalId",
|
||||
"time": 1528133319641,
|
||||
"origin": "metamask",
|
||||
"type": "transaction",
|
||||
"requestData": { "txId": "testTransactionId" },
|
||||
"requestState": { "test": "value" }
|
||||
}
|
||||
},
|
||||
"pendingApprovalCount": 1
|
||||
},
|
||||
"send": {
|
||||
"amountMode": "INPUT",
|
||||
|
@ -62,9 +62,7 @@ describe('ERC721 NFTs testdapp interaction', function () {
|
||||
);
|
||||
|
||||
// Verify transaction
|
||||
const completedTx = await driver.findElement('.list-item__title');
|
||||
const completedTxText = await completedTx.getText();
|
||||
assert.equal(completedTxText, 'Send Token');
|
||||
await driver.findElement({ text: 'Send TDC' });
|
||||
},
|
||||
);
|
||||
});
|
||||
|
@ -119,16 +119,16 @@ describe('Create token, approve token and approve token without gas', function (
|
||||
);
|
||||
|
||||
await driver.clickElement({
|
||||
text: 'Verify contract details',
|
||||
text: 'Verify third-party details',
|
||||
css: '.token-allowance-container__verify-link',
|
||||
});
|
||||
|
||||
const modalTitle = await driver.waitForSelector({
|
||||
text: 'Contract details',
|
||||
text: 'Third-party details',
|
||||
tag: 'h5',
|
||||
});
|
||||
|
||||
assert.equal(await modalTitle.getText(), 'Contract details');
|
||||
assert.equal(await modalTitle.getText(), 'Third-party details');
|
||||
|
||||
await driver.clickElement({
|
||||
text: 'Got it',
|
||||
|
@ -60,7 +60,7 @@ describe('Sign Typed Data V4 Signature Request', function () {
|
||||
assert.equal(await origin.getText(), 'http://127.0.0.1:8080');
|
||||
|
||||
verifyContractDetailsButton.click();
|
||||
await driver.findElement({ text: 'Contract details', tag: 'h5' });
|
||||
await driver.findElement({ text: 'Third-party details', tag: 'h5' });
|
||||
await driver.findElement('[data-testid="recipient"]');
|
||||
await driver.clickElement({ text: 'Got it', tag: 'button' });
|
||||
|
||||
@ -142,7 +142,7 @@ describe('Sign Typed Data V3 Signature Request', function () {
|
||||
assert.equal(await origin.getText(), 'http://127.0.0.1:8080');
|
||||
|
||||
verifyContractDetailsButton.click();
|
||||
await driver.findElement({ text: 'Contract details', tag: 'h5' });
|
||||
await driver.findElement({ text: 'Third-party details', tag: 'h5' });
|
||||
await driver.findElement('[data-testid="recipient"]');
|
||||
await driver.clickElement({ text: 'Got it', tag: 'button' });
|
||||
|
||||
|
50
types/eth-query.d.ts
vendored
Normal file
50
types/eth-query.d.ts
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
declare module 'eth-query' {
|
||||
// What it says on the tin. We omit `null` because confusingly, this is used
|
||||
// for a successful response to indicate a lack of an error.
|
||||
type EverythingButNull =
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| object
|
||||
| symbol
|
||||
| undefined;
|
||||
|
||||
type ProviderSendAsyncResponse<Result> = {
|
||||
error?: { message: string };
|
||||
result?: Result;
|
||||
};
|
||||
|
||||
type ProviderSendAsyncCallback<Result> = (
|
||||
error: unknown,
|
||||
response: ProviderSendAsyncResponse<Result>,
|
||||
) => void;
|
||||
|
||||
type Provider = {
|
||||
sendAsync<Params, Result>(
|
||||
payload: SendAsyncPayload<Params>,
|
||||
callback: ProviderSendAsyncCallback<Result>,
|
||||
): void;
|
||||
};
|
||||
|
||||
type SendAsyncPayload<Params> = {
|
||||
id: number;
|
||||
jsonrpc: '2.0';
|
||||
method: string;
|
||||
params: Params;
|
||||
};
|
||||
|
||||
type SendAsyncCallback<Result> = (
|
||||
...args:
|
||||
| [error: EverythingButNull, result: undefined]
|
||||
| [error: null, result: Result]
|
||||
) => void;
|
||||
|
||||
export default class EthQuery {
|
||||
constructor(provider: Provider);
|
||||
|
||||
sendAsync<Params, Result>(
|
||||
opts: Partial<SendAsyncPayload<Params>>,
|
||||
callback: SendAsyncCallback<Result>,
|
||||
): void;
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { object } from '@storybook/addon-knobs';
|
||||
import { panel, text, heading, divider, copyable } from '@metamask/snaps-ui';
|
||||
import configureStore from '../../../../store/store';
|
||||
import testData from '../../../../../.storybook/test-data';
|
||||
@ -10,8 +9,13 @@ const store = configureStore(testData);
|
||||
|
||||
export default {
|
||||
title: 'Components/App/SnapUIRenderer',
|
||||
|
||||
component: SnapUIRenderer,
|
||||
decorators: [(story) => <Provider store={store}>{story()}</Provider>],
|
||||
argTypes: {
|
||||
data: {
|
||||
control: 'object',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const DATA = panel([
|
||||
@ -22,13 +26,18 @@ const DATA = panel([
|
||||
copyable('Text you can copy'),
|
||||
]);
|
||||
|
||||
export const DefaultStory = () => (
|
||||
<SnapUIRenderer
|
||||
snapId="local:http://localhost:8080/"
|
||||
data={object('data', DATA)}
|
||||
/>
|
||||
export const DefaultStory = (args) => (
|
||||
<SnapUIRenderer snapId="local:http://localhost:8080/" data={args.data} />
|
||||
);
|
||||
|
||||
export const ErrorStory = () => (
|
||||
<SnapUIRenderer snapId="local:http://localhost:8080/" data="foo" />
|
||||
DefaultStory.args = {
|
||||
data: DATA,
|
||||
};
|
||||
|
||||
export const ErrorStory = (args) => (
|
||||
<SnapUIRenderer snapId="local:http://localhost:8080/" data={args.data} />
|
||||
);
|
||||
|
||||
ErrorStory.args = {
|
||||
data: 'foo',
|
||||
};
|
||||
|
@ -170,8 +170,9 @@ export default function HoldToRevealButton({ buttonText, onLongPressed }) {
|
||||
onMouseDown={onMouseDown}
|
||||
onMouseUp={onMouseUp}
|
||||
className="hold-to-reveal-button__button-hold"
|
||||
textProps={{ display: DISPLAY.FLEX, alignItems: AlignItems.center }}
|
||||
>
|
||||
<Box className="hold-to-reveal-button__icon-container">
|
||||
<Box className="hold-to-reveal-button__icon-container" marginRight={2}>
|
||||
{renderPreCompleteContent()}
|
||||
{renderPostCompleteContent()}
|
||||
</Box>
|
||||
|
@ -224,7 +224,7 @@ exports[`Signature Request Component render should match snapshot when we are us
|
||||
<h6
|
||||
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography typography--h7 typography--weight-normal typography--style-normal typography--color-primary-default"
|
||||
>
|
||||
Verify contract details
|
||||
Verify third-party details
|
||||
</h6>
|
||||
</a>
|
||||
</div>
|
||||
@ -999,7 +999,7 @@ exports[`Signature Request Component render should match snapshot when we want t
|
||||
<h6
|
||||
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography typography--h7 typography--weight-normal typography--style-normal typography--color-primary-default"
|
||||
>
|
||||
Verify contract details
|
||||
Verify third-party details
|
||||
</h6>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -152,7 +152,7 @@ export function useTransactionDisplayData(transactionGroup) {
|
||||
async function getAndSetAssetDetails() {
|
||||
if (isTokenCategory && !token) {
|
||||
const assetDetails = await getAssetDetails(
|
||||
recipientAddress,
|
||||
to,
|
||||
senderAddress,
|
||||
initialTransaction?.txParams?.data,
|
||||
knownNfts,
|
||||
@ -168,6 +168,7 @@ export function useTransactionDisplayData(transactionGroup) {
|
||||
senderAddress,
|
||||
initialTransaction?.txParams?.data,
|
||||
knownNfts,
|
||||
to,
|
||||
]);
|
||||
if (currentAssetDetails) {
|
||||
token = {
|
||||
|
@ -57,7 +57,7 @@ exports[`ConfirmApproveContent Component should render Confirm approve page corr
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Verify contract details
|
||||
Verify third-party details
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
@ -244,7 +244,7 @@ exports[`ConfirmApproveContent Component should render Confirm approve page corr
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Verify contract details
|
||||
Verify third-party details
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
@ -431,7 +431,7 @@ exports[`ConfirmApproveContent Component should render Confirm approve page corr
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Verify contract details
|
||||
Verify third-party details
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
@ -618,7 +618,7 @@ exports[`ConfirmApproveContent Component should render Confirm approve page corr
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Verify contract details
|
||||
Verify third-party details
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
|
@ -56,7 +56,7 @@ describe('ConfirmApproveContent Component', () => {
|
||||
'This allows a third party to access and transfer the following NFTs without further notice until you revoke its access.',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
expect(queryByText('Verify contract details')).toBeInTheDocument();
|
||||
expect(queryByText('Verify third-party details')).toBeInTheDocument();
|
||||
expect(
|
||||
queryByText(
|
||||
'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.',
|
||||
@ -119,7 +119,7 @@ describe('ConfirmApproveContent Component', () => {
|
||||
'This allows a third party to access and transfer the following NFTs without further notice until you revoke its access.',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
expect(queryByText('Verify contract details')).toBeInTheDocument();
|
||||
expect(queryByText('Verify third-party details')).toBeInTheDocument();
|
||||
expect(
|
||||
queryByText(
|
||||
'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.',
|
||||
@ -181,7 +181,7 @@ describe('ConfirmApproveContent Component', () => {
|
||||
'This allows a third party to access and transfer the following NFTs without further notice until you revoke its access.',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
expect(queryByText('Verify contract details')).toBeInTheDocument();
|
||||
expect(queryByText('Verify third-party details')).toBeInTheDocument();
|
||||
expect(
|
||||
queryByText(
|
||||
'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.',
|
||||
@ -239,7 +239,7 @@ describe('ConfirmApproveContent Component', () => {
|
||||
'This allows a third party to access and transfer the following NFTs without further notice until you revoke its access.',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
expect(queryByText('Verify contract details')).toBeInTheDocument();
|
||||
expect(queryByText('Verify third-party details')).toBeInTheDocument();
|
||||
expect(
|
||||
queryByText(
|
||||
'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.',
|
||||
|
@ -223,7 +223,7 @@ exports[`Signature Request Component render should match snapshot 1`] = `
|
||||
<h6
|
||||
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography typography--h7 typography--weight-normal typography--style-normal typography--color-primary-default"
|
||||
>
|
||||
Verify contract details
|
||||
Verify third-party details
|
||||
</h6>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -5,10 +5,10 @@ import UrlIcon from '../../../components/ui/url-icon';
|
||||
import Popover from '../../../components/ui/popover';
|
||||
import Button from '../../../components/ui/button';
|
||||
import Box from '../../../components/ui/box';
|
||||
import Typography from '../../../components/ui/typography';
|
||||
import { Text } from '../../../components/component-library';
|
||||
import ActionableMessage from '../../../components/ui/actionable-message/actionable-message';
|
||||
import {
|
||||
TypographyVariant,
|
||||
TextVariant,
|
||||
FONT_WEIGHT,
|
||||
AlignItems,
|
||||
DISPLAY,
|
||||
@ -62,21 +62,26 @@ export default function ImportToken({
|
||||
fallbackClassName="import-token__token-icon"
|
||||
name={tokenForImport.symbol}
|
||||
/>
|
||||
<Typography
|
||||
ariant={TypographyVariant.H4}
|
||||
<Text
|
||||
variant={TextVariant.headingSm}
|
||||
as="h4"
|
||||
fontWeight={FONT_WEIGHT.BOLD}
|
||||
boxProps={{ marginTop: 2, marginBottom: 3 }}
|
||||
marginTop={2}
|
||||
marginBottom={3}
|
||||
>
|
||||
{tokenForImport.name || ''}
|
||||
</Typography>
|
||||
<Typography variant={TypographyVariant.H6}>{t('contract')}:</Typography>
|
||||
<Typography
|
||||
</Text>
|
||||
<Text variant={TextVariant.bodySm} as="h6">
|
||||
{t('contract')}:
|
||||
</Text>
|
||||
<Text
|
||||
variant={TextVariant.bodySm}
|
||||
className="import-token__contract-address"
|
||||
variant={TypographyVariant.H7}
|
||||
boxProps={{ marginBottom: 6 }}
|
||||
as="h6"
|
||||
marginBottom={6}
|
||||
>
|
||||
{tokenForImport.address || ''}
|
||||
</Typography>
|
||||
</Text>
|
||||
</Box>
|
||||
</Popover>
|
||||
);
|
||||
|
@ -14,6 +14,7 @@
|
||||
border-radius: 8px;
|
||||
background-color: var(--color-background-alternative);
|
||||
padding: 5px 10px;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
&__token-icon {
|
||||
|
@ -16,8 +16,8 @@
|
||||
|
||||
a.token-allowance-container__verify-link {
|
||||
width: fit-content;
|
||||
margin-inline-start: 96px;
|
||||
margin-inline-end: 96px;
|
||||
margin-inline-start: auto;
|
||||
margin-inline-end: auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
@ -237,16 +237,16 @@ describe('TokenAllowancePage', () => {
|
||||
expect(getByText('Set a spending cap for your')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should click Verify contract details and show popup Contract details, then close popup', () => {
|
||||
it('should click Verify third-party details and show popup Third-party details, then close popup', () => {
|
||||
const { getByText } = renderWithProvider(
|
||||
<TokenAllowance {...props} />,
|
||||
store,
|
||||
);
|
||||
|
||||
const verifyContractDetails = getByText('Verify contract details');
|
||||
fireEvent.click(verifyContractDetails);
|
||||
const verifyThirdPartyDetails = getByText('Verify third-party details');
|
||||
fireEvent.click(verifyThirdPartyDetails);
|
||||
|
||||
expect(getByText('Contract details')).toBeInTheDocument();
|
||||
expect(getByText('Third-party details')).toBeInTheDocument();
|
||||
|
||||
const gotIt = getByText('Got it');
|
||||
fireEvent.click(gotIt);
|
||||
|
@ -484,21 +484,14 @@ export function getCurrentCurrency(state) {
|
||||
|
||||
export function getTotalUnapprovedCount(state) {
|
||||
const {
|
||||
unapprovedMsgCount = 0,
|
||||
unapprovedPersonalMsgCount = 0,
|
||||
unapprovedDecryptMsgCount = 0,
|
||||
unapprovedEncryptionPublicKeyMsgCount = 0,
|
||||
unapprovedTypedMessagesCount = 0,
|
||||
pendingApprovalCount = 0,
|
||||
} = state.metamask;
|
||||
|
||||
return (
|
||||
unapprovedMsgCount +
|
||||
unapprovedPersonalMsgCount +
|
||||
unapprovedDecryptMsgCount +
|
||||
unapprovedEncryptionPublicKeyMsgCount +
|
||||
unapprovedTypedMessagesCount +
|
||||
getUnapprovedTxCount(state) +
|
||||
pendingApprovalCount +
|
||||
getSuggestedAssetCount(state)
|
||||
);
|
||||
|
66
yarn.lock
66
yarn.lock
@ -3984,13 +3984,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@metamask/gas-fee-controller@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "@metamask/gas-fee-controller@npm:1.0.0"
|
||||
"@metamask/gas-fee-controller@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "@metamask/gas-fee-controller@npm:3.0.0"
|
||||
dependencies:
|
||||
"@metamask/base-controller": ~1.0.0
|
||||
"@metamask/controller-utils": ~1.0.0
|
||||
"@metamask/network-controller": ~1.0.0
|
||||
"@metamask/base-controller": ^1.1.2
|
||||
"@metamask/controller-utils": ^2.0.0
|
||||
"@metamask/network-controller": ^3.0.0
|
||||
"@types/uuid": ^8.3.0
|
||||
babel-runtime: ^6.26.0
|
||||
eth-query: ^2.1.2
|
||||
@ -3998,7 +3998,9 @@ __metadata:
|
||||
ethjs-unit: ^0.1.6
|
||||
immer: ^9.0.6
|
||||
uuid: ^8.3.2
|
||||
checksum: fef5255532a6cd5325ddfbbfec11140e6629c011a8cc6b126672ef7a6e93a327d059935cdc6fc7089562f3277fb70541b5ea54cd31c0e5b350ceebbe73d5d59f
|
||||
peerDependencies:
|
||||
"@metamask/network-controller": ^3.0.0
|
||||
checksum: 8cdd43a265094dd5e41f0094c278cde351d290446711e6b39de26f842faa993c050e5506cafe8d1c2fb0c4ee3f0f97c5af5fa6528de10e76d071b56fb9673da8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -4074,6 +4076,22 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@metamask/network-controller@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "@metamask/network-controller@npm:3.0.0"
|
||||
dependencies:
|
||||
"@metamask/base-controller": ^1.1.2
|
||||
"@metamask/controller-utils": ^2.0.0
|
||||
async-mutex: ^0.2.6
|
||||
babel-runtime: ^6.26.0
|
||||
eth-json-rpc-infura: ^5.1.0
|
||||
eth-query: ^2.1.2
|
||||
immer: ^9.0.6
|
||||
web3-provider-engine: ^16.0.3
|
||||
checksum: 3ae56a252c11dbd6dc843f9db8b30768d2475afd499c99bdccdc850517031b447bab9ca4f6647da7e64c7a0efd61d029f59a89e4ec702e34a99733dd8e7f93ff
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@metamask/network-controller@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "@metamask/network-controller@npm:4.0.0"
|
||||
@ -4090,22 +4108,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@metamask/network-controller@npm:~1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "@metamask/network-controller@npm:1.0.0"
|
||||
dependencies:
|
||||
"@metamask/base-controller": ~1.0.0
|
||||
"@metamask/controller-utils": ~1.0.0
|
||||
async-mutex: ^0.2.6
|
||||
babel-runtime: ^6.26.0
|
||||
eth-json-rpc-infura: ^5.1.0
|
||||
eth-query: ^2.1.2
|
||||
immer: ^9.0.6
|
||||
web3-provider-engine: ^16.0.3
|
||||
checksum: a138943fecc27630e6fe392b9d237405e61b55e17b9dcfc7c434ccc59582fc775aec54e765c2e98f2b1579f760c7d163156450184172128079ce3c4d8e4bc725
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@metamask/notification-controller@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "@metamask/notification-controller@npm:1.0.0"
|
||||
@ -4150,13 +4152,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@metamask/obs-store@npm:^8.0.0":
|
||||
version: 8.0.0
|
||||
resolution: "@metamask/obs-store@npm:8.0.0"
|
||||
"@metamask/obs-store@npm:^8.1.0":
|
||||
version: 8.1.0
|
||||
resolution: "@metamask/obs-store@npm:8.1.0"
|
||||
dependencies:
|
||||
"@metamask/safe-event-emitter": ^2.0.0
|
||||
through2: ^2.0.3
|
||||
checksum: 232362e65a3563f0bd3299cec48f5adb37e68d4f066b7de90f2b044480d3b16c2d918c12d672c825e1d9b55344ae818fb8494d91129e4613555097653b9bb887
|
||||
checksum: 92356067fa3517526d656f2f0bdfbc4d39f65e27fb30d84240cfc9c1aa9cd5d743498952df18ed8efbb8887b6cc1bc1fab37bde3fb0fc059539e0dfcc67ff86f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -24299,14 +24301,14 @@ __metadata:
|
||||
"@metamask/eth-token-tracker": ^4.0.0
|
||||
"@metamask/etherscan-link": ^2.2.0
|
||||
"@metamask/forwarder": ^1.1.0
|
||||
"@metamask/gas-fee-controller": ^1.0.0
|
||||
"@metamask/gas-fee-controller": ^3.0.0
|
||||
"@metamask/jazzicon": ^2.0.0
|
||||
"@metamask/key-tree": ^7.0.0
|
||||
"@metamask/logo": ^3.1.1
|
||||
"@metamask/message-manager": ^2.1.0
|
||||
"@metamask/metamask-eth-abis": ^3.0.0
|
||||
"@metamask/notification-controller": ^1.0.0
|
||||
"@metamask/obs-store": ^8.0.0
|
||||
"@metamask/obs-store": ^8.1.0
|
||||
"@metamask/permission-controller": ^3.1.0
|
||||
"@metamask/phishing-controller": ^2.0.0
|
||||
"@metamask/phishing-warning": ^2.1.0
|
||||
@ -34616,14 +34618,14 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"vm2@npm:^3.9.3":
|
||||
version: 3.9.11
|
||||
resolution: "vm2@npm:3.9.11"
|
||||
version: 3.9.15
|
||||
resolution: "vm2@npm:3.9.15"
|
||||
dependencies:
|
||||
acorn: ^8.7.0
|
||||
acorn-walk: ^8.2.0
|
||||
bin:
|
||||
vm2: bin/vm2
|
||||
checksum: aab39e6e4b59146d24abacd79f490e854a6e058a8b23d93d2be5aca7720778e2605d2cc028ccc4a5f50d3d91b0c38be9a6247a80d2da1a6de09425cc437770b4
|
||||
checksum: 1df70d5a88173651c0062901aba67e5edfeeb3f699fe6c305f5efb6a5a7391e5724cbf98a6516600b65016c6824dc07cc79947ea4222f8537ae1d9ce0b730ad7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user