1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 18:00:18 +01:00

Merge branch 'develop' of github.com:MetaMask/metamask-extension into minimal

This commit is contained in:
Matthias Kretschmann 2023-04-12 13:02:03 +01:00
commit 27e9cb7f88
Signed by: m
GPG Key ID: 606EEEF3C479A91F
41 changed files with 1874 additions and 844 deletions

View File

@ -815,7 +815,7 @@
"message": "Contract deployment" "message": "Contract deployment"
}, },
"contractDescription": { "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": { "contractInteraction": {
"message": "Contract interaction" "message": "Contract interaction"
@ -830,10 +830,10 @@
"message": "Contract requesting signature" "message": "Contract requesting signature"
}, },
"contractRequestingSpendingCap": { "contractRequestingSpendingCap": {
"message": "Contract requesting spending cap" "message": "Third party requesting spending cap"
}, },
"contractTitle": { "contractTitle": {
"message": "Contract details" "message": "Third-party details"
}, },
"contractToken": { "contractToken": {
"message": "Token contract" "message": "Token contract"
@ -1808,14 +1808,14 @@
"message": "Your initial transaction was confirmed by the network. Click OK to go back." "message": "Your initial transaction was confirmed by the network. Click OK to go back."
}, },
"inputLogicEmptyState": { "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": { "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" "description": "$1 is the current token balance in the account and the name of the current token"
}, },
"inputLogicHigherNumber": { "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": { "insightsFromSnap": {
"message": "Insights from $1", "message": "Insights from $1",
@ -2486,6 +2486,9 @@
"message": "OpenSea is the first provider for this feature. More providers coming soon!", "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." "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": { "notifications19ActionText": {
"message": "Enable NFT autodetection" "message": "Enable NFT autodetection"
}, },
@ -3328,7 +3331,7 @@
"description": "$1 is a token symbol" "description": "$1 is a token symbol"
}, },
"revokeSpendingCapTooltipText": { "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": { "rpcUrl": {
"message": "New RPC URL" "message": "New RPC URL"
@ -4660,7 +4663,7 @@
"message": "Username" "message": "Username"
}, },
"verifyContractDetails": { "verifyContractDetails": {
"message": "Verify contract details" "message": "Verify third-party details"
}, },
"verifyThisTokenDecimalOn": { "verifyThisTokenDecimalOn": {
"message": "Token decimal can be found on $1", "message": "Token decimal can be found on $1",
@ -4746,7 +4749,7 @@
"message": "Warning" "message": "Warning"
}, },
"warningTooltipText": { "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" "description": "$1 is a warning icon with text 'Be careful' in 'warning' colour"
}, },
"weak": { "weak": {

View File

@ -727,7 +727,6 @@ export function setupController(
} }
function getUnapprovedTransactionCount() { function getUnapprovedTransactionCount() {
const unapprovedTxCount = controller.txController.getUnapprovedTxCount();
const { unapprovedDecryptMsgCount } = controller.decryptMessageManager; const { unapprovedDecryptMsgCount } = controller.decryptMessageManager;
const { unapprovedEncryptionPublicKeyMsgCount } = const { unapprovedEncryptionPublicKeyMsgCount } =
controller.encryptionPublicKeyManager; controller.encryptionPublicKeyManager;
@ -736,7 +735,6 @@ export function setupController(
const waitingForUnlockCount = const waitingForUnlockCount =
controller.appStateController.waitingForUnlock.length; controller.appStateController.waitingForUnlock.length;
return ( return (
unapprovedTxCount +
unapprovedDecryptMsgCount + unapprovedDecryptMsgCount +
unapprovedEncryptionPublicKeyMsgCount + unapprovedEncryptionPublicKeyMsgCount +
pendingApprovalCount + pendingApprovalCount +

View File

@ -13,7 +13,7 @@ import { convertHexToDecimal } from '@metamask/controller-utils';
import { NETWORK_TYPES } from '../../../shared/constants/network'; import { NETWORK_TYPES } from '../../../shared/constants/network';
import { toChecksumHexAddress } from '../../../shared/modules/hexstring-utils'; import { toChecksumHexAddress } from '../../../shared/modules/hexstring-utils';
import DetectTokensController from './detect-tokens'; import DetectTokensController from './detect-tokens';
import NetworkController, { NetworkControllerEventTypes } from './network'; import { NetworkController, NetworkControllerEventType } from './network';
import PreferencesController from './preferences'; import PreferencesController from './preferences';
describe('DetectTokensController', function () { describe('DetectTokensController', function () {
@ -248,7 +248,7 @@ describe('DetectTokensController', function () {
), ),
onNetworkStateChange: (cb) => onNetworkStateChange: (cb) =>
networkControllerMessenger.subscribe( networkControllerMessenger.subscribe(
NetworkControllerEventTypes.NetworkDidChange, NetworkControllerEventType.NetworkDidChange,
() => { () => {
const networkState = network.store.getState(); const networkState = network.store.getState();
const modifiedNetworkState = { const modifiedNetworkState = {

View File

@ -1 +0,0 @@
export { default, NetworkControllerEventTypes } from './network-controller';

View File

@ -0,0 +1 @@
export * from './network-controller';

View File

@ -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);
}
}

View File

@ -6,7 +6,7 @@ import sinon from 'sinon';
import { ControllerMessenger } from '@metamask/base-controller'; import { ControllerMessenger } from '@metamask/base-controller';
import { BUILT_IN_NETWORKS } from '../../../../shared/constants/network'; import { BUILT_IN_NETWORKS } from '../../../../shared/constants/network';
import { MetaMetricsNetworkEventSource } from '../../../../shared/constants/metametrics'; import { MetaMetricsNetworkEventSource } from '../../../../shared/constants/metametrics';
import NetworkController from './network-controller'; import { NetworkController } from './network-controller';
jest.mock('uuid', () => { jest.mock('uuid', () => {
const actual = jest.requireActual('uuid'); const actual = jest.requireActual('uuid');
@ -1100,7 +1100,7 @@ describe('NetworkController', () => {
}); });
describe('when the request for the latest block responds with null', () => { 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( await withController(
{ {
state: { state: {
@ -1118,13 +1118,13 @@ describe('NetworkController', () => {
await controller.getEIP1559Compatibility(); await controller.getEIP1559Compatibility();
expect(controller.store.getState().networkDetails.EIPS[1559]).toBe( expect(controller.store.getState().networkDetails.EIPS[1559]).toBe(
null, false,
); );
}, },
); );
}); });
it('returns null', async () => { it('returns false', async () => {
await withController(async ({ controller, network }) => { await withController(async ({ controller, network }) => {
network.mockEssentialRpcCalls({ network.mockEssentialRpcCalls({
latestBlock: null, latestBlock: null,
@ -1133,7 +1133,7 @@ describe('NetworkController', () => {
const supportsEIP1559 = await controller.getEIP1559Compatibility(); const supportsEIP1559 = await controller.getEIP1559Compatibility();
expect(supportsEIP1559).toBe(null); expect(supportsEIP1559).toBe(false);
}); });
}); });
}); });

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@ import { ControllerMessenger } from '@metamask/base-controller';
import { TokenListController } from '@metamask/assets-controllers'; import { TokenListController } from '@metamask/assets-controllers';
import { CHAIN_IDS } from '../../../shared/constants/network'; import { CHAIN_IDS } from '../../../shared/constants/network';
import PreferencesController from './preferences'; import PreferencesController from './preferences';
import NetworkController from './network'; import { NetworkController } from './network';
describe('preferences controller', function () { describe('preferences controller', function () {
let preferencesController; let preferencesController;

View File

@ -52,7 +52,10 @@ import {
determineTransactionType, determineTransactionType,
isEIP1559Transaction, isEIP1559Transaction,
} from '../../../../shared/modules/transaction.utils'; } from '../../../../shared/modules/transaction.utils';
import { ORIGIN_METAMASK } from '../../../../shared/constants/app'; import {
ORIGIN_METAMASK,
MESSAGE_TYPE,
} from '../../../../shared/constants/app';
import { import {
calcGasTotal, calcGasTotal,
getSwapsTokensReceivedFromTxMeta, getSwapsTokensReceivedFromTxMeta,
@ -156,6 +159,7 @@ export default class TransactionController extends EventEmitter {
this.getAccountType = opts.getAccountType; this.getAccountType = opts.getAccountType;
this.getTokenStandardAndDetails = opts.getTokenStandardAndDetails; this.getTokenStandardAndDetails = opts.getTokenStandardAndDetails;
this.securityProviderRequest = opts.securityProviderRequest; this.securityProviderRequest = opts.securityProviderRequest;
this.messagingSystem = opts.messenger;
this.memStore = new ObservableStore({}); this.memStore = new ObservableStore({});
@ -798,6 +802,7 @@ export default class TransactionController extends EventEmitter {
this.txStateManager.getTransactionWithActionId(actionId); this.txStateManager.getTransactionWithActionId(actionId);
if (existingTxMeta) { if (existingTxMeta) {
this.emit('newUnapprovedTx', existingTxMeta); this.emit('newUnapprovedTx', existingTxMeta);
this._requestApproval(existingTxMeta);
existingTxMeta = await this.addTransactionGasDefaults(existingTxMeta); existingTxMeta = await this.addTransactionGasDefaults(existingTxMeta);
return existingTxMeta; return existingTxMeta;
} }
@ -870,6 +875,7 @@ export default class TransactionController extends EventEmitter {
this.addTransaction(txMeta); this.addTransaction(txMeta);
this.emit('newUnapprovedTx', txMeta); this.emit('newUnapprovedTx', txMeta);
this._requestApproval(txMeta);
txMeta = await this.addTransactionGasDefaults(txMeta); txMeta = await this.addTransactionGasDefaults(txMeta);
@ -1355,6 +1361,7 @@ export default class TransactionController extends EventEmitter {
try { try {
// approve // approve
this.txStateManager.setTxStatusApproved(txId); this.txStateManager.setTxStatusApproved(txId);
this._acceptApproval(txMeta);
// get next nonce // get next nonce
const fromAddress = txMeta.txParams.from; const fromAddress = txMeta.txParams.from;
// wait for a nonce // wait for a nonce
@ -1734,6 +1741,7 @@ export default class TransactionController extends EventEmitter {
async cancelTransaction(txId, actionId) { async cancelTransaction(txId, actionId) {
const txMeta = this.txStateManager.getTransaction(txId); const txMeta = this.txStateManager.getTransaction(txId);
this.txStateManager.setTxStatusRejected(txId); this.txStateManager.setTxStatusRejected(txId);
this._rejectApproval(txMeta);
this._trackTransactionMetricsEvent( this._trackTransactionMetricsEvent(
txMeta, txMeta,
TransactionMetaMetricsEvent.rejected, 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);
}
} }

View File

@ -29,7 +29,10 @@ import {
GasRecommendations, GasRecommendations,
} from '../../../../shared/constants/gas'; } from '../../../../shared/constants/gas';
import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller'; 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 { NetworkStatus } from '../../../../shared/constants/network';
import { TRANSACTION_ENVELOPE_TYPE_NAMES } from '../../../../shared/lib/transactions-controller-utils'; import { TRANSACTION_ENVELOPE_TYPE_NAMES } from '../../../../shared/lib/transactions-controller-utils';
import TransactionController from '.'; import TransactionController from '.';
@ -52,7 +55,8 @@ describe('Transaction Controller', function () {
fromAccount, fromAccount,
fragmentExists, fragmentExists,
networkStatusStore, networkStatusStore,
getCurrentChainId; getCurrentChainId,
messengerMock;
beforeEach(function () { beforeEach(function () {
fragmentExists = false; fragmentExists = false;
@ -76,6 +80,7 @@ describe('Transaction Controller', function () {
blockTrackerStub.getLatestBlock = noop; blockTrackerStub.getLatestBlock = noop;
getCurrentChainId = sinon.stub().callsFake(() => currentChainId); getCurrentChainId = sinon.stub().callsFake(() => currentChainId);
messengerMock = { call: sinon.stub().returns(Promise.resolve()) };
txController = new TransactionController({ txController = new TransactionController({
provider, provider,
@ -108,6 +113,7 @@ describe('Transaction Controller', function () {
getAccountType: () => 'MetaMask', getAccountType: () => 'MetaMask',
getDeviceModel: () => 'N/A', getDeviceModel: () => 'N/A',
securityProviderRequest: () => undefined, securityProviderRequest: () => undefined,
messenger: messengerMock,
}); });
txController.nonceTracker.getNonceLock = () => txController.nonceTracker.getNonceLock = () =>
Promise.resolve({ nextNonce: 0, releaseLock: noop }); Promise.resolve({ nextNonce: 0, releaseLock: noop });
@ -489,6 +495,67 @@ describe('Transaction Controller', function () {
{ message: 'MetaMask is having trouble connecting to the network' }, { 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 () { describe('#createCancelTransaction', function () {
@ -997,9 +1064,11 @@ describe('Transaction Controller', function () {
}); });
describe('#approveTransaction', function () { describe('#approveTransaction', function () {
it('does not overwrite set values', async function () { let originalValue, txMeta, signStub, pubStub;
const originalValue = '0x01';
const txMeta = { beforeEach(function () {
originalValue = '0x01';
txMeta = {
id: '1', id: '1',
status: TransactionStatus.unapproved, status: TransactionStatus.unapproved,
metamaskNetworkId: currentNetworkId, metamaskNetworkId: currentNetworkId,
@ -1019,17 +1088,22 @@ describe('Transaction Controller', function () {
providerResultStub.eth_gasPrice = wrongValue; providerResultStub.eth_gasPrice = wrongValue;
providerResultStub.eth_estimateGas = '0x5209'; providerResultStub.eth_estimateGas = '0x5209';
const signStub = sinon signStub = sinon
.stub(txController, 'signTransaction') .stub(txController, 'signTransaction')
.callsFake(() => Promise.resolve()); .callsFake(() => Promise.resolve());
const pubStub = sinon pubStub = sinon.stub(txController, 'publishTransaction').callsFake(() => {
.stub(txController, 'publishTransaction') txController.setTxHash('1', originalValue);
.callsFake(() => { txController.txStateManager.setTxStatusSubmitted('1');
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); await txController.approveTransaction(txMeta.id);
const result = txController.txStateManager.getTransaction(txMeta.id); const result = txController.txStateManager.getTransaction(txMeta.id);
const params = result.txParams; const params = result.txParams;
@ -1042,8 +1116,21 @@ describe('Transaction Controller', function () {
TransactionStatus.submitted, TransactionStatus.submitted,
'should have reached the submitted status.', '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 () { describe('#cancelTransaction', function () {
it('should emit a status change to rejected', function (done) { beforeEach(function () {
txController.txStateManager._addTransactionsToState([ txController.txStateManager._addTransactionsToState([
{ {
id: 0, id: 0,
@ -1181,7 +1268,9 @@ describe('Transaction Controller', function () {
history: [{}], history: [{}],
}, },
]); ]);
});
it('should emit a status change to rejected', function (done) {
txController.once('tx:status-update', (txId, status) => { txController.once('tx:status-update', (txId, status) => {
try { try {
assert.equal( assert.equal(
@ -1198,6 +1287,22 @@ describe('Transaction Controller', function () {
txController.cancelTransaction(0); 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 () { describe('#createSpeedUpTransaction', function () {

View File

@ -143,8 +143,9 @@ import createTabIdMiddleware from './lib/createTabIdMiddleware';
import createOnboardingMiddleware from './lib/createOnboardingMiddleware'; import createOnboardingMiddleware from './lib/createOnboardingMiddleware';
import { setupMultiplex } from './lib/stream-utils'; import { setupMultiplex } from './lib/stream-utils';
import EnsController from './controllers/ens'; import EnsController from './controllers/ens';
import NetworkController, { import {
NetworkControllerEventTypes, NetworkController,
NetworkControllerEventType,
} from './controllers/network'; } from './controllers/network';
import PreferencesController from './controllers/preferences'; import PreferencesController from './controllers/preferences';
import AppStateController from './controllers/app-state'; import AppStateController from './controllers/app-state';
@ -263,7 +264,7 @@ export default class MetamaskController extends EventEmitter {
const networkControllerMessenger = this.controllerMessenger.getRestricted({ const networkControllerMessenger = this.controllerMessenger.getRestricted({
name: 'NetworkController', name: 'NetworkController',
allowedEvents: Object.values(NetworkControllerEventTypes), allowedEvents: Object.values(NetworkControllerEventType),
}); });
this.networkController = new NetworkController({ this.networkController = new NetworkController({
messenger: networkControllerMessenger, messenger: networkControllerMessenger,
@ -310,11 +311,11 @@ export default class MetamaskController extends EventEmitter {
initLangCode: opts.initLangCode, initLangCode: opts.initLangCode,
onInfuraIsBlocked: networkControllerMessenger.subscribe.bind( onInfuraIsBlocked: networkControllerMessenger.subscribe.bind(
networkControllerMessenger, networkControllerMessenger,
NetworkControllerEventTypes.InfuraIsBlocked, NetworkControllerEventType.InfuraIsBlocked,
), ),
onInfuraIsUnblocked: networkControllerMessenger.subscribe.bind( onInfuraIsUnblocked: networkControllerMessenger.subscribe.bind(
networkControllerMessenger, networkControllerMessenger,
NetworkControllerEventTypes.InfuraIsUnblocked, NetworkControllerEventType.InfuraIsUnblocked,
), ),
tokenListController: this.tokenListController, tokenListController: this.tokenListController,
provider: this.provider, provider: this.provider,
@ -452,7 +453,7 @@ export default class MetamaskController extends EventEmitter {
preferencesStore: this.preferencesController.store, preferencesStore: this.preferencesController.store,
onNetworkDidChange: networkControllerMessenger.subscribe.bind( onNetworkDidChange: networkControllerMessenger.subscribe.bind(
networkControllerMessenger, networkControllerMessenger,
NetworkControllerEventTypes.NetworkDidChange, NetworkControllerEventType.NetworkDidChange,
), ),
getNetworkIdentifier: () => { getNetworkIdentifier: () => {
const { type, rpcUrl } = const { type, rpcUrl } =
@ -491,7 +492,7 @@ export default class MetamaskController extends EventEmitter {
// onNetworkDidChange // onNetworkDidChange
onNetworkStateChange: networkControllerMessenger.subscribe.bind( onNetworkStateChange: networkControllerMessenger.subscribe.bind(
networkControllerMessenger, networkControllerMessenger,
NetworkControllerEventTypes.NetworkDidChange, NetworkControllerEventType.NetworkDidChange,
), ),
getCurrentNetworkEIP1559Compatibility: getCurrentNetworkEIP1559Compatibility:
this.networkController.getEIP1559Compatibility.bind( this.networkController.getEIP1559Compatibility.bind(
@ -609,7 +610,7 @@ export default class MetamaskController extends EventEmitter {
this.networkController.store.getState().provider.chainId, this.networkController.store.getState().provider.chainId,
onNetworkDidChange: networkControllerMessenger.subscribe.bind( onNetworkDidChange: networkControllerMessenger.subscribe.bind(
networkControllerMessenger, networkControllerMessenger,
NetworkControllerEventTypes.NetworkDidChange, NetworkControllerEventType.NetworkDidChange,
), ),
}); });
@ -621,7 +622,7 @@ export default class MetamaskController extends EventEmitter {
blockTracker: this.blockTracker, blockTracker: this.blockTracker,
onNetworkDidChange: networkControllerMessenger.subscribe.bind( onNetworkDidChange: networkControllerMessenger.subscribe.bind(
networkControllerMessenger, networkControllerMessenger,
NetworkControllerEventTypes.NetworkDidChange, NetworkControllerEventType.NetworkDidChange,
), ),
getCurrentChainId: () => getCurrentChainId: () =>
this.networkController.store.getState().provider.chainId, this.networkController.store.getState().provider.chainId,
@ -1007,8 +1008,15 @@ export default class MetamaskController extends EventEmitter {
getDeviceModel: this.getDeviceModel.bind(this), getDeviceModel: this.getDeviceModel.bind(this),
getTokenStandardAndDetails: this.getTokenStandardAndDetails.bind(this), getTokenStandardAndDetails: this.getTokenStandardAndDetails.bind(this),
securityProviderRequest: this.securityProviderRequest.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) => { this.txController.on(`tx:status-update`, async (txId, status) => {
if ( if (
@ -1099,7 +1107,7 @@ export default class MetamaskController extends EventEmitter {
}); });
networkControllerMessenger.subscribe( networkControllerMessenger.subscribe(
NetworkControllerEventTypes.NetworkDidChange, NetworkControllerEventType.NetworkDidChange,
async () => { async () => {
const { ticker } = this.networkController.store.getState().provider; const { ticker } = this.networkController.store.getState().provider;
try { try {
@ -1145,7 +1153,7 @@ export default class MetamaskController extends EventEmitter {
networkController: this.networkController, networkController: this.networkController,
onNetworkDidChange: networkControllerMessenger.subscribe.bind( onNetworkDidChange: networkControllerMessenger.subscribe.bind(
networkControllerMessenger, networkControllerMessenger,
NetworkControllerEventTypes.NetworkDidChange, NetworkControllerEventType.NetworkDidChange,
), ),
provider: this.provider, provider: this.provider,
getProviderConfig: () => this.networkController.store.getState().provider, getProviderConfig: () => this.networkController.store.getState().provider,
@ -1188,7 +1196,7 @@ export default class MetamaskController extends EventEmitter {
// ensure accountTracker updates balances after network change // ensure accountTracker updates balances after network change
networkControllerMessenger.subscribe( networkControllerMessenger.subscribe(
NetworkControllerEventTypes.NetworkDidChange, NetworkControllerEventType.NetworkDidChange,
() => { () => {
this.accountTracker._updateAccounts(); this.accountTracker._updateAccounts();
}, },
@ -1196,7 +1204,7 @@ export default class MetamaskController extends EventEmitter {
// clear unapproved transactions and messages when the network will change // clear unapproved transactions and messages when the network will change
networkControllerMessenger.subscribe( networkControllerMessenger.subscribe(
NetworkControllerEventTypes.NetworkWillChange, NetworkControllerEventType.NetworkWillChange,
() => { () => {
this.txController.txStateManager.clearUnapprovedTxs(); this.txController.txStateManager.clearUnapprovedTxs();
this.encryptionPublicKeyManager.clearUnapproved(); this.encryptionPublicKeyManager.clearUnapproved();

View 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);
});
});

View 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,
},
};
}

View File

@ -87,6 +87,7 @@ import m080 from './080';
import * as m081 from './081'; import * as m081 from './081';
import * as m082 from './082'; import * as m082 from './082';
import * as m083 from './083'; import * as m083 from './083';
import * as m084 from './084';
const migrations = [ const migrations = [
m002, m002,
@ -171,6 +172,7 @@ const migrations = [
m081, m081,
m082, m082,
m083, m083,
m084,
]; ];
export default migrations; export default migrations;

View File

@ -51,7 +51,7 @@ module.exports = {
'<rootDir>/ui/**/*.test.(js|ts|tsx)', '<rootDir>/ui/**/*.test.(js|ts|tsx)',
'<rootDir>/development/fitness-functions/**/*.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 // 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 // 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 // head of the file. So it may be worthwhile to switch to 'node' in any

View File

@ -1193,7 +1193,7 @@
"setInterval": true "setInterval": true
}, },
"packages": { "packages": {
"@metamask/gas-fee-controller>@metamask/base-controller": true, "@metamask/base-controller": true,
"@metamask/gas-fee-controller>@metamask/controller-utils": true, "@metamask/gas-fee-controller>@metamask/controller-utils": true,
"eth-query": true, "eth-query": true,
"ethereumjs-util": true, "ethereumjs-util": true,
@ -1201,11 +1201,6 @@
"uuid": true "uuid": true
} }
}, },
"@metamask/gas-fee-controller>@metamask/base-controller": {
"packages": {
"immer": true
}
},
"@metamask/gas-fee-controller>@metamask/controller-utils": { "@metamask/gas-fee-controller>@metamask/controller-utils": {
"globals": { "globals": {
"console.error": true, "console.error": true,

View File

@ -1265,7 +1265,7 @@
"setInterval": true "setInterval": true
}, },
"packages": { "packages": {
"@metamask/gas-fee-controller>@metamask/base-controller": true, "@metamask/base-controller": true,
"@metamask/gas-fee-controller>@metamask/controller-utils": true, "@metamask/gas-fee-controller>@metamask/controller-utils": true,
"eth-query": true, "eth-query": true,
"ethereumjs-util": true, "ethereumjs-util": true,
@ -1273,11 +1273,6 @@
"uuid": true "uuid": true
} }
}, },
"@metamask/gas-fee-controller>@metamask/base-controller": {
"packages": {
"immer": true
}
},
"@metamask/gas-fee-controller>@metamask/controller-utils": { "@metamask/gas-fee-controller>@metamask/controller-utils": {
"globals": { "globals": {
"console.error": true, "console.error": true,

View File

@ -1265,7 +1265,7 @@
"setInterval": true "setInterval": true
}, },
"packages": { "packages": {
"@metamask/gas-fee-controller>@metamask/base-controller": true, "@metamask/base-controller": true,
"@metamask/gas-fee-controller>@metamask/controller-utils": true, "@metamask/gas-fee-controller>@metamask/controller-utils": true,
"eth-query": true, "eth-query": true,
"ethereumjs-util": true, "ethereumjs-util": true,
@ -1273,11 +1273,6 @@
"uuid": true "uuid": true
} }
}, },
"@metamask/gas-fee-controller>@metamask/base-controller": {
"packages": {
"immer": true
}
},
"@metamask/gas-fee-controller>@metamask/controller-utils": { "@metamask/gas-fee-controller>@metamask/controller-utils": {
"globals": { "globals": {
"console.error": true, "console.error": true,

View File

@ -1193,7 +1193,7 @@
"setInterval": true "setInterval": true
}, },
"packages": { "packages": {
"@metamask/gas-fee-controller>@metamask/base-controller": true, "@metamask/base-controller": true,
"@metamask/gas-fee-controller>@metamask/controller-utils": true, "@metamask/gas-fee-controller>@metamask/controller-utils": true,
"eth-query": true, "eth-query": true,
"ethereumjs-util": true, "ethereumjs-util": true,
@ -1201,11 +1201,6 @@
"uuid": true "uuid": true
} }
}, },
"@metamask/gas-fee-controller>@metamask/base-controller": {
"packages": {
"immer": true
}
},
"@metamask/gas-fee-controller>@metamask/controller-utils": { "@metamask/gas-fee-controller>@metamask/controller-utils": {
"globals": { "globals": {
"console.error": true, "console.error": true,

View File

@ -242,14 +242,14 @@
"@metamask/eth-ledger-bridge-keyring": "^0.13.0", "@metamask/eth-ledger-bridge-keyring": "^0.13.0",
"@metamask/eth-token-tracker": "^4.0.0", "@metamask/eth-token-tracker": "^4.0.0",
"@metamask/etherscan-link": "^2.2.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/jazzicon": "^2.0.0",
"@metamask/key-tree": "^7.0.0", "@metamask/key-tree": "^7.0.0",
"@metamask/logo": "^3.1.1", "@metamask/logo": "^3.1.1",
"@metamask/message-manager": "^2.1.0", "@metamask/message-manager": "^2.1.0",
"@metamask/metamask-eth-abis": "^3.0.0", "@metamask/metamask-eth-abis": "^3.0.0",
"@metamask/notification-controller": "^1.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/permission-controller": "^3.1.0",
"@metamask/phishing-controller": "^2.0.0", "@metamask/phishing-controller": "^2.0.0",
"@metamask/post-message-stream": "^6.0.0", "@metamask/post-message-stream": "^6.0.0",

View File

@ -53,6 +53,7 @@ export const MESSAGE_TYPE = {
PERSONAL_SIGN: 'personal_sign', PERSONAL_SIGN: 'personal_sign',
SEND_METADATA: 'metamask_sendDomainMetadata', SEND_METADATA: 'metamask_sendDomainMetadata',
SWITCH_ETHEREUM_CHAIN: 'wallet_switchEthereumChain', SWITCH_ETHEREUM_CHAIN: 'wallet_switchEthereumChain',
TRANSACTION: 'transaction',
WALLET_REQUEST_PERMISSIONS: 'wallet_requestPermissions', WALLET_REQUEST_PERMISSIONS: 'wallet_requestPermissions',
WATCH_ASSET: 'wallet_watchAsset', WATCH_ASSET: 'wallet_watchAsset',
WATCH_ASSET_LEGACY: 'metamask_watchAsset', WATCH_ASSET_LEGACY: 'metamask_watchAsset',

View File

@ -238,7 +238,7 @@ export const INFURA_PROVIDER_TYPES = [
NETWORK_TYPES.MAINNET, NETWORK_TYPES.MAINNET,
NETWORK_TYPES.GOERLI, NETWORK_TYPES.GOERLI,
NETWORK_TYPES.SEPOLIA, NETWORK_TYPES.SEPOLIA,
]; ] as const;
export const TEST_CHAINS = [ export const TEST_CHAINS = [
CHAIN_IDS.GOERLI, CHAIN_IDS.GOERLI,

View File

@ -1535,7 +1535,18 @@
"origin": "tmashuang.github.io" "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": { "send": {
"amountMode": "INPUT", "amountMode": "INPUT",

View File

@ -62,9 +62,7 @@ describe('ERC721 NFTs testdapp interaction', function () {
); );
// Verify transaction // Verify transaction
const completedTx = await driver.findElement('.list-item__title'); await driver.findElement({ text: 'Send TDC' });
const completedTxText = await completedTx.getText();
assert.equal(completedTxText, 'Send Token');
}, },
); );
}); });

View File

@ -119,16 +119,16 @@ describe('Create token, approve token and approve token without gas', function (
); );
await driver.clickElement({ await driver.clickElement({
text: 'Verify contract details', text: 'Verify third-party details',
css: '.token-allowance-container__verify-link', css: '.token-allowance-container__verify-link',
}); });
const modalTitle = await driver.waitForSelector({ const modalTitle = await driver.waitForSelector({
text: 'Contract details', text: 'Third-party details',
tag: 'h5', tag: 'h5',
}); });
assert.equal(await modalTitle.getText(), 'Contract details'); assert.equal(await modalTitle.getText(), 'Third-party details');
await driver.clickElement({ await driver.clickElement({
text: 'Got it', text: 'Got it',

View File

@ -60,7 +60,7 @@ describe('Sign Typed Data V4 Signature Request', function () {
assert.equal(await origin.getText(), 'http://127.0.0.1:8080'); assert.equal(await origin.getText(), 'http://127.0.0.1:8080');
verifyContractDetailsButton.click(); 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.findElement('[data-testid="recipient"]');
await driver.clickElement({ text: 'Got it', tag: 'button' }); 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'); assert.equal(await origin.getText(), 'http://127.0.0.1:8080');
verifyContractDetailsButton.click(); 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.findElement('[data-testid="recipient"]');
await driver.clickElement({ text: 'Got it', tag: 'button' }); await driver.clickElement({ text: 'Got it', tag: 'button' });

50
types/eth-query.d.ts vendored Normal file
View 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;
}
}

View File

@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { object } from '@storybook/addon-knobs';
import { panel, text, heading, divider, copyable } from '@metamask/snaps-ui'; import { panel, text, heading, divider, copyable } from '@metamask/snaps-ui';
import configureStore from '../../../../store/store'; import configureStore from '../../../../store/store';
import testData from '../../../../../.storybook/test-data'; import testData from '../../../../../.storybook/test-data';
@ -10,8 +9,13 @@ const store = configureStore(testData);
export default { export default {
title: 'Components/App/SnapUIRenderer', title: 'Components/App/SnapUIRenderer',
component: SnapUIRenderer,
decorators: [(story) => <Provider store={store}>{story()}</Provider>], decorators: [(story) => <Provider store={store}>{story()}</Provider>],
argTypes: {
data: {
control: 'object',
},
},
}; };
const DATA = panel([ const DATA = panel([
@ -22,13 +26,18 @@ const DATA = panel([
copyable('Text you can copy'), copyable('Text you can copy'),
]); ]);
export const DefaultStory = () => ( export const DefaultStory = (args) => (
<SnapUIRenderer <SnapUIRenderer snapId="local:http://localhost:8080/" data={args.data} />
snapId="local:http://localhost:8080/"
data={object('data', DATA)}
/>
); );
export const ErrorStory = () => ( DefaultStory.args = {
<SnapUIRenderer snapId="local:http://localhost:8080/" data="foo" /> data: DATA,
};
export const ErrorStory = (args) => (
<SnapUIRenderer snapId="local:http://localhost:8080/" data={args.data} />
); );
ErrorStory.args = {
data: 'foo',
};

View File

@ -170,8 +170,9 @@ export default function HoldToRevealButton({ buttonText, onLongPressed }) {
onMouseDown={onMouseDown} onMouseDown={onMouseDown}
onMouseUp={onMouseUp} onMouseUp={onMouseUp}
className="hold-to-reveal-button__button-hold" 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()} {renderPreCompleteContent()}
{renderPostCompleteContent()} {renderPostCompleteContent()}
</Box> </Box>

View File

@ -224,7 +224,7 @@ exports[`Signature Request Component render should match snapshot when we are us
<h6 <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" 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> </h6>
</a> </a>
</div> </div>
@ -999,7 +999,7 @@ exports[`Signature Request Component render should match snapshot when we want t
<h6 <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" 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> </h6>
</a> </a>
</div> </div>

View File

@ -152,7 +152,7 @@ export function useTransactionDisplayData(transactionGroup) {
async function getAndSetAssetDetails() { async function getAndSetAssetDetails() {
if (isTokenCategory && !token) { if (isTokenCategory && !token) {
const assetDetails = await getAssetDetails( const assetDetails = await getAssetDetails(
recipientAddress, to,
senderAddress, senderAddress,
initialTransaction?.txParams?.data, initialTransaction?.txParams?.data,
knownNfts, knownNfts,
@ -168,6 +168,7 @@ export function useTransactionDisplayData(transactionGroup) {
senderAddress, senderAddress,
initialTransaction?.txParams?.data, initialTransaction?.txParams?.data,
knownNfts, knownNfts,
to,
]); ]);
if (currentAssetDetails) { if (currentAssetDetails) {
token = { token = {

View File

@ -57,7 +57,7 @@ exports[`ConfirmApproveContent Component should render Confirm approve page corr
role="button" role="button"
tabindex="0" tabindex="0"
> >
Verify contract details Verify third-party details
</a> </a>
</div> </div>
<div <div
@ -244,7 +244,7 @@ exports[`ConfirmApproveContent Component should render Confirm approve page corr
role="button" role="button"
tabindex="0" tabindex="0"
> >
Verify contract details Verify third-party details
</a> </a>
</div> </div>
<div <div
@ -431,7 +431,7 @@ exports[`ConfirmApproveContent Component should render Confirm approve page corr
role="button" role="button"
tabindex="0" tabindex="0"
> >
Verify contract details Verify third-party details
</a> </a>
</div> </div>
<div <div
@ -618,7 +618,7 @@ exports[`ConfirmApproveContent Component should render Confirm approve page corr
role="button" role="button"
tabindex="0" tabindex="0"
> >
Verify contract details Verify third-party details
</a> </a>
</div> </div>
<div <div

View File

@ -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.', 'This allows a third party to access and transfer the following NFTs without further notice until you revoke its access.',
), ),
).toBeInTheDocument(); ).toBeInTheDocument();
expect(queryByText('Verify contract details')).toBeInTheDocument(); expect(queryByText('Verify third-party details')).toBeInTheDocument();
expect( expect(
queryByText( queryByText(
'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.', '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.', 'This allows a third party to access and transfer the following NFTs without further notice until you revoke its access.',
), ),
).toBeInTheDocument(); ).toBeInTheDocument();
expect(queryByText('Verify contract details')).toBeInTheDocument(); expect(queryByText('Verify third-party details')).toBeInTheDocument();
expect( expect(
queryByText( queryByText(
'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.', '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.', 'This allows a third party to access and transfer the following NFTs without further notice until you revoke its access.',
), ),
).toBeInTheDocument(); ).toBeInTheDocument();
expect(queryByText('Verify contract details')).toBeInTheDocument(); expect(queryByText('Verify third-party details')).toBeInTheDocument();
expect( expect(
queryByText( queryByText(
'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.', '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.', 'This allows a third party to access and transfer the following NFTs without further notice until you revoke its access.',
), ),
).toBeInTheDocument(); ).toBeInTheDocument();
expect(queryByText('Verify contract details')).toBeInTheDocument(); expect(queryByText('Verify third-party details')).toBeInTheDocument();
expect( expect(
queryByText( queryByText(
'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.', 'We were not able to estimate gas. There might be an error in the contract and this transaction may fail.',

View File

@ -223,7 +223,7 @@ exports[`Signature Request Component render should match snapshot 1`] = `
<h6 <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" 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> </h6>
</a> </a>
</div> </div>

View File

@ -5,10 +5,10 @@ import UrlIcon from '../../../components/ui/url-icon';
import Popover from '../../../components/ui/popover'; import Popover from '../../../components/ui/popover';
import Button from '../../../components/ui/button'; import Button from '../../../components/ui/button';
import Box from '../../../components/ui/box'; 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 ActionableMessage from '../../../components/ui/actionable-message/actionable-message';
import { import {
TypographyVariant, TextVariant,
FONT_WEIGHT, FONT_WEIGHT,
AlignItems, AlignItems,
DISPLAY, DISPLAY,
@ -62,21 +62,26 @@ export default function ImportToken({
fallbackClassName="import-token__token-icon" fallbackClassName="import-token__token-icon"
name={tokenForImport.symbol} name={tokenForImport.symbol}
/> />
<Typography <Text
ariant={TypographyVariant.H4} variant={TextVariant.headingSm}
as="h4"
fontWeight={FONT_WEIGHT.BOLD} fontWeight={FONT_WEIGHT.BOLD}
boxProps={{ marginTop: 2, marginBottom: 3 }} marginTop={2}
marginBottom={3}
> >
{tokenForImport.name || ''} {tokenForImport.name || ''}
</Typography> </Text>
<Typography variant={TypographyVariant.H6}>{t('contract')}:</Typography> <Text variant={TextVariant.bodySm} as="h6">
<Typography {t('contract')}:
</Text>
<Text
variant={TextVariant.bodySm}
className="import-token__contract-address" className="import-token__contract-address"
variant={TypographyVariant.H7} as="h6"
boxProps={{ marginBottom: 6 }} marginBottom={6}
> >
{tokenForImport.address || ''} {tokenForImport.address || ''}
</Typography> </Text>
</Box> </Box>
</Popover> </Popover>
); );

View File

@ -14,6 +14,7 @@
border-radius: 8px; border-radius: 8px;
background-color: var(--color-background-alternative); background-color: var(--color-background-alternative);
padding: 5px 10px; padding: 5px 10px;
font-size: 0.75rem;
} }
&__token-icon { &__token-icon {

View File

@ -16,8 +16,8 @@
a.token-allowance-container__verify-link { a.token-allowance-container__verify-link {
width: fit-content; width: fit-content;
margin-inline-start: 96px; margin-inline-start: auto;
margin-inline-end: 96px; margin-inline-end: auto;
padding: 0; padding: 0;
} }

View File

@ -237,16 +237,16 @@ describe('TokenAllowancePage', () => {
expect(getByText('Set a spending cap for your')).toBeInTheDocument(); 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( const { getByText } = renderWithProvider(
<TokenAllowance {...props} />, <TokenAllowance {...props} />,
store, store,
); );
const verifyContractDetails = getByText('Verify contract details'); const verifyThirdPartyDetails = getByText('Verify third-party details');
fireEvent.click(verifyContractDetails); fireEvent.click(verifyThirdPartyDetails);
expect(getByText('Contract details')).toBeInTheDocument(); expect(getByText('Third-party details')).toBeInTheDocument();
const gotIt = getByText('Got it'); const gotIt = getByText('Got it');
fireEvent.click(gotIt); fireEvent.click(gotIt);

View File

@ -484,21 +484,14 @@ export function getCurrentCurrency(state) {
export function getTotalUnapprovedCount(state) { export function getTotalUnapprovedCount(state) {
const { const {
unapprovedMsgCount = 0,
unapprovedPersonalMsgCount = 0,
unapprovedDecryptMsgCount = 0, unapprovedDecryptMsgCount = 0,
unapprovedEncryptionPublicKeyMsgCount = 0, unapprovedEncryptionPublicKeyMsgCount = 0,
unapprovedTypedMessagesCount = 0,
pendingApprovalCount = 0, pendingApprovalCount = 0,
} = state.metamask; } = state.metamask;
return ( return (
unapprovedMsgCount +
unapprovedPersonalMsgCount +
unapprovedDecryptMsgCount + unapprovedDecryptMsgCount +
unapprovedEncryptionPublicKeyMsgCount + unapprovedEncryptionPublicKeyMsgCount +
unapprovedTypedMessagesCount +
getUnapprovedTxCount(state) +
pendingApprovalCount + pendingApprovalCount +
getSuggestedAssetCount(state) getSuggestedAssetCount(state)
); );

View File

@ -3984,13 +3984,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@metamask/gas-fee-controller@npm:^1.0.0": "@metamask/gas-fee-controller@npm:^3.0.0":
version: 1.0.0 version: 3.0.0
resolution: "@metamask/gas-fee-controller@npm:1.0.0" resolution: "@metamask/gas-fee-controller@npm:3.0.0"
dependencies: dependencies:
"@metamask/base-controller": ~1.0.0 "@metamask/base-controller": ^1.1.2
"@metamask/controller-utils": ~1.0.0 "@metamask/controller-utils": ^2.0.0
"@metamask/network-controller": ~1.0.0 "@metamask/network-controller": ^3.0.0
"@types/uuid": ^8.3.0 "@types/uuid": ^8.3.0
babel-runtime: ^6.26.0 babel-runtime: ^6.26.0
eth-query: ^2.1.2 eth-query: ^2.1.2
@ -3998,7 +3998,9 @@ __metadata:
ethjs-unit: ^0.1.6 ethjs-unit: ^0.1.6
immer: ^9.0.6 immer: ^9.0.6
uuid: ^8.3.2 uuid: ^8.3.2
checksum: fef5255532a6cd5325ddfbbfec11140e6629c011a8cc6b126672ef7a6e93a327d059935cdc6fc7089562f3277fb70541b5ea54cd31c0e5b350ceebbe73d5d59f peerDependencies:
"@metamask/network-controller": ^3.0.0
checksum: 8cdd43a265094dd5e41f0094c278cde351d290446711e6b39de26f842faa993c050e5506cafe8d1c2fb0c4ee3f0f97c5af5fa6528de10e76d071b56fb9673da8
languageName: node languageName: node
linkType: hard linkType: hard
@ -4074,6 +4076,22 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "@metamask/network-controller@npm:^4.0.0":
version: 4.0.0 version: 4.0.0
resolution: "@metamask/network-controller@npm:4.0.0" resolution: "@metamask/network-controller@npm:4.0.0"
@ -4090,22 +4108,6 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "@metamask/notification-controller@npm:^1.0.0":
version: 1.0.0 version: 1.0.0
resolution: "@metamask/notification-controller@npm:1.0.0" resolution: "@metamask/notification-controller@npm:1.0.0"
@ -4150,13 +4152,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@metamask/obs-store@npm:^8.0.0": "@metamask/obs-store@npm:^8.1.0":
version: 8.0.0 version: 8.1.0
resolution: "@metamask/obs-store@npm:8.0.0" resolution: "@metamask/obs-store@npm:8.1.0"
dependencies: dependencies:
"@metamask/safe-event-emitter": ^2.0.0 "@metamask/safe-event-emitter": ^2.0.0
through2: ^2.0.3 through2: ^2.0.3
checksum: 232362e65a3563f0bd3299cec48f5adb37e68d4f066b7de90f2b044480d3b16c2d918c12d672c825e1d9b55344ae818fb8494d91129e4613555097653b9bb887 checksum: 92356067fa3517526d656f2f0bdfbc4d39f65e27fb30d84240cfc9c1aa9cd5d743498952df18ed8efbb8887b6cc1bc1fab37bde3fb0fc059539e0dfcc67ff86f
languageName: node languageName: node
linkType: hard linkType: hard
@ -24299,14 +24301,14 @@ __metadata:
"@metamask/eth-token-tracker": ^4.0.0 "@metamask/eth-token-tracker": ^4.0.0
"@metamask/etherscan-link": ^2.2.0 "@metamask/etherscan-link": ^2.2.0
"@metamask/forwarder": ^1.1.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/jazzicon": ^2.0.0
"@metamask/key-tree": ^7.0.0 "@metamask/key-tree": ^7.0.0
"@metamask/logo": ^3.1.1 "@metamask/logo": ^3.1.1
"@metamask/message-manager": ^2.1.0 "@metamask/message-manager": ^2.1.0
"@metamask/metamask-eth-abis": ^3.0.0 "@metamask/metamask-eth-abis": ^3.0.0
"@metamask/notification-controller": ^1.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/permission-controller": ^3.1.0
"@metamask/phishing-controller": ^2.0.0 "@metamask/phishing-controller": ^2.0.0
"@metamask/phishing-warning": ^2.1.0 "@metamask/phishing-warning": ^2.1.0
@ -34616,14 +34618,14 @@ __metadata:
linkType: hard linkType: hard
"vm2@npm:^3.9.3": "vm2@npm:^3.9.3":
version: 3.9.11 version: 3.9.15
resolution: "vm2@npm:3.9.11" resolution: "vm2@npm:3.9.15"
dependencies: dependencies:
acorn: ^8.7.0 acorn: ^8.7.0
acorn-walk: ^8.2.0 acorn-walk: ^8.2.0
bin: bin:
vm2: bin/vm2 vm2: bin/vm2
checksum: aab39e6e4b59146d24abacd79f490e854a6e058a8b23d93d2be5aca7720778e2605d2cc028ccc4a5f50d3d91b0c38be9a6247a80d2da1a6de09425cc437770b4 checksum: 1df70d5a88173651c0062901aba67e5edfeeb3f699fe6c305f5efb6a5a7391e5724cbf98a6516600b65016c6824dc07cc79947ea4222f8537ae1d9ce0b730ad7
languageName: node languageName: node
linkType: hard linkType: hard