mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 09:57:02 +01:00
Merge remote-tracking branch 'upstream/develop' into minimal
This commit is contained in:
commit
edd6d489ba
@ -266,7 +266,8 @@ module.exports = {
|
||||
'**/__snapshots__/*.snap',
|
||||
'app/scripts/controllers/app-state.test.js',
|
||||
'app/scripts/controllers/network/**/*.test.js',
|
||||
'app/scripts/controllers/network/provider-api-tests/*.js',
|
||||
'app/scripts/controllers/network/**/*.test.ts',
|
||||
'app/scripts/controllers/network/provider-api-tests/*.ts',
|
||||
'app/scripts/controllers/permissions/**/*.test.js',
|
||||
'app/scripts/lib/**/*.test.js',
|
||||
'app/scripts/migrations/*.test.js',
|
||||
|
@ -606,13 +606,6 @@ const state = {
|
||||
rpcUrl: '',
|
||||
chainId: '0x5',
|
||||
},
|
||||
previousProviderStore: {
|
||||
type: 'goerli',
|
||||
ticker: 'ETH',
|
||||
nickname: '',
|
||||
rpcUrl: '',
|
||||
chainId: '0x5',
|
||||
},
|
||||
network: '5',
|
||||
accounts: {
|
||||
'0x64a845a5b02460acf8a3d84503b0d68d028b4bb4': {
|
||||
|
52
app/_locales/en/messages.json
generated
52
app/_locales/en/messages.json
generated
@ -109,6 +109,9 @@
|
||||
"about": {
|
||||
"message": "About"
|
||||
},
|
||||
"accept": {
|
||||
"message": "Accept"
|
||||
},
|
||||
"acceptTermsOfUse": {
|
||||
"message": "I have read and agree to the $1",
|
||||
"description": "$1 is the `terms` message"
|
||||
@ -305,6 +308,10 @@
|
||||
"advancedPriorityFeeToolTip": {
|
||||
"message": "Priority fee (aka “miner tip”) goes directly to miners and incentivizes them to prioritize your transaction."
|
||||
},
|
||||
"agreeTermsOfUse": {
|
||||
"message": "I agree to Metamask's $1",
|
||||
"description": "$1 is the `terms` link"
|
||||
},
|
||||
"airgapVault": {
|
||||
"message": "AirGap Vault"
|
||||
},
|
||||
@ -815,7 +822,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 +837,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"
|
||||
@ -929,6 +936,21 @@
|
||||
"custodianAccount": {
|
||||
"message": "Custodian account"
|
||||
},
|
||||
"custodyRefreshTokenModalDescription": {
|
||||
"message": "Please go to $1 and click the 'Connect to MMI' button within their user interface to connect your accounts to MMI again."
|
||||
},
|
||||
"custodyRefreshTokenModalDescription1": {
|
||||
"message": "Your custodian issues a token that authenticates the MetaMask Institutional extension, allowing you to connect your accounts."
|
||||
},
|
||||
"custodyRefreshTokenModalDescription2": {
|
||||
"message": "This token expires after a certain period for security reasons. This requires you to reconnect to MMI."
|
||||
},
|
||||
"custodyRefreshTokenModalSubtitle": {
|
||||
"message": "Why am I seeing this?"
|
||||
},
|
||||
"custodyRefreshTokenModalTitle": {
|
||||
"message": "Your custodian session has expired"
|
||||
},
|
||||
"custom": {
|
||||
"message": "Advanced"
|
||||
},
|
||||
@ -1808,14 +1830,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 +2508,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 +3353,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"
|
||||
@ -4292,6 +4317,15 @@
|
||||
"termsOfUse": {
|
||||
"message": "terms of use"
|
||||
},
|
||||
"termsOfUseAgreeText": {
|
||||
"message": " I agree to the Terms of Use, which apply to my use of MetaMask and all of its features"
|
||||
},
|
||||
"termsOfUseFooterText": {
|
||||
"message": "Please scroll to read all sections"
|
||||
},
|
||||
"termsOfUseTitle": {
|
||||
"message": "Our Terms of Use have updated"
|
||||
},
|
||||
"testNetworks": {
|
||||
"message": "Test networks"
|
||||
},
|
||||
@ -4660,7 +4694,7 @@
|
||||
"message": "Username"
|
||||
},
|
||||
"verifyContractDetails": {
|
||||
"message": "Verify contract details"
|
||||
"message": "Verify third-party details"
|
||||
},
|
||||
"verifyThisTokenDecimalOn": {
|
||||
"message": "Token decimal can be found on $1",
|
||||
@ -4746,7 +4780,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": {
|
||||
|
@ -80,7 +80,6 @@ log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'info');
|
||||
|
||||
const platform = new ExtensionPlatform();
|
||||
const notificationManager = new NotificationManager();
|
||||
global.METAMASK_NOTIFIER = notificationManager;
|
||||
|
||||
let popupIsOpen = false;
|
||||
let notificationIsOpen = false;
|
||||
@ -209,6 +208,7 @@ browser.runtime.onConnectExternal.addListener(async (...args) => {
|
||||
* @property {boolean} isInitialized - Whether the first vault has been created.
|
||||
* @property {boolean} isUnlocked - Whether the vault is currently decrypted and accounts are available for selection.
|
||||
* @property {boolean} isAccountMenuOpen - Represents whether the main account selection UI is currently displayed.
|
||||
* @property {boolean} isNetworkMenuOpen - Represents whether the main network selection UI is currently displayed.
|
||||
* @property {object} identities - An object matching lower-case hex addresses to Identity objects with "address" and "name" (nickname) keys.
|
||||
* @property {object} unapprovedTxs - An object mapping transaction hashes to unapproved transactions.
|
||||
* @property {object} networkConfigurations - A list of network configurations, containing RPC provider details (eg chainId, rpcUrl, rpcPreferences).
|
||||
@ -688,7 +688,7 @@ export function setupController(
|
||||
METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE,
|
||||
updateBadge,
|
||||
);
|
||||
controller.encryptionPublicKeyManager.on(
|
||||
controller.encryptionPublicKeyController.hub.on(
|
||||
METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE,
|
||||
updateBadge,
|
||||
);
|
||||
@ -727,20 +727,13 @@ export function setupController(
|
||||
}
|
||||
|
||||
function getUnapprovedTransactionCount() {
|
||||
const unapprovedTxCount = controller.txController.getUnapprovedTxCount();
|
||||
const { unapprovedDecryptMsgCount } = controller.decryptMessageManager;
|
||||
const { unapprovedEncryptionPublicKeyMsgCount } =
|
||||
controller.encryptionPublicKeyManager;
|
||||
const pendingApprovalCount =
|
||||
controller.approvalController.getTotalApprovalCount();
|
||||
const waitingForUnlockCount =
|
||||
controller.appStateController.waitingForUnlock.length;
|
||||
return (
|
||||
unapprovedTxCount +
|
||||
unapprovedDecryptMsgCount +
|
||||
unapprovedEncryptionPublicKeyMsgCount +
|
||||
pendingApprovalCount +
|
||||
waitingForUnlockCount
|
||||
unapprovedDecryptMsgCount + pendingApprovalCount + waitingForUnlockCount
|
||||
);
|
||||
}
|
||||
|
||||
@ -770,14 +763,9 @@ export function setupController(
|
||||
REJECT_NOTIFICATION_CLOSE,
|
||||
),
|
||||
);
|
||||
controller.encryptionPublicKeyManager.messages
|
||||
.filter((msg) => msg.status === 'unapproved')
|
||||
.forEach((tx) =>
|
||||
controller.encryptionPublicKeyManager.rejectMsg(
|
||||
tx.id,
|
||||
REJECT_NOTIFICATION_CLOSE,
|
||||
),
|
||||
);
|
||||
controller.encryptionPublicKeyController.rejectUnapproved(
|
||||
REJECT_NOTIFICATION_CLOSE,
|
||||
);
|
||||
|
||||
// Finally, resolve snap dialog approvals on Flask and reject all the others managed by the ApprovalController.
|
||||
Object.values(controller.approvalController.state.pendingApprovals).forEach(
|
||||
|
@ -1,5 +1,7 @@
|
||||
import EventEmitter from 'events';
|
||||
import { ObservableStore } from '@metamask/obs-store';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import log from 'loglevel';
|
||||
import { METAMASK_CONTROLLER_EVENTS } from '../metamask-controller';
|
||||
import { MINUTE } from '../../../shared/constants/time';
|
||||
import { AUTO_LOCK_TIMEOUT_ALARM } from '../../../shared/constants/alarms';
|
||||
@ -8,8 +10,11 @@ import { isBeta } from '../../../ui/helpers/utils/build-types';
|
||||
import {
|
||||
ENVIRONMENT_TYPE_BACKGROUND,
|
||||
POLLING_TOKEN_ENVIRONMENT_TYPES,
|
||||
ORIGIN_METAMASK,
|
||||
} from '../../../shared/constants/app';
|
||||
|
||||
const APPROVAL_REQUEST_TYPE = 'unlock';
|
||||
|
||||
export default class AppStateController extends EventEmitter {
|
||||
/**
|
||||
* @param {object} opts
|
||||
@ -20,9 +25,9 @@ export default class AppStateController extends EventEmitter {
|
||||
isUnlocked,
|
||||
initState,
|
||||
onInactiveTimeout,
|
||||
showUnlockRequest,
|
||||
preferencesStore,
|
||||
qrHardwareStore,
|
||||
messenger,
|
||||
} = opts;
|
||||
super();
|
||||
|
||||
@ -59,8 +64,6 @@ export default class AppStateController extends EventEmitter {
|
||||
this.waitingForUnlock = [];
|
||||
addUnlockListener(this.handleUnlock.bind(this));
|
||||
|
||||
this._showUnlockRequest = showUnlockRequest;
|
||||
|
||||
preferencesStore.subscribe(({ preferences }) => {
|
||||
const currentState = this.store.getState();
|
||||
if (currentState.timeoutMinutes !== preferences.autoLockTimeLimit) {
|
||||
@ -74,6 +77,9 @@ export default class AppStateController extends EventEmitter {
|
||||
|
||||
const { preferences } = preferencesStore.getState();
|
||||
this._setInactiveTimeout(preferences.autoLockTimeLimit);
|
||||
|
||||
this.messagingSystem = messenger;
|
||||
this._approvalRequestId = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -108,7 +114,7 @@ export default class AppStateController extends EventEmitter {
|
||||
this.waitingForUnlock.push({ resolve });
|
||||
this.emit(METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE);
|
||||
if (shouldShowUnlockRequest) {
|
||||
this._showUnlockRequest();
|
||||
this._requestApproval();
|
||||
}
|
||||
}
|
||||
|
||||
@ -122,6 +128,8 @@ export default class AppStateController extends EventEmitter {
|
||||
}
|
||||
this.emit(METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE);
|
||||
}
|
||||
|
||||
this._acceptApproval();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -144,6 +152,37 @@ export default class AppStateController extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Record that the user has been shown the recovery phrase reminder.
|
||||
*/
|
||||
setRecoveryPhraseReminderHasBeenShown() {
|
||||
this.store.updateState({
|
||||
recoveryPhraseReminderHasBeenShown: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Record the timestamp of the last time the user has seen the recovery phrase reminder
|
||||
*
|
||||
* @param {number} lastShown - timestamp when user was last shown the reminder.
|
||||
*/
|
||||
setRecoveryPhraseReminderLastShown(lastShown) {
|
||||
this.store.updateState({
|
||||
recoveryPhraseReminderLastShown: lastShown,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Record the timestamp of the last time the user has acceoted the terms of use
|
||||
*
|
||||
* @param {number} lastAgreed - timestamp when user last accepted the terms of use
|
||||
*/
|
||||
setTermsOfUseLastAgreed(lastAgreed) {
|
||||
this.store.updateState({
|
||||
termsOfUseLastAgreed: lastAgreed,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Record the timestamp of the last time the user has seen the outdated browser warning
|
||||
*
|
||||
@ -349,4 +388,39 @@ export default class AppStateController extends EventEmitter {
|
||||
serviceWorkerLastActiveTime,
|
||||
});
|
||||
}
|
||||
|
||||
_requestApproval() {
|
||||
this._approvalRequestId = uuid();
|
||||
|
||||
this.messagingSystem
|
||||
.call(
|
||||
'ApprovalController:addRequest',
|
||||
{
|
||||
id: this._approvalRequestId,
|
||||
origin: ORIGIN_METAMASK,
|
||||
type: APPROVAL_REQUEST_TYPE,
|
||||
},
|
||||
true,
|
||||
)
|
||||
.catch(() => {
|
||||
// Intentionally ignored as promise not currently used
|
||||
});
|
||||
}
|
||||
|
||||
_acceptApproval() {
|
||||
if (!this._approvalRequestId) {
|
||||
log.error('Attempted to accept missing unlock approval request');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.messagingSystem.call(
|
||||
'ApprovalController:acceptRequest',
|
||||
this._approvalRequestId,
|
||||
);
|
||||
} catch (error) {
|
||||
log.error('Failed to accept transaction approval request', error);
|
||||
}
|
||||
|
||||
this._approvalRequestId = null;
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,158 @@
|
||||
import { ObservableStore } from '@metamask/obs-store';
|
||||
import log from 'loglevel';
|
||||
import { ORIGIN_METAMASK } from '../../../shared/constants/app';
|
||||
import AppStateController from './app-state';
|
||||
|
||||
jest.mock('loglevel');
|
||||
|
||||
let appStateController, mockStore;
|
||||
|
||||
describe('AppStateController', () => {
|
||||
mockStore = new ObservableStore();
|
||||
const createAppStateController = (initState = {}) => {
|
||||
return new AppStateController({
|
||||
addUnlockListener: jest.fn(),
|
||||
isUnlocked: jest.fn(() => true),
|
||||
initState,
|
||||
onInactiveTimeout: jest.fn(),
|
||||
showUnlockRequest: jest.fn(),
|
||||
preferencesStore: {
|
||||
subscribe: jest.fn(),
|
||||
getState: jest.fn(() => ({
|
||||
preferences: {
|
||||
autoLockTimeLimit: 0,
|
||||
},
|
||||
})),
|
||||
},
|
||||
qrHardwareStore: {
|
||||
subscribe: jest.fn(),
|
||||
},
|
||||
messenger: {
|
||||
call: jest.fn(() => ({
|
||||
catch: jest.fn(),
|
||||
})),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
appStateController = createAppStateController({ store: mockStore });
|
||||
});
|
||||
|
||||
describe('setOutdatedBrowserWarningLastShown', () => {
|
||||
it('should set the last shown time', () => {
|
||||
const appStateController = new AppStateController({
|
||||
it('sets the last shown time', () => {
|
||||
appStateController = createAppStateController();
|
||||
const date = new Date();
|
||||
|
||||
appStateController.setOutdatedBrowserWarningLastShown(date);
|
||||
|
||||
expect(
|
||||
appStateController.store.getState().outdatedBrowserWarningLastShown,
|
||||
).toStrictEqual(date);
|
||||
});
|
||||
|
||||
it('sets outdated browser warning last shown timestamp', () => {
|
||||
const lastShownTimestamp = Date.now();
|
||||
appStateController = createAppStateController();
|
||||
const updateStateSpy = jest.spyOn(
|
||||
appStateController.store,
|
||||
'updateState',
|
||||
);
|
||||
|
||||
appStateController.setOutdatedBrowserWarningLastShown(lastShownTimestamp);
|
||||
|
||||
expect(updateStateSpy).toHaveBeenCalledTimes(1);
|
||||
expect(updateStateSpy).toHaveBeenCalledWith({
|
||||
outdatedBrowserWarningLastShown: lastShownTimestamp,
|
||||
});
|
||||
|
||||
updateStateSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUnlockPromise', () => {
|
||||
it('waits for unlock if the extension is locked', async () => {
|
||||
appStateController = createAppStateController();
|
||||
const isUnlockedMock = jest
|
||||
.spyOn(appStateController, 'isUnlocked')
|
||||
.mockReturnValue(false);
|
||||
const waitForUnlockSpy = jest.spyOn(appStateController, 'waitForUnlock');
|
||||
|
||||
appStateController.getUnlockPromise(true);
|
||||
expect(isUnlockedMock).toHaveBeenCalled();
|
||||
expect(waitForUnlockSpy).toHaveBeenCalledWith(expect.any(Function), true);
|
||||
});
|
||||
|
||||
it('resolves immediately if the extension is already unlocked', async () => {
|
||||
appStateController = createAppStateController();
|
||||
const isUnlockedMock = jest
|
||||
.spyOn(appStateController, 'isUnlocked')
|
||||
.mockReturnValue(true);
|
||||
|
||||
await expect(
|
||||
appStateController.getUnlockPromise(false),
|
||||
).resolves.toBeUndefined();
|
||||
|
||||
expect(isUnlockedMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('waitForUnlock', () => {
|
||||
it('resolves immediately if already unlocked', async () => {
|
||||
const emitSpy = jest.spyOn(appStateController, 'emit');
|
||||
const resolveFn = jest.fn();
|
||||
appStateController.waitForUnlock(resolveFn, false);
|
||||
expect(emitSpy).toHaveBeenCalledWith('updateBadge');
|
||||
expect(appStateController.messagingSystem.call).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('creates approval request when waitForUnlock is called with shouldShowUnlockRequest as true', async () => {
|
||||
jest.spyOn(appStateController, 'isUnlocked').mockReturnValue(false);
|
||||
|
||||
const resolveFn = jest.fn();
|
||||
appStateController.waitForUnlock(resolveFn, true);
|
||||
|
||||
expect(appStateController.messagingSystem.call).toHaveBeenCalledTimes(1);
|
||||
expect(appStateController.messagingSystem.call).toHaveBeenCalledWith(
|
||||
'ApprovalController:addRequest',
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
origin: ORIGIN_METAMASK,
|
||||
type: 'unlock',
|
||||
}),
|
||||
true,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleUnlock', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(appStateController, 'isUnlocked').mockReturnValue(false);
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
it('accepts approval request revolving all the related promises', async () => {
|
||||
const emitSpy = jest.spyOn(appStateController, 'emit');
|
||||
const resolveFn = jest.fn();
|
||||
appStateController.waitForUnlock(resolveFn, true);
|
||||
|
||||
appStateController.handleUnlock();
|
||||
|
||||
expect(emitSpy).toHaveBeenCalled();
|
||||
expect(emitSpy).toHaveBeenCalledWith('updateBadge');
|
||||
expect(appStateController.messagingSystem.call).toHaveBeenCalled();
|
||||
expect(appStateController.messagingSystem.call).toHaveBeenCalledWith(
|
||||
'ApprovalController:acceptRequest',
|
||||
expect.any(String),
|
||||
);
|
||||
});
|
||||
|
||||
it('logs if rejecting approval request throws', async () => {
|
||||
appStateController._approvalRequestId = 'mock-approval-request-id';
|
||||
appStateController = new AppStateController({
|
||||
addUnlockListener: jest.fn(),
|
||||
isUnlocked: jest.fn(() => true),
|
||||
initState: {},
|
||||
onInactiveTimeout: jest.fn(),
|
||||
showUnlockRequest: jest.fn(),
|
||||
preferencesStore: {
|
||||
@ -20,14 +166,184 @@ describe('AppStateController', () => {
|
||||
qrHardwareStore: {
|
||||
subscribe: jest.fn(),
|
||||
},
|
||||
messenger: {
|
||||
call: jest.fn(() => {
|
||||
throw new Error('mock error');
|
||||
}),
|
||||
},
|
||||
});
|
||||
const date = new Date();
|
||||
|
||||
appStateController.setOutdatedBrowserWarningLastShown(date);
|
||||
appStateController.handleUnlock();
|
||||
|
||||
expect(log.error).toHaveBeenCalledTimes(1);
|
||||
expect(log.error).toHaveBeenCalledWith(
|
||||
'Attempted to accept missing unlock approval request',
|
||||
);
|
||||
});
|
||||
|
||||
it('returns without call messenger if no approval request in pending', async () => {
|
||||
const emitSpy = jest.spyOn(appStateController, 'emit');
|
||||
|
||||
appStateController.handleUnlock();
|
||||
|
||||
expect(emitSpy).toHaveBeenCalledTimes(0);
|
||||
expect(appStateController.messagingSystem.call).toHaveBeenCalledTimes(0);
|
||||
expect(log.error).toHaveBeenCalledTimes(1);
|
||||
expect(log.error).toHaveBeenCalledWith(
|
||||
'Attempted to accept missing unlock approval request',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setDefaultHomeActiveTabName', () => {
|
||||
it('sets the default home tab name', () => {
|
||||
appStateController.setDefaultHomeActiveTabName('testTabName');
|
||||
expect(appStateController.store.getState().defaultHomeActiveTabName).toBe(
|
||||
'testTabName',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setConnectedStatusPopoverHasBeenShown', () => {
|
||||
it('sets connected status popover as shown', () => {
|
||||
appStateController.setConnectedStatusPopoverHasBeenShown();
|
||||
expect(
|
||||
appStateController.store.getState().connectedStatusPopoverHasBeenShown,
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setRecoveryPhraseReminderHasBeenShown', () => {
|
||||
it('sets recovery phrase reminder as shown', () => {
|
||||
appStateController.setRecoveryPhraseReminderHasBeenShown();
|
||||
expect(
|
||||
appStateController.store.getState().recoveryPhraseReminderHasBeenShown,
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setRecoveryPhraseReminderLastShown', () => {
|
||||
it('sets the last shown time of recovery phrase reminder', () => {
|
||||
const timestamp = Date.now();
|
||||
appStateController.setRecoveryPhraseReminderLastShown(timestamp);
|
||||
|
||||
expect(
|
||||
appStateController.store.getState().outdatedBrowserWarningLastShown,
|
||||
).toStrictEqual(date);
|
||||
appStateController.store.getState().recoveryPhraseReminderLastShown,
|
||||
).toBe(timestamp);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setLastActiveTime', () => {
|
||||
it('sets the last active time to the current time', () => {
|
||||
const spy = jest.spyOn(appStateController, '_resetTimer');
|
||||
appStateController.setLastActiveTime();
|
||||
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('setBrowserEnvironment', () => {
|
||||
it('sets the current browser and OS environment', () => {
|
||||
appStateController.setBrowserEnvironment('Windows', 'Chrome');
|
||||
expect(
|
||||
appStateController.store.getState().browserEnvironment,
|
||||
).toStrictEqual({
|
||||
os: 'Windows',
|
||||
browser: 'Chrome',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('addPollingToken', () => {
|
||||
it('adds a pollingToken for a given environmentType', () => {
|
||||
const pollingTokenType = 'popupGasPollTokens';
|
||||
appStateController.addPollingToken('token1', pollingTokenType);
|
||||
expect(appStateController.store.getState()[pollingTokenType]).toContain(
|
||||
'token1',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('removePollingToken', () => {
|
||||
it('removes a pollingToken for a given environmentType', () => {
|
||||
const pollingTokenType = 'popupGasPollTokens';
|
||||
appStateController.addPollingToken('token1', pollingTokenType);
|
||||
appStateController.removePollingToken('token1', pollingTokenType);
|
||||
expect(
|
||||
appStateController.store.getState()[pollingTokenType],
|
||||
).not.toContain('token1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('clearPollingTokens', () => {
|
||||
it('clears all pollingTokens', () => {
|
||||
appStateController.addPollingToken('token1', 'popupGasPollTokens');
|
||||
appStateController.addPollingToken('token2', 'notificationGasPollTokens');
|
||||
appStateController.addPollingToken('token3', 'fullScreenGasPollTokens');
|
||||
appStateController.clearPollingTokens();
|
||||
|
||||
expect(
|
||||
appStateController.store.getState().popupGasPollTokens,
|
||||
).toStrictEqual([]);
|
||||
expect(
|
||||
appStateController.store.getState().notificationGasPollTokens,
|
||||
).toStrictEqual([]);
|
||||
expect(
|
||||
appStateController.store.getState().fullScreenGasPollTokens,
|
||||
).toStrictEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setShowTestnetMessageInDropdown', () => {
|
||||
it('sets whether the testnet dismissal link should be shown in the network dropdown', () => {
|
||||
appStateController.setShowTestnetMessageInDropdown(true);
|
||||
expect(
|
||||
appStateController.store.getState().showTestnetMessageInDropdown,
|
||||
).toBe(true);
|
||||
|
||||
appStateController.setShowTestnetMessageInDropdown(false);
|
||||
expect(
|
||||
appStateController.store.getState().showTestnetMessageInDropdown,
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setShowBetaHeader', () => {
|
||||
it('sets whether the beta notification heading on the home page', () => {
|
||||
appStateController.setShowBetaHeader(true);
|
||||
expect(appStateController.store.getState().showBetaHeader).toBe(true);
|
||||
|
||||
appStateController.setShowBetaHeader(false);
|
||||
expect(appStateController.store.getState().showBetaHeader).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setCurrentPopupId', () => {
|
||||
it('sets the currentPopupId in the appState', () => {
|
||||
const popupId = 'popup1';
|
||||
|
||||
appStateController.setCurrentPopupId(popupId);
|
||||
expect(appStateController.store.getState().currentPopupId).toBe(popupId);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCurrentPopupId', () => {
|
||||
it('retrieves the currentPopupId saved in the appState', () => {
|
||||
const popupId = 'popup1';
|
||||
|
||||
appStateController.setCurrentPopupId(popupId);
|
||||
expect(appStateController.getCurrentPopupId()).toBe(popupId);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setFirstTimeUsedNetwork', () => {
|
||||
it('updates the array of the first time used networks', () => {
|
||||
const chainId = '0x1';
|
||||
|
||||
appStateController.setFirstTimeUsedNetwork(chainId);
|
||||
expect(appStateController.store.getState().usedNetworks[chainId]).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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 = {
|
||||
|
400
app/scripts/controllers/encryption-public-key.test.ts
Normal file
400
app/scripts/controllers/encryption-public-key.test.ts
Normal file
@ -0,0 +1,400 @@
|
||||
import { EncryptionPublicKeyManager } from '@metamask/message-manager';
|
||||
import {
|
||||
AbstractMessage,
|
||||
OriginalRequest,
|
||||
} from '@metamask/message-manager/dist/AbstractMessageManager';
|
||||
import { KeyringType } from '../../../shared/constants/keyring';
|
||||
import { MetaMetricsEventCategory } from '../../../shared/constants/metametrics';
|
||||
import EncryptionPublicKeyController, {
|
||||
EncryptionPublicKeyControllerMessenger,
|
||||
EncryptionPublicKeyControllerOptions,
|
||||
} from './encryption-public-key';
|
||||
|
||||
jest.mock('@metamask/message-manager', () => ({
|
||||
EncryptionPublicKeyManager: jest.fn(),
|
||||
}));
|
||||
|
||||
const messageIdMock = '123';
|
||||
const messageIdMock2 = '456';
|
||||
const stateMock = { test: 123 };
|
||||
const addressMock = '0xc38bf1ad06ef69f0c04e29dbeb4152b4175f0a8d';
|
||||
const publicKeyMock = '32762347862378feb87123781623a=';
|
||||
const keyringMock = { type: KeyringType.hdKeyTree };
|
||||
|
||||
const messageParamsMock = {
|
||||
from: addressMock,
|
||||
origin: 'http://test.com',
|
||||
data: addressMock,
|
||||
metamaskId: messageIdMock,
|
||||
};
|
||||
|
||||
const messageMock = {
|
||||
id: messageIdMock,
|
||||
time: 123,
|
||||
status: 'unapproved',
|
||||
type: 'testType',
|
||||
rawSig: undefined,
|
||||
} as any as AbstractMessage;
|
||||
|
||||
const coreMessageMock = {
|
||||
...messageMock,
|
||||
messageParams: messageParamsMock,
|
||||
};
|
||||
|
||||
const stateMessageMock = {
|
||||
...messageMock,
|
||||
msgParams: addressMock,
|
||||
origin: messageParamsMock.origin,
|
||||
};
|
||||
|
||||
const requestMock = {
|
||||
origin: 'http://test2.com',
|
||||
} as OriginalRequest;
|
||||
|
||||
const createMessengerMock = () =>
|
||||
({
|
||||
registerActionHandler: jest.fn(),
|
||||
publish: jest.fn(),
|
||||
call: jest.fn(),
|
||||
} as any as jest.Mocked<EncryptionPublicKeyControllerMessenger>);
|
||||
|
||||
const createEncryptionPublicKeyManagerMock = <T>() =>
|
||||
({
|
||||
getUnapprovedMessages: jest.fn(),
|
||||
getUnapprovedMessagesCount: jest.fn(),
|
||||
addUnapprovedMessageAsync: jest.fn(),
|
||||
approveMessage: jest.fn(),
|
||||
setMessageStatusAndResult: jest.fn(),
|
||||
rejectMessage: jest.fn(),
|
||||
subscribe: jest.fn(),
|
||||
update: jest.fn(),
|
||||
hub: {
|
||||
on: jest.fn(),
|
||||
},
|
||||
} as any as jest.Mocked<T>);
|
||||
|
||||
const createKeyringControllerMock = () => ({
|
||||
getKeyringForAccount: jest.fn(),
|
||||
getEncryptionPublicKey: jest.fn(),
|
||||
});
|
||||
|
||||
describe('EncryptionPublicKeyController', () => {
|
||||
let encryptionPublicKeyController: EncryptionPublicKeyController;
|
||||
|
||||
const encryptionPublicKeyManagerConstructorMock =
|
||||
EncryptionPublicKeyManager as jest.MockedClass<
|
||||
typeof EncryptionPublicKeyManager
|
||||
>;
|
||||
const encryptionPublicKeyManagerMock =
|
||||
createEncryptionPublicKeyManagerMock<EncryptionPublicKeyManager>();
|
||||
const messengerMock = createMessengerMock();
|
||||
const keyringControllerMock = createKeyringControllerMock();
|
||||
const getStateMock = jest.fn();
|
||||
const metricsEventMock = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
|
||||
encryptionPublicKeyManagerConstructorMock.mockReturnValue(
|
||||
encryptionPublicKeyManagerMock,
|
||||
);
|
||||
|
||||
encryptionPublicKeyController = new EncryptionPublicKeyController({
|
||||
messenger: messengerMock as any,
|
||||
keyringController: keyringControllerMock as any,
|
||||
getState: getStateMock as any,
|
||||
metricsEvent: metricsEventMock as any,
|
||||
} as EncryptionPublicKeyControllerOptions);
|
||||
});
|
||||
|
||||
describe('unapprovedMsgCount', () => {
|
||||
it('returns value from message manager getter', () => {
|
||||
encryptionPublicKeyManagerMock.getUnapprovedMessagesCount.mockReturnValueOnce(
|
||||
10,
|
||||
);
|
||||
expect(encryptionPublicKeyController.unapprovedMsgCount).toBe(10);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resetState', () => {
|
||||
it('sets state to initial state', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
encryptionPublicKeyController.update(() => ({
|
||||
unapprovedEncryptionPublicKeyMsgs: {
|
||||
[messageIdMock]: messageMock,
|
||||
} as any,
|
||||
unapprovedEncryptionPublicKeyMsgCount: 1,
|
||||
}));
|
||||
|
||||
encryptionPublicKeyController.resetState();
|
||||
|
||||
expect(encryptionPublicKeyController.state).toEqual({
|
||||
unapprovedEncryptionPublicKeyMsgs: {},
|
||||
unapprovedEncryptionPublicKeyMsgCount: 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('rejectUnapproved', () => {
|
||||
beforeEach(() => {
|
||||
const messages = {
|
||||
[messageIdMock]: messageMock,
|
||||
[messageIdMock2]: messageMock,
|
||||
};
|
||||
encryptionPublicKeyManagerMock.getUnapprovedMessages.mockReturnValueOnce(
|
||||
messages as any,
|
||||
);
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
encryptionPublicKeyController.update(() => ({
|
||||
unapprovedEncryptionPublicKeyMsgs: messages as any,
|
||||
}));
|
||||
});
|
||||
|
||||
it('rejects all messages in the message manager', () => {
|
||||
encryptionPublicKeyController.rejectUnapproved('Test Reason');
|
||||
expect(
|
||||
encryptionPublicKeyManagerMock.rejectMessage,
|
||||
).toHaveBeenCalledTimes(2);
|
||||
expect(encryptionPublicKeyManagerMock.rejectMessage).toHaveBeenCalledWith(
|
||||
messageIdMock,
|
||||
);
|
||||
expect(encryptionPublicKeyManagerMock.rejectMessage).toHaveBeenCalledWith(
|
||||
messageIdMock2,
|
||||
);
|
||||
});
|
||||
|
||||
it('fires metrics event with reject reason', () => {
|
||||
encryptionPublicKeyController.rejectUnapproved('Test Reason');
|
||||
expect(metricsEventMock).toHaveBeenCalledTimes(2);
|
||||
expect(metricsEventMock).toHaveBeenLastCalledWith({
|
||||
event: 'Test Reason',
|
||||
category: MetaMetricsEventCategory.Messages,
|
||||
properties: {
|
||||
action: 'Encryption public key Request',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('clearUnapproved', () => {
|
||||
it('resets state in all message managers', () => {
|
||||
encryptionPublicKeyController.clearUnapproved();
|
||||
|
||||
const defaultState = {
|
||||
unapprovedMessages: {},
|
||||
unapprovedMessagesCount: 0,
|
||||
};
|
||||
|
||||
expect(encryptionPublicKeyManagerMock.update).toHaveBeenCalledTimes(1);
|
||||
expect(encryptionPublicKeyManagerMock.update).toHaveBeenCalledWith(
|
||||
defaultState,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('newRequestEncryptionPublicKey', () => {
|
||||
it.each([
|
||||
['Ledger', KeyringType.ledger],
|
||||
['Trezor', KeyringType.trezor],
|
||||
['Lattice', KeyringType.lattice],
|
||||
['QR hardware', KeyringType.qr],
|
||||
])(
|
||||
'throws if keyring is not supported',
|
||||
async (keyringName, keyringType) => {
|
||||
keyringControllerMock.getKeyringForAccount.mockResolvedValueOnce({
|
||||
type: keyringType,
|
||||
});
|
||||
|
||||
await expect(
|
||||
encryptionPublicKeyController.newRequestEncryptionPublicKey(
|
||||
addressMock,
|
||||
requestMock,
|
||||
),
|
||||
).rejects.toThrowError(
|
||||
`${keyringName} does not support eth_getEncryptionPublicKey.`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it('adds message to message manager', async () => {
|
||||
keyringControllerMock.getKeyringForAccount.mockResolvedValueOnce(
|
||||
keyringMock,
|
||||
);
|
||||
|
||||
await encryptionPublicKeyController.newRequestEncryptionPublicKey(
|
||||
addressMock,
|
||||
requestMock,
|
||||
);
|
||||
|
||||
expect(
|
||||
encryptionPublicKeyManagerMock.addUnapprovedMessageAsync,
|
||||
).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
encryptionPublicKeyManagerMock.addUnapprovedMessageAsync,
|
||||
).toHaveBeenCalledWith({ from: addressMock }, requestMock);
|
||||
});
|
||||
});
|
||||
|
||||
describe('encryptionPublicKey', () => {
|
||||
beforeEach(() => {
|
||||
encryptionPublicKeyManagerMock.approveMessage.mockResolvedValueOnce({
|
||||
from: messageParamsMock.data,
|
||||
});
|
||||
|
||||
keyringControllerMock.getEncryptionPublicKey.mockResolvedValueOnce(
|
||||
publicKeyMock,
|
||||
);
|
||||
});
|
||||
|
||||
it('approves message and signs', async () => {
|
||||
await encryptionPublicKeyController.encryptionPublicKey(
|
||||
messageParamsMock,
|
||||
);
|
||||
|
||||
expect(
|
||||
keyringControllerMock.getEncryptionPublicKey,
|
||||
).toHaveBeenCalledTimes(1);
|
||||
expect(keyringControllerMock.getEncryptionPublicKey).toHaveBeenCalledWith(
|
||||
messageParamsMock.data,
|
||||
);
|
||||
|
||||
expect(
|
||||
encryptionPublicKeyManagerMock.setMessageStatusAndResult,
|
||||
).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
encryptionPublicKeyManagerMock.setMessageStatusAndResult,
|
||||
).toHaveBeenCalledWith(
|
||||
messageParamsMock.metamaskId,
|
||||
publicKeyMock,
|
||||
'received',
|
||||
);
|
||||
});
|
||||
|
||||
it('returns current state', async () => {
|
||||
getStateMock.mockReturnValueOnce(stateMock);
|
||||
expect(
|
||||
await encryptionPublicKeyController.encryptionPublicKey(
|
||||
messageParamsMock,
|
||||
),
|
||||
).toEqual(stateMock);
|
||||
});
|
||||
|
||||
it('accepts approval', async () => {
|
||||
await encryptionPublicKeyController.encryptionPublicKey(
|
||||
messageParamsMock,
|
||||
);
|
||||
|
||||
expect(messengerMock.call).toHaveBeenCalledTimes(1);
|
||||
expect(messengerMock.call).toHaveBeenCalledWith(
|
||||
'ApprovalController:acceptRequest',
|
||||
messageParamsMock.metamaskId,
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects message on error', async () => {
|
||||
keyringControllerMock.getEncryptionPublicKey.mockReset();
|
||||
keyringControllerMock.getEncryptionPublicKey.mockRejectedValue(
|
||||
new Error('Test Error'),
|
||||
);
|
||||
|
||||
await expect(
|
||||
encryptionPublicKeyController.encryptionPublicKey(messageParamsMock),
|
||||
).rejects.toThrow('Test Error');
|
||||
|
||||
expect(
|
||||
encryptionPublicKeyManagerMock.rejectMessage,
|
||||
).toHaveBeenCalledTimes(1);
|
||||
expect(encryptionPublicKeyManagerMock.rejectMessage).toHaveBeenCalledWith(
|
||||
messageParamsMock.metamaskId,
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects approval on error', async () => {
|
||||
keyringControllerMock.getEncryptionPublicKey.mockReset();
|
||||
keyringControllerMock.getEncryptionPublicKey.mockRejectedValue(
|
||||
new Error('Test Error'),
|
||||
);
|
||||
|
||||
await expect(
|
||||
encryptionPublicKeyController.encryptionPublicKey(messageParamsMock),
|
||||
).rejects.toThrow('Test Error');
|
||||
|
||||
expect(messengerMock.call).toHaveBeenCalledTimes(1);
|
||||
expect(messengerMock.call).toHaveBeenCalledWith(
|
||||
'ApprovalController:rejectRequest',
|
||||
messageParamsMock.metamaskId,
|
||||
'Cancel',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('cancelEncryptionPublicKey', () => {
|
||||
it('rejects message using message manager', async () => {
|
||||
encryptionPublicKeyController.cancelEncryptionPublicKey(messageIdMock);
|
||||
|
||||
expect(
|
||||
encryptionPublicKeyManagerMock.rejectMessage,
|
||||
).toHaveBeenCalledTimes(1);
|
||||
expect(encryptionPublicKeyManagerMock.rejectMessage).toHaveBeenCalledWith(
|
||||
messageParamsMock.metamaskId,
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects approval using approval controller', async () => {
|
||||
encryptionPublicKeyController.cancelEncryptionPublicKey(messageIdMock);
|
||||
|
||||
expect(messengerMock.call).toHaveBeenCalledTimes(1);
|
||||
expect(messengerMock.call).toHaveBeenCalledWith(
|
||||
'ApprovalController:rejectRequest',
|
||||
messageParamsMock.metamaskId,
|
||||
'Cancel',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('message manager events', () => {
|
||||
it('bubbles update badge event from EncryptionPublicKeyManager', () => {
|
||||
const mockListener = jest.fn();
|
||||
|
||||
encryptionPublicKeyController.hub.on('updateBadge', mockListener);
|
||||
(encryptionPublicKeyManagerMock.hub.on as any).mock.calls[0][1]();
|
||||
|
||||
expect(mockListener).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('requires approval on unapproved message event from EncryptionPublicKeyManager', () => {
|
||||
messengerMock.call.mockResolvedValueOnce({});
|
||||
|
||||
(encryptionPublicKeyManagerMock.hub.on as any).mock.calls[1][1](
|
||||
messageParamsMock,
|
||||
);
|
||||
|
||||
expect(messengerMock.call).toHaveBeenCalledTimes(1);
|
||||
expect(messengerMock.call).toHaveBeenCalledWith(
|
||||
'ApprovalController:addRequest',
|
||||
{
|
||||
id: messageIdMock,
|
||||
origin: messageParamsMock.origin,
|
||||
type: 'eth_getEncryptionPublicKey',
|
||||
},
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('updates state on EncryptionPublicKeyManager state change', async () => {
|
||||
await encryptionPublicKeyManagerMock.subscribe.mock.calls[0][0]({
|
||||
unapprovedMessages: { [messageIdMock]: coreMessageMock as any },
|
||||
unapprovedMessagesCount: 3,
|
||||
});
|
||||
|
||||
expect(encryptionPublicKeyController.state).toEqual({
|
||||
unapprovedEncryptionPublicKeyMsgs: {
|
||||
[messageIdMock]: stateMessageMock as any,
|
||||
},
|
||||
unapprovedEncryptionPublicKeyMsgCount: 3,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
421
app/scripts/controllers/encryption-public-key.ts
Normal file
421
app/scripts/controllers/encryption-public-key.ts
Normal file
@ -0,0 +1,421 @@
|
||||
import EventEmitter from 'events';
|
||||
import log from 'loglevel';
|
||||
import {
|
||||
EncryptionPublicKeyManager,
|
||||
EncryptionPublicKeyParamsMetamask,
|
||||
} from '@metamask/message-manager';
|
||||
import { KeyringController } from '@metamask/eth-keyring-controller';
|
||||
import {
|
||||
AbstractMessageManager,
|
||||
AbstractMessage,
|
||||
MessageManagerState,
|
||||
AbstractMessageParams,
|
||||
AbstractMessageParamsMetamask,
|
||||
OriginalRequest,
|
||||
} from '@metamask/message-manager/dist/AbstractMessageManager';
|
||||
import {
|
||||
BaseControllerV2,
|
||||
RestrictedControllerMessenger,
|
||||
} from '@metamask/base-controller';
|
||||
import { Patch } from 'immer';
|
||||
import {
|
||||
AcceptRequest,
|
||||
AddApprovalRequest,
|
||||
RejectRequest,
|
||||
} from '@metamask/approval-controller';
|
||||
import { MetaMetricsEventCategory } from '../../../shared/constants/metametrics';
|
||||
import { KeyringType } from '../../../shared/constants/keyring';
|
||||
import { ORIGIN_METAMASK } from '../../../shared/constants/app';
|
||||
|
||||
const controllerName = 'EncryptionPublicKeyController';
|
||||
const methodNameGetEncryptionPublicKey = 'eth_getEncryptionPublicKey';
|
||||
|
||||
const stateMetadata = {
|
||||
unapprovedEncryptionPublicKeyMsgs: { persist: false, anonymous: false },
|
||||
unapprovedEncryptionPublicKeyMsgCount: { persist: false, anonymous: false },
|
||||
};
|
||||
|
||||
const getDefaultState = () => ({
|
||||
unapprovedEncryptionPublicKeyMsgs: {},
|
||||
unapprovedEncryptionPublicKeyMsgCount: 0,
|
||||
});
|
||||
|
||||
export type CoreMessage = AbstractMessage & {
|
||||
messageParams: AbstractMessageParams;
|
||||
};
|
||||
|
||||
export type StateMessage = Required<
|
||||
Omit<AbstractMessage, 'securityProviderResponse'>
|
||||
> & {
|
||||
msgParams: string;
|
||||
};
|
||||
|
||||
export type EncryptionPublicKeyControllerState = {
|
||||
unapprovedEncryptionPublicKeyMsgs: Record<string, StateMessage>;
|
||||
unapprovedEncryptionPublicKeyMsgCount: number;
|
||||
};
|
||||
|
||||
export type GetEncryptionPublicKeyState = {
|
||||
type: `${typeof controllerName}:getState`;
|
||||
handler: () => EncryptionPublicKeyControllerState;
|
||||
};
|
||||
|
||||
export type EncryptionPublicKeyStateChange = {
|
||||
type: `${typeof controllerName}:stateChange`;
|
||||
payload: [EncryptionPublicKeyControllerState, Patch[]];
|
||||
};
|
||||
|
||||
export type EncryptionPublicKeyControllerActions = GetEncryptionPublicKeyState;
|
||||
|
||||
export type EncryptionPublicKeyControllerEvents =
|
||||
EncryptionPublicKeyStateChange;
|
||||
|
||||
type AllowedActions = AddApprovalRequest | AcceptRequest | RejectRequest;
|
||||
|
||||
export type EncryptionPublicKeyControllerMessenger =
|
||||
RestrictedControllerMessenger<
|
||||
typeof controllerName,
|
||||
EncryptionPublicKeyControllerActions | AllowedActions,
|
||||
EncryptionPublicKeyControllerEvents,
|
||||
AllowedActions['type'],
|
||||
never
|
||||
>;
|
||||
|
||||
export type EncryptionPublicKeyControllerOptions = {
|
||||
messenger: EncryptionPublicKeyControllerMessenger;
|
||||
keyringController: KeyringController;
|
||||
getState: () => any;
|
||||
metricsEvent: (payload: any, options?: any) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Controller for requesting encryption public key requests requiring user approval.
|
||||
*/
|
||||
export default class EncryptionPublicKeyController extends BaseControllerV2<
|
||||
typeof controllerName,
|
||||
EncryptionPublicKeyControllerState,
|
||||
EncryptionPublicKeyControllerMessenger
|
||||
> {
|
||||
hub: EventEmitter;
|
||||
|
||||
private _keyringController: KeyringController;
|
||||
|
||||
private _getState: () => any;
|
||||
|
||||
private _encryptionPublicKeyManager: EncryptionPublicKeyManager;
|
||||
|
||||
private _metricsEvent: (payload: any, options?: any) => void;
|
||||
|
||||
/**
|
||||
* Construct a EncryptionPublicKey controller.
|
||||
*
|
||||
* @param options - The controller options.
|
||||
* @param options.messenger - The restricted controller messenger for the EncryptionPublicKey controller.
|
||||
* @param options.keyringController - An instance of a keyring controller used to extract the encryption public key.
|
||||
* @param options.getState - Callback to retrieve all user state.
|
||||
* @param options.metricsEvent - A function for emitting a metric event.
|
||||
*/
|
||||
constructor({
|
||||
messenger,
|
||||
keyringController,
|
||||
getState,
|
||||
metricsEvent,
|
||||
}: EncryptionPublicKeyControllerOptions) {
|
||||
super({
|
||||
name: controllerName,
|
||||
metadata: stateMetadata,
|
||||
messenger,
|
||||
state: getDefaultState(),
|
||||
});
|
||||
|
||||
this._keyringController = keyringController;
|
||||
this._getState = getState;
|
||||
this._metricsEvent = metricsEvent;
|
||||
|
||||
this.hub = new EventEmitter();
|
||||
this._encryptionPublicKeyManager = new EncryptionPublicKeyManager(
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
['received'],
|
||||
);
|
||||
|
||||
this._encryptionPublicKeyManager.hub.on('updateBadge', () => {
|
||||
this.hub.emit('updateBadge');
|
||||
});
|
||||
|
||||
this._encryptionPublicKeyManager.hub.on(
|
||||
'unapprovedMessage',
|
||||
(msgParams: AbstractMessageParamsMetamask) => {
|
||||
this._requestApproval(msgParams, methodNameGetEncryptionPublicKey);
|
||||
},
|
||||
);
|
||||
|
||||
this._subscribeToMessageState(
|
||||
this._encryptionPublicKeyManager,
|
||||
(state, newMessages, messageCount) => {
|
||||
state.unapprovedEncryptionPublicKeyMsgs = newMessages;
|
||||
state.unapprovedEncryptionPublicKeyMsgCount = messageCount;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A getter for the number of 'unapproved' Messages in this.messages
|
||||
*
|
||||
* @returns The number of 'unapproved' Messages in this.messages
|
||||
*/
|
||||
get unapprovedMsgCount(): number {
|
||||
return this._encryptionPublicKeyManager.getUnapprovedMessagesCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the controller state to the initial state.
|
||||
*/
|
||||
resetState() {
|
||||
this.update(() => getDefaultState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a Dapp uses the eth_getEncryptionPublicKey method, to request user approval.
|
||||
*
|
||||
* @param address - The address from the encryption public key will be extracted.
|
||||
* @param [req] - The original request, containing the origin.
|
||||
*/
|
||||
async newRequestEncryptionPublicKey(
|
||||
address: string,
|
||||
req: OriginalRequest,
|
||||
): Promise<string> {
|
||||
const keyring = await this._keyringController.getKeyringForAccount(address);
|
||||
|
||||
switch (keyring.type) {
|
||||
case KeyringType.ledger: {
|
||||
return new Promise((_, reject) => {
|
||||
reject(
|
||||
new Error('Ledger does not support eth_getEncryptionPublicKey.'),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
case KeyringType.trezor: {
|
||||
return new Promise((_, reject) => {
|
||||
reject(
|
||||
new Error('Trezor does not support eth_getEncryptionPublicKey.'),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
case KeyringType.lattice: {
|
||||
return new Promise((_, reject) => {
|
||||
reject(
|
||||
new Error('Lattice does not support eth_getEncryptionPublicKey.'),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
case KeyringType.qr: {
|
||||
return Promise.reject(
|
||||
new Error('QR hardware does not support eth_getEncryptionPublicKey.'),
|
||||
);
|
||||
}
|
||||
|
||||
default: {
|
||||
return this._encryptionPublicKeyManager.addUnapprovedMessageAsync(
|
||||
{ from: address },
|
||||
req,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Signifies a user's approval to receiving encryption public key in queue.
|
||||
*
|
||||
* @param msgParams - The params of the message to receive & return to the Dapp.
|
||||
* @returns A full state update.
|
||||
*/
|
||||
async encryptionPublicKey(msgParams: EncryptionPublicKeyParamsMetamask) {
|
||||
log.info('MetaMaskController - encryptionPublicKey');
|
||||
const messageId = msgParams.metamaskId as string;
|
||||
// sets the status op the message to 'approved'
|
||||
// and removes the metamaskId for decryption
|
||||
try {
|
||||
const cleanMessageParams =
|
||||
await this._encryptionPublicKeyManager.approveMessage(msgParams);
|
||||
|
||||
// EncryptionPublicKey message
|
||||
const publicKey = await this._keyringController.getEncryptionPublicKey(
|
||||
cleanMessageParams.from,
|
||||
);
|
||||
|
||||
// tells the listener that the message has been processed
|
||||
// and can be returned to the dapp
|
||||
this._encryptionPublicKeyManager.setMessageStatusAndResult(
|
||||
messageId,
|
||||
publicKey,
|
||||
'received',
|
||||
);
|
||||
|
||||
this._acceptApproval(messageId);
|
||||
|
||||
return this._getState();
|
||||
} catch (error) {
|
||||
log.info(
|
||||
'MetaMaskController - eth_getEncryptionPublicKey failed.',
|
||||
error,
|
||||
);
|
||||
this._cancelAbstractMessage(this._encryptionPublicKeyManager, messageId);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to cancel a message submitted via eth_getEncryptionPublicKey.
|
||||
*
|
||||
* @param msgId - The id of the message to cancel.
|
||||
*/
|
||||
cancelEncryptionPublicKey(msgId: string) {
|
||||
this._cancelAbstractMessage(this._encryptionPublicKeyManager, msgId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reject all unapproved messages of any type.
|
||||
*
|
||||
* @param reason - A message to indicate why.
|
||||
*/
|
||||
rejectUnapproved(reason?: string) {
|
||||
Object.keys(
|
||||
this._encryptionPublicKeyManager.getUnapprovedMessages(),
|
||||
).forEach((messageId) => {
|
||||
this._cancelAbstractMessage(
|
||||
this._encryptionPublicKeyManager,
|
||||
messageId,
|
||||
reason,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all unapproved messages from memory.
|
||||
*/
|
||||
clearUnapproved() {
|
||||
this._encryptionPublicKeyManager.update({
|
||||
unapprovedMessages: {},
|
||||
unapprovedMessagesCount: 0,
|
||||
});
|
||||
}
|
||||
|
||||
private _cancelAbstractMessage(
|
||||
messageManager: AbstractMessageManager<
|
||||
AbstractMessage,
|
||||
AbstractMessageParams,
|
||||
AbstractMessageParamsMetamask
|
||||
>,
|
||||
messageId: string,
|
||||
reason?: string,
|
||||
) {
|
||||
if (reason) {
|
||||
this._metricsEvent({
|
||||
event: reason,
|
||||
category: MetaMetricsEventCategory.Messages,
|
||||
properties: {
|
||||
action: 'Encryption public key Request',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
messageManager.rejectMessage(messageId);
|
||||
this._rejectApproval(messageId);
|
||||
|
||||
return this._getState();
|
||||
}
|
||||
|
||||
private _subscribeToMessageState(
|
||||
messageManager: AbstractMessageManager<
|
||||
AbstractMessage,
|
||||
AbstractMessageParams,
|
||||
AbstractMessageParamsMetamask
|
||||
>,
|
||||
updateState: (
|
||||
state: EncryptionPublicKeyControllerState,
|
||||
newMessages: Record<string, StateMessage>,
|
||||
messageCount: number,
|
||||
) => void,
|
||||
) {
|
||||
messageManager.subscribe(
|
||||
async (state: MessageManagerState<AbstractMessage>) => {
|
||||
const newMessages = await this._migrateMessages(
|
||||
state.unapprovedMessages as any,
|
||||
);
|
||||
this.update((draftState) => {
|
||||
updateState(draftState, newMessages, state.unapprovedMessagesCount);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
private async _migrateMessages(
|
||||
coreMessages: Record<string, CoreMessage>,
|
||||
): Promise<Record<string, StateMessage>> {
|
||||
const stateMessages: Record<string, StateMessage> = {};
|
||||
|
||||
for (const messageId of Object.keys(coreMessages)) {
|
||||
const coreMessage = coreMessages[messageId];
|
||||
const stateMessage = await this._migrateMessage(coreMessage);
|
||||
|
||||
stateMessages[messageId] = stateMessage;
|
||||
}
|
||||
|
||||
return stateMessages;
|
||||
}
|
||||
|
||||
private async _migrateMessage(
|
||||
coreMessage: CoreMessage,
|
||||
): Promise<StateMessage> {
|
||||
const { messageParams, ...coreMessageData } = coreMessage;
|
||||
|
||||
// Core message managers use messageParams but frontend uses msgParams with lots of references
|
||||
const stateMessage = {
|
||||
...coreMessageData,
|
||||
rawSig: coreMessage.rawSig as string,
|
||||
msgParams: messageParams.from,
|
||||
origin: messageParams.origin,
|
||||
};
|
||||
|
||||
return stateMessage;
|
||||
}
|
||||
|
||||
private _requestApproval(
|
||||
msgParams: AbstractMessageParamsMetamask,
|
||||
type: string,
|
||||
) {
|
||||
const id = msgParams.metamaskId as string;
|
||||
const origin = msgParams.origin || ORIGIN_METAMASK;
|
||||
|
||||
this.messagingSystem
|
||||
.call(
|
||||
'ApprovalController:addRequest',
|
||||
{
|
||||
id,
|
||||
origin,
|
||||
type,
|
||||
},
|
||||
true,
|
||||
)
|
||||
.catch(() => {
|
||||
// Intentionally ignored as promise not currently used
|
||||
});
|
||||
}
|
||||
|
||||
private _acceptApproval(messageId: string) {
|
||||
this.messagingSystem.call('ApprovalController:acceptRequest', messageId);
|
||||
}
|
||||
|
||||
private _rejectApproval(messageId: string) {
|
||||
this.messagingSystem.call(
|
||||
'ApprovalController:rejectRequest',
|
||||
messageId,
|
||||
'Cancel',
|
||||
);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
1168
app/scripts/controllers/network/network-controller.ts
Normal file
1168
app/scripts/controllers/network/network-controller.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,15 @@
|
||||
/* eslint-disable jest/require-top-level-describe, jest/no-export */
|
||||
|
||||
import { withMockedCommunications, withNetworkClient } from './helpers';
|
||||
import {
|
||||
ProviderType,
|
||||
withMockedCommunications,
|
||||
withNetworkClient,
|
||||
} from './helpers';
|
||||
|
||||
type TestsForRpcMethodThatCheckForBlockHashInResponseOptions = {
|
||||
providerType: ProviderType;
|
||||
numberOfParameters: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines tests which exercise the behavior exhibited by an RPC method that
|
||||
@ -15,8 +24,11 @@ import { withMockedCommunications, withNetworkClient } from './helpers';
|
||||
* either `infura` or `custom` (default: "infura").
|
||||
*/
|
||||
export function testsForRpcMethodsThatCheckForBlockHashInResponse(
|
||||
method,
|
||||
{ numberOfParameters, providerType },
|
||||
method: string,
|
||||
{
|
||||
numberOfParameters,
|
||||
providerType,
|
||||
}: TestsForRpcMethodThatCheckForBlockHashInResponseOptions,
|
||||
) {
|
||||
if (providerType !== 'infura' && providerType !== 'custom') {
|
||||
throw new Error(
|
@ -3,6 +3,7 @@
|
||||
import {
|
||||
buildMockParams,
|
||||
buildRequestWithReplacedBlockParam,
|
||||
ProviderType,
|
||||
waitForPromiseToBeFulfilledAfterRunningAllTimers,
|
||||
withMockedCommunications,
|
||||
withNetworkClient,
|
||||
@ -13,6 +14,12 @@ import {
|
||||
buildJsonRpcEngineEmptyResponseErrorMessage,
|
||||
} from './shared-tests';
|
||||
|
||||
type TestsForRpcMethodSupportingBlockParam = {
|
||||
providerType: ProviderType;
|
||||
blockParamIndex: number;
|
||||
numberOfParameters: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines tests which exercise the behavior exhibited by an RPC method that
|
||||
* takes a block parameter. The value of this parameter can be either a block
|
||||
@ -28,8 +35,12 @@ import {
|
||||
*/
|
||||
/* eslint-disable-next-line jest/no-export */
|
||||
export function testsForRpcMethodSupportingBlockParam(
|
||||
method,
|
||||
{ blockParamIndex, numberOfParameters, providerType },
|
||||
method: string,
|
||||
{
|
||||
blockParamIndex,
|
||||
numberOfParameters,
|
||||
providerType,
|
||||
}: TestsForRpcMethodSupportingBlockParam,
|
||||
) {
|
||||
describe.each([
|
||||
['given no block tag', undefined],
|
||||
@ -1718,9 +1729,9 @@ export function testsForRpcMethodSupportingBlockParam(
|
||||
[
|
||||
['less than the current block number', '0x200'],
|
||||
['equal to the curent block number', '0x100'],
|
||||
],
|
||||
] as any,
|
||||
'%s',
|
||||
(_nestedDesc, currentBlockNumber) => {
|
||||
(_nestedDesc: string, currentBlockNumber: string) => {
|
||||
it('makes an additional request to the RPC endpoint', async () => {
|
||||
await withMockedCommunications({ providerType }, async (comms) => {
|
||||
const request = {
|
@ -1,14 +1,13 @@
|
||||
import nock from 'nock';
|
||||
import nock, { Scope as NockScope } from 'nock';
|
||||
import sinon from 'sinon';
|
||||
import type { JSONRPCResponse } from '@json-rpc-specification/meta-schema';
|
||||
import EthQuery from 'eth-query';
|
||||
import { createNetworkClient } from '../create-network-client';
|
||||
|
||||
/**
|
||||
* @typedef {import('nock').Scope} NockScope
|
||||
*
|
||||
* A object returned by the `nock` function for mocking requests to a particular
|
||||
* base URL.
|
||||
*/
|
||||
import { Hex } from '@metamask/utils';
|
||||
import { BuiltInInfuraNetwork } from '../../../../../shared/constants/network';
|
||||
import {
|
||||
createNetworkClient,
|
||||
NetworkClientType,
|
||||
} from '../create-network-client';
|
||||
|
||||
/**
|
||||
* A dummy value for the `infuraProjectId` option that `createInfuraClient`
|
||||
@ -41,9 +40,9 @@ const originalSetTimeout = setTimeout;
|
||||
* keeps failing, you can set `process.env.DEBUG_PROVIDER_TESTS` to `1`. This
|
||||
* will turn on some extra logging.
|
||||
*
|
||||
* @param {any[]} args - The arguments that `console.log` takes.
|
||||
* @param args - The arguments that `console.log` takes.
|
||||
*/
|
||||
function debug(...args) {
|
||||
function debug(...args: any) {
|
||||
if (process.env.DEBUG_PROVIDER_TESTS === '1') {
|
||||
console.log(...args);
|
||||
}
|
||||
@ -52,96 +51,89 @@ function debug(...args) {
|
||||
/**
|
||||
* Builds a Nock scope object for mocking provider requests.
|
||||
*
|
||||
* @param {string} rpcUrl - The URL of the RPC endpoint.
|
||||
* @returns {NockScope} The nock scope.
|
||||
* @param rpcUrl - The URL of the RPC endpoint.
|
||||
* @returns The nock scope.
|
||||
*/
|
||||
function buildScopeForMockingRequests(rpcUrl) {
|
||||
function buildScopeForMockingRequests(rpcUrl: string): NockScope {
|
||||
return nock(rpcUrl).filteringRequestBody((body) => {
|
||||
debug('Nock Received Request: ', body);
|
||||
return body;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {{ nockScope: NockScope, blockNumber: string }} MockBlockTrackerRequestOptions
|
||||
*
|
||||
* The options to `mockNextBlockTrackerRequest` and `mockAllBlockTrackerRequests`.
|
||||
*/
|
||||
type Request = { method: string; params?: any[] };
|
||||
type Response = {
|
||||
id?: number | string;
|
||||
jsonrpc?: '2.0';
|
||||
error?: any;
|
||||
result?: any;
|
||||
httpStatus?: number;
|
||||
};
|
||||
type ResponseBody = { body: JSONRPCResponse };
|
||||
type BodyOrResponse = ResponseBody | Response;
|
||||
type CurriedMockRpcCallOptions = {
|
||||
request: Request;
|
||||
// The response data.
|
||||
response?: BodyOrResponse;
|
||||
/**
|
||||
* An error to throw while making the request.
|
||||
* Takes precedence over `response`.
|
||||
*/
|
||||
error?: Error | string;
|
||||
/**
|
||||
* The amount of time that should pass before the
|
||||
* request resolves with the response.
|
||||
*/
|
||||
delay?: number;
|
||||
/**
|
||||
* The number of times that the request is
|
||||
* expected to be made.
|
||||
*/
|
||||
times?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Mocks the next request for the latest block that the block tracker will make.
|
||||
*
|
||||
* @param {MockBlockTrackerRequestOptions} args - The arguments.
|
||||
* @param {NockScope} args.nockScope - A nock scope (a set of mocked requests
|
||||
* scoped to a certain base URL).
|
||||
* @param {string} args.blockNumber - The block number that the block tracker
|
||||
* should report, as a 0x-prefixed hex string.
|
||||
*/
|
||||
async function mockNextBlockTrackerRequest({
|
||||
nockScope,
|
||||
blockNumber = DEFAULT_LATEST_BLOCK_NUMBER,
|
||||
}) {
|
||||
await mockRpcCall({
|
||||
nockScope,
|
||||
request: { method: 'eth_blockNumber', params: [] },
|
||||
response: { result: blockNumber },
|
||||
});
|
||||
}
|
||||
type MockRpcCallOptions = {
|
||||
// A nock scope (a set of mocked requests scoped to a certain base URL).
|
||||
nockScope: nock.Scope;
|
||||
} & CurriedMockRpcCallOptions;
|
||||
|
||||
/**
|
||||
* Mocks all requests for the latest block that the block tracker will make.
|
||||
*
|
||||
* @param {MockBlockTrackerRequestOptions} args - The arguments.
|
||||
* @param {NockScope} args.nockScope - A nock scope (a set of mocked requests
|
||||
* scoped to a certain base URL).
|
||||
* @param {string} args.blockNumber - The block number that the block tracker
|
||||
* should report, as a 0x-prefixed hex string.
|
||||
*/
|
||||
async function mockAllBlockTrackerRequests({
|
||||
nockScope,
|
||||
blockNumber = DEFAULT_LATEST_BLOCK_NUMBER,
|
||||
}) {
|
||||
await mockRpcCall({
|
||||
nockScope,
|
||||
request: { method: 'eth_blockNumber', params: [] },
|
||||
response: { result: blockNumber },
|
||||
}).persist();
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {{ nockScope: NockScope, request: object, response: object, delay?: number }} MockRpcCallOptions
|
||||
*
|
||||
* The options to `mockRpcCall`.
|
||||
*/
|
||||
type MockRpcCallResult = nock.Interceptor | nock.Scope;
|
||||
|
||||
/**
|
||||
* Mocks a JSON-RPC request sent to the provider with the given response.
|
||||
* Provider type is inferred from the base url set on the nockScope.
|
||||
*
|
||||
* @param {MockRpcCallOptions} args - The arguments.
|
||||
* @param {NockScope} args.nockScope - A nock scope (a set of mocked requests
|
||||
* scoped to a certain base URL).
|
||||
* @param {object} args.request - The request data.
|
||||
* @param {{body: string} | {httpStatus?: number; id?: number; method?: string; params?: string[]}} [args.response] - Information
|
||||
* concerning the response that the request should have. If a `body` property is
|
||||
* present, this is taken as the complete response body. If an `httpStatus`
|
||||
* property is present, then it is taken as the HTTP status code to respond
|
||||
* with. Properties other than these two are used to build a complete response
|
||||
* body (including `id` and `jsonrpc` properties).
|
||||
* @param {Error | string} [args.error] - An error to throw while making the
|
||||
* request. Takes precedence over `response`.
|
||||
* @param {number} [args.delay] - The amount of time that should pass before the
|
||||
* request resolves with the response.
|
||||
* @param {number} [args.times] - The number of times that the request is
|
||||
* expected to be made.
|
||||
* @returns {NockScope} The nock scope.
|
||||
* @param args - The arguments.
|
||||
* @param args.nockScope - A nock scope (a set of mocked requests scoped to a
|
||||
* certain base URL).
|
||||
* @param args.request - The request data.
|
||||
* @param args.response - Information concerning the response that the request
|
||||
* should have. If a `body` property is present, this is taken as the complete
|
||||
* response body. If an `httpStatus` property is present, then it is taken as
|
||||
* the HTTP status code to respond with. Properties other than these two are
|
||||
* used to build a complete response body (including `id` and `jsonrpc`
|
||||
* properties).
|
||||
* @param args.error - An error to throw while making the request. Takes
|
||||
* precedence over `response`.
|
||||
* @param args.delay - The amount of time that should pass before the request
|
||||
* resolves with the response.
|
||||
* @param args.times - The number of times that the request is expected to be
|
||||
* made.
|
||||
* @returns The nock scope.
|
||||
*/
|
||||
function mockRpcCall({ nockScope, request, response, error, delay, times }) {
|
||||
function mockRpcCall({
|
||||
nockScope,
|
||||
request,
|
||||
response,
|
||||
error,
|
||||
delay,
|
||||
times,
|
||||
}: MockRpcCallOptions): MockRpcCallResult {
|
||||
// eth-query always passes `params`, so even if we don't supply this property,
|
||||
// for consistency with makeRpcCall, assume that the `body` contains it
|
||||
const { method, params = [], ...rest } = request;
|
||||
let httpStatus = 200;
|
||||
let completeResponse = { id: 2, jsonrpc: '2.0' };
|
||||
let completeResponse: JSONRPCResponse = { id: 2, jsonrpc: '2.0' };
|
||||
if (response !== undefined) {
|
||||
if ('body' in response) {
|
||||
completeResponse = response.body;
|
||||
@ -156,6 +148,7 @@ function mockRpcCall({ nockScope, request, response, error, delay, times }) {
|
||||
}
|
||||
}
|
||||
}
|
||||
/* @ts-expect-error The types for Nock do not include `basePath` in the interface for Nock.Scope. */
|
||||
const url = nockScope.basePath.includes('infura.io')
|
||||
? `/v3/${MOCK_INFURA_PROJECT_ID}`
|
||||
: '/';
|
||||
@ -189,7 +182,7 @@ function mockRpcCall({ nockScope, request, response, error, delay, times }) {
|
||||
if (error !== undefined) {
|
||||
return nockRequest.replyWithError(error);
|
||||
} else if (completeResponse !== undefined) {
|
||||
return nockRequest.reply(httpStatus, (_, requestBody) => {
|
||||
return nockRequest.reply(httpStatus, (_, requestBody: any) => {
|
||||
if (response !== undefined && !('body' in response)) {
|
||||
if (response.id === undefined) {
|
||||
completeResponse.id = requestBody.id;
|
||||
@ -204,16 +197,72 @@ function mockRpcCall({ nockScope, request, response, error, delay, times }) {
|
||||
return nockRequest;
|
||||
}
|
||||
|
||||
type MockBlockTrackerRequestOptions = {
|
||||
/**
|
||||
* A nock scope (a set of mocked requests scoped to a certain base url).
|
||||
*/
|
||||
nockScope: NockScope;
|
||||
/**
|
||||
* The block number that the block tracker should report, as a 0x-prefixed hex
|
||||
* string.
|
||||
*/
|
||||
blockNumber: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Mocks the next request for the latest block that the block tracker will make.
|
||||
*
|
||||
* @param args - The arguments.
|
||||
* @param args.nockScope - A nock scope (a set of mocked requests scoped to a
|
||||
* certain base URL).
|
||||
* @param args.blockNumber - The block number that the block tracker should
|
||||
* report, as a 0x-prefixed hex string.
|
||||
*/
|
||||
function mockNextBlockTrackerRequest({
|
||||
nockScope,
|
||||
blockNumber = DEFAULT_LATEST_BLOCK_NUMBER,
|
||||
}: MockBlockTrackerRequestOptions) {
|
||||
mockRpcCall({
|
||||
nockScope,
|
||||
request: { method: 'eth_blockNumber', params: [] },
|
||||
response: { result: blockNumber },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Mocks all requests for the latest block that the block tracker will make.
|
||||
*
|
||||
* @param args - The arguments.
|
||||
* @param args.nockScope - A nock scope (a set of mocked requests scoped to a
|
||||
* certain base URL).
|
||||
* @param args.blockNumber - The block number that the block tracker should
|
||||
* report, as a 0x-prefixed hex string.
|
||||
*/
|
||||
async function mockAllBlockTrackerRequests({
|
||||
nockScope,
|
||||
blockNumber = DEFAULT_LATEST_BLOCK_NUMBER,
|
||||
}: MockBlockTrackerRequestOptions) {
|
||||
const result = await mockRpcCall({
|
||||
nockScope,
|
||||
request: { method: 'eth_blockNumber', params: [] },
|
||||
response: { result: blockNumber },
|
||||
});
|
||||
|
||||
if ('persist' in result) {
|
||||
result.persist();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a JSON-RPC call through the given eth-query object.
|
||||
*
|
||||
* @param {any} ethQuery - The eth-query object.
|
||||
* @param {object} request - The request data.
|
||||
* @returns {Promise<any>} A promise that either resolves with the result from
|
||||
* the JSON-RPC response if it is successful or rejects with the error from the
|
||||
* JSON-RPC response otherwise.
|
||||
* @param ethQuery - The eth-query object.
|
||||
* @param request - The request data.
|
||||
* @returns A promise that either resolves with the result from the JSON-RPC
|
||||
* response if it is successful or rejects with the error from the JSON-RPC
|
||||
* response otherwise.
|
||||
*/
|
||||
function makeRpcCall(ethQuery, request) {
|
||||
function makeRpcCall(ethQuery: EthQuery, request: Request) {
|
||||
return new Promise((resolve, reject) => {
|
||||
debug('[makeRpcCall] making request', request);
|
||||
ethQuery.sendAsync(request, (error, result) => {
|
||||
@ -227,41 +276,43 @@ function makeRpcCall(ethQuery, request) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {{providerType: 'infura' | 'custom', infuraNetwork?: string}} WithMockedCommunicationsOptions
|
||||
*
|
||||
* The options bag that `Communications` takes.
|
||||
*/
|
||||
export type ProviderType = 'infura' | 'custom';
|
||||
|
||||
/**
|
||||
* @typedef {{mockNextBlockTrackerRequest: (options: Omit<MockBlockTrackerRequestOptions, 'nockScope'>) => void, mockAllBlockTrackerRequests: (options: Omit<MockBlockTrackerRequestOptions, 'nockScope'>) => void, mockRpcCall: (options: Omit<MockRpcCallOptions, 'nockScope'>) => NockScope, rpcUrl: string, infuraNetwork: string}} Communications
|
||||
*
|
||||
* Provides methods to mock different kinds of requests to the provider.
|
||||
*/
|
||||
export type MockOptions = {
|
||||
infuraNetwork?: BuiltInInfuraNetwork;
|
||||
providerType: ProviderType;
|
||||
customRpcUrl?: string;
|
||||
customChainId?: Hex;
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {(comms: Communications) => Promise<any>} WithMockedCommunicationsCallback
|
||||
*
|
||||
* The callback that `mockingCommunications` takes.
|
||||
*/
|
||||
export type MockCommunications = {
|
||||
mockNextBlockTrackerRequest: (options?: any) => void;
|
||||
mockAllBlockTrackerRequests: (options?: any) => void;
|
||||
mockRpcCall: (options: CurriedMockRpcCallOptions) => MockRpcCallResult;
|
||||
rpcUrl: string;
|
||||
infuraNetwork: BuiltInInfuraNetwork;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets up request mocks for requests to the provider.
|
||||
*
|
||||
* @param {WithMockedCommunicationsOptions} options - An options bag.
|
||||
* @param {"infura" | "custom"} options.providerType - The type of network
|
||||
* client being tested.
|
||||
* @param {string} [options.infuraNetwork] - The name of the Infura network being
|
||||
* tested, assuming that `providerType` is "infura" (default: "mainnet").
|
||||
* @param {string} [options.customRpcUrl] - The URL of the custom RPC endpoint,
|
||||
* assuming that `providerType` is "custom".
|
||||
* @param {WithMockedCommunicationsCallback} fn - A function which will be
|
||||
* called with an object that allows interaction with the network client.
|
||||
* @returns {Promise<any>} The return value of the given function.
|
||||
* @param options - An options bag.
|
||||
* @param options.providerType - The type of network client being tested.
|
||||
* @param options.infuraNetwork - The name of the Infura network being tested,
|
||||
* assuming that `providerType` is "infura" (default: "mainnet").
|
||||
* @param options.customRpcUrl - The URL of the custom RPC endpoint, assuming
|
||||
* that `providerType` is "custom".
|
||||
* @param fn - A function which will be called with an object that allows
|
||||
* interaction with the network client.
|
||||
* @returns The return value of the given function.
|
||||
*/
|
||||
export async function withMockedCommunications(
|
||||
{ providerType, infuraNetwork = 'mainnet', customRpcUrl = MOCK_RPC_URL },
|
||||
fn,
|
||||
{
|
||||
providerType,
|
||||
infuraNetwork = 'mainnet',
|
||||
customRpcUrl = MOCK_RPC_URL,
|
||||
}: MockOptions,
|
||||
fn: (comms: MockCommunications) => Promise<void>,
|
||||
) {
|
||||
if (providerType !== 'infura' && providerType !== 'custom') {
|
||||
throw new Error(
|
||||
@ -274,11 +325,11 @@ export async function withMockedCommunications(
|
||||
? `https://${infuraNetwork}.infura.io`
|
||||
: customRpcUrl;
|
||||
const nockScope = buildScopeForMockingRequests(rpcUrl);
|
||||
const curriedMockNextBlockTrackerRequest = (localOptions) =>
|
||||
const curriedMockNextBlockTrackerRequest = (localOptions: any) =>
|
||||
mockNextBlockTrackerRequest({ nockScope, ...localOptions });
|
||||
const curriedMockAllBlockTrackerRequests = (localOptions) =>
|
||||
const curriedMockAllBlockTrackerRequests = (localOptions: any) =>
|
||||
mockAllBlockTrackerRequests({ nockScope, ...localOptions });
|
||||
const curriedMockRpcCall = (localOptions) =>
|
||||
const curriedMockRpcCall = (localOptions: any) =>
|
||||
mockRpcCall({ nockScope, ...localOptions });
|
||||
|
||||
const comms = {
|
||||
@ -297,12 +348,12 @@ export async function withMockedCommunications(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {{blockTracker: import('eth-block-tracker').PollingBlockTracker, clock: sinon.SinonFakeTimers, makeRpcCall: (request: Partial<JsonRpcRequest>) => Promise<any>, makeRpcCallsInSeries: (requests: Partial<JsonRpcRequest>[]) => Promise<any>}} MockNetworkClient
|
||||
*
|
||||
* Provides methods to interact with the suite of middleware that
|
||||
* `createInfuraClient` or `createJsonRpcClient` exposes.
|
||||
*/
|
||||
type MockNetworkClient = {
|
||||
blockTracker: any;
|
||||
clock: sinon.SinonFakeTimers;
|
||||
makeRpcCall: (request: Request) => Promise<any>;
|
||||
makeRpcCallsInSeries: (requests: Request[]) => Promise<any[]>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Some middleware contain logic which retries the request if some condition
|
||||
@ -321,14 +372,14 @@ export async function withMockedCommunications(
|
||||
* `setTimeout` handler.
|
||||
*/
|
||||
export async function waitForPromiseToBeFulfilledAfterRunningAllTimers(
|
||||
promise,
|
||||
clock,
|
||||
promise: any,
|
||||
clock: any,
|
||||
) {
|
||||
let hasPromiseBeenFulfilled = false;
|
||||
let numTimesClockHasBeenAdvanced = 0;
|
||||
|
||||
promise
|
||||
.catch((error) => {
|
||||
.catch((error: any) => {
|
||||
// This is used to silence Node.js warnings about the rejection
|
||||
// being handled asynchronously. The error is handled later when
|
||||
// `promise` is awaited, but we log it here anyway in case it gets
|
||||
@ -350,36 +401,22 @@ export async function waitForPromiseToBeFulfilledAfterRunningAllTimers(
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {{providerType: "infura" | "custom", infuraNetwork?: string, customRpcUrl?: string, customChainId?: string}} WithClientOptions
|
||||
*
|
||||
* The options bag that `withNetworkClient` takes.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {(client: MockNetworkClient) => Promise<any>} WithClientCallback
|
||||
*
|
||||
* The callback that `withNetworkClient` takes.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Builds a provider from the middleware (for the provider type) along with a
|
||||
* block tracker, runs the given function with those two things, and then
|
||||
* ensures the block tracker is stopped at the end.
|
||||
*
|
||||
* @param {WithClientOptions} options - An options bag.
|
||||
* @param {"infura" | "custom"} options.providerType - The type of network
|
||||
* client being tested.
|
||||
* @param {string} [options.infuraNetwork] - The name of the Infura network being
|
||||
* tested, assuming that `providerType` is "infura" (default: "mainnet").
|
||||
* @param {string} [options.customRpcUrl] - The URL of the custom RPC endpoint,
|
||||
* assuming that `providerType` is "custom".
|
||||
* @param {string} [options.customChainId] - The chain id belonging to the
|
||||
* custom RPC endpoint, assuming that `providerType` is "custom" (default:
|
||||
* "0x1").
|
||||
* @param {WithClientCallback} fn - A function which will be called with an
|
||||
* object that allows interaction with the network client.
|
||||
* @returns {Promise<any>} The return value of the given function.
|
||||
* @param options - An options bag.
|
||||
* @param options.providerType - The type of network client being tested.
|
||||
* @param options.infuraNetwork - The name of the Infura network being tested,
|
||||
* assuming that `providerType` is "infura" (default: "mainnet").
|
||||
* @param options.customRpcUrl - The URL of the custom RPC endpoint, assuming
|
||||
* that `providerType` is "custom".
|
||||
* @param options.customChainId - The chain id belonging to the custom RPC
|
||||
* endpoint, assuming that `providerType` is "custom" (default: "0x1").
|
||||
* @param fn - A function which will be called with an object that allows
|
||||
* interaction with the network client.
|
||||
* @returns The return value of the given function.
|
||||
*/
|
||||
export async function withNetworkClient(
|
||||
{
|
||||
@ -387,8 +424,8 @@ export async function withNetworkClient(
|
||||
infuraNetwork = 'mainnet',
|
||||
customRpcUrl = MOCK_RPC_URL,
|
||||
customChainId = '0x1',
|
||||
},
|
||||
fn,
|
||||
}: MockOptions,
|
||||
fn: (client: MockNetworkClient) => Promise<any>,
|
||||
) {
|
||||
if (providerType !== 'infura' && providerType !== 'custom') {
|
||||
throw new Error(
|
||||
@ -414,20 +451,21 @@ export async function withNetworkClient(
|
||||
? createNetworkClient({
|
||||
network: infuraNetwork,
|
||||
infuraProjectId: MOCK_INFURA_PROJECT_ID,
|
||||
type: 'infura',
|
||||
type: NetworkClientType.Infura,
|
||||
})
|
||||
: createNetworkClient({
|
||||
chainId: customChainId,
|
||||
rpcUrl: customRpcUrl,
|
||||
type: 'custom',
|
||||
type: NetworkClientType.Custom,
|
||||
});
|
||||
process.env.IN_TEST = inTest;
|
||||
|
||||
const { provider, blockTracker } = clientUnderTest;
|
||||
|
||||
const ethQuery = new EthQuery(provider);
|
||||
const curriedMakeRpcCall = (request) => makeRpcCall(ethQuery, request);
|
||||
const makeRpcCallsInSeries = async (requests) => {
|
||||
const curriedMakeRpcCall = (request: Request) =>
|
||||
makeRpcCall(ethQuery, request);
|
||||
const makeRpcCallsInSeries = async (requests: Request[]) => {
|
||||
const responses = [];
|
||||
for (const request of requests) {
|
||||
responses.push(await curriedMakeRpcCall(request));
|
||||
@ -451,6 +489,13 @@ export async function withNetworkClient(
|
||||
}
|
||||
}
|
||||
|
||||
type BuildMockParamsOptions = {
|
||||
// The block parameter value to set.
|
||||
blockParam: any;
|
||||
// The index of the block parameter.
|
||||
blockParamIndex: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Build mock parameters for a JSON-RPC call.
|
||||
*
|
||||
@ -460,16 +505,15 @@ export async function withNetworkClient(
|
||||
* The block parameter can be set to a custom value. If no value is given, it
|
||||
* is set as undefined.
|
||||
*
|
||||
* @param {object} args - Arguments.
|
||||
* @param {number} args.blockParamIndex - The index of the block parameter.
|
||||
* @param {any} [args.blockParam] - The block parameter value to set.
|
||||
* @returns {any[]} The mock params.
|
||||
* @param args - Arguments.
|
||||
* @param args.blockParamIndex - The index of the block parameter.
|
||||
* @param args.blockParam - The block parameter value to set.
|
||||
* @returns The mock params.
|
||||
*/
|
||||
export function buildMockParams({ blockParam, blockParamIndex }) {
|
||||
if (blockParamIndex === undefined) {
|
||||
throw new Error(`Missing 'blockParamIndex'`);
|
||||
}
|
||||
|
||||
export function buildMockParams({
|
||||
blockParam,
|
||||
blockParamIndex,
|
||||
}: BuildMockParamsOptions) {
|
||||
const params = new Array(blockParamIndex).fill('some value');
|
||||
params[blockParamIndex] = blockParam;
|
||||
|
||||
@ -480,18 +524,18 @@ export function buildMockParams({ blockParam, blockParamIndex }) {
|
||||
* Returns a partial JSON-RPC request object, with the "block" param replaced
|
||||
* with the given value.
|
||||
*
|
||||
* @param {object} request - The request object.
|
||||
* @param {string} request.method - The request method.
|
||||
* @param {params} [request.params] - The request params.
|
||||
* @param {number} blockParamIndex - The index within the `params` array of the
|
||||
* block param.
|
||||
* @param {any} blockParam - The desired block param value.
|
||||
* @returns {object} The updated request object.
|
||||
* @param request - The request object.
|
||||
* @param request.method - The request method.
|
||||
* @param request.params - The request params.
|
||||
* @param blockParamIndex - The index within the `params` array of the block
|
||||
* param.
|
||||
* @param blockParam - The desired block param value.
|
||||
* @returns The updated request object.
|
||||
*/
|
||||
export function buildRequestWithReplacedBlockParam(
|
||||
{ method, params = [] },
|
||||
blockParamIndex,
|
||||
blockParam,
|
||||
{ method, params = [] }: Request,
|
||||
blockParamIndex: number,
|
||||
blockParam: any,
|
||||
) {
|
||||
const updatedParams = params.slice();
|
||||
updatedParams[blockParamIndex] = blockParam;
|
@ -1,6 +1,7 @@
|
||||
/* eslint-disable jest/require-top-level-describe, jest/no-export */
|
||||
|
||||
import {
|
||||
ProviderType,
|
||||
waitForPromiseToBeFulfilledAfterRunningAllTimers,
|
||||
withMockedCommunications,
|
||||
withNetworkClient,
|
||||
@ -11,6 +12,11 @@ import {
|
||||
buildJsonRpcEngineEmptyResponseErrorMessage,
|
||||
} from './shared-tests';
|
||||
|
||||
type TestsForRpcMethodAssumingNoBlockParamOptions = {
|
||||
providerType: ProviderType;
|
||||
numberOfParameters: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines tests which exercise the behavior exhibited by an RPC method which is
|
||||
* assumed to not take a block parameter. Even if it does, the value of this
|
||||
@ -23,8 +29,11 @@ import {
|
||||
* either `infura` or `custom` (default: "infura").
|
||||
*/
|
||||
export function testsForRpcMethodAssumingNoBlockParam(
|
||||
method,
|
||||
{ numberOfParameters, providerType },
|
||||
method: string,
|
||||
{
|
||||
numberOfParameters,
|
||||
providerType,
|
||||
}: TestsForRpcMethodAssumingNoBlockParamOptions,
|
||||
) {
|
||||
if (providerType !== 'infura' && providerType !== 'custom') {
|
||||
throw new Error(
|
@ -1,7 +1,16 @@
|
||||
/* eslint-disable jest/require-top-level-describe, jest/no-export */
|
||||
|
||||
import { fill } from 'lodash';
|
||||
import { withMockedCommunications, withNetworkClient } from './helpers';
|
||||
import {
|
||||
ProviderType,
|
||||
withMockedCommunications,
|
||||
withNetworkClient,
|
||||
} from './helpers';
|
||||
|
||||
type TestsForRpcMethodNotHandledByMiddlewareOptions = {
|
||||
providerType: ProviderType;
|
||||
numberOfParameters: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines tests which exercise the behavior exhibited by an RPC method that
|
||||
@ -15,8 +24,11 @@ import { withMockedCommunications, withNetworkClient } from './helpers';
|
||||
* RPC method takes.
|
||||
*/
|
||||
export function testsForRpcMethodNotHandledByMiddleware(
|
||||
method,
|
||||
{ providerType, numberOfParameters },
|
||||
method: string,
|
||||
{
|
||||
providerType,
|
||||
numberOfParameters,
|
||||
}: TestsForRpcMethodNotHandledByMiddlewareOptions,
|
||||
) {
|
||||
if (providerType !== 'infura' && providerType !== 'custom') {
|
||||
throw new Error(
|
@ -2,7 +2,11 @@
|
||||
|
||||
import { testsForRpcMethodsThatCheckForBlockHashInResponse } from './block-hash-in-response';
|
||||
import { testsForRpcMethodSupportingBlockParam } from './block-param';
|
||||
import { withMockedCommunications, withNetworkClient } from './helpers';
|
||||
import {
|
||||
ProviderType,
|
||||
withMockedCommunications,
|
||||
withNetworkClient,
|
||||
} from './helpers';
|
||||
import { testsForRpcMethodAssumingNoBlockParam } from './no-block-param';
|
||||
import { testsForRpcMethodNotHandledByMiddleware } from './not-handled-by-middleware';
|
||||
|
||||
@ -13,7 +17,7 @@ import { testsForRpcMethodNotHandledByMiddleware } from './not-handled-by-middle
|
||||
* @param reason - The exact reason for failure.
|
||||
* @returns The error message.
|
||||
*/
|
||||
export function buildInfuraClientRetriesExhaustedErrorMessage(reason) {
|
||||
export function buildInfuraClientRetriesExhaustedErrorMessage(reason: string) {
|
||||
return new RegExp(
|
||||
`^InfuraProvider - cannot complete request. All retries exhausted\\..+${reason}`,
|
||||
'us',
|
||||
@ -27,7 +31,7 @@ export function buildInfuraClientRetriesExhaustedErrorMessage(reason) {
|
||||
* @param method - The RPC method.
|
||||
* @returns The error message.
|
||||
*/
|
||||
export function buildJsonRpcEngineEmptyResponseErrorMessage(method) {
|
||||
export function buildJsonRpcEngineEmptyResponseErrorMessage(method: string) {
|
||||
return new RegExp(
|
||||
`^JsonRpcEngine: Response has no error or result for request:.+"method": "${method}"`,
|
||||
'us',
|
||||
@ -42,7 +46,7 @@ export function buildJsonRpcEngineEmptyResponseErrorMessage(method) {
|
||||
* @param reason - The reason.
|
||||
* @returns The error message.
|
||||
*/
|
||||
export function buildFetchFailedErrorMessage(url, reason) {
|
||||
export function buildFetchFailedErrorMessage(url: string, reason: string) {
|
||||
return new RegExp(
|
||||
`^request to ${url}(/[^/ ]*)+ failed, reason: ${reason}`,
|
||||
'us',
|
||||
@ -57,7 +61,7 @@ export function buildFetchFailedErrorMessage(url, reason) {
|
||||
* exposed by `createInfuraClient` is tested; if `custom`, then the middleware
|
||||
* exposed by `createJsonRpcClient` will be tested.
|
||||
*/
|
||||
export function testsForProviderType(providerType) {
|
||||
export function testsForProviderType(providerType: ProviderType) {
|
||||
// Ethereum JSON-RPC spec: <https://ethereum.github.io/execution-apis/api-documentation/>
|
||||
// Infura documentation: <https://docs.infura.io/infura/networks/ethereum/json-rpc-methods>
|
||||
|
@ -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;
|
||||
|
@ -409,6 +409,14 @@ describe('SignController', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('does not throw if accepting approval throws', async () => {
|
||||
messengerMock.call.mockImplementation(() => {
|
||||
throw new Error('Test Error');
|
||||
});
|
||||
|
||||
await signController[signMethodName](messageParamsMock);
|
||||
});
|
||||
|
||||
it('rejects message on error', async () => {
|
||||
keyringControllerMock[signMethodName].mockReset();
|
||||
keyringControllerMock[signMethodName].mockRejectedValue(
|
||||
@ -468,6 +476,14 @@ describe('SignController', () => {
|
||||
'Cancel',
|
||||
);
|
||||
});
|
||||
|
||||
it('does not throw if rejecting approval throws', async () => {
|
||||
messengerMock.call.mockImplementation(() => {
|
||||
throw new Error('Test Error');
|
||||
});
|
||||
|
||||
await signController[cancelMethodName](messageParamsMock);
|
||||
});
|
||||
});
|
||||
|
||||
describe('message manager events', () => {
|
||||
|
@ -33,12 +33,13 @@ import {
|
||||
RejectRequest,
|
||||
} from '@metamask/approval-controller';
|
||||
import { MetaMetricsEventCategory } from '../../../shared/constants/metametrics';
|
||||
import { MESSAGE_TYPE } from '../../../shared/constants/app';
|
||||
import PreferencesController from './preferences';
|
||||
|
||||
const controllerName = 'SignController';
|
||||
const methodNameSign = 'eth_sign';
|
||||
const methodNamePersonalSign = 'personal_sign';
|
||||
const methodNameTypedSign = 'eth_signTypedData';
|
||||
const methodNameSign = MESSAGE_TYPE.ETH_SIGN;
|
||||
const methodNamePersonalSign = MESSAGE_TYPE.PERSONAL_SIGN;
|
||||
const methodNameTypedSign = MESSAGE_TYPE.ETH_SIGN_TYPED_DATA;
|
||||
|
||||
const stateMetadata = {
|
||||
unapprovedMsgs: { persist: false, anonymous: false },
|
||||
@ -104,7 +105,6 @@ export type SignControllerOptions = {
|
||||
messenger: SignControllerMessenger;
|
||||
keyringController: KeyringController;
|
||||
preferencesController: PreferencesController;
|
||||
sendUpdate: () => void;
|
||||
getState: () => any;
|
||||
metricsEvent: (payload: any, options?: any) => void;
|
||||
securityProviderRequest: (
|
||||
@ -637,14 +637,22 @@ export default class SignController extends BaseControllerV2<
|
||||
}
|
||||
|
||||
private _acceptApproval(messageId: string) {
|
||||
this.messagingSystem.call('ApprovalController:acceptRequest', messageId);
|
||||
try {
|
||||
this.messagingSystem.call('ApprovalController:acceptRequest', messageId);
|
||||
} catch (error) {
|
||||
log.info('Failed to accept signature approval request', error);
|
||||
}
|
||||
}
|
||||
|
||||
private _rejectApproval(messageId: string) {
|
||||
this.messagingSystem.call(
|
||||
'ApprovalController:rejectRequest',
|
||||
messageId,
|
||||
'Cancel',
|
||||
);
|
||||
try {
|
||||
this.messagingSystem.call(
|
||||
'ApprovalController:rejectRequest',
|
||||
messageId,
|
||||
'Cancel',
|
||||
);
|
||||
} catch (error) {
|
||||
log.info('Failed to reject signature approval request', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 () {
|
||||
|
@ -1,318 +0,0 @@
|
||||
import EventEmitter from 'events';
|
||||
import { ObservableStore } from '@metamask/obs-store';
|
||||
import { ethErrors } from 'eth-rpc-errors';
|
||||
import log from 'loglevel';
|
||||
import { MESSAGE_TYPE } from '../../../shared/constants/app';
|
||||
import { MetaMetricsEventCategory } from '../../../shared/constants/metametrics';
|
||||
import { METAMASK_CONTROLLER_EVENTS } from '../metamask-controller';
|
||||
import createId from '../../../shared/modules/random-id';
|
||||
|
||||
/**
|
||||
* Represents, and contains data about, an 'eth_getEncryptionPublicKey' type request. These are created when
|
||||
* an eth_getEncryptionPublicKey call is requested.
|
||||
*
|
||||
* @typedef {object} EncryptionPublicKey
|
||||
* @property {number} id An id to track and identify the message object
|
||||
* @property {object} msgParams The parameters to pass to the encryptionPublicKey method once the request is
|
||||
* approved.
|
||||
* @property {object} msgParams.metamaskId Added to msgParams for tracking and identification within MetaMask.
|
||||
* @property {string} msgParams.data A hex string conversion of the raw buffer data of the request
|
||||
* @property {number} time The epoch time at which the this message was created
|
||||
* @property {string} status Indicates whether the request is 'unapproved', 'approved', 'received' or 'rejected'
|
||||
* @property {string} type The json-prc method for which a request has been made. A 'Message' will
|
||||
* always have a 'eth_getEncryptionPublicKey' type.
|
||||
*/
|
||||
|
||||
export default class EncryptionPublicKeyManager extends EventEmitter {
|
||||
/**
|
||||
* Controller in charge of managing - storing, adding, removing, updating - EncryptionPublicKey.
|
||||
*
|
||||
* @param {object} opts - Controller options
|
||||
* @param {Function} opts.metricEvent - A function for emitting a metric event.
|
||||
*/
|
||||
constructor(opts) {
|
||||
super();
|
||||
this.memStore = new ObservableStore({
|
||||
unapprovedEncryptionPublicKeyMsgs: {},
|
||||
unapprovedEncryptionPublicKeyMsgCount: 0,
|
||||
});
|
||||
|
||||
this.resetState = () => {
|
||||
this.memStore.updateState({
|
||||
unapprovedEncryptionPublicKeyMsgs: {},
|
||||
unapprovedEncryptionPublicKeyMsgCount: 0,
|
||||
});
|
||||
};
|
||||
|
||||
this.messages = [];
|
||||
this.metricsEvent = opts.metricsEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* A getter for the number of 'unapproved' EncryptionPublicKeys in this.messages
|
||||
*
|
||||
* @returns {number} The number of 'unapproved' EncryptionPublicKeys in this.messages
|
||||
*/
|
||||
get unapprovedEncryptionPublicKeyMsgCount() {
|
||||
return Object.keys(this.getUnapprovedMsgs()).length;
|
||||
}
|
||||
|
||||
/**
|
||||
* A getter for the 'unapproved' EncryptionPublicKeys in this.messages
|
||||
*
|
||||
* @returns {object} An index of EncryptionPublicKey ids to EncryptionPublicKeys, for all 'unapproved' EncryptionPublicKeys in
|
||||
* this.messages
|
||||
*/
|
||||
getUnapprovedMsgs() {
|
||||
return this.messages
|
||||
.filter((msg) => msg.status === 'unapproved')
|
||||
.reduce((result, msg) => {
|
||||
result[msg.id] = msg;
|
||||
return result;
|
||||
}, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new EncryptionPublicKey with an 'unapproved' status using the passed msgParams. this.addMsg is called to add
|
||||
* the new EncryptionPublicKey to this.messages, and to save the unapproved EncryptionPublicKeys from that list to
|
||||
* this.memStore.
|
||||
*
|
||||
* @param {object} address - The param for the eth_getEncryptionPublicKey call to be made after the message is approved.
|
||||
* @param {object} [req] - The original request object possibly containing the origin
|
||||
* @returns {Promise<Buffer>} The raw public key contents
|
||||
*/
|
||||
addUnapprovedMessageAsync(address, req) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!address) {
|
||||
reject(new Error('MetaMask Message: address field is required.'));
|
||||
return;
|
||||
}
|
||||
const msgId = this.addUnapprovedMessage(address, req);
|
||||
this.once(`${msgId}:finished`, (data) => {
|
||||
switch (data.status) {
|
||||
case 'received':
|
||||
resolve(data.rawData);
|
||||
return;
|
||||
case 'rejected':
|
||||
reject(
|
||||
ethErrors.provider.userRejectedRequest(
|
||||
'MetaMask EncryptionPublicKey: User denied message EncryptionPublicKey.',
|
||||
),
|
||||
);
|
||||
return;
|
||||
default:
|
||||
reject(
|
||||
new Error(
|
||||
`MetaMask EncryptionPublicKey: Unknown problem: ${JSON.stringify(
|
||||
address,
|
||||
)}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new EncryptionPublicKey with an 'unapproved' status using the passed msgParams. this.addMsg is called to add
|
||||
* the new EncryptionPublicKey to this.messages, and to save the unapproved EncryptionPublicKeys from that list to
|
||||
* this.memStore.
|
||||
*
|
||||
* @param {object} address - The param for the eth_getEncryptionPublicKey call to be made after the message is approved.
|
||||
* @param {object} [req] - The original request object possibly containing the origin
|
||||
* @returns {number} The id of the newly created EncryptionPublicKey.
|
||||
*/
|
||||
addUnapprovedMessage(address, req) {
|
||||
log.debug(`EncryptionPublicKeyManager addUnapprovedMessage: address`);
|
||||
// create txData obj with parameters and meta data
|
||||
const time = new Date().getTime();
|
||||
const msgId = createId();
|
||||
const msgData = {
|
||||
id: msgId,
|
||||
msgParams: address,
|
||||
time,
|
||||
status: 'unapproved',
|
||||
type: MESSAGE_TYPE.ETH_GET_ENCRYPTION_PUBLIC_KEY,
|
||||
};
|
||||
|
||||
if (req) {
|
||||
msgData.origin = req.origin;
|
||||
}
|
||||
|
||||
this.addMsg(msgData);
|
||||
|
||||
// signal update
|
||||
this.emit('update');
|
||||
return msgId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a passed EncryptionPublicKey to this.messages, and calls this._saveMsgList() to save the unapproved EncryptionPublicKeys from that
|
||||
* list to this.memStore.
|
||||
*
|
||||
* @param {Message} msg - The EncryptionPublicKey to add to this.messages
|
||||
*/
|
||||
addMsg(msg) {
|
||||
this.messages.push(msg);
|
||||
this._saveMsgList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a specified EncryptionPublicKey.
|
||||
*
|
||||
* @param {number} msgId - The id of the EncryptionPublicKey to get
|
||||
* @returns {EncryptionPublicKey|undefined} The EncryptionPublicKey with the id that matches the passed msgId, or undefined
|
||||
* if no EncryptionPublicKey has that id.
|
||||
*/
|
||||
getMsg(msgId) {
|
||||
return this.messages.find((msg) => msg.id === msgId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Approves a EncryptionPublicKey. Sets the message status via a call to this.setMsgStatusApproved, and returns a promise
|
||||
* with any the message params modified for proper providing.
|
||||
*
|
||||
* @param {object} msgParams - The msgParams to be used when eth_getEncryptionPublicKey is called, plus data added by MetaMask.
|
||||
* @param {object} msgParams.metamaskId - Added to msgParams for tracking and identification within MetaMask.
|
||||
* @returns {Promise<object>} Promises the msgParams object with metamaskId removed.
|
||||
*/
|
||||
approveMessage(msgParams) {
|
||||
this.setMsgStatusApproved(msgParams.metamaskId);
|
||||
return this.prepMsgForEncryptionPublicKey(msgParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a EncryptionPublicKey status to 'approved' via a call to this._setMsgStatus.
|
||||
*
|
||||
* @param {number} msgId - The id of the EncryptionPublicKey to approve.
|
||||
*/
|
||||
setMsgStatusApproved(msgId) {
|
||||
this._setMsgStatus(msgId, 'approved');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a EncryptionPublicKey status to 'received' via a call to this._setMsgStatus and updates that EncryptionPublicKey in
|
||||
* this.messages by adding the raw data of request to the EncryptionPublicKey
|
||||
*
|
||||
* @param {number} msgId - The id of the EncryptionPublicKey.
|
||||
* @param {buffer} rawData - The raw data of the message request
|
||||
*/
|
||||
setMsgStatusReceived(msgId, rawData) {
|
||||
const msg = this.getMsg(msgId);
|
||||
msg.rawData = rawData;
|
||||
this._updateMsg(msg);
|
||||
this._setMsgStatus(msgId, 'received');
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the metamaskId property from passed msgParams and returns a promise which resolves the updated msgParams
|
||||
*
|
||||
* @param {object} msgParams - The msgParams to modify
|
||||
* @returns {Promise<object>} Promises the msgParams with the metamaskId property removed
|
||||
*/
|
||||
async prepMsgForEncryptionPublicKey(msgParams) {
|
||||
delete msgParams.metamaskId;
|
||||
return msgParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a EncryptionPublicKey status to 'rejected' via a call to this._setMsgStatus.
|
||||
*
|
||||
* @param {number} msgId - The id of the EncryptionPublicKey to reject.
|
||||
* @param reason
|
||||
*/
|
||||
rejectMsg(msgId, reason = undefined) {
|
||||
if (reason) {
|
||||
this.metricsEvent({
|
||||
event: reason,
|
||||
category: MetaMetricsEventCategory.Messages,
|
||||
properties: {
|
||||
action: 'Encryption public key Request',
|
||||
},
|
||||
});
|
||||
}
|
||||
this._setMsgStatus(msgId, 'rejected');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a TypedMessage status to 'errored' via a call to this._setMsgStatus.
|
||||
*
|
||||
* @param {number} msgId - The id of the TypedMessage to error
|
||||
* @param error
|
||||
*/
|
||||
errorMessage(msgId, error) {
|
||||
const msg = this.getMsg(msgId);
|
||||
msg.error = error;
|
||||
this._updateMsg(msg);
|
||||
this._setMsgStatus(msgId, 'errored');
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all unapproved messages from memory.
|
||||
*/
|
||||
clearUnapproved() {
|
||||
this.messages = this.messages.filter((msg) => msg.status !== 'unapproved');
|
||||
this._saveMsgList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the status of a EncryptionPublicKey in this.messages via a call to this._updateMsg
|
||||
*
|
||||
* @private
|
||||
* @param {number} msgId - The id of the EncryptionPublicKey to update.
|
||||
* @param {string} status - The new status of the EncryptionPublicKey.
|
||||
* @throws A 'EncryptionPublicKeyManager - EncryptionPublicKey not found for id: "${msgId}".' if there is no EncryptionPublicKey
|
||||
* in this.messages with an id equal to the passed msgId
|
||||
* @fires An event with a name equal to `${msgId}:${status}`. The EncryptionPublicKey is also fired.
|
||||
* @fires If status is 'rejected' or 'received', an event with a name equal to `${msgId}:finished` is fired along
|
||||
* with the EncryptionPublicKey
|
||||
*/
|
||||
_setMsgStatus(msgId, status) {
|
||||
const msg = this.getMsg(msgId);
|
||||
if (!msg) {
|
||||
throw new Error(
|
||||
`EncryptionPublicKeyManager - Message not found for id: "${msgId}".`,
|
||||
);
|
||||
}
|
||||
msg.status = status;
|
||||
this._updateMsg(msg);
|
||||
this.emit(`${msgId}:${status}`, msg);
|
||||
if (status === 'rejected' || status === 'received') {
|
||||
this.emit(`${msgId}:finished`, msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a EncryptionPublicKey in this.messages to the passed EncryptionPublicKey if the ids are equal. Then saves the
|
||||
* unapprovedEncryptionPublicKeyMsgs index to storage via this._saveMsgList
|
||||
*
|
||||
* @private
|
||||
* @param {EncryptionPublicKey} msg - A EncryptionPublicKey that will replace an existing EncryptionPublicKey (with the same
|
||||
* id) in this.messages
|
||||
*/
|
||||
_updateMsg(msg) {
|
||||
const index = this.messages.findIndex((message) => message.id === msg.id);
|
||||
if (index !== -1) {
|
||||
this.messages[index] = msg;
|
||||
}
|
||||
this._saveMsgList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the unapproved EncryptionPublicKeys, and their count, to this.memStore
|
||||
*
|
||||
* @private
|
||||
* @fires 'updateBadge'
|
||||
*/
|
||||
_saveMsgList() {
|
||||
const unapprovedEncryptionPublicKeyMsgs = this.getUnapprovedMsgs();
|
||||
const unapprovedEncryptionPublicKeyMsgCount = Object.keys(
|
||||
unapprovedEncryptionPublicKeyMsgs,
|
||||
).length;
|
||||
this.memStore.updateState({
|
||||
unapprovedEncryptionPublicKeyMsgs,
|
||||
unapprovedEncryptionPublicKeyMsgCount,
|
||||
});
|
||||
this.emit(METAMASK_CONTROLLER_EVENTS.UPDATE_BADGE);
|
||||
}
|
||||
}
|
@ -97,8 +97,8 @@ import {
|
||||
import { MILLISECOND, SECOND } from '../../shared/constants/time';
|
||||
import {
|
||||
ORIGIN_METAMASK,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
MESSAGE_TYPE,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
SNAP_DIALOG_TYPES,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
POLLING_TOKEN_ENVIRONMENT_TYPES,
|
||||
@ -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';
|
||||
@ -154,7 +155,6 @@ import OnboardingController from './controllers/onboarding';
|
||||
import BackupController from './controllers/backup';
|
||||
import IncomingTransactionsController from './controllers/incoming-transactions';
|
||||
import DecryptMessageManager from './lib/decrypt-message-manager';
|
||||
import EncryptionPublicKeyManager from './lib/encryption-public-key-manager';
|
||||
import TransactionController from './controllers/transactions';
|
||||
import DetectTokensController from './controllers/detect-tokens';
|
||||
import SwapsController from './controllers/swaps';
|
||||
@ -166,6 +166,7 @@ import createMetaRPCHandler from './lib/createMetaRPCHandler';
|
||||
import { previousValueComparator } from './lib/util';
|
||||
import createMetamaskMiddleware from './lib/createMetamaskMiddleware';
|
||||
import SignController from './controllers/sign';
|
||||
import EncryptionPublicKeyController from './controllers/encryption-public-key';
|
||||
|
||||
import {
|
||||
CaveatMutatorFactories,
|
||||
@ -259,11 +260,16 @@ export default class MetamaskController extends EventEmitter {
|
||||
name: 'ApprovalController',
|
||||
}),
|
||||
showApprovalRequest: opts.showUserConfirmation,
|
||||
typesExcludedFromRateLimiting: [
|
||||
MESSAGE_TYPE.ETH_SIGN,
|
||||
MESSAGE_TYPE.PERSONAL_SIGN,
|
||||
MESSAGE_TYPE.ETH_SIGN_TYPED_DATA,
|
||||
],
|
||||
});
|
||||
|
||||
const networkControllerMessenger = this.controllerMessenger.getRestricted({
|
||||
name: 'NetworkController',
|
||||
allowedEvents: Object.values(NetworkControllerEventTypes),
|
||||
allowedEvents: Object.values(NetworkControllerEventType),
|
||||
});
|
||||
this.networkController = new NetworkController({
|
||||
messenger: networkControllerMessenger,
|
||||
@ -310,11 +316,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 +458,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 +497,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
// onNetworkDidChange
|
||||
onNetworkStateChange: networkControllerMessenger.subscribe.bind(
|
||||
networkControllerMessenger,
|
||||
NetworkControllerEventTypes.NetworkDidChange,
|
||||
NetworkControllerEventType.NetworkDidChange,
|
||||
),
|
||||
getCurrentNetworkEIP1559Compatibility:
|
||||
this.networkController.getEIP1559Compatibility.bind(
|
||||
@ -519,9 +525,15 @@ export default class MetamaskController extends EventEmitter {
|
||||
isUnlocked: this.isUnlocked.bind(this),
|
||||
initState: initState.AppStateController,
|
||||
onInactiveTimeout: () => this.setLocked(),
|
||||
showUnlockRequest: opts.showUserConfirmation,
|
||||
preferencesStore: this.preferencesController.store,
|
||||
qrHardwareStore: this.qrHardwareKeyring.getMemStore(),
|
||||
messenger: this.controllerMessenger.getRestricted({
|
||||
name: 'AppStateController',
|
||||
allowedActions: [
|
||||
`${this.approvalController.name}:addRequest`,
|
||||
`${this.approvalController.name}:acceptRequest`,
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
const currencyRateMessenger = this.controllerMessenger.getRestricted({
|
||||
@ -609,7 +621,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.networkController.store.getState().provider.chainId,
|
||||
onNetworkDidChange: networkControllerMessenger.subscribe.bind(
|
||||
networkControllerMessenger,
|
||||
NetworkControllerEventTypes.NetworkDidChange,
|
||||
NetworkControllerEventType.NetworkDidChange,
|
||||
),
|
||||
});
|
||||
|
||||
@ -621,7 +633,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 +1019,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 +1118,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
});
|
||||
|
||||
networkControllerMessenger.subscribe(
|
||||
NetworkControllerEventTypes.NetworkDidChange,
|
||||
NetworkControllerEventType.NetworkDidChange,
|
||||
async () => {
|
||||
const { ticker } = this.networkController.store.getState().provider;
|
||||
try {
|
||||
@ -1117,7 +1136,18 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.metaMetricsController,
|
||||
),
|
||||
});
|
||||
this.encryptionPublicKeyManager = new EncryptionPublicKeyManager({
|
||||
|
||||
this.encryptionPublicKeyController = new EncryptionPublicKeyController({
|
||||
messenger: this.controllerMessenger.getRestricted({
|
||||
name: 'EncryptionPublicKeyController',
|
||||
allowedActions: [
|
||||
`${this.approvalController.name}:addRequest`,
|
||||
`${this.approvalController.name}:acceptRequest`,
|
||||
`${this.approvalController.name}:rejectRequest`,
|
||||
],
|
||||
}),
|
||||
keyringController: this.keyringController,
|
||||
getState: this.getState.bind(this),
|
||||
metricsEvent: this.metaMetricsController.trackEvent.bind(
|
||||
this.metaMetricsController,
|
||||
),
|
||||
@ -1145,7 +1175,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 +1218,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
|
||||
// ensure accountTracker updates balances after network change
|
||||
networkControllerMessenger.subscribe(
|
||||
NetworkControllerEventTypes.NetworkDidChange,
|
||||
NetworkControllerEventType.NetworkDidChange,
|
||||
() => {
|
||||
this.accountTracker._updateAccounts();
|
||||
},
|
||||
@ -1196,10 +1226,10 @@ 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();
|
||||
this.encryptionPublicKeyController.clearUnapproved();
|
||||
this.decryptMessageManager.clearUnapproved();
|
||||
this.signController.clearUnapproved();
|
||||
},
|
||||
@ -1266,7 +1296,10 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.signController,
|
||||
),
|
||||
processDecryptMessage: this.newRequestDecryptMessage.bind(this),
|
||||
processEncryptionPublicKey: this.newRequestEncryptionPublicKey.bind(this),
|
||||
processEncryptionPublicKey:
|
||||
this.encryptionPublicKeyController.newRequestEncryptionPublicKey.bind(
|
||||
this.encryptionPublicKeyController,
|
||||
),
|
||||
getPendingNonce: this.getPendingNonce.bind(this),
|
||||
getPendingTransactionByHash: (hash) =>
|
||||
this.txController.getTransactions({
|
||||
@ -1289,7 +1322,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
TxController: this.txController.memStore,
|
||||
TokenRatesController: this.tokenRatesController,
|
||||
DecryptMessageManager: this.decryptMessageManager.memStore,
|
||||
EncryptionPublicKeyManager: this.encryptionPublicKeyManager.memStore,
|
||||
EncryptionPublicKeyController: this.encryptionPublicKeyController,
|
||||
SignController: this.signController,
|
||||
SwapsController: this.swapsController.store,
|
||||
EnsController: this.ensController.store,
|
||||
@ -1371,7 +1404,9 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.accountTracker.resetState,
|
||||
this.txController.resetState,
|
||||
this.decryptMessageManager.resetState,
|
||||
this.encryptionPublicKeyManager.resetState,
|
||||
this.encryptionPublicKeyController.resetState.bind(
|
||||
this.encryptionPublicKeyController,
|
||||
),
|
||||
this.signController.resetState.bind(this.signController),
|
||||
this.swapsController.resetState,
|
||||
this.ensController.resetState,
|
||||
@ -1994,6 +2029,16 @@ export default class MetamaskController extends EventEmitter {
|
||||
appStateController.setConnectedStatusPopoverHasBeenShown.bind(
|
||||
appStateController,
|
||||
),
|
||||
setRecoveryPhraseReminderHasBeenShown:
|
||||
appStateController.setRecoveryPhraseReminderHasBeenShown.bind(
|
||||
appStateController,
|
||||
),
|
||||
setRecoveryPhraseReminderLastShown:
|
||||
appStateController.setRecoveryPhraseReminderLastShown.bind(
|
||||
appStateController,
|
||||
),
|
||||
setTermsOfUseLastAgreed:
|
||||
appStateController.setTermsOfUseLastAgreed.bind(appStateController),
|
||||
setOutdatedBrowserWarningLastShown:
|
||||
appStateController.setOutdatedBrowserWarningLastShown.bind(
|
||||
appStateController,
|
||||
@ -2074,9 +2119,15 @@ export default class MetamaskController extends EventEmitter {
|
||||
decryptMessageInline: this.decryptMessageInline.bind(this),
|
||||
cancelDecryptMessage: this.cancelDecryptMessage.bind(this),
|
||||
|
||||
// EncryptionPublicKeyManager
|
||||
encryptionPublicKey: this.encryptionPublicKey.bind(this),
|
||||
cancelEncryptionPublicKey: this.cancelEncryptionPublicKey.bind(this),
|
||||
// EncryptionPublicKeyController
|
||||
encryptionPublicKey:
|
||||
this.encryptionPublicKeyController.encryptionPublicKey.bind(
|
||||
this.encryptionPublicKeyController,
|
||||
),
|
||||
cancelEncryptionPublicKey:
|
||||
this.encryptionPublicKeyController.cancelEncryptionPublicKey.bind(
|
||||
this.encryptionPublicKeyController,
|
||||
),
|
||||
|
||||
// onboarding controller
|
||||
setSeedPhraseBackedUp:
|
||||
@ -3301,109 +3352,6 @@ export default class MetamaskController extends EventEmitter {
|
||||
return this.getState();
|
||||
}
|
||||
|
||||
// eth_getEncryptionPublicKey methods
|
||||
|
||||
/**
|
||||
* Called when a dapp uses the eth_getEncryptionPublicKey method.
|
||||
*
|
||||
* @param {object} msgParams - The params of the message to sign & return to the Dapp.
|
||||
* @param {object} req - (optional) the original request, containing the origin
|
||||
* Passed back to the requesting Dapp.
|
||||
*/
|
||||
async newRequestEncryptionPublicKey(msgParams, req) {
|
||||
const address = msgParams;
|
||||
const keyring = await this.keyringController.getKeyringForAccount(address);
|
||||
|
||||
switch (keyring.type) {
|
||||
case KeyringType.ledger: {
|
||||
return new Promise((_, reject) => {
|
||||
reject(
|
||||
new Error('Ledger does not support eth_getEncryptionPublicKey.'),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
case KeyringType.trezor: {
|
||||
return new Promise((_, reject) => {
|
||||
reject(
|
||||
new Error('Trezor does not support eth_getEncryptionPublicKey.'),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
case KeyringType.lattice: {
|
||||
return new Promise((_, reject) => {
|
||||
reject(
|
||||
new Error('Lattice does not support eth_getEncryptionPublicKey.'),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
case KeyringType.qr: {
|
||||
return Promise.reject(
|
||||
new Error('QR hardware does not support eth_getEncryptionPublicKey.'),
|
||||
);
|
||||
}
|
||||
|
||||
default: {
|
||||
const promise =
|
||||
this.encryptionPublicKeyManager.addUnapprovedMessageAsync(
|
||||
msgParams,
|
||||
req,
|
||||
);
|
||||
this.sendUpdate();
|
||||
this.opts.showUserConfirmation();
|
||||
return promise;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Signifies a user's approval to receiving encryption public key in queue.
|
||||
* Triggers receiving, and the callback function from newUnsignedEncryptionPublicKey.
|
||||
*
|
||||
* @param {object} msgParams - The params of the message to receive & return to the Dapp.
|
||||
* @returns {Promise<object>} A full state update.
|
||||
*/
|
||||
async encryptionPublicKey(msgParams) {
|
||||
log.info('MetaMaskController - encryptionPublicKey');
|
||||
const msgId = msgParams.metamaskId;
|
||||
// sets the status op the message to 'approved'
|
||||
// and removes the metamaskId for decryption
|
||||
try {
|
||||
const params = await this.encryptionPublicKeyManager.approveMessage(
|
||||
msgParams,
|
||||
);
|
||||
|
||||
// EncryptionPublicKey message
|
||||
const publicKey = await this.keyringController.getEncryptionPublicKey(
|
||||
params.data,
|
||||
);
|
||||
|
||||
// tells the listener that the message has been processed
|
||||
// and can be returned to the dapp
|
||||
this.encryptionPublicKeyManager.setMsgStatusReceived(msgId, publicKey);
|
||||
} catch (error) {
|
||||
log.info(
|
||||
'MetaMaskController - eth_getEncryptionPublicKey failed.',
|
||||
error,
|
||||
);
|
||||
this.encryptionPublicKeyManager.errorMessage(msgId, error);
|
||||
}
|
||||
return this.getState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to cancel a eth_getEncryptionPublicKey type message.
|
||||
*
|
||||
* @param {string} msgId - The ID of the message to cancel.
|
||||
*/
|
||||
cancelEncryptionPublicKey(msgId) {
|
||||
const messageManager = this.encryptionPublicKeyManager;
|
||||
messageManager.rejectMsg(msgId);
|
||||
return this.getState();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean} true if the keyring type supports EIP-1559
|
||||
*/
|
||||
|
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,
|
||||
},
|
||||
};
|
||||
}
|
91
app/scripts/migrations/085.test.js
Normal file
91
app/scripts/migrations/085.test.js
Normal file
@ -0,0 +1,91 @@
|
||||
import { migrate, version } from './085';
|
||||
|
||||
jest.mock('uuid', () => {
|
||||
const actual = jest.requireActual('uuid');
|
||||
|
||||
return {
|
||||
...actual,
|
||||
v4: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('migration #85', () => {
|
||||
it('should update the version metadata', async () => {
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 84,
|
||||
},
|
||||
data: {},
|
||||
};
|
||||
|
||||
const newStorage = await migrate(oldStorage);
|
||||
expect(newStorage.meta).toStrictEqual({
|
||||
version,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return state unaltered if there is no network controller state', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 84,
|
||||
},
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
const newStorage = await migrate(oldStorage);
|
||||
expect(newStorage.data).toStrictEqual(oldData);
|
||||
});
|
||||
|
||||
it('should return state unaltered if there is no network controller previous provider state', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
NetworkController: {
|
||||
provider: {
|
||||
some: 'provider',
|
||||
},
|
||||
},
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 84,
|
||||
},
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
const newStorage = await migrate(oldStorage);
|
||||
expect(newStorage.data).toStrictEqual(oldData);
|
||||
});
|
||||
|
||||
it('should remove the previous provider state', async () => {
|
||||
const oldData = {
|
||||
other: 'data',
|
||||
NetworkController: {
|
||||
previousProviderStore: {
|
||||
example: 'config',
|
||||
},
|
||||
provider: {
|
||||
some: 'provider',
|
||||
},
|
||||
},
|
||||
};
|
||||
const oldStorage = {
|
||||
meta: {
|
||||
version: 84,
|
||||
},
|
||||
data: oldData,
|
||||
};
|
||||
|
||||
const newStorage = await migrate(oldStorage);
|
||||
expect(newStorage.data).toStrictEqual({
|
||||
other: 'data',
|
||||
NetworkController: {
|
||||
provider: {
|
||||
some: 'provider',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
33
app/scripts/migrations/085.ts
Normal file
33
app/scripts/migrations/085.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { isObject } from '@metamask/utils';
|
||||
|
||||
export const version = 85;
|
||||
|
||||
/**
|
||||
* Remove the now-obsolete network controller `previousProviderStore` state.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
delete state.NetworkController.previousProviderStore;
|
||||
|
||||
return state;
|
||||
}
|
@ -87,6 +87,8 @@ import m080 from './080';
|
||||
import * as m081 from './081';
|
||||
import * as m082 from './082';
|
||||
import * as m083 from './083';
|
||||
import * as m084 from './084';
|
||||
import * as m085 from './085';
|
||||
|
||||
const migrations = [
|
||||
m002,
|
||||
@ -171,6 +173,8 @@ const migrations = [
|
||||
m081,
|
||||
m082,
|
||||
m083,
|
||||
m084,
|
||||
m085,
|
||||
];
|
||||
|
||||
export default migrations;
|
||||
|
@ -6,10 +6,10 @@
|
||||
// subset of files to check against these targets.
|
||||
module.exports = {
|
||||
global: {
|
||||
lines: 66,
|
||||
branches: 54.4,
|
||||
statements: 65,
|
||||
functions: 58.5,
|
||||
lines: 67.8,
|
||||
branches: 55.84,
|
||||
statements: 67.13,
|
||||
functions: 59.66,
|
||||
},
|
||||
transforms: {
|
||||
branches: 100,
|
||||
|
@ -2,6 +2,7 @@ module.exports = {
|
||||
collectCoverageFrom: [
|
||||
'<rootDir>/app/scripts/constants/error-utils.js',
|
||||
'<rootDir>/app/scripts/controllers/network/**/*.js',
|
||||
'<rootDir>/app/scripts/controllers/network/**/*.ts',
|
||||
'<rootDir>/app/scripts/controllers/permissions/**/*.js',
|
||||
'<rootDir>/app/scripts/controllers/sign.ts',
|
||||
'<rootDir>/app/scripts/flask/**/*.js',
|
||||
@ -39,6 +40,7 @@ module.exports = {
|
||||
'<rootDir>/app/scripts/constants/error-utils.test.js',
|
||||
'<rootDir>/app/scripts/controllers/app-state.test.js',
|
||||
'<rootDir>/app/scripts/controllers/network/**/*.test.js',
|
||||
'<rootDir>/app/scripts/controllers/network/**/*.test.ts',
|
||||
'<rootDir>/app/scripts/controllers/permissions/**/*.test.js',
|
||||
'<rootDir>/app/scripts/controllers/sign.test.ts',
|
||||
'<rootDir>/app/scripts/flask/**/*.test.js',
|
||||
@ -51,7 +53,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
|
||||
|
@ -660,28 +660,8 @@
|
||||
},
|
||||
"@metamask/address-book-controller": {
|
||||
"packages": {
|
||||
"@metamask/address-book-controller>@metamask/base-controller": true,
|
||||
"@metamask/address-book-controller>@metamask/controller-utils": true
|
||||
}
|
||||
},
|
||||
"@metamask/address-book-controller>@metamask/base-controller": {
|
||||
"packages": {
|
||||
"immer": true
|
||||
}
|
||||
},
|
||||
"@metamask/address-book-controller>@metamask/controller-utils": {
|
||||
"globals": {
|
||||
"console.error": true,
|
||||
"fetch": true,
|
||||
"setTimeout": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/phishing-controller>isomorphic-fetch": true,
|
||||
"browserify>buffer": true,
|
||||
"eslint>fast-deep-equal": true,
|
||||
"eth-ens-namehash": true,
|
||||
"ethereumjs-util": true,
|
||||
"ethjs>ethjs-unit": true
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/controller-utils": true
|
||||
}
|
||||
},
|
||||
"@metamask/announcement-controller": {
|
||||
@ -716,12 +696,12 @@
|
||||
"@ethersproject/contracts": true,
|
||||
"@ethersproject/providers": true,
|
||||
"@metamask/assets-controllers>@metamask/abi-utils": true,
|
||||
"@metamask/assets-controllers>@metamask/controller-utils": true,
|
||||
"@metamask/assets-controllers>@metamask/utils": true,
|
||||
"@metamask/assets-controllers>abort-controller": true,
|
||||
"@metamask/assets-controllers>multiformats": true,
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/contract-metadata": true,
|
||||
"@metamask/controller-utils": true,
|
||||
"@metamask/metamask-eth-abis": true,
|
||||
"browserify>events": true,
|
||||
"eth-json-rpc-filters>async-mutex": true,
|
||||
@ -750,21 +730,6 @@
|
||||
"semver": true
|
||||
}
|
||||
},
|
||||
"@metamask/assets-controllers>@metamask/controller-utils": {
|
||||
"globals": {
|
||||
"console.error": true,
|
||||
"fetch": true,
|
||||
"setTimeout": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/phishing-controller>isomorphic-fetch": true,
|
||||
"browserify>buffer": true,
|
||||
"eslint>fast-deep-equal": true,
|
||||
"eth-ens-namehash": true,
|
||||
"ethereumjs-util": true,
|
||||
"ethjs>ethjs-unit": true
|
||||
}
|
||||
},
|
||||
"@metamask/assets-controllers>@metamask/utils": {
|
||||
"globals": {
|
||||
"TextDecoder": true,
|
||||
@ -1089,7 +1054,7 @@
|
||||
"@metamask/eth-token-tracker>deep-equal>is-date-object": true,
|
||||
"@ngraveio/bc-ur>assert>object-is": true,
|
||||
"@storybook/api>telejson>is-regex": true,
|
||||
"mocha>object.assign>object-keys": true,
|
||||
"globalthis>define-properties>object-keys": true,
|
||||
"string.prototype.matchall>regexp.prototype.flags": true
|
||||
}
|
||||
},
|
||||
@ -1193,34 +1158,14 @@
|
||||
"setInterval": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/gas-fee-controller>@metamask/base-controller": true,
|
||||
"@metamask/gas-fee-controller>@metamask/controller-utils": true,
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/controller-utils": true,
|
||||
"eth-query": true,
|
||||
"ethereumjs-util": true,
|
||||
"ethjs>ethjs-unit": true,
|
||||
"uuid": true
|
||||
}
|
||||
},
|
||||
"@metamask/gas-fee-controller>@metamask/base-controller": {
|
||||
"packages": {
|
||||
"immer": true
|
||||
}
|
||||
},
|
||||
"@metamask/gas-fee-controller>@metamask/controller-utils": {
|
||||
"globals": {
|
||||
"console.error": true,
|
||||
"fetch": true,
|
||||
"setTimeout": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/phishing-controller>isomorphic-fetch": true,
|
||||
"browserify>buffer": true,
|
||||
"eslint>fast-deep-equal": true,
|
||||
"eth-ens-namehash": true,
|
||||
"ethereumjs-util": true,
|
||||
"ethjs>ethjs-unit": true
|
||||
}
|
||||
},
|
||||
"@metamask/jazzicon": {
|
||||
"globals": {
|
||||
"document.createElement": true,
|
||||
@ -1319,8 +1264,8 @@
|
||||
},
|
||||
"@metamask/message-manager": {
|
||||
"packages": {
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/controller-utils": true,
|
||||
"@metamask/message-manager>@metamask/base-controller": true,
|
||||
"@metamask/message-manager>jsonschema": true,
|
||||
"browserify>buffer": true,
|
||||
"browserify>events": true,
|
||||
@ -1329,11 +1274,6 @@
|
||||
"uuid": true
|
||||
}
|
||||
},
|
||||
"@metamask/message-manager>@metamask/base-controller": {
|
||||
"packages": {
|
||||
"immer": true
|
||||
}
|
||||
},
|
||||
"@metamask/message-manager>jsonschema": {
|
||||
"packages": {
|
||||
"browserify>url": true
|
||||
@ -1364,8 +1304,8 @@
|
||||
"console.error": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/controller-utils": true,
|
||||
"@metamask/permission-controller>@metamask/base-controller": true,
|
||||
"@metamask/permission-controller>nanoid": true,
|
||||
"deep-freeze-strict": true,
|
||||
"eth-rpc-errors": true,
|
||||
@ -1373,11 +1313,6 @@
|
||||
"json-rpc-engine": true
|
||||
}
|
||||
},
|
||||
"@metamask/permission-controller>@metamask/base-controller": {
|
||||
"packages": {
|
||||
"immer": true
|
||||
}
|
||||
},
|
||||
"@metamask/permission-controller>nanoid": {
|
||||
"globals": {
|
||||
"crypto.getRandomValues": true
|
||||
@ -1389,46 +1324,11 @@
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/phishing-controller>@metamask/controller-utils": true,
|
||||
"@metamask/phishing-controller>isomorphic-fetch": true,
|
||||
"@metamask/controller-utils": true,
|
||||
"@metamask/phishing-warning>eth-phishing-detect": true,
|
||||
"punycode": true
|
||||
}
|
||||
},
|
||||
"@metamask/phishing-controller>@metamask/controller-utils": {
|
||||
"globals": {
|
||||
"console.error": true,
|
||||
"fetch": true,
|
||||
"setTimeout": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/phishing-controller>isomorphic-fetch": true,
|
||||
"browserify>buffer": true,
|
||||
"eslint>fast-deep-equal": true,
|
||||
"eth-ens-namehash": true,
|
||||
"ethereumjs-util": true,
|
||||
"ethjs>ethjs-unit": true
|
||||
}
|
||||
},
|
||||
"@metamask/phishing-controller>isomorphic-fetch": {
|
||||
"globals": {
|
||||
"fetch.bind": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/phishing-controller>isomorphic-fetch>whatwg-fetch": true
|
||||
}
|
||||
},
|
||||
"@metamask/phishing-controller>isomorphic-fetch>whatwg-fetch": {
|
||||
"globals": {
|
||||
"Blob": true,
|
||||
"FileReader": true,
|
||||
"FormData": true,
|
||||
"URLSearchParams.prototype.isPrototypeOf": true,
|
||||
"XMLHttpRequest": true,
|
||||
"define": true,
|
||||
"setTimeout": true
|
||||
}
|
||||
},
|
||||
"@metamask/phishing-warning>eth-phishing-detect": {
|
||||
"packages": {
|
||||
"eslint>optionator>fast-levenshtein": true
|
||||
@ -1491,14 +1391,19 @@
|
||||
"@ethersproject/abi>@ethersproject/bytes": true,
|
||||
"@ethersproject/bignumber": true,
|
||||
"@ethersproject/providers": true,
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/phishing-controller>isomorphic-fetch": true,
|
||||
"@metamask/smart-transactions-controller>@metamask/base-controller": true,
|
||||
"@metamask/smart-transactions-controller>@metamask/controller-utils": true,
|
||||
"@metamask/smart-transactions-controller>bignumber.js": true,
|
||||
"@metamask/smart-transactions-controller>isomorphic-fetch": true,
|
||||
"fast-json-patch": true,
|
||||
"lodash": true
|
||||
}
|
||||
},
|
||||
"@metamask/smart-transactions-controller>@metamask/base-controller": {
|
||||
"packages": {
|
||||
"immer": true
|
||||
}
|
||||
},
|
||||
"@metamask/smart-transactions-controller>@metamask/controller-utils": {
|
||||
"globals": {
|
||||
"console.error": true,
|
||||
@ -1506,7 +1411,7 @@
|
||||
"setTimeout": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/phishing-controller>isomorphic-fetch": true,
|
||||
"@metamask/smart-transactions-controller>isomorphic-fetch": true,
|
||||
"browserify>buffer": true,
|
||||
"eslint>fast-deep-equal": true,
|
||||
"eth-ens-namehash": true,
|
||||
@ -1525,6 +1430,25 @@
|
||||
"define": true
|
||||
}
|
||||
},
|
||||
"@metamask/smart-transactions-controller>isomorphic-fetch": {
|
||||
"globals": {
|
||||
"fetch.bind": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/smart-transactions-controller>isomorphic-fetch>whatwg-fetch": true
|
||||
}
|
||||
},
|
||||
"@metamask/smart-transactions-controller>isomorphic-fetch>whatwg-fetch": {
|
||||
"globals": {
|
||||
"Blob": true,
|
||||
"FileReader": true,
|
||||
"FormData": true,
|
||||
"URLSearchParams.prototype.isPrototypeOf": true,
|
||||
"XMLHttpRequest": true,
|
||||
"define": true,
|
||||
"setTimeout": true
|
||||
}
|
||||
},
|
||||
"@metamask/snaps-controllers>nanoid": {
|
||||
"globals": {
|
||||
"crypto.getRandomValues": true
|
||||
@ -2560,7 +2484,7 @@
|
||||
},
|
||||
"browserify>has": {
|
||||
"packages": {
|
||||
"mocha>object.assign>function-bind": true
|
||||
"browserify>has>function-bind": true
|
||||
}
|
||||
},
|
||||
"browserify>os-browserify": {
|
||||
@ -3615,7 +3539,7 @@
|
||||
"globalthis>define-properties": {
|
||||
"packages": {
|
||||
"globalthis>define-properties>has-property-descriptors": true,
|
||||
"mocha>object.assign>object-keys": true
|
||||
"globalthis>define-properties>object-keys": true
|
||||
}
|
||||
},
|
||||
"globalthis>define-properties>has-property-descriptors": {
|
||||
@ -4235,7 +4159,7 @@
|
||||
},
|
||||
"string.prototype.matchall>call-bind": {
|
||||
"packages": {
|
||||
"mocha>object.assign>function-bind": true,
|
||||
"browserify>has>function-bind": true,
|
||||
"string.prototype.matchall>get-intrinsic": true
|
||||
}
|
||||
},
|
||||
@ -4247,7 +4171,7 @@
|
||||
},
|
||||
"packages": {
|
||||
"browserify>has": true,
|
||||
"mocha>object.assign>function-bind": true,
|
||||
"browserify>has>function-bind": true,
|
||||
"string.prototype.matchall>has-symbols": true
|
||||
}
|
||||
},
|
||||
|
@ -660,28 +660,8 @@
|
||||
},
|
||||
"@metamask/address-book-controller": {
|
||||
"packages": {
|
||||
"@metamask/address-book-controller>@metamask/base-controller": true,
|
||||
"@metamask/address-book-controller>@metamask/controller-utils": true
|
||||
}
|
||||
},
|
||||
"@metamask/address-book-controller>@metamask/base-controller": {
|
||||
"packages": {
|
||||
"immer": true
|
||||
}
|
||||
},
|
||||
"@metamask/address-book-controller>@metamask/controller-utils": {
|
||||
"globals": {
|
||||
"console.error": true,
|
||||
"fetch": true,
|
||||
"setTimeout": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/phishing-controller>isomorphic-fetch": true,
|
||||
"browserify>buffer": true,
|
||||
"eslint>fast-deep-equal": true,
|
||||
"eth-ens-namehash": true,
|
||||
"ethereumjs-util": true,
|
||||
"ethjs>ethjs-unit": true
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/controller-utils": true
|
||||
}
|
||||
},
|
||||
"@metamask/announcement-controller": {
|
||||
@ -716,12 +696,12 @@
|
||||
"@ethersproject/contracts": true,
|
||||
"@ethersproject/providers": true,
|
||||
"@metamask/assets-controllers>@metamask/abi-utils": true,
|
||||
"@metamask/assets-controllers>@metamask/controller-utils": true,
|
||||
"@metamask/assets-controllers>@metamask/utils": true,
|
||||
"@metamask/assets-controllers>abort-controller": true,
|
||||
"@metamask/assets-controllers>multiformats": true,
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/contract-metadata": true,
|
||||
"@metamask/controller-utils": true,
|
||||
"@metamask/metamask-eth-abis": true,
|
||||
"browserify>events": true,
|
||||
"eth-json-rpc-filters>async-mutex": true,
|
||||
@ -750,21 +730,6 @@
|
||||
"semver": true
|
||||
}
|
||||
},
|
||||
"@metamask/assets-controllers>@metamask/controller-utils": {
|
||||
"globals": {
|
||||
"console.error": true,
|
||||
"fetch": true,
|
||||
"setTimeout": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/phishing-controller>isomorphic-fetch": true,
|
||||
"browserify>buffer": true,
|
||||
"eslint>fast-deep-equal": true,
|
||||
"eth-ens-namehash": true,
|
||||
"ethereumjs-util": true,
|
||||
"ethjs>ethjs-unit": true
|
||||
}
|
||||
},
|
||||
"@metamask/assets-controllers>@metamask/utils": {
|
||||
"globals": {
|
||||
"TextDecoder": true,
|
||||
@ -1161,7 +1126,7 @@
|
||||
"@metamask/eth-token-tracker>deep-equal>is-date-object": true,
|
||||
"@ngraveio/bc-ur>assert>object-is": true,
|
||||
"@storybook/api>telejson>is-regex": true,
|
||||
"mocha>object.assign>object-keys": true,
|
||||
"globalthis>define-properties>object-keys": true,
|
||||
"string.prototype.matchall>regexp.prototype.flags": true
|
||||
}
|
||||
},
|
||||
@ -1265,34 +1230,14 @@
|
||||
"setInterval": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/gas-fee-controller>@metamask/base-controller": true,
|
||||
"@metamask/gas-fee-controller>@metamask/controller-utils": true,
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/controller-utils": true,
|
||||
"eth-query": true,
|
||||
"ethereumjs-util": true,
|
||||
"ethjs>ethjs-unit": true,
|
||||
"uuid": true
|
||||
}
|
||||
},
|
||||
"@metamask/gas-fee-controller>@metamask/base-controller": {
|
||||
"packages": {
|
||||
"immer": true
|
||||
}
|
||||
},
|
||||
"@metamask/gas-fee-controller>@metamask/controller-utils": {
|
||||
"globals": {
|
||||
"console.error": true,
|
||||
"fetch": true,
|
||||
"setTimeout": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/phishing-controller>isomorphic-fetch": true,
|
||||
"browserify>buffer": true,
|
||||
"eslint>fast-deep-equal": true,
|
||||
"eth-ens-namehash": true,
|
||||
"ethereumjs-util": true,
|
||||
"ethjs>ethjs-unit": true
|
||||
}
|
||||
},
|
||||
"@metamask/jazzicon": {
|
||||
"globals": {
|
||||
"document.createElement": true,
|
||||
@ -1391,8 +1336,8 @@
|
||||
},
|
||||
"@metamask/message-manager": {
|
||||
"packages": {
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/controller-utils": true,
|
||||
"@metamask/message-manager>@metamask/base-controller": true,
|
||||
"@metamask/message-manager>jsonschema": true,
|
||||
"browserify>buffer": true,
|
||||
"browserify>events": true,
|
||||
@ -1401,11 +1346,6 @@
|
||||
"uuid": true
|
||||
}
|
||||
},
|
||||
"@metamask/message-manager>@metamask/base-controller": {
|
||||
"packages": {
|
||||
"immer": true
|
||||
}
|
||||
},
|
||||
"@metamask/message-manager>jsonschema": {
|
||||
"packages": {
|
||||
"browserify>url": true
|
||||
@ -1413,31 +1353,11 @@
|
||||
},
|
||||
"@metamask/notification-controller": {
|
||||
"packages": {
|
||||
"@metamask/notification-controller>@metamask/base-controller": true,
|
||||
"@metamask/notification-controller>@metamask/controller-utils": true,
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/controller-utils": true,
|
||||
"@metamask/notification-controller>nanoid": true
|
||||
}
|
||||
},
|
||||
"@metamask/notification-controller>@metamask/base-controller": {
|
||||
"packages": {
|
||||
"immer": true
|
||||
}
|
||||
},
|
||||
"@metamask/notification-controller>@metamask/controller-utils": {
|
||||
"globals": {
|
||||
"console.error": true,
|
||||
"fetch": true,
|
||||
"setTimeout": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/phishing-controller>isomorphic-fetch": true,
|
||||
"browserify>buffer": true,
|
||||
"eslint>fast-deep-equal": true,
|
||||
"eth-ens-namehash": true,
|
||||
"ethereumjs-util": true,
|
||||
"ethjs>ethjs-unit": true
|
||||
}
|
||||
},
|
||||
"@metamask/notification-controller>nanoid": {
|
||||
"globals": {
|
||||
"crypto.getRandomValues": true
|
||||
@ -1463,8 +1383,8 @@
|
||||
"console.error": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/controller-utils": true,
|
||||
"@metamask/permission-controller>@metamask/base-controller": true,
|
||||
"@metamask/permission-controller>nanoid": true,
|
||||
"deep-freeze-strict": true,
|
||||
"eth-rpc-errors": true,
|
||||
@ -1472,11 +1392,6 @@
|
||||
"json-rpc-engine": true
|
||||
}
|
||||
},
|
||||
"@metamask/permission-controller>@metamask/base-controller": {
|
||||
"packages": {
|
||||
"immer": true
|
||||
}
|
||||
},
|
||||
"@metamask/permission-controller>nanoid": {
|
||||
"globals": {
|
||||
"crypto.getRandomValues": true
|
||||
@ -1488,46 +1403,11 @@
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/phishing-controller>@metamask/controller-utils": true,
|
||||
"@metamask/phishing-controller>isomorphic-fetch": true,
|
||||
"@metamask/controller-utils": true,
|
||||
"@metamask/phishing-warning>eth-phishing-detect": true,
|
||||
"punycode": true
|
||||
}
|
||||
},
|
||||
"@metamask/phishing-controller>@metamask/controller-utils": {
|
||||
"globals": {
|
||||
"console.error": true,
|
||||
"fetch": true,
|
||||
"setTimeout": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/phishing-controller>isomorphic-fetch": true,
|
||||
"browserify>buffer": true,
|
||||
"eslint>fast-deep-equal": true,
|
||||
"eth-ens-namehash": true,
|
||||
"ethereumjs-util": true,
|
||||
"ethjs>ethjs-unit": true
|
||||
}
|
||||
},
|
||||
"@metamask/phishing-controller>isomorphic-fetch": {
|
||||
"globals": {
|
||||
"fetch.bind": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/phishing-controller>isomorphic-fetch>whatwg-fetch": true
|
||||
}
|
||||
},
|
||||
"@metamask/phishing-controller>isomorphic-fetch>whatwg-fetch": {
|
||||
"globals": {
|
||||
"Blob": true,
|
||||
"FileReader": true,
|
||||
"FormData": true,
|
||||
"URLSearchParams.prototype.isPrototypeOf": true,
|
||||
"XMLHttpRequest": true,
|
||||
"define": true,
|
||||
"setTimeout": true
|
||||
}
|
||||
},
|
||||
"@metamask/phishing-warning>eth-phishing-detect": {
|
||||
"packages": {
|
||||
"eslint>optionator>fast-levenshtein": true
|
||||
@ -1607,15 +1487,10 @@
|
||||
"setTimeout": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/rate-limit-controller>@metamask/base-controller": true,
|
||||
"@metamask/base-controller": true,
|
||||
"eth-rpc-errors": true
|
||||
}
|
||||
},
|
||||
"@metamask/rate-limit-controller>@metamask/base-controller": {
|
||||
"packages": {
|
||||
"immer": true
|
||||
}
|
||||
},
|
||||
"@metamask/rpc-methods": {
|
||||
"packages": {
|
||||
"@metamask/key-tree": true,
|
||||
@ -1679,14 +1554,19 @@
|
||||
"@ethersproject/abi>@ethersproject/bytes": true,
|
||||
"@ethersproject/bignumber": true,
|
||||
"@ethersproject/providers": true,
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/phishing-controller>isomorphic-fetch": true,
|
||||
"@metamask/smart-transactions-controller>@metamask/base-controller": true,
|
||||
"@metamask/smart-transactions-controller>@metamask/controller-utils": true,
|
||||
"@metamask/smart-transactions-controller>bignumber.js": true,
|
||||
"@metamask/smart-transactions-controller>isomorphic-fetch": true,
|
||||
"fast-json-patch": true,
|
||||
"lodash": true
|
||||
}
|
||||
},
|
||||
"@metamask/smart-transactions-controller>@metamask/base-controller": {
|
||||
"packages": {
|
||||
"immer": true
|
||||
}
|
||||
},
|
||||
"@metamask/smart-transactions-controller>@metamask/controller-utils": {
|
||||
"globals": {
|
||||
"console.error": true,
|
||||
@ -1694,7 +1574,7 @@
|
||||
"setTimeout": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/phishing-controller>isomorphic-fetch": true,
|
||||
"@metamask/smart-transactions-controller>isomorphic-fetch": true,
|
||||
"browserify>buffer": true,
|
||||
"eslint>fast-deep-equal": true,
|
||||
"eth-ens-namehash": true,
|
||||
@ -1713,6 +1593,25 @@
|
||||
"define": true
|
||||
}
|
||||
},
|
||||
"@metamask/smart-transactions-controller>isomorphic-fetch": {
|
||||
"globals": {
|
||||
"fetch.bind": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/smart-transactions-controller>isomorphic-fetch>whatwg-fetch": true
|
||||
}
|
||||
},
|
||||
"@metamask/smart-transactions-controller>isomorphic-fetch>whatwg-fetch": {
|
||||
"globals": {
|
||||
"Blob": true,
|
||||
"FileReader": true,
|
||||
"FormData": true,
|
||||
"URLSearchParams.prototype.isPrototypeOf": true,
|
||||
"XMLHttpRequest": true,
|
||||
"define": true,
|
||||
"setTimeout": true
|
||||
}
|
||||
},
|
||||
"@metamask/snaps-controllers": {
|
||||
"globals": {
|
||||
"URL": true,
|
||||
@ -1724,12 +1623,11 @@
|
||||
"setTimeout": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/permission-controller": true,
|
||||
"@metamask/post-message-stream": true,
|
||||
"@metamask/providers>@metamask/object-multiplex": true,
|
||||
"@metamask/rpc-methods": true,
|
||||
"@metamask/snaps-controllers>@metamask/base-controller": true,
|
||||
"@metamask/snaps-controllers>@metamask/subject-metadata-controller": true,
|
||||
"@metamask/snaps-controllers>@xstate/fsm": true,
|
||||
"@metamask/snaps-controllers>concat-stream": true,
|
||||
"@metamask/snaps-controllers>gunzip-maybe": true,
|
||||
@ -1738,6 +1636,7 @@
|
||||
"@metamask/snaps-controllers>tar-stream": true,
|
||||
"@metamask/snaps-utils": true,
|
||||
"@metamask/snaps-utils>@metamask/snaps-registry": true,
|
||||
"@metamask/subject-metadata-controller": true,
|
||||
"@metamask/utils": true,
|
||||
"eth-rpc-errors": true,
|
||||
"json-rpc-engine": true,
|
||||
@ -1745,16 +1644,6 @@
|
||||
"pump": true
|
||||
}
|
||||
},
|
||||
"@metamask/snaps-controllers>@metamask/base-controller": {
|
||||
"packages": {
|
||||
"immer": true
|
||||
}
|
||||
},
|
||||
"@metamask/snaps-controllers>@metamask/subject-metadata-controller": {
|
||||
"packages": {
|
||||
"@metamask/snaps-controllers>@metamask/base-controller": true
|
||||
}
|
||||
},
|
||||
"@metamask/snaps-controllers>concat-stream": {
|
||||
"packages": {
|
||||
"@metamask/snaps-controllers>concat-stream>readable-stream": true,
|
||||
@ -2988,7 +2877,7 @@
|
||||
},
|
||||
"browserify>has": {
|
||||
"packages": {
|
||||
"mocha>object.assign>function-bind": true
|
||||
"browserify>has>function-bind": true
|
||||
}
|
||||
},
|
||||
"browserify>os-browserify": {
|
||||
@ -4043,7 +3932,7 @@
|
||||
"globalthis>define-properties": {
|
||||
"packages": {
|
||||
"globalthis>define-properties>has-property-descriptors": true,
|
||||
"mocha>object.assign>object-keys": true
|
||||
"globalthis>define-properties>object-keys": true
|
||||
}
|
||||
},
|
||||
"globalthis>define-properties>has-property-descriptors": {
|
||||
@ -4457,9 +4346,9 @@
|
||||
"react-markdown>unified": {
|
||||
"packages": {
|
||||
"jsdom>request>extend": true,
|
||||
"mocha>yargs-unparser>is-plain-obj": true,
|
||||
"react-markdown>unified>bail": true,
|
||||
"react-markdown>unified>is-buffer": true,
|
||||
"react-markdown>unified>is-plain-obj": true,
|
||||
"react-markdown>unified>trough": true,
|
||||
"react-markdown>vfile": true
|
||||
}
|
||||
@ -4795,7 +4684,7 @@
|
||||
},
|
||||
"string.prototype.matchall>call-bind": {
|
||||
"packages": {
|
||||
"mocha>object.assign>function-bind": true,
|
||||
"browserify>has>function-bind": true,
|
||||
"string.prototype.matchall>get-intrinsic": true
|
||||
}
|
||||
},
|
||||
@ -4807,7 +4696,7 @@
|
||||
},
|
||||
"packages": {
|
||||
"browserify>has": true,
|
||||
"mocha>object.assign>function-bind": true,
|
||||
"browserify>has>function-bind": true,
|
||||
"string.prototype.matchall>has-symbols": true
|
||||
}
|
||||
},
|
||||
|
@ -660,28 +660,8 @@
|
||||
},
|
||||
"@metamask/address-book-controller": {
|
||||
"packages": {
|
||||
"@metamask/address-book-controller>@metamask/base-controller": true,
|
||||
"@metamask/address-book-controller>@metamask/controller-utils": true
|
||||
}
|
||||
},
|
||||
"@metamask/address-book-controller>@metamask/base-controller": {
|
||||
"packages": {
|
||||
"immer": true
|
||||
}
|
||||
},
|
||||
"@metamask/address-book-controller>@metamask/controller-utils": {
|
||||
"globals": {
|
||||
"console.error": true,
|
||||
"fetch": true,
|
||||
"setTimeout": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/phishing-controller>isomorphic-fetch": true,
|
||||
"browserify>buffer": true,
|
||||
"eslint>fast-deep-equal": true,
|
||||
"eth-ens-namehash": true,
|
||||
"ethereumjs-util": true,
|
||||
"ethjs>ethjs-unit": true
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/controller-utils": true
|
||||
}
|
||||
},
|
||||
"@metamask/announcement-controller": {
|
||||
@ -716,12 +696,12 @@
|
||||
"@ethersproject/contracts": true,
|
||||
"@ethersproject/providers": true,
|
||||
"@metamask/assets-controllers>@metamask/abi-utils": true,
|
||||
"@metamask/assets-controllers>@metamask/controller-utils": true,
|
||||
"@metamask/assets-controllers>@metamask/utils": true,
|
||||
"@metamask/assets-controllers>abort-controller": true,
|
||||
"@metamask/assets-controllers>multiformats": true,
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/contract-metadata": true,
|
||||
"@metamask/controller-utils": true,
|
||||
"@metamask/metamask-eth-abis": true,
|
||||
"browserify>events": true,
|
||||
"eth-json-rpc-filters>async-mutex": true,
|
||||
@ -750,21 +730,6 @@
|
||||
"semver": true
|
||||
}
|
||||
},
|
||||
"@metamask/assets-controllers>@metamask/controller-utils": {
|
||||
"globals": {
|
||||
"console.error": true,
|
||||
"fetch": true,
|
||||
"setTimeout": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/phishing-controller>isomorphic-fetch": true,
|
||||
"browserify>buffer": true,
|
||||
"eslint>fast-deep-equal": true,
|
||||
"eth-ens-namehash": true,
|
||||
"ethereumjs-util": true,
|
||||
"ethjs>ethjs-unit": true
|
||||
}
|
||||
},
|
||||
"@metamask/assets-controllers>@metamask/utils": {
|
||||
"globals": {
|
||||
"TextDecoder": true,
|
||||
@ -1161,7 +1126,7 @@
|
||||
"@metamask/eth-token-tracker>deep-equal>is-date-object": true,
|
||||
"@ngraveio/bc-ur>assert>object-is": true,
|
||||
"@storybook/api>telejson>is-regex": true,
|
||||
"mocha>object.assign>object-keys": true,
|
||||
"globalthis>define-properties>object-keys": true,
|
||||
"string.prototype.matchall>regexp.prototype.flags": true
|
||||
}
|
||||
},
|
||||
@ -1265,34 +1230,14 @@
|
||||
"setInterval": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/gas-fee-controller>@metamask/base-controller": true,
|
||||
"@metamask/gas-fee-controller>@metamask/controller-utils": true,
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/controller-utils": true,
|
||||
"eth-query": true,
|
||||
"ethereumjs-util": true,
|
||||
"ethjs>ethjs-unit": true,
|
||||
"uuid": true
|
||||
}
|
||||
},
|
||||
"@metamask/gas-fee-controller>@metamask/base-controller": {
|
||||
"packages": {
|
||||
"immer": true
|
||||
}
|
||||
},
|
||||
"@metamask/gas-fee-controller>@metamask/controller-utils": {
|
||||
"globals": {
|
||||
"console.error": true,
|
||||
"fetch": true,
|
||||
"setTimeout": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/phishing-controller>isomorphic-fetch": true,
|
||||
"browserify>buffer": true,
|
||||
"eslint>fast-deep-equal": true,
|
||||
"eth-ens-namehash": true,
|
||||
"ethereumjs-util": true,
|
||||
"ethjs>ethjs-unit": true
|
||||
}
|
||||
},
|
||||
"@metamask/jazzicon": {
|
||||
"globals": {
|
||||
"document.createElement": true,
|
||||
@ -1391,8 +1336,8 @@
|
||||
},
|
||||
"@metamask/message-manager": {
|
||||
"packages": {
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/controller-utils": true,
|
||||
"@metamask/message-manager>@metamask/base-controller": true,
|
||||
"@metamask/message-manager>jsonschema": true,
|
||||
"browserify>buffer": true,
|
||||
"browserify>events": true,
|
||||
@ -1401,11 +1346,6 @@
|
||||
"uuid": true
|
||||
}
|
||||
},
|
||||
"@metamask/message-manager>@metamask/base-controller": {
|
||||
"packages": {
|
||||
"immer": true
|
||||
}
|
||||
},
|
||||
"@metamask/message-manager>jsonschema": {
|
||||
"packages": {
|
||||
"browserify>url": true
|
||||
@ -1413,31 +1353,11 @@
|
||||
},
|
||||
"@metamask/notification-controller": {
|
||||
"packages": {
|
||||
"@metamask/notification-controller>@metamask/base-controller": true,
|
||||
"@metamask/notification-controller>@metamask/controller-utils": true,
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/controller-utils": true,
|
||||
"@metamask/notification-controller>nanoid": true
|
||||
}
|
||||
},
|
||||
"@metamask/notification-controller>@metamask/base-controller": {
|
||||
"packages": {
|
||||
"immer": true
|
||||
}
|
||||
},
|
||||
"@metamask/notification-controller>@metamask/controller-utils": {
|
||||
"globals": {
|
||||
"console.error": true,
|
||||
"fetch": true,
|
||||
"setTimeout": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/phishing-controller>isomorphic-fetch": true,
|
||||
"browserify>buffer": true,
|
||||
"eslint>fast-deep-equal": true,
|
||||
"eth-ens-namehash": true,
|
||||
"ethereumjs-util": true,
|
||||
"ethjs>ethjs-unit": true
|
||||
}
|
||||
},
|
||||
"@metamask/notification-controller>nanoid": {
|
||||
"globals": {
|
||||
"crypto.getRandomValues": true
|
||||
@ -1463,8 +1383,8 @@
|
||||
"console.error": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/controller-utils": true,
|
||||
"@metamask/permission-controller>@metamask/base-controller": true,
|
||||
"@metamask/permission-controller>nanoid": true,
|
||||
"deep-freeze-strict": true,
|
||||
"eth-rpc-errors": true,
|
||||
@ -1472,11 +1392,6 @@
|
||||
"json-rpc-engine": true
|
||||
}
|
||||
},
|
||||
"@metamask/permission-controller>@metamask/base-controller": {
|
||||
"packages": {
|
||||
"immer": true
|
||||
}
|
||||
},
|
||||
"@metamask/permission-controller>nanoid": {
|
||||
"globals": {
|
||||
"crypto.getRandomValues": true
|
||||
@ -1488,46 +1403,11 @@
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/phishing-controller>@metamask/controller-utils": true,
|
||||
"@metamask/phishing-controller>isomorphic-fetch": true,
|
||||
"@metamask/controller-utils": true,
|
||||
"@metamask/phishing-warning>eth-phishing-detect": true,
|
||||
"punycode": true
|
||||
}
|
||||
},
|
||||
"@metamask/phishing-controller>@metamask/controller-utils": {
|
||||
"globals": {
|
||||
"console.error": true,
|
||||
"fetch": true,
|
||||
"setTimeout": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/phishing-controller>isomorphic-fetch": true,
|
||||
"browserify>buffer": true,
|
||||
"eslint>fast-deep-equal": true,
|
||||
"eth-ens-namehash": true,
|
||||
"ethereumjs-util": true,
|
||||
"ethjs>ethjs-unit": true
|
||||
}
|
||||
},
|
||||
"@metamask/phishing-controller>isomorphic-fetch": {
|
||||
"globals": {
|
||||
"fetch.bind": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/phishing-controller>isomorphic-fetch>whatwg-fetch": true
|
||||
}
|
||||
},
|
||||
"@metamask/phishing-controller>isomorphic-fetch>whatwg-fetch": {
|
||||
"globals": {
|
||||
"Blob": true,
|
||||
"FileReader": true,
|
||||
"FormData": true,
|
||||
"URLSearchParams.prototype.isPrototypeOf": true,
|
||||
"XMLHttpRequest": true,
|
||||
"define": true,
|
||||
"setTimeout": true
|
||||
}
|
||||
},
|
||||
"@metamask/phishing-warning>eth-phishing-detect": {
|
||||
"packages": {
|
||||
"eslint>optionator>fast-levenshtein": true
|
||||
@ -1607,15 +1487,10 @@
|
||||
"setTimeout": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/rate-limit-controller>@metamask/base-controller": true,
|
||||
"@metamask/base-controller": true,
|
||||
"eth-rpc-errors": true
|
||||
}
|
||||
},
|
||||
"@metamask/rate-limit-controller>@metamask/base-controller": {
|
||||
"packages": {
|
||||
"immer": true
|
||||
}
|
||||
},
|
||||
"@metamask/rpc-methods": {
|
||||
"packages": {
|
||||
"@metamask/key-tree": true,
|
||||
@ -1679,14 +1554,19 @@
|
||||
"@ethersproject/abi>@ethersproject/bytes": true,
|
||||
"@ethersproject/bignumber": true,
|
||||
"@ethersproject/providers": true,
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/phishing-controller>isomorphic-fetch": true,
|
||||
"@metamask/smart-transactions-controller>@metamask/base-controller": true,
|
||||
"@metamask/smart-transactions-controller>@metamask/controller-utils": true,
|
||||
"@metamask/smart-transactions-controller>bignumber.js": true,
|
||||
"@metamask/smart-transactions-controller>isomorphic-fetch": true,
|
||||
"fast-json-patch": true,
|
||||
"lodash": true
|
||||
}
|
||||
},
|
||||
"@metamask/smart-transactions-controller>@metamask/base-controller": {
|
||||
"packages": {
|
||||
"immer": true
|
||||
}
|
||||
},
|
||||
"@metamask/smart-transactions-controller>@metamask/controller-utils": {
|
||||
"globals": {
|
||||
"console.error": true,
|
||||
@ -1694,7 +1574,7 @@
|
||||
"setTimeout": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/phishing-controller>isomorphic-fetch": true,
|
||||
"@metamask/smart-transactions-controller>isomorphic-fetch": true,
|
||||
"browserify>buffer": true,
|
||||
"eslint>fast-deep-equal": true,
|
||||
"eth-ens-namehash": true,
|
||||
@ -1713,6 +1593,25 @@
|
||||
"define": true
|
||||
}
|
||||
},
|
||||
"@metamask/smart-transactions-controller>isomorphic-fetch": {
|
||||
"globals": {
|
||||
"fetch.bind": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/smart-transactions-controller>isomorphic-fetch>whatwg-fetch": true
|
||||
}
|
||||
},
|
||||
"@metamask/smart-transactions-controller>isomorphic-fetch>whatwg-fetch": {
|
||||
"globals": {
|
||||
"Blob": true,
|
||||
"FileReader": true,
|
||||
"FormData": true,
|
||||
"URLSearchParams.prototype.isPrototypeOf": true,
|
||||
"XMLHttpRequest": true,
|
||||
"define": true,
|
||||
"setTimeout": true
|
||||
}
|
||||
},
|
||||
"@metamask/snaps-controllers": {
|
||||
"globals": {
|
||||
"URL": true,
|
||||
@ -1724,12 +1623,11 @@
|
||||
"setTimeout": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/permission-controller": true,
|
||||
"@metamask/post-message-stream": true,
|
||||
"@metamask/providers>@metamask/object-multiplex": true,
|
||||
"@metamask/rpc-methods": true,
|
||||
"@metamask/snaps-controllers>@metamask/base-controller": true,
|
||||
"@metamask/snaps-controllers>@metamask/subject-metadata-controller": true,
|
||||
"@metamask/snaps-controllers>@xstate/fsm": true,
|
||||
"@metamask/snaps-controllers>concat-stream": true,
|
||||
"@metamask/snaps-controllers>gunzip-maybe": true,
|
||||
@ -1738,6 +1636,7 @@
|
||||
"@metamask/snaps-controllers>tar-stream": true,
|
||||
"@metamask/snaps-utils": true,
|
||||
"@metamask/snaps-utils>@metamask/snaps-registry": true,
|
||||
"@metamask/subject-metadata-controller": true,
|
||||
"@metamask/utils": true,
|
||||
"eth-rpc-errors": true,
|
||||
"json-rpc-engine": true,
|
||||
@ -1745,16 +1644,6 @@
|
||||
"pump": true
|
||||
}
|
||||
},
|
||||
"@metamask/snaps-controllers>@metamask/base-controller": {
|
||||
"packages": {
|
||||
"immer": true
|
||||
}
|
||||
},
|
||||
"@metamask/snaps-controllers>@metamask/subject-metadata-controller": {
|
||||
"packages": {
|
||||
"@metamask/snaps-controllers>@metamask/base-controller": true
|
||||
}
|
||||
},
|
||||
"@metamask/snaps-controllers>concat-stream": {
|
||||
"packages": {
|
||||
"@metamask/snaps-controllers>concat-stream>readable-stream": true,
|
||||
@ -2988,7 +2877,7 @@
|
||||
},
|
||||
"browserify>has": {
|
||||
"packages": {
|
||||
"mocha>object.assign>function-bind": true
|
||||
"browserify>has>function-bind": true
|
||||
}
|
||||
},
|
||||
"browserify>os-browserify": {
|
||||
@ -4043,7 +3932,7 @@
|
||||
"globalthis>define-properties": {
|
||||
"packages": {
|
||||
"globalthis>define-properties>has-property-descriptors": true,
|
||||
"mocha>object.assign>object-keys": true
|
||||
"globalthis>define-properties>object-keys": true
|
||||
}
|
||||
},
|
||||
"globalthis>define-properties>has-property-descriptors": {
|
||||
@ -4457,9 +4346,9 @@
|
||||
"react-markdown>unified": {
|
||||
"packages": {
|
||||
"jsdom>request>extend": true,
|
||||
"mocha>yargs-unparser>is-plain-obj": true,
|
||||
"react-markdown>unified>bail": true,
|
||||
"react-markdown>unified>is-buffer": true,
|
||||
"react-markdown>unified>is-plain-obj": true,
|
||||
"react-markdown>unified>trough": true,
|
||||
"react-markdown>vfile": true
|
||||
}
|
||||
@ -4795,7 +4684,7 @@
|
||||
},
|
||||
"string.prototype.matchall>call-bind": {
|
||||
"packages": {
|
||||
"mocha>object.assign>function-bind": true,
|
||||
"browserify>has>function-bind": true,
|
||||
"string.prototype.matchall>get-intrinsic": true
|
||||
}
|
||||
},
|
||||
@ -4807,7 +4696,7 @@
|
||||
},
|
||||
"packages": {
|
||||
"browserify>has": true,
|
||||
"mocha>object.assign>function-bind": true,
|
||||
"browserify>has>function-bind": true,
|
||||
"string.prototype.matchall>has-symbols": true
|
||||
}
|
||||
},
|
||||
|
@ -660,28 +660,8 @@
|
||||
},
|
||||
"@metamask/address-book-controller": {
|
||||
"packages": {
|
||||
"@metamask/address-book-controller>@metamask/base-controller": true,
|
||||
"@metamask/address-book-controller>@metamask/controller-utils": true
|
||||
}
|
||||
},
|
||||
"@metamask/address-book-controller>@metamask/base-controller": {
|
||||
"packages": {
|
||||
"immer": true
|
||||
}
|
||||
},
|
||||
"@metamask/address-book-controller>@metamask/controller-utils": {
|
||||
"globals": {
|
||||
"console.error": true,
|
||||
"fetch": true,
|
||||
"setTimeout": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/phishing-controller>isomorphic-fetch": true,
|
||||
"browserify>buffer": true,
|
||||
"eslint>fast-deep-equal": true,
|
||||
"eth-ens-namehash": true,
|
||||
"ethereumjs-util": true,
|
||||
"ethjs>ethjs-unit": true
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/controller-utils": true
|
||||
}
|
||||
},
|
||||
"@metamask/announcement-controller": {
|
||||
@ -716,12 +696,12 @@
|
||||
"@ethersproject/contracts": true,
|
||||
"@ethersproject/providers": true,
|
||||
"@metamask/assets-controllers>@metamask/abi-utils": true,
|
||||
"@metamask/assets-controllers>@metamask/controller-utils": true,
|
||||
"@metamask/assets-controllers>@metamask/utils": true,
|
||||
"@metamask/assets-controllers>abort-controller": true,
|
||||
"@metamask/assets-controllers>multiformats": true,
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/contract-metadata": true,
|
||||
"@metamask/controller-utils": true,
|
||||
"@metamask/metamask-eth-abis": true,
|
||||
"browserify>events": true,
|
||||
"eth-json-rpc-filters>async-mutex": true,
|
||||
@ -750,21 +730,6 @@
|
||||
"semver": true
|
||||
}
|
||||
},
|
||||
"@metamask/assets-controllers>@metamask/controller-utils": {
|
||||
"globals": {
|
||||
"console.error": true,
|
||||
"fetch": true,
|
||||
"setTimeout": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/phishing-controller>isomorphic-fetch": true,
|
||||
"browserify>buffer": true,
|
||||
"eslint>fast-deep-equal": true,
|
||||
"eth-ens-namehash": true,
|
||||
"ethereumjs-util": true,
|
||||
"ethjs>ethjs-unit": true
|
||||
}
|
||||
},
|
||||
"@metamask/assets-controllers>@metamask/utils": {
|
||||
"globals": {
|
||||
"TextDecoder": true,
|
||||
@ -1089,7 +1054,7 @@
|
||||
"@metamask/eth-token-tracker>deep-equal>is-date-object": true,
|
||||
"@ngraveio/bc-ur>assert>object-is": true,
|
||||
"@storybook/api>telejson>is-regex": true,
|
||||
"mocha>object.assign>object-keys": true,
|
||||
"globalthis>define-properties>object-keys": true,
|
||||
"string.prototype.matchall>regexp.prototype.flags": true
|
||||
}
|
||||
},
|
||||
@ -1193,34 +1158,14 @@
|
||||
"setInterval": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/gas-fee-controller>@metamask/base-controller": true,
|
||||
"@metamask/gas-fee-controller>@metamask/controller-utils": true,
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/controller-utils": true,
|
||||
"eth-query": true,
|
||||
"ethereumjs-util": true,
|
||||
"ethjs>ethjs-unit": true,
|
||||
"uuid": true
|
||||
}
|
||||
},
|
||||
"@metamask/gas-fee-controller>@metamask/base-controller": {
|
||||
"packages": {
|
||||
"immer": true
|
||||
}
|
||||
},
|
||||
"@metamask/gas-fee-controller>@metamask/controller-utils": {
|
||||
"globals": {
|
||||
"console.error": true,
|
||||
"fetch": true,
|
||||
"setTimeout": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/phishing-controller>isomorphic-fetch": true,
|
||||
"browserify>buffer": true,
|
||||
"eslint>fast-deep-equal": true,
|
||||
"eth-ens-namehash": true,
|
||||
"ethereumjs-util": true,
|
||||
"ethjs>ethjs-unit": true
|
||||
}
|
||||
},
|
||||
"@metamask/jazzicon": {
|
||||
"globals": {
|
||||
"document.createElement": true,
|
||||
@ -1319,8 +1264,8 @@
|
||||
},
|
||||
"@metamask/message-manager": {
|
||||
"packages": {
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/controller-utils": true,
|
||||
"@metamask/message-manager>@metamask/base-controller": true,
|
||||
"@metamask/message-manager>jsonschema": true,
|
||||
"browserify>buffer": true,
|
||||
"browserify>events": true,
|
||||
@ -1329,11 +1274,6 @@
|
||||
"uuid": true
|
||||
}
|
||||
},
|
||||
"@metamask/message-manager>@metamask/base-controller": {
|
||||
"packages": {
|
||||
"immer": true
|
||||
}
|
||||
},
|
||||
"@metamask/message-manager>jsonschema": {
|
||||
"packages": {
|
||||
"browserify>url": true
|
||||
@ -1364,8 +1304,8 @@
|
||||
"console.error": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/controller-utils": true,
|
||||
"@metamask/permission-controller>@metamask/base-controller": true,
|
||||
"@metamask/permission-controller>nanoid": true,
|
||||
"deep-freeze-strict": true,
|
||||
"eth-rpc-errors": true,
|
||||
@ -1373,11 +1313,6 @@
|
||||
"json-rpc-engine": true
|
||||
}
|
||||
},
|
||||
"@metamask/permission-controller>@metamask/base-controller": {
|
||||
"packages": {
|
||||
"immer": true
|
||||
}
|
||||
},
|
||||
"@metamask/permission-controller>nanoid": {
|
||||
"globals": {
|
||||
"crypto.getRandomValues": true
|
||||
@ -1389,46 +1324,11 @@
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/phishing-controller>@metamask/controller-utils": true,
|
||||
"@metamask/phishing-controller>isomorphic-fetch": true,
|
||||
"@metamask/controller-utils": true,
|
||||
"@metamask/phishing-warning>eth-phishing-detect": true,
|
||||
"punycode": true
|
||||
}
|
||||
},
|
||||
"@metamask/phishing-controller>@metamask/controller-utils": {
|
||||
"globals": {
|
||||
"console.error": true,
|
||||
"fetch": true,
|
||||
"setTimeout": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/phishing-controller>isomorphic-fetch": true,
|
||||
"browserify>buffer": true,
|
||||
"eslint>fast-deep-equal": true,
|
||||
"eth-ens-namehash": true,
|
||||
"ethereumjs-util": true,
|
||||
"ethjs>ethjs-unit": true
|
||||
}
|
||||
},
|
||||
"@metamask/phishing-controller>isomorphic-fetch": {
|
||||
"globals": {
|
||||
"fetch.bind": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/phishing-controller>isomorphic-fetch>whatwg-fetch": true
|
||||
}
|
||||
},
|
||||
"@metamask/phishing-controller>isomorphic-fetch>whatwg-fetch": {
|
||||
"globals": {
|
||||
"Blob": true,
|
||||
"FileReader": true,
|
||||
"FormData": true,
|
||||
"URLSearchParams.prototype.isPrototypeOf": true,
|
||||
"XMLHttpRequest": true,
|
||||
"define": true,
|
||||
"setTimeout": true
|
||||
}
|
||||
},
|
||||
"@metamask/phishing-warning>eth-phishing-detect": {
|
||||
"packages": {
|
||||
"eslint>optionator>fast-levenshtein": true
|
||||
@ -1491,14 +1391,19 @@
|
||||
"@ethersproject/abi>@ethersproject/bytes": true,
|
||||
"@ethersproject/bignumber": true,
|
||||
"@ethersproject/providers": true,
|
||||
"@metamask/base-controller": true,
|
||||
"@metamask/phishing-controller>isomorphic-fetch": true,
|
||||
"@metamask/smart-transactions-controller>@metamask/base-controller": true,
|
||||
"@metamask/smart-transactions-controller>@metamask/controller-utils": true,
|
||||
"@metamask/smart-transactions-controller>bignumber.js": true,
|
||||
"@metamask/smart-transactions-controller>isomorphic-fetch": true,
|
||||
"fast-json-patch": true,
|
||||
"lodash": true
|
||||
}
|
||||
},
|
||||
"@metamask/smart-transactions-controller>@metamask/base-controller": {
|
||||
"packages": {
|
||||
"immer": true
|
||||
}
|
||||
},
|
||||
"@metamask/smart-transactions-controller>@metamask/controller-utils": {
|
||||
"globals": {
|
||||
"console.error": true,
|
||||
@ -1506,7 +1411,7 @@
|
||||
"setTimeout": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/phishing-controller>isomorphic-fetch": true,
|
||||
"@metamask/smart-transactions-controller>isomorphic-fetch": true,
|
||||
"browserify>buffer": true,
|
||||
"eslint>fast-deep-equal": true,
|
||||
"eth-ens-namehash": true,
|
||||
@ -1525,6 +1430,25 @@
|
||||
"define": true
|
||||
}
|
||||
},
|
||||
"@metamask/smart-transactions-controller>isomorphic-fetch": {
|
||||
"globals": {
|
||||
"fetch.bind": true
|
||||
},
|
||||
"packages": {
|
||||
"@metamask/smart-transactions-controller>isomorphic-fetch>whatwg-fetch": true
|
||||
}
|
||||
},
|
||||
"@metamask/smart-transactions-controller>isomorphic-fetch>whatwg-fetch": {
|
||||
"globals": {
|
||||
"Blob": true,
|
||||
"FileReader": true,
|
||||
"FormData": true,
|
||||
"URLSearchParams.prototype.isPrototypeOf": true,
|
||||
"XMLHttpRequest": true,
|
||||
"define": true,
|
||||
"setTimeout": true
|
||||
}
|
||||
},
|
||||
"@metamask/snaps-controllers>nanoid": {
|
||||
"globals": {
|
||||
"crypto.getRandomValues": true
|
||||
@ -2560,7 +2484,7 @@
|
||||
},
|
||||
"browserify>has": {
|
||||
"packages": {
|
||||
"mocha>object.assign>function-bind": true
|
||||
"browserify>has>function-bind": true
|
||||
}
|
||||
},
|
||||
"browserify>os-browserify": {
|
||||
@ -3615,7 +3539,7 @@
|
||||
"globalthis>define-properties": {
|
||||
"packages": {
|
||||
"globalthis>define-properties>has-property-descriptors": true,
|
||||
"mocha>object.assign>object-keys": true
|
||||
"globalthis>define-properties>object-keys": true
|
||||
}
|
||||
},
|
||||
"globalthis>define-properties>has-property-descriptors": {
|
||||
@ -4235,7 +4159,7 @@
|
||||
},
|
||||
"string.prototype.matchall>call-bind": {
|
||||
"packages": {
|
||||
"mocha>object.assign>function-bind": true,
|
||||
"browserify>has>function-bind": true,
|
||||
"string.prototype.matchall>get-intrinsic": true
|
||||
}
|
||||
},
|
||||
@ -4247,7 +4171,7 @@
|
||||
},
|
||||
"packages": {
|
||||
"browserify>has": true,
|
||||
"mocha>object.assign>function-bind": true,
|
||||
"browserify>has>function-bind": true,
|
||||
"string.prototype.matchall>has-symbols": true
|
||||
}
|
||||
},
|
||||
|
@ -1697,7 +1697,7 @@
|
||||
},
|
||||
"browserify>has": {
|
||||
"packages": {
|
||||
"mocha>object.assign>function-bind": true
|
||||
"browserify>has>function-bind": true
|
||||
}
|
||||
},
|
||||
"browserify>insert-module-globals": {
|
||||
@ -1885,12 +1885,7 @@
|
||||
"process.platform": true
|
||||
},
|
||||
"packages": {
|
||||
"chalk>supports-color>has-flag": true
|
||||
}
|
||||
},
|
||||
"chalk>supports-color>has-flag": {
|
||||
"globals": {
|
||||
"process.argv": true
|
||||
"sinon>supports-color>has-flag": true
|
||||
}
|
||||
},
|
||||
"chokidar": {
|
||||
@ -2002,7 +1997,7 @@
|
||||
"packages": {
|
||||
"cross-spawn>path-key": true,
|
||||
"cross-spawn>shebang-command": true,
|
||||
"cross-spawn>which": true
|
||||
"mocha>which": true
|
||||
}
|
||||
},
|
||||
"cross-spawn>path-key": {
|
||||
@ -2016,21 +2011,6 @@
|
||||
"cross-spawn>shebang-command>shebang-regex": true
|
||||
}
|
||||
},
|
||||
"cross-spawn>which": {
|
||||
"builtin": {
|
||||
"path.join": true
|
||||
},
|
||||
"globals": {
|
||||
"process.cwd": true,
|
||||
"process.env.OSTYPE": true,
|
||||
"process.env.PATH": true,
|
||||
"process.env.PATHEXT": true,
|
||||
"process.platform": true
|
||||
},
|
||||
"packages": {
|
||||
"mocha>which>isexe": true
|
||||
}
|
||||
},
|
||||
"debounce-stream>duplexer": {
|
||||
"builtin": {
|
||||
"stream": true
|
||||
@ -2350,7 +2330,7 @@
|
||||
"process": true
|
||||
},
|
||||
"packages": {
|
||||
"gulp-livereload>debug>ms": true,
|
||||
"mocha>ms": true,
|
||||
"mocha>supports-color": true
|
||||
}
|
||||
},
|
||||
@ -2471,7 +2451,7 @@
|
||||
"process": true
|
||||
},
|
||||
"packages": {
|
||||
"gulp-livereload>debug>ms": true,
|
||||
"mocha>ms": true,
|
||||
"mocha>supports-color": true
|
||||
}
|
||||
},
|
||||
@ -2871,8 +2851,8 @@
|
||||
"eslint>@eslint/eslintrc>globals": true,
|
||||
"eslint>ajv": true,
|
||||
"eslint>minimatch": true,
|
||||
"eslint>strip-json-comments": true,
|
||||
"globby>ignore": true,
|
||||
"mocha>strip-json-comments": true,
|
||||
"nock>debug": true
|
||||
}
|
||||
},
|
||||
@ -3094,9 +3074,9 @@
|
||||
"process.cwd": true
|
||||
},
|
||||
"packages": {
|
||||
"chokidar>glob-parent": true,
|
||||
"fast-glob>@nodelib/fs.stat": true,
|
||||
"fast-glob>@nodelib/fs.walk": true,
|
||||
"fast-glob>glob-parent": true,
|
||||
"globby>merge2": true,
|
||||
"stylelint>micromatch": true
|
||||
}
|
||||
@ -3151,6 +3131,15 @@
|
||||
"fast-glob>@nodelib/fs.walk>fastq>reusify": true
|
||||
}
|
||||
},
|
||||
"fast-glob>glob-parent": {
|
||||
"builtin": {
|
||||
"os.platform": true,
|
||||
"path.posix.dirname": true
|
||||
},
|
||||
"packages": {
|
||||
"eslint>is-glob": true
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"builtin": {
|
||||
"assert": true,
|
||||
@ -3200,7 +3189,7 @@
|
||||
"globalthis>define-properties": {
|
||||
"packages": {
|
||||
"globalthis>define-properties>has-property-descriptors": true,
|
||||
"mocha>object.assign>object-keys": true
|
||||
"globalthis>define-properties>object-keys": true
|
||||
}
|
||||
},
|
||||
"globalthis>define-properties>has-property-descriptors": {
|
||||
@ -3370,8 +3359,8 @@
|
||||
},
|
||||
"packages": {
|
||||
"gulp-dart-sass>chalk>ansi-styles": true,
|
||||
"gulp-dart-sass>chalk>supports-color": true,
|
||||
"mocha>escape-string-regexp": true
|
||||
"gulp-dart-sass>chalk>escape-string-regexp": true,
|
||||
"gulp-dart-sass>chalk>supports-color": true
|
||||
}
|
||||
},
|
||||
"gulp-dart-sass>chalk>ansi-styles": {
|
||||
@ -3391,7 +3380,12 @@
|
||||
"process.versions.node.split": true
|
||||
},
|
||||
"packages": {
|
||||
"mocha>supports-color>has-flag": true
|
||||
"gulp-dart-sass>chalk>supports-color>has-flag": true
|
||||
}
|
||||
},
|
||||
"gulp-dart-sass>chalk>supports-color>has-flag": {
|
||||
"globals": {
|
||||
"process.argv": true
|
||||
}
|
||||
},
|
||||
"gulp-dart-sass>strip-ansi": {
|
||||
@ -3430,9 +3424,9 @@
|
||||
"process.platform": true
|
||||
},
|
||||
"packages": {
|
||||
"gulp-dart-sass>chalk>escape-string-regexp": true,
|
||||
"gulp-livereload>chalk>ansi-styles": true,
|
||||
"gulp-livereload>chalk>supports-color": true,
|
||||
"mocha>escape-string-regexp": true
|
||||
"gulp-livereload>chalk>supports-color": true
|
||||
}
|
||||
},
|
||||
"gulp-livereload>chalk>ansi-styles": {
|
||||
@ -3452,7 +3446,12 @@
|
||||
"process.versions.node.split": true
|
||||
},
|
||||
"packages": {
|
||||
"mocha>supports-color>has-flag": true
|
||||
"gulp-livereload>chalk>supports-color>has-flag": true
|
||||
}
|
||||
},
|
||||
"gulp-livereload>chalk>supports-color>has-flag": {
|
||||
"globals": {
|
||||
"process.argv": true
|
||||
}
|
||||
},
|
||||
"gulp-livereload>debug": {
|
||||
@ -3469,7 +3468,7 @@
|
||||
},
|
||||
"packages": {
|
||||
"gulp-livereload>chalk>supports-color": true,
|
||||
"gulp-livereload>debug>ms": true
|
||||
"mocha>ms": true
|
||||
}
|
||||
},
|
||||
"gulp-livereload>event-stream": {
|
||||
@ -3595,7 +3594,7 @@
|
||||
"process": true
|
||||
},
|
||||
"packages": {
|
||||
"gulp-livereload>debug>ms": true,
|
||||
"mocha>ms": true,
|
||||
"mocha>supports-color": true
|
||||
}
|
||||
},
|
||||
@ -3699,9 +3698,9 @@
|
||||
"process.platform": true
|
||||
},
|
||||
"packages": {
|
||||
"gulp-dart-sass>chalk>escape-string-regexp": true,
|
||||
"gulp-rtlcss>rtlcss>chalk>ansi-styles": true,
|
||||
"gulp-rtlcss>rtlcss>chalk>supports-color": true,
|
||||
"mocha>escape-string-regexp": true
|
||||
"gulp-rtlcss>rtlcss>chalk>supports-color": true
|
||||
}
|
||||
},
|
||||
"gulp-rtlcss>rtlcss>chalk>ansi-styles": {
|
||||
@ -3721,7 +3720,12 @@
|
||||
"process.versions.node.split": true
|
||||
},
|
||||
"packages": {
|
||||
"mocha>supports-color>has-flag": true
|
||||
"gulp-rtlcss>rtlcss>chalk>supports-color>has-flag": true
|
||||
}
|
||||
},
|
||||
"gulp-rtlcss>rtlcss>chalk>supports-color>has-flag": {
|
||||
"globals": {
|
||||
"process.argv": true
|
||||
}
|
||||
},
|
||||
"gulp-rtlcss>rtlcss>postcss": {
|
||||
@ -3947,7 +3951,7 @@
|
||||
"process": true
|
||||
},
|
||||
"packages": {
|
||||
"gulp-livereload>debug>ms": true,
|
||||
"mocha>ms": true,
|
||||
"mocha>supports-color": true
|
||||
}
|
||||
},
|
||||
@ -5204,11 +5208,11 @@
|
||||
"chokidar>normalize-path": true,
|
||||
"eslint>is-glob": true,
|
||||
"gulp-watch>chokidar>async-each": true,
|
||||
"gulp-watch>glob-parent": true,
|
||||
"gulp-watch>path-is-absolute": true,
|
||||
"gulp>glob-watcher>anymatch": true,
|
||||
"gulp>glob-watcher>chokidar>braces": true,
|
||||
"gulp>glob-watcher>chokidar>fsevents": true,
|
||||
"gulp>glob-watcher>chokidar>glob-parent": true,
|
||||
"gulp>glob-watcher>chokidar>is-binary-path": true,
|
||||
"gulp>glob-watcher>chokidar>readdirp": true,
|
||||
"gulp>glob-watcher>chokidar>upath": true,
|
||||
@ -5274,21 +5278,6 @@
|
||||
"gulp-watch>chokidar>fsevents>node-pre-gyp": true
|
||||
}
|
||||
},
|
||||
"gulp>glob-watcher>chokidar>glob-parent": {
|
||||
"builtin": {
|
||||
"os.platform": true,
|
||||
"path": true
|
||||
},
|
||||
"packages": {
|
||||
"gulp-watch>glob-parent>path-dirname": true,
|
||||
"gulp>glob-watcher>chokidar>glob-parent>is-glob": true
|
||||
}
|
||||
},
|
||||
"gulp>glob-watcher>chokidar>glob-parent>is-glob": {
|
||||
"packages": {
|
||||
"gulp>glob-watcher>chokidar>glob-parent>is-glob>is-extglob": true
|
||||
}
|
||||
},
|
||||
"gulp>glob-watcher>chokidar>is-binary-path": {
|
||||
"builtin": {
|
||||
"path.extname": true
|
||||
@ -5663,8 +5652,8 @@
|
||||
"process.nextTick": true
|
||||
},
|
||||
"packages": {
|
||||
"gulp-watch>glob-parent": true,
|
||||
"gulp>glob-watcher>is-negated-glob": true,
|
||||
"gulp>vinyl-fs>glob-stream>glob-parent": true,
|
||||
"gulp>vinyl-fs>glob-stream>ordered-read-streams": true,
|
||||
"gulp>vinyl-fs>glob-stream>pumpify": true,
|
||||
"gulp>vinyl-fs>glob-stream>to-absolute-glob": true,
|
||||
@ -5675,21 +5664,6 @@
|
||||
"vinyl>remove-trailing-separator": true
|
||||
}
|
||||
},
|
||||
"gulp>vinyl-fs>glob-stream>glob-parent": {
|
||||
"builtin": {
|
||||
"os.platform": true,
|
||||
"path": true
|
||||
},
|
||||
"packages": {
|
||||
"gulp-watch>glob-parent>path-dirname": true,
|
||||
"gulp>vinyl-fs>glob-stream>glob-parent>is-glob": true
|
||||
}
|
||||
},
|
||||
"gulp>vinyl-fs>glob-stream>glob-parent>is-glob": {
|
||||
"packages": {
|
||||
"gulp>vinyl-fs>glob-stream>glob-parent>is-glob>is-extglob": true
|
||||
}
|
||||
},
|
||||
"gulp>vinyl-fs>glob-stream>ordered-read-streams": {
|
||||
"builtin": {
|
||||
"util.inherits": true
|
||||
@ -5791,7 +5765,7 @@
|
||||
"gulp>vinyl-fs>object.assign": {
|
||||
"packages": {
|
||||
"globalthis>define-properties": true,
|
||||
"mocha>object.assign>object-keys": true,
|
||||
"globalthis>define-properties>object-keys": true,
|
||||
"string.prototype.matchall>call-bind": true,
|
||||
"string.prototype.matchall>has-symbols": true
|
||||
}
|
||||
@ -6092,9 +6066,9 @@
|
||||
"process.platform": true
|
||||
},
|
||||
"packages": {
|
||||
"gulp-dart-sass>chalk>escape-string-regexp": true,
|
||||
"lavamoat>@babel/highlight>chalk>ansi-styles": true,
|
||||
"lavamoat>@babel/highlight>chalk>supports-color": true,
|
||||
"mocha>escape-string-regexp": true
|
||||
"lavamoat>@babel/highlight>chalk>supports-color": true
|
||||
}
|
||||
},
|
||||
"lavamoat>@babel/highlight>chalk>ansi-styles": {
|
||||
@ -6114,7 +6088,12 @@
|
||||
"process.versions.node.split": true
|
||||
},
|
||||
"packages": {
|
||||
"mocha>supports-color>has-flag": true
|
||||
"lavamoat>@babel/highlight>chalk>supports-color>has-flag": true
|
||||
}
|
||||
},
|
||||
"lavamoat>@babel/highlight>chalk>supports-color>has-flag": {
|
||||
"globals": {
|
||||
"process.argv": true
|
||||
}
|
||||
},
|
||||
"lavamoat>@lavamoat/aa": {
|
||||
@ -6272,6 +6251,31 @@
|
||||
"process.platform": true
|
||||
}
|
||||
},
|
||||
"mocha>log-symbols": {
|
||||
"packages": {
|
||||
"madge>ora>is-unicode-supported": true,
|
||||
"mocha>log-symbols>chalk": true
|
||||
}
|
||||
},
|
||||
"mocha>log-symbols>chalk": {
|
||||
"packages": {
|
||||
"chalk>ansi-styles": true,
|
||||
"mocha>log-symbols>chalk>supports-color": true
|
||||
}
|
||||
},
|
||||
"mocha>log-symbols>chalk>supports-color": {
|
||||
"builtin": {
|
||||
"os.release": true,
|
||||
"tty.isatty": true
|
||||
},
|
||||
"globals": {
|
||||
"process.env": true,
|
||||
"process.platform": true
|
||||
},
|
||||
"packages": {
|
||||
"sinon>supports-color>has-flag": true
|
||||
}
|
||||
},
|
||||
"mocha>minimatch>brace-expansion": {
|
||||
"packages": {
|
||||
"mocha>minimatch>brace-expansion>concat-map": true,
|
||||
@ -6280,22 +6284,15 @@
|
||||
},
|
||||
"mocha>supports-color": {
|
||||
"builtin": {
|
||||
"os.release": true
|
||||
"os.release": true,
|
||||
"tty.isatty": true
|
||||
},
|
||||
"globals": {
|
||||
"process.env": true,
|
||||
"process.platform": true,
|
||||
"process.stderr": true,
|
||||
"process.stdout": true,
|
||||
"process.versions.node.split": true
|
||||
"process.platform": true
|
||||
},
|
||||
"packages": {
|
||||
"mocha>supports-color>has-flag": true
|
||||
}
|
||||
},
|
||||
"mocha>supports-color>has-flag": {
|
||||
"globals": {
|
||||
"process.argv": true
|
||||
"sinon>supports-color>has-flag": true
|
||||
}
|
||||
},
|
||||
"mocha>which": {
|
||||
@ -6565,9 +6562,9 @@
|
||||
"react-markdown>unified": {
|
||||
"packages": {
|
||||
"jsdom>request>extend": true,
|
||||
"mocha>yargs-unparser>is-plain-obj": true,
|
||||
"react-markdown>unified>bail": true,
|
||||
"react-markdown>unified>is-buffer": true,
|
||||
"react-markdown>unified>is-plain-obj": true,
|
||||
"react-markdown>unified>trough": true,
|
||||
"react-markdown>vfile": true
|
||||
}
|
||||
@ -6767,6 +6764,11 @@
|
||||
"process.platform": true
|
||||
}
|
||||
},
|
||||
"sinon>supports-color>has-flag": {
|
||||
"globals": {
|
||||
"process.argv": true
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"builtin": {
|
||||
"fs.readFile": true,
|
||||
@ -6801,7 +6803,7 @@
|
||||
},
|
||||
"string.prototype.matchall>call-bind": {
|
||||
"packages": {
|
||||
"mocha>object.assign>function-bind": true,
|
||||
"browserify>has>function-bind": true,
|
||||
"string.prototype.matchall>get-intrinsic": true
|
||||
}
|
||||
},
|
||||
@ -6840,7 +6842,7 @@
|
||||
},
|
||||
"packages": {
|
||||
"browserify>has": true,
|
||||
"mocha>object.assign>function-bind": true,
|
||||
"browserify>has>function-bind": true,
|
||||
"string.prototype.matchall>has-symbols": true
|
||||
}
|
||||
},
|
||||
@ -6896,6 +6898,7 @@
|
||||
"globby>ignore": true,
|
||||
"globby>slash": true,
|
||||
"lodash": true,
|
||||
"mocha>log-symbols": true,
|
||||
"nock>debug": true,
|
||||
"nyc>resolve-from": true,
|
||||
"stylelint>@stylelint/postcss-css-in-js": true,
|
||||
@ -6912,7 +6915,6 @@
|
||||
"stylelint>import-lazy": true,
|
||||
"stylelint>known-css-properties": true,
|
||||
"stylelint>leven": true,
|
||||
"stylelint>log-symbols": true,
|
||||
"stylelint>mathml-tag-names": true,
|
||||
"stylelint>micromatch": true,
|
||||
"stylelint>normalize-selector": true,
|
||||
@ -7094,12 +7096,7 @@
|
||||
"process.platform": true
|
||||
},
|
||||
"packages": {
|
||||
"stylelint>chalk>supports-color>has-flag": true
|
||||
}
|
||||
},
|
||||
"stylelint>chalk>supports-color>has-flag": {
|
||||
"globals": {
|
||||
"process.argv": true
|
||||
"sinon>supports-color>has-flag": true
|
||||
}
|
||||
},
|
||||
"stylelint>cosmiconfig": {
|
||||
@ -7212,8 +7209,8 @@
|
||||
"process.platform": true
|
||||
},
|
||||
"packages": {
|
||||
"mocha>which": true,
|
||||
"stylelint>global-modules>global-prefix>ini": true
|
||||
"stylelint>global-modules>global-prefix>ini": true,
|
||||
"stylelint>global-modules>global-prefix>which": true
|
||||
}
|
||||
},
|
||||
"stylelint>global-modules>global-prefix>ini": {
|
||||
@ -7221,39 +7218,24 @@
|
||||
"process": true
|
||||
}
|
||||
},
|
||||
"stylelint>globjoin": {
|
||||
"stylelint>global-modules>global-prefix>which": {
|
||||
"builtin": {
|
||||
"path.join": true
|
||||
}
|
||||
},
|
||||
"stylelint>log-symbols": {
|
||||
"packages": {
|
||||
"madge>ora>is-unicode-supported": true,
|
||||
"stylelint>log-symbols>chalk": true
|
||||
}
|
||||
},
|
||||
"stylelint>log-symbols>chalk": {
|
||||
"packages": {
|
||||
"chalk>ansi-styles": true,
|
||||
"stylelint>log-symbols>chalk>supports-color": true
|
||||
}
|
||||
},
|
||||
"stylelint>log-symbols>chalk>supports-color": {
|
||||
"builtin": {
|
||||
"os.release": true,
|
||||
"tty.isatty": true
|
||||
},
|
||||
"globals": {
|
||||
"process.env": true,
|
||||
"process.cwd": true,
|
||||
"process.env.OSTYPE": true,
|
||||
"process.env.PATH": true,
|
||||
"process.env.PATHEXT": true,
|
||||
"process.platform": true
|
||||
},
|
||||
"packages": {
|
||||
"stylelint>log-symbols>chalk>supports-color>has-flag": true
|
||||
"mocha>which>isexe": true
|
||||
}
|
||||
},
|
||||
"stylelint>log-symbols>chalk>supports-color>has-flag": {
|
||||
"globals": {
|
||||
"process.argv": true
|
||||
"stylelint>globjoin": {
|
||||
"builtin": {
|
||||
"path.join": true
|
||||
}
|
||||
},
|
||||
"stylelint>micromatch": {
|
||||
@ -7557,9 +7539,9 @@
|
||||
},
|
||||
"stylelint>table>slice-ansi": {
|
||||
"packages": {
|
||||
"mocha>yargs>string-width>is-fullwidth-code-point": true,
|
||||
"stylelint>table>slice-ansi>ansi-styles": true,
|
||||
"stylelint>table>slice-ansi>astral-regex": true
|
||||
"stylelint>table>slice-ansi>astral-regex": true,
|
||||
"stylelint>table>slice-ansi>is-fullwidth-code-point": true
|
||||
}
|
||||
},
|
||||
"stylelint>table>slice-ansi>ansi-styles": {
|
||||
@ -7569,7 +7551,7 @@
|
||||
},
|
||||
"stylelint>table>string-width": {
|
||||
"packages": {
|
||||
"mocha>yargs>string-width>is-fullwidth-code-point": true,
|
||||
"stylelint>table>slice-ansi>is-fullwidth-code-point": true,
|
||||
"stylelint>table>string-width>emoji-regex": true,
|
||||
"stylelint>table>string-width>strip-ansi": true
|
||||
}
|
||||
|
30
package.json
30
package.json
@ -226,11 +226,11 @@
|
||||
"@keystonehq/metamask-airgapped-keyring": "^0.6.1",
|
||||
"@lavamoat/snow": "^1.5.0",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@metamask/address-book-controller": "^1.0.0",
|
||||
"@metamask/announcement-controller": "^2.0.1",
|
||||
"@metamask/approval-controller": "^1.0.0",
|
||||
"@metamask/assets-controllers": "^4.0.1",
|
||||
"@metamask/base-controller": "^1.0.0",
|
||||
"@metamask/address-book-controller": "^2.0.0",
|
||||
"@metamask/announcement-controller": "^3.0.0",
|
||||
"@metamask/approval-controller": "^2.1.0",
|
||||
"@metamask/assets-controllers": "^5.0.0",
|
||||
"@metamask/base-controller": "^2.0.0",
|
||||
"@metamask/contract-metadata": "^2.3.1",
|
||||
"@metamask/controller-utils": "^3.1.0",
|
||||
"@metamask/design-tokens": "^1.9.0",
|
||||
@ -242,19 +242,19 @@
|
||||
"@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": "^4.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/message-manager": "^3.0.0",
|
||||
"@metamask/metamask-eth-abis": "^3.0.0",
|
||||
"@metamask/notification-controller": "^1.0.0",
|
||||
"@metamask/obs-store": "^8.0.0",
|
||||
"@metamask/notification-controller": "^2.0.0",
|
||||
"@metamask/obs-store": "^8.1.0",
|
||||
"@metamask/permission-controller": "^3.1.0",
|
||||
"@metamask/phishing-controller": "^2.0.0",
|
||||
"@metamask/phishing-controller": "^3.0.0",
|
||||
"@metamask/post-message-stream": "^6.0.0",
|
||||
"@metamask/providers": "^10.2.1",
|
||||
"@metamask/rate-limit-controller": "^1.0.0",
|
||||
"@metamask/rate-limit-controller": "^2.0.0",
|
||||
"@metamask/rpc-methods": "^0.32.2",
|
||||
"@metamask/safe-event-emitter": "^2.0.0",
|
||||
"@metamask/scure-bip39": "^2.0.3",
|
||||
@ -263,7 +263,7 @@
|
||||
"@metamask/snaps-controllers": "^0.32.2",
|
||||
"@metamask/snaps-ui": "^0.32.2",
|
||||
"@metamask/snaps-utils": "^0.32.2",
|
||||
"@metamask/subject-metadata-controller": "^1.0.0",
|
||||
"@metamask/subject-metadata-controller": "^2.0.0",
|
||||
"@metamask/swappable-obj-proxy": "^2.1.0",
|
||||
"@metamask/utils": "^5.0.0",
|
||||
"@ngraveio/bc-ur": "^1.1.6",
|
||||
@ -368,6 +368,7 @@
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
"@babel/register": "^7.5.5",
|
||||
"@ethersproject/bignumber": "^5.7.0",
|
||||
"@json-rpc-specification/meta-schema": "^1.0.6",
|
||||
"@lavamoat/allow-scripts": "^2.0.3",
|
||||
"@lavamoat/lavapack": "^5.0.0",
|
||||
"@metamask/auto-changelog": "^2.1.0",
|
||||
@ -419,6 +420,7 @@
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"@types/react-redux": "^7.1.25",
|
||||
"@types/remote-redux-devtools": "^0.5.5",
|
||||
"@types/sinon": "^10.0.13",
|
||||
"@types/w3c-web-hid": "^1.0.3",
|
||||
"@types/watchify": "^3.11.1",
|
||||
"@types/yargs": "^17.0.8",
|
||||
@ -451,7 +453,7 @@
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-jest": "^26.6.0",
|
||||
"eslint-plugin-jsdoc": "^39.3.3",
|
||||
"eslint-plugin-mocha": "^8.1.0",
|
||||
"eslint-plugin-mocha": "^10.1.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-react": "^7.23.1",
|
||||
@ -495,7 +497,7 @@
|
||||
"lockfile-lint": "^4.9.6",
|
||||
"loose-envify": "^1.4.0",
|
||||
"madge": "^5.0.1",
|
||||
"mocha": "^7.2.0",
|
||||
"mocha": "^9.2.2",
|
||||
"mockttp": "^2.6.0",
|
||||
"nock": "^13.2.9",
|
||||
"node-fetch": "^2.6.1",
|
||||
|
@ -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',
|
||||
|
@ -510,6 +510,8 @@ export enum MetaMetricsEventName {
|
||||
SignatureFailed = 'Signature Failed',
|
||||
SignatureRejected = 'Signature Rejected',
|
||||
SignatureRequested = 'Signature Requested',
|
||||
TermsOfUseShown = 'Terms of Use Shown',
|
||||
TermsOfUseAccepted = 'Terms of Use Accepted',
|
||||
TokenImportButtonClicked = 'Import Token Button Clicked',
|
||||
TokenScreenOpened = 'Token Screen Opened',
|
||||
SupportLinkClicked = 'Support Link Clicked',
|
||||
|
@ -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,
|
||||
|
1
shared/constants/terms.js
Normal file
1
shared/constants/terms.js
Normal file
@ -0,0 +1 @@
|
||||
export const TERMS_OF_USE_LAST_UPDATED = '2023-03-25';
|
@ -2,6 +2,13 @@
|
||||
"DNS": {
|
||||
"resolution": ""
|
||||
},
|
||||
"activeTab": {
|
||||
"id": 113,
|
||||
"title": "E2E Test Dapp",
|
||||
"origin": "https://metamask.github.io",
|
||||
"protocol": "https:",
|
||||
"url": "https://metamask.github.io/test-dapp/"
|
||||
},
|
||||
"appState": {
|
||||
"networkDropdownOpen": false,
|
||||
"gasIsLoading": false,
|
||||
@ -16,7 +23,8 @@
|
||||
"name": null
|
||||
}
|
||||
},
|
||||
"warning": null
|
||||
"warning": null,
|
||||
"alertOpen": false
|
||||
},
|
||||
"confirmTransaction": {
|
||||
"txData": {
|
||||
@ -42,6 +50,10 @@
|
||||
"history": {
|
||||
"mostRecentOverviewPage": "/mostRecentOverviewPage"
|
||||
},
|
||||
"invalidCustomNetwork": {
|
||||
"state": "CLOSED",
|
||||
"networkName": ""
|
||||
},
|
||||
"metamask": {
|
||||
"ipfsGateway": "",
|
||||
"dismissSeedBackUpReminder": false,
|
||||
@ -88,6 +100,14 @@
|
||||
"ensResolutionsByAddress": {},
|
||||
"isAccountMenuOpen": false,
|
||||
"isUnlocked": true,
|
||||
"completedOnboarding": true,
|
||||
"usedNetworks": {
|
||||
"0x1": true,
|
||||
"0x5": true,
|
||||
"0x539": true
|
||||
},
|
||||
"showTestnetMessageInDropdown": true,
|
||||
"networkConfigurations": {},
|
||||
"alertEnabledness": {
|
||||
"unconnectedAccount": true
|
||||
},
|
||||
@ -1358,5 +1378,8 @@
|
||||
"balance": "0x4563918244f40000"
|
||||
},
|
||||
"stage": "DRAFT"
|
||||
},
|
||||
"unconnectedAccount": {
|
||||
"state": "CLOSED"
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -140,6 +140,7 @@ function defaultFixture() {
|
||||
browserEnvironment: {},
|
||||
nftsDropdownState: {},
|
||||
connectedStatusPopoverHasBeenShown: true,
|
||||
termsOfUseLastAgreed: 86400000000000,
|
||||
defaultHomeActiveTabName: null,
|
||||
fullScreenGasPollTokens: [],
|
||||
notificationGasPollTokens: [],
|
||||
|
@ -222,6 +222,9 @@ const getWindowHandles = async (driver, handlesCount) => {
|
||||
};
|
||||
|
||||
const importSRPOnboardingFlow = async (driver, seedPhrase, password) => {
|
||||
// agree to terms of use
|
||||
await driver.clickElement('[data-testid="onboarding-terms-checkbox"]');
|
||||
|
||||
// welcome
|
||||
await driver.clickElement('[data-testid="onboarding-import-wallet"]');
|
||||
|
||||
@ -262,6 +265,9 @@ const completeImportSRPOnboardingFlowWordByWord = async (
|
||||
seedPhrase,
|
||||
password,
|
||||
) => {
|
||||
// agree to terms of use
|
||||
await driver.clickElement('[data-testid="onboarding-terms-checkbox"]');
|
||||
|
||||
// welcome
|
||||
await driver.clickElement('[data-testid="onboarding-import-wallet"]');
|
||||
|
||||
@ -293,6 +299,9 @@ const completeImportSRPOnboardingFlowWordByWord = async (
|
||||
};
|
||||
|
||||
const completeCreateNewWalletOnboardingFlow = async (driver, password) => {
|
||||
// agree to terms of use
|
||||
await driver.clickElement('[data-testid="onboarding-terms-checkbox"]');
|
||||
|
||||
// welcome
|
||||
await driver.clickElement('[data-testid="onboarding-create-wallet"]');
|
||||
|
||||
@ -342,6 +351,9 @@ const completeCreateNewWalletOnboardingFlow = async (driver, password) => {
|
||||
};
|
||||
|
||||
const importWrongSRPOnboardingFlow = async (driver, seedPhrase) => {
|
||||
// agree to terms of use
|
||||
await driver.clickElement('[data-testid="onboarding-terms-checkbox"]');
|
||||
|
||||
// welcome
|
||||
await driver.clickElement('[data-testid="onboarding-import-wallet"]');
|
||||
|
||||
|
@ -84,6 +84,7 @@ describe('MetaMask', function () {
|
||||
|
||||
describe('Going through the first time flow', function () {
|
||||
it('clicks the "Create New Wallet" button on the welcome screen', async function () {
|
||||
await driver.clickElement('[data-testid="onboarding-terms-checkbox"]');
|
||||
await driver.clickElement('[data-testid="onboarding-create-wallet"]');
|
||||
});
|
||||
|
||||
|
@ -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' });
|
||||
},
|
||||
);
|
||||
});
|
||||
|
@ -392,7 +392,9 @@ describe('Custom network', function () {
|
||||
text: 'Delete',
|
||||
});
|
||||
|
||||
await driver.findElement('.modal-container__footer');
|
||||
await driver.waitForSelector('.modal-container__footer', {
|
||||
timeout: 15000,
|
||||
});
|
||||
// should be deleted from the modal shown again to complete deletion custom network
|
||||
await driver.clickElement({
|
||||
tag: 'button',
|
||||
|
@ -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',
|
||||
@ -292,10 +292,13 @@ describe('Create token, approve token and approve token without gas', function (
|
||||
await gasLimitInput.fill('60001');
|
||||
await driver.clickElement({ text: 'Save', tag: 'button' });
|
||||
|
||||
await driver.waitForSelector({
|
||||
css: '.box--flex-direction-row > h6',
|
||||
text: '0.0006 ETH',
|
||||
});
|
||||
await driver.waitForSelector(
|
||||
{
|
||||
css: '.box--flex-direction-row > h6',
|
||||
text: '0.0006 ETH',
|
||||
},
|
||||
{ timeout: 15000 },
|
||||
);
|
||||
|
||||
// editing spending cap
|
||||
await driver.clickElement({
|
||||
|
@ -29,6 +29,8 @@ describe('Incremental Security', function () {
|
||||
},
|
||||
async ({ driver }) => {
|
||||
await driver.navigate();
|
||||
// agree to terms of use
|
||||
await driver.clickElement('[data-testid="onboarding-terms-checkbox"]');
|
||||
|
||||
// welcome
|
||||
await driver.clickElement('[data-testid="onboarding-create-wallet"]');
|
||||
|
@ -15,6 +15,8 @@ describe('MetaMask Responsive UI', function () {
|
||||
},
|
||||
async ({ driver }) => {
|
||||
await driver.navigate();
|
||||
// agree to terms of use
|
||||
await driver.clickElement('[data-testid="onboarding-terms-checkbox"]');
|
||||
|
||||
// welcome
|
||||
await driver.clickElement('[data-testid="onboarding-create-wallet"]');
|
||||
|
@ -108,6 +108,9 @@ describe('MetaMask onboarding', function () {
|
||||
async ({ driver }) => {
|
||||
await driver.navigate();
|
||||
|
||||
// accept terms of use
|
||||
await driver.clickElement('[data-testid="onboarding-terms-checkbox"]');
|
||||
|
||||
// welcome
|
||||
await driver.clickElement('[data-testid="onboarding-import-wallet"]');
|
||||
|
||||
@ -145,6 +148,7 @@ describe('MetaMask onboarding', function () {
|
||||
async ({ driver }) => {
|
||||
await driver.navigate();
|
||||
|
||||
await driver.clickElement('[data-testid="onboarding-terms-checkbox"]');
|
||||
await driver.clickElement('[data-testid="onboarding-create-wallet"]');
|
||||
|
||||
// metrics
|
||||
@ -208,6 +212,7 @@ describe('MetaMask onboarding', function () {
|
||||
async ({ driver }) => {
|
||||
await driver.navigate();
|
||||
|
||||
await driver.clickElement('[data-testid="onboarding-terms-checkbox"]');
|
||||
await driver.clickElement('[data-testid="onboarding-create-wallet"]');
|
||||
|
||||
// metrics
|
||||
|
@ -354,9 +354,12 @@ describe('Send ETH from dapp using advanced gas controls', function () {
|
||||
'.transaction-list-item__primary-currency',
|
||||
);
|
||||
await txValue.click();
|
||||
const baseFeeValue = await driver.waitForSelector({
|
||||
text: '0.000000025',
|
||||
});
|
||||
const baseFeeValue = await driver.waitForSelector(
|
||||
{
|
||||
text: '0.000000025',
|
||||
},
|
||||
{ timeout: 15000 },
|
||||
);
|
||||
assert.equal(await baseFeeValue.getText(), '0.000000025');
|
||||
},
|
||||
);
|
||||
|
@ -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' });
|
||||
|
||||
|
6
types/eth-keyring-controller.d.ts
vendored
6
types/eth-keyring-controller.d.ts
vendored
@ -5,5 +5,11 @@ declare module '@metamask/eth-keyring-controller' {
|
||||
signPersonalMessage: (...any) => any;
|
||||
|
||||
signTypedMessage: (...any) => any;
|
||||
|
||||
getKeyringForAccount: (address: string) => Promise<{
|
||||
type: string;
|
||||
}>;
|
||||
|
||||
getEncryptionPublicKey: (address: string) => Promise<string>;
|
||||
}
|
||||
}
|
||||
|
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;
|
||||
}
|
||||
}
|
@ -82,6 +82,7 @@
|
||||
@import 'transaction-status-label/index';
|
||||
@import 'wallet-overview/index';
|
||||
@import 'whats-new-popup/index';
|
||||
@import 'terms-of-use-popup/index';
|
||||
@import 'loading-network-screen/index';
|
||||
@import 'transaction-decoding/index';
|
||||
@import 'advanced-gas-fee-popover/index';
|
||||
|
@ -89,6 +89,7 @@ export default class AppHeader extends PureComponent {
|
||||
className={classnames('account-menu__icon', {
|
||||
'account-menu__icon--disabled': disabled,
|
||||
})}
|
||||
disabled={Boolean(disabled)}
|
||||
onClick={() => {
|
||||
if (!disabled) {
|
||||
!isAccountMenuOpen &&
|
||||
|
@ -14,7 +14,7 @@ import {
|
||||
import { BETA_BUGS_URL } from '../../../helpers/constants/beta';
|
||||
|
||||
import { hideBetaHeader } from '../../../store/actions';
|
||||
import { ButtonIcon } from '../../component-library';
|
||||
import { ButtonIcon } from '../../component-library/button-icon/deprecated';
|
||||
import {
|
||||
ICON_NAMES,
|
||||
ICON_SIZES,
|
||||
|
@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
|
||||
import React, { useRef } from 'react';
|
||||
import { Menu } from '../../../ui/menu';
|
||||
import { ICON_NAMES } from '../../../component-library/icon/deprecated';
|
||||
import { ButtonIcon } from '../../../component-library';
|
||||
import { ButtonIcon } from '../../../component-library/button-icon/deprecated';
|
||||
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
||||
|
||||
const ConnectedAccountsListOptions = ({
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState, useContext, useEffect } from 'react';
|
||||
import React, { useState, useContext, useEffect, useRef } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
@ -41,6 +41,7 @@ export default function CustomSpendingCap({
|
||||
}) {
|
||||
const t = useContext(I18nContext);
|
||||
const dispatch = useDispatch();
|
||||
const inputRef = useRef(null);
|
||||
|
||||
const value = useSelector(getCustomTokenAmount);
|
||||
|
||||
@ -139,6 +140,15 @@ export default function CustomSpendingCap({
|
||||
passTheErrorText(error);
|
||||
}, [error, passTheErrorText]);
|
||||
|
||||
useEffect(() => {
|
||||
if (inputRef.current) {
|
||||
inputRef.current.focus({
|
||||
preventScroll: true,
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [inputRef.current]);
|
||||
|
||||
const chooseTooltipContentText = decConversionGreaterThan(
|
||||
value,
|
||||
currentTokenBalance,
|
||||
@ -182,8 +192,8 @@ export default function CustomSpendingCap({
|
||||
}
|
||||
>
|
||||
<FormField
|
||||
inputRef={inputRef}
|
||||
dataTestId="custom-spending-cap-input"
|
||||
autoFocus
|
||||
wrappingLabelProps={{ as: 'div' }}
|
||||
id={
|
||||
decConversionGreaterThan(value, currentTokenBalance)
|
||||
|
@ -20,7 +20,8 @@ import {
|
||||
getSnapName,
|
||||
removeSnapIdPrefix,
|
||||
} from '../../../../helpers/utils/util';
|
||||
import { Text, ButtonIcon } from '../../../component-library';
|
||||
import { ButtonIcon } from '../../../component-library/button-icon/deprecated';
|
||||
import { Text } from '../../../component-library';
|
||||
import {
|
||||
ICON_NAMES,
|
||||
ICON_SIZES,
|
||||
|
1
ui/components/app/flask/snap-permissions-list/index.js
Normal file
1
ui/components/app/flask/snap-permissions-list/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './snap-permissions-list';
|
@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { getWeightedPermissions } from '../../../../helpers/utils/permission';
|
||||
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
||||
import PermissionCell from '../../permission-cell';
|
||||
import Box from '../../../ui/box';
|
||||
|
||||
export default function SnapPermissionsList({
|
||||
permissions,
|
||||
targetSubjectMetadata,
|
||||
}) {
|
||||
const t = useI18nContext();
|
||||
|
||||
return (
|
||||
<Box paddingTop={2} paddingBottom={2} className="snap-permissions-list">
|
||||
{getWeightedPermissions(t, permissions, targetSubjectMetadata).map(
|
||||
(permission, index) => {
|
||||
return (
|
||||
<PermissionCell
|
||||
title={permission.label}
|
||||
description={permission.description}
|
||||
weight={permission.weight}
|
||||
avatarIcon={permission.leftIcon}
|
||||
dateApproved={permission?.permissionValue?.date}
|
||||
key={`${permission.permissionName}-${index}`}
|
||||
/>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
SnapPermissionsList.propTypes = {
|
||||
permissions: PropTypes.object.isRequired,
|
||||
targetSubjectMetadata: PropTypes.object.isRequired,
|
||||
};
|
@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
|
||||
import SnapPermissionsList from '.';
|
||||
|
||||
export default {
|
||||
title: 'Components/App/flask/SnapPermissionsList',
|
||||
|
||||
component: SnapPermissionsList,
|
||||
argTypes: {
|
||||
permissions: {
|
||||
control: 'object',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const DefaultStory = (args) => <SnapPermissionsList {...args} />;
|
||||
|
||||
DefaultStory.storyName = 'Default';
|
||||
|
||||
DefaultStory.args = {
|
||||
permissions: {
|
||||
eth_accounts: {},
|
||||
snap_dialog: {},
|
||||
snap_getBip32PublicKey: {
|
||||
caveats: [
|
||||
{
|
||||
value: [
|
||||
{
|
||||
path: ['m', `44'`, `0'`],
|
||||
curve: 'secp256k1',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import { screen } from '@testing-library/react';
|
||||
import { renderWithProvider } from '../../../../../test/jest';
|
||||
import SnapPermissionsList from './snap-permissions-list';
|
||||
|
||||
describe('Snap Permission List', () => {
|
||||
const mockPermissionData = {
|
||||
snap_dialog: {
|
||||
caveats: null,
|
||||
date: 1680709920602,
|
||||
id: '4dduR1BpsmS0ZJfeVtiAh',
|
||||
invoker: 'local:http://localhost:8080',
|
||||
parentCapability: 'snap_dialog',
|
||||
},
|
||||
};
|
||||
const mockTargetSubjectMetadata = {
|
||||
extensionId: null,
|
||||
iconUrl: null,
|
||||
name: 'TypeScript Example Snap',
|
||||
origin: 'local:http://localhost:8080',
|
||||
subjectType: 'snap',
|
||||
version: '0.2.2',
|
||||
};
|
||||
|
||||
it('renders permissions list for snaps', () => {
|
||||
renderWithProvider(
|
||||
<SnapPermissionsList
|
||||
permissions={mockPermissionData}
|
||||
targetSubjectMetadata={mockTargetSubjectMetadata}
|
||||
/>,
|
||||
);
|
||||
expect(
|
||||
screen.getByText('Display dialog windows in MetaMask.'),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('Approved on 2023-04-05')).toBeInTheDocument();
|
||||
});
|
||||
});
|
@ -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>
|
||||
|
@ -14,9 +14,8 @@ import { CONNECTED_ACCOUNTS_ROUTE } from '../../../helpers/constants/routes';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import { getOriginOfCurrentTab } from '../../../selectors';
|
||||
import { MetaMetricsContext } from '../../../contexts/metametrics';
|
||||
import { ButtonIcon } from '../../component-library';
|
||||
import { ButtonIcon } from '../../component-library/button-icon/deprecated';
|
||||
import { ICON_NAMES } from '../../component-library/icon/deprecated';
|
||||
import { GlobalMenu } from '../../multichain/global-menu';
|
||||
import AccountOptionsMenu from './account-options-menu';
|
||||
|
||||
export default function MenuBar() {
|
||||
@ -34,7 +33,7 @@ export default function MenuBar() {
|
||||
|
||||
return (
|
||||
<div className="menu-bar">
|
||||
{showStatus ? ( // TODO: Move the connection status menu icon to the correct position in header once we implement the new header
|
||||
{showStatus ? (
|
||||
<ConnectedStatusIndicator
|
||||
onClick={() => history.push(CONNECTED_ACCOUNTS_ROUTE)}
|
||||
/>
|
||||
@ -58,18 +57,12 @@ export default function MenuBar() {
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
{accountOptionsMenuOpen &&
|
||||
(process.env.MULTICHAIN ? (
|
||||
<GlobalMenu
|
||||
anchorElement={ref.current}
|
||||
closeMenu={() => setAccountOptionsMenuOpen(false)}
|
||||
/>
|
||||
) : (
|
||||
<AccountOptionsMenu
|
||||
anchorElement={ref.current}
|
||||
onClose={() => setAccountOptionsMenuOpen(false)}
|
||||
/>
|
||||
))}
|
||||
{accountOptionsMenuOpen && (
|
||||
<AccountOptionsMenu
|
||||
anchorElement={ref.current}
|
||||
onClose={() => setAccountOptionsMenuOpen(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ import { useCopyToClipboard } from '../../../../hooks/useCopyToClipboard';
|
||||
import { getAddressBookEntry } from '../../../../selectors';
|
||||
import { TokenStandard } from '../../../../../shared/constants/transaction';
|
||||
import NftCollectionImage from '../../../ui/nft-collection-image/nft-collection-image';
|
||||
import { ButtonIcon } from '../../../component-library';
|
||||
import { ButtonIcon } from '../../../component-library/button-icon/deprecated';
|
||||
import { ICON_NAMES } from '../../../component-library/icon/deprecated';
|
||||
|
||||
export default function ContractDetailsModal({
|
||||
|
@ -15,7 +15,7 @@ import Box from '../../../ui/box';
|
||||
import withModalProps from '../../../../helpers/higher-order-components/with-modal-props';
|
||||
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
||||
import ZENDESK_URLS from '../../../../helpers/constants/zendesk-url';
|
||||
import { ButtonIcon } from '../../../component-library';
|
||||
import { ButtonIcon } from '../../../component-library/button-icon/deprecated';
|
||||
import {
|
||||
ICON_NAMES,
|
||||
ICON_SIZES,
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
calcTokenAmount,
|
||||
toPrecisionWithoutTrailingZeros,
|
||||
} from '../../../../../shared/lib/transactions-controller-utils';
|
||||
import { ButtonIcon } from '../../../component-library';
|
||||
import { ButtonIcon } from '../../../component-library/button-icon/deprecated';
|
||||
import {
|
||||
ICON_SIZES,
|
||||
ICON_NAMES,
|
||||
|
@ -2,12 +2,8 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import withModalProps from '../../../../helpers/higher-order-components/with-modal-props';
|
||||
import Box from '../../../ui/box';
|
||||
import {
|
||||
Text,
|
||||
Button,
|
||||
BUTTON_TYPES,
|
||||
ButtonIcon,
|
||||
} from '../../../component-library';
|
||||
import { Text, Button, BUTTON_TYPES } from '../../../component-library';
|
||||
import { ButtonIcon } from '../../../component-library/button-icon/deprecated';
|
||||
import { ICON_NAMES } from '../../../component-library/icon/deprecated';
|
||||
import {
|
||||
AlignItems,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Button from '../../../ui/button/button.component';
|
||||
import { ButtonIcon } from '../../../component-library';
|
||||
import { ButtonIcon } from '../../../component-library/button-icon/deprecated';
|
||||
import { ICON_NAMES } from '../../../component-library/icon/deprecated';
|
||||
|
||||
export default class NewAccountModal extends Component {
|
||||
|
@ -53,7 +53,7 @@ import {
|
||||
TokenStandard,
|
||||
} from '../../../../shared/constants/transaction';
|
||||
import NftDefaultImage from '../nft-default-image';
|
||||
import { ButtonIcon } from '../../component-library';
|
||||
import { ButtonIcon } from '../../component-library/button-icon/deprecated';
|
||||
import { ICON_NAMES } from '../../component-library/icon/deprecated';
|
||||
import Tooltip from '../../ui/tooltip';
|
||||
import { decWEIToDecETH } from '../../../../shared/modules/conversion.utils';
|
||||
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
|
||||
import { I18nContext } from '../../../contexts/i18n';
|
||||
import { Menu, MenuItem } from '../../ui/menu';
|
||||
import { ButtonIcon } from '../../component-library';
|
||||
import { ButtonIcon } from '../../component-library/button-icon/deprecated';
|
||||
import { ICON_NAMES } from '../../component-library/icon/deprecated';
|
||||
import { Color } from '../../../helpers/constants/design-system';
|
||||
|
||||
|
@ -53,6 +53,11 @@ const PermissionCell = ({
|
||||
iconBackgroundColor = Color.backgroundAlternative;
|
||||
}
|
||||
|
||||
let permissionIcon = avatarIcon;
|
||||
if (typeof avatarIcon !== 'string' && avatarIcon?.props?.iconName) {
|
||||
permissionIcon = avatarIcon.props.iconName;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
className="permission-cell"
|
||||
@ -64,9 +69,9 @@ const PermissionCell = ({
|
||||
paddingBottom={2}
|
||||
>
|
||||
<Box>
|
||||
{typeof avatarIcon === 'string' ? (
|
||||
{typeof permissionIcon === 'string' ? (
|
||||
<AvatarIcon
|
||||
iconName={avatarIcon}
|
||||
iconName={permissionIcon}
|
||||
size={ICON_SIZES.MD}
|
||||
iconProps={{
|
||||
size: ICON_SIZES.SM,
|
||||
@ -75,7 +80,7 @@ const PermissionCell = ({
|
||||
backgroundColor={iconBackgroundColor}
|
||||
/>
|
||||
) : (
|
||||
avatarIcon
|
||||
permissionIcon
|
||||
)}
|
||||
</Box>
|
||||
<Box width="full" marginLeft={4} marginRight={4}>
|
||||
|
@ -28,11 +28,14 @@ export default class PermissionPageContainerContent extends PureComponent {
|
||||
};
|
||||
|
||||
renderRequestedPermissions() {
|
||||
const { selectedPermissions } = this.props;
|
||||
const { selectedPermissions, subjectMetadata } = this.props;
|
||||
|
||||
return (
|
||||
<div className="permission-approval-container__content__requested">
|
||||
<PermissionsConnectPermissionList permissions={selectedPermissions} />
|
||||
<PermissionsConnectPermissionList
|
||||
permissions={selectedPermissions}
|
||||
targetSubjectMetadata={subjectMetadata}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,9 +1,29 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { getWeightedPermissions } from '../../../helpers/utils/permission';
|
||||
import {
|
||||
getRightIcon,
|
||||
getWeightedPermissions,
|
||||
} from '../../../helpers/utils/permission';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import PermissionCell from '../permission-cell';
|
||||
import Box from '../../ui/box';
|
||||
|
||||
/**
|
||||
* Get one or more permission descriptions for a permission name.
|
||||
*
|
||||
* @param permission - The permission to render.
|
||||
* @param index - The index of the permission.
|
||||
* @returns {JSX.Element} A permission description node.
|
||||
*/
|
||||
function getDescriptionNode(permission, index) {
|
||||
const { label, leftIcon, permissionName } = permission;
|
||||
|
||||
return (
|
||||
<div className="permission" key={`${permissionName}-${index}`}>
|
||||
{typeof leftIcon === 'string' ? <i className={leftIcon} /> : leftIcon}
|
||||
{label}
|
||||
{getRightIcon(permission)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function PermissionsConnectPermissionList({
|
||||
permissions,
|
||||
@ -12,22 +32,11 @@ export default function PermissionsConnectPermissionList({
|
||||
const t = useI18nContext();
|
||||
|
||||
return (
|
||||
<Box paddingTop={2} paddingBottom={2}>
|
||||
<div className="permissions-connect-permission-list">
|
||||
{getWeightedPermissions(t, permissions, targetSubjectMetadata).map(
|
||||
(permission, index) => {
|
||||
return (
|
||||
<PermissionCell
|
||||
title={permission.label}
|
||||
description={permission.description}
|
||||
weight={permission.weight}
|
||||
avatarIcon={permission.leftIcon}
|
||||
dateApproved={permission?.permissionValue?.date}
|
||||
key={`${permission.permissionName}-${index}`}
|
||||
/>
|
||||
);
|
||||
},
|
||||
getDescriptionNode,
|
||||
)}
|
||||
</Box>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -20,18 +20,5 @@ DefaultStory.storyName = 'Default';
|
||||
DefaultStory.args = {
|
||||
permissions: {
|
||||
eth_accounts: {},
|
||||
snap_dialog: {},
|
||||
snap_getBip32PublicKey: {
|
||||
caveats: [
|
||||
{
|
||||
value: [
|
||||
{
|
||||
path: ['m', `44'`, `0'`],
|
||||
curve: 'secp256k1',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -7,6 +7,7 @@ import Popover from '../../ui/popover';
|
||||
import Checkbox from '../../ui/check-box';
|
||||
import { I18nContext } from '../../../contexts/i18n';
|
||||
import { PageContainerFooter } from '../../ui/page-container';
|
||||
import { isAddressLedger } from '../../../ducks/metamask/metamask';
|
||||
import {
|
||||
accountsWithSendEtherInfoSelector,
|
||||
getSubjectMetadata,
|
||||
@ -20,6 +21,7 @@ import {
|
||||
|
||||
import SecurityProviderBannerMessage from '../security-provider-banner-message/security-provider-banner-message';
|
||||
import { SECURITY_PROVIDER_MESSAGE_SEVERITIES } from '../security-provider-banner-message/security-provider-banner-message.constants';
|
||||
import LedgerInstructionField from '../ledger-instruction-field';
|
||||
import Header from './signature-request-siwe-header';
|
||||
import Message from './signature-request-siwe-message';
|
||||
|
||||
@ -39,6 +41,8 @@ export default function SignatureRequestSIWE({
|
||||
},
|
||||
} = txData;
|
||||
|
||||
const isLedgerWallet = useSelector((state) => isAddressLedger(state, from));
|
||||
|
||||
const fromAccount = getAccountByAddress(allAccounts, from);
|
||||
const targetSubjectMetadata = subjectMetadata[origin];
|
||||
|
||||
@ -115,6 +119,13 @@ export default function SignatureRequestSIWE({
|
||||
])}
|
||||
</BannerAlert>
|
||||
)}
|
||||
|
||||
{isLedgerWallet && (
|
||||
<div className="confirm-approve-content__ledger-instruction-wrapper">
|
||||
<LedgerInstructionField showDataInstruction />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isSIWEDomainValid && (
|
||||
<BannerAlert
|
||||
severity={SEVERITIES.DANGER}
|
||||
|
@ -52,6 +52,15 @@ const mockProps = {
|
||||
},
|
||||
};
|
||||
|
||||
jest.mock('../ledger-instruction-field', () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
default: () => {
|
||||
return <div className="mock-ledger-instruction-field" />;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const render = (txData = mockProps.txData) => {
|
||||
const store = configureStore(mockStoreInitialState);
|
||||
|
||||
@ -110,4 +119,21 @@ describe('SignatureRequestSIWE (Sign in with Ethereum)', () => {
|
||||
expect(bannerAlert).toBeTruthy();
|
||||
expect(bannerAlert).toHaveTextContent('Deceptive site request.');
|
||||
});
|
||||
|
||||
it('should not show Ledger instructions if the address is not a Ledger address', () => {
|
||||
const { container } = render();
|
||||
expect(
|
||||
container.querySelector('.mock-ledger-instruction-field'),
|
||||
).not.toBeTruthy();
|
||||
});
|
||||
|
||||
it('should show Ledger instructions if the address is a Ledger address', () => {
|
||||
const mockTxData = cloneDeep(mockProps.txData);
|
||||
mockTxData.msgParams.from = '0xc42edfcc21ed14dda456aa0756c153f7985d8813';
|
||||
const { container } = render(mockTxData);
|
||||
|
||||
expect(
|
||||
container.querySelector('.mock-ledger-instruction-field'),
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
@ -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>
|
||||
|
1
ui/components/app/terms-of-use-popup/index.js
Normal file
1
ui/components/app/terms-of-use-popup/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './terms-of-use-popup';
|
30
ui/components/app/terms-of-use-popup/index.scss
Normal file
30
ui/components/app/terms-of-use-popup/index.scss
Normal file
@ -0,0 +1,30 @@
|
||||
.popover-wrap.terms-of-use__popover {
|
||||
.terms-of-use {
|
||||
&__terms-list {
|
||||
list-style: decimal none outside;
|
||||
}
|
||||
|
||||
&__footer-text {
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
.popover-header {
|
||||
&__title {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.popover-footer {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
@include screen-sm-min {
|
||||
max-height: 750px;
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
@include screen-sm-max {
|
||||
max-height: 568px;
|
||||
}
|
||||
}
|
1181
ui/components/app/terms-of-use-popup/terms-of-use-popup.js
Normal file
1181
ui/components/app/terms-of-use-popup/terms-of-use-popup.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import TermsOfUsePopup from '.';
|
||||
|
||||
export default {
|
||||
title: 'Components/App/TermsOfUsePopup',
|
||||
component: TermsOfUsePopup,
|
||||
argTypes: {
|
||||
onAccept: {
|
||||
action: 'onAccept',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const DefaultStory = (args) => <TermsOfUsePopup {...args} />;
|
||||
|
||||
DefaultStory.storyName = 'Default';
|
@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import { fireEvent, screen } from '@testing-library/react';
|
||||
import { renderWithProvider } from '../../../../test/jest';
|
||||
import configureStore from '../../../store/store';
|
||||
import mockState from '../../../../test/data/mock-state.json';
|
||||
import TermsOfUsePopup from './terms-of-use-popup';
|
||||
|
||||
const render = () => {
|
||||
const store = configureStore({
|
||||
metamask: {
|
||||
...mockState.metamask,
|
||||
},
|
||||
});
|
||||
const onAccept = jest.fn();
|
||||
return renderWithProvider(<TermsOfUsePopup onAccept={onAccept} />, store);
|
||||
};
|
||||
|
||||
describe('TermsOfUsePopup', () => {
|
||||
beforeEach(() => {
|
||||
const mockIntersectionObserver = jest.fn();
|
||||
mockIntersectionObserver.mockReturnValue({
|
||||
observe: () => null,
|
||||
unobserve: () => null,
|
||||
disconnect: () => null,
|
||||
});
|
||||
window.IntersectionObserver = mockIntersectionObserver;
|
||||
});
|
||||
|
||||
it('renders TermsOfUse component and shows Terms of Use text', () => {
|
||||
render();
|
||||
expect(
|
||||
screen.getByText('Our Terms of Use have updated'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('scrolls down when handleScrollDownClick is called', () => {
|
||||
render();
|
||||
const mockScrollIntoView = jest.fn();
|
||||
window.HTMLElement.prototype.scrollIntoView = mockScrollIntoView;
|
||||
const button = document.querySelector(
|
||||
"[data-testid='popover-scroll-button']",
|
||||
);
|
||||
fireEvent.click(button);
|
||||
expect(mockScrollIntoView).toHaveBeenCalledWith({
|
||||
behavior: 'smooth',
|
||||
});
|
||||
});
|
||||
});
|
@ -1,12 +1,22 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { toChecksumHexAddress } from '../../../../shared/modules/hexstring-utils';
|
||||
import { getSelectedIdentity } from '../../../selectors';
|
||||
import { AddressCopyButton } from '../../multichain';
|
||||
|
||||
const WalletOverview = ({ balance, buttons, className, icon, loading }) => {
|
||||
const selectedIdentity = useSelector(getSelectedIdentity);
|
||||
const checksummedAddress = toChecksumHexAddress(selectedIdentity?.address);
|
||||
return (
|
||||
<div className={classnames('wallet-overview', className)}>
|
||||
<div className="wallet-overview__balance">
|
||||
{loading ? null : icon}
|
||||
{process.env.MULTICHAIN ? (
|
||||
<AddressCopyButton address={checksummedAddress} shorten />
|
||||
) : (
|
||||
<>{loading ? null : icon}</>
|
||||
)}
|
||||
{balance}
|
||||
</div>
|
||||
<div className="wallet-overview__buttons">{buttons}</div>
|
||||
|
@ -66,28 +66,6 @@
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__scroll-button {
|
||||
position: absolute;
|
||||
bottom: 12px;
|
||||
right: 12px;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid var(--color-border-default);
|
||||
background: var(--color-background-alternative);
|
||||
color: var(--color-icon-default);
|
||||
z-index: 201;
|
||||
cursor: pointer;
|
||||
opacity: 0.8;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popover-wrap.whats-new-popup__popover {
|
||||
|
@ -1,8 +1,9 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { ButtonIcon, ButtonLink, Text } from '..';
|
||||
import { IconName } from '../icon';
|
||||
import { ButtonIcon } from '../button-icon/deprecated';
|
||||
import { ButtonLink, Text } from '..';
|
||||
import { ICON_NAMES } from '../icon/deprecated';
|
||||
|
||||
import Box from '../../ui/box';
|
||||
|
||||
@ -72,7 +73,7 @@ export const BannerBase = ({
|
||||
<ButtonIcon
|
||||
className="mm-banner-base__close-button"
|
||||
marginLeft="auto"
|
||||
iconName={IconName.Close}
|
||||
iconName={ICON_NAMES.CLOSE}
|
||||
size={Size.SM}
|
||||
ariaLabel="Close" // TODO: i18n
|
||||
onClick={onClose}
|
||||
|
@ -34,26 +34,23 @@ import { IconName } from '../icon';
|
||||
|
||||
### Size
|
||||
|
||||
Use the `size` prop and the `Size` object from `./ui/helpers/constants/design-system.js`
|
||||
to change the size of `ButtonIcon`. Defaults to `Size.SM`
|
||||
|
||||
Optional: `BUTTON_ICON_SIZES` from `./button-icon` object can be used instead of `Size`.
|
||||
Use the `size` prop and the `ButtonIconSize` enum from `./ui/components/component-library/icon` to change the size of `ButtonIcon`. Defaults to `ButtonIconSize.Sm`
|
||||
|
||||
Possible sizes include:
|
||||
|
||||
- `Size.SM` 24px
|
||||
- `Size.LG` 32px
|
||||
- `ButtonIconSize.Sm` 24px
|
||||
- `ButtonIconSize.Lg` 32px
|
||||
|
||||
<Canvas>
|
||||
<Story id="components-componentlibrary-buttonicon--size-story" />
|
||||
</Canvas>
|
||||
|
||||
```jsx
|
||||
import { Size } from '../../../helpers/constants/design-system';
|
||||
import { ButtonIconSize } from '../../../helpers/constants/design-system';
|
||||
import { ButtonIcon } from '../ui/component-library';
|
||||
|
||||
<ButtonIcon size={Size.SM} iconName={IconName.Close} ariaLabel="Close"/>
|
||||
<ButtonIcon size={Size.LG} iconName={IconName.Close} ariaLabel="Close"/>
|
||||
<ButtonIcon size={ButtonIconSize.Sm} iconName={IconName.Close} ariaLabel="Close"/>
|
||||
<ButtonIcon size={ButtonIconSize.Lg} iconName={IconName.Close} ariaLabel="Close"/>
|
||||
```
|
||||
|
||||
### Aria Label
|
||||
|
@ -1,35 +1,11 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
AlignItems,
|
||||
Color,
|
||||
DISPLAY,
|
||||
FLEX_DIRECTION,
|
||||
Size,
|
||||
} from '../../../helpers/constants/design-system';
|
||||
import Box from '../../ui/box/box';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import { Color } from '../../../helpers/constants/design-system';
|
||||
import { IconName } from '..';
|
||||
import { BUTTON_ICON_SIZES } from './button-icon.constants';
|
||||
import { ButtonIconSize } from './button-icon.types';
|
||||
import { ButtonIcon } from './button-icon';
|
||||
import README from './README.mdx';
|
||||
|
||||
const marginSizeControlOptions = [
|
||||
undefined,
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
'auto',
|
||||
];
|
||||
|
||||
export default {
|
||||
title: 'Components/ComponentLibrary/ButtonIcon',
|
||||
|
||||
@ -40,58 +16,18 @@ export default {
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
ariaLabel: {
|
||||
control: 'text',
|
||||
},
|
||||
as: {
|
||||
control: 'select',
|
||||
options: ['button', 'a'],
|
||||
},
|
||||
className: {
|
||||
control: 'text',
|
||||
},
|
||||
color: {
|
||||
control: 'select',
|
||||
options: Object.values(Color),
|
||||
},
|
||||
disabled: {
|
||||
control: 'boolean',
|
||||
},
|
||||
href: {
|
||||
control: 'text',
|
||||
},
|
||||
iconName: {
|
||||
control: 'select',
|
||||
options: Object.values(IconName),
|
||||
},
|
||||
size: {
|
||||
control: 'select',
|
||||
options: Object.values(BUTTON_ICON_SIZES),
|
||||
},
|
||||
marginTop: {
|
||||
options: marginSizeControlOptions,
|
||||
control: 'select',
|
||||
table: { category: 'box props' },
|
||||
},
|
||||
marginRight: {
|
||||
options: marginSizeControlOptions,
|
||||
control: 'select',
|
||||
table: { category: 'box props' },
|
||||
},
|
||||
marginBottom: {
|
||||
options: marginSizeControlOptions,
|
||||
control: 'select',
|
||||
table: { category: 'box props' },
|
||||
},
|
||||
marginLeft: {
|
||||
options: marginSizeControlOptions,
|
||||
control: 'select',
|
||||
table: { category: 'box props' },
|
||||
},
|
||||
},
|
||||
};
|
||||
} as ComponentMeta<typeof ButtonIcon>;
|
||||
|
||||
export const DefaultStory = (args) => <ButtonIcon {...args} />;
|
||||
const Template: ComponentStory<typeof ButtonIcon> = (args) => (
|
||||
<ButtonIcon {...args} />
|
||||
);
|
||||
|
||||
export const DefaultStory = Template.bind({});
|
||||
|
||||
DefaultStory.args = {
|
||||
iconName: IconName.Close,
|
||||
@ -100,7 +36,9 @@ DefaultStory.args = {
|
||||
|
||||
DefaultStory.storyName = 'Default';
|
||||
|
||||
export const IconNameStory = (args) => <ButtonIcon {...args} />;
|
||||
export const IconNameStory: ComponentStory<typeof ButtonIcon> = (args) => (
|
||||
<ButtonIcon {...args} />
|
||||
);
|
||||
|
||||
IconNameStory.args = {
|
||||
iconName: IconName.Close,
|
||||
@ -109,32 +47,27 @@ IconNameStory.args = {
|
||||
|
||||
IconNameStory.storyName = 'IconName';
|
||||
|
||||
export const SizeStory = (args) => (
|
||||
<Box
|
||||
display={DISPLAY.FLEX}
|
||||
alignItems={AlignItems.baseline}
|
||||
gap={1}
|
||||
marginBottom={2}
|
||||
>
|
||||
export const SizeStory: ComponentStory<typeof ButtonIcon> = (args) => (
|
||||
<>
|
||||
<ButtonIcon
|
||||
{...args}
|
||||
size={Size.SM}
|
||||
size={ButtonIconSize.Sm}
|
||||
iconName={IconName.Close}
|
||||
ariaLabel="Close"
|
||||
/>
|
||||
<ButtonIcon
|
||||
{...args}
|
||||
size={Size.LG}
|
||||
size={ButtonIconSize.Lg}
|
||||
color={Color.primaryDefault}
|
||||
iconName={IconName.Close}
|
||||
ariaLabel="Close"
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
|
||||
SizeStory.storyName = 'Size';
|
||||
|
||||
export const AriaLabel = (args) => (
|
||||
export const AriaLabel: ComponentStory<typeof ButtonIcon> = (args) => (
|
||||
<>
|
||||
<ButtonIcon
|
||||
as="button"
|
||||
@ -154,8 +87,8 @@ export const AriaLabel = (args) => (
|
||||
</>
|
||||
);
|
||||
|
||||
export const As = (args) => (
|
||||
<Box display={DISPLAY.FLEX} flexDirection={FLEX_DIRECTION.ROW} gap={2}>
|
||||
export const As: ComponentStory<typeof ButtonIcon> = (args) => (
|
||||
<>
|
||||
<ButtonIcon {...args} iconName={IconName.Close} ariaLabel="close" />
|
||||
<ButtonIcon
|
||||
as="a"
|
||||
@ -165,10 +98,10 @@ export const As = (args) => (
|
||||
iconName={IconName.Export}
|
||||
ariaLabel="demo"
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
|
||||
export const Href = (args) => (
|
||||
export const Href: ComponentStory<typeof ButtonIcon> = (args) => (
|
||||
<ButtonIcon iconName={IconName.Export} {...args} target="_blank" />
|
||||
);
|
||||
|
||||
@ -178,7 +111,7 @@ Href.args = {
|
||||
color: Color.primaryDefault,
|
||||
};
|
||||
|
||||
export const ColorStory = (args) => (
|
||||
export const ColorStory: ComponentStory<typeof ButtonIcon> = (args) => (
|
||||
<ButtonIcon {...args} iconName={IconName.Close} ariaLabel="close" />
|
||||
);
|
||||
ColorStory.storyName = 'Color';
|
||||
@ -187,7 +120,7 @@ ColorStory.args = {
|
||||
color: Color.primaryDefault,
|
||||
};
|
||||
|
||||
export const Disabled = (args) => (
|
||||
export const Disabled: ComponentStory<typeof ButtonIcon> = (args) => (
|
||||
<ButtonIcon {...args} iconName={IconName.Close} ariaLabel="close" />
|
||||
);
|
||||
|
147
ui/components/component-library/button-icon/button-icon.test.tsx
Normal file
147
ui/components/component-library/button-icon/button-icon.test.tsx
Normal file
@ -0,0 +1,147 @@
|
||||
/* eslint-disable jest/require-top-level-describe */
|
||||
import { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { IconColor } from '../../../helpers/constants/design-system';
|
||||
import { IconName } from '..';
|
||||
import { ButtonIconSize } from './button-icon.types';
|
||||
import { ButtonIcon } from './button-icon';
|
||||
|
||||
describe('ButtonIcon', () => {
|
||||
it('should render button element correctly', () => {
|
||||
const { getByTestId, container } = render(
|
||||
<ButtonIcon
|
||||
data-testid="button-icon"
|
||||
iconName={IconName.AddSquare}
|
||||
ariaLabel="add"
|
||||
/>,
|
||||
);
|
||||
expect(container.querySelector('button')).toBeDefined();
|
||||
expect(getByTestId('button-icon')).toHaveClass('mm-button-icon');
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render anchor element correctly', () => {
|
||||
const { getByTestId, container } = render(
|
||||
<ButtonIcon
|
||||
as="a"
|
||||
data-testid="button-icon"
|
||||
iconName={IconName.AddSquare}
|
||||
ariaLabel="add"
|
||||
/>,
|
||||
);
|
||||
expect(getByTestId('button-icon')).toHaveClass('mm-button-icon');
|
||||
const anchor = container.getElementsByTagName('a').length;
|
||||
expect(anchor).toBe(1);
|
||||
});
|
||||
|
||||
it('should render anchor element correctly using href', () => {
|
||||
const { getByTestId, getByRole } = render(
|
||||
<ButtonIcon
|
||||
href="/metamask"
|
||||
data-testid="button-icon"
|
||||
iconName={IconName.AddSquare}
|
||||
ariaLabel="add"
|
||||
/>,
|
||||
);
|
||||
expect(getByTestId('button-icon')).toHaveClass('mm-button-icon');
|
||||
expect(getByRole('link')).toBeDefined();
|
||||
});
|
||||
|
||||
it('should render with different size classes', () => {
|
||||
const { getByTestId } = render(
|
||||
<>
|
||||
<ButtonIcon
|
||||
iconName={IconName.AddSquare}
|
||||
ariaLabel="add"
|
||||
size={ButtonIconSize.Sm}
|
||||
data-testid={ButtonIconSize.Sm}
|
||||
/>
|
||||
<ButtonIcon
|
||||
iconName={IconName.AddSquare}
|
||||
ariaLabel="add"
|
||||
size={ButtonIconSize.Lg}
|
||||
data-testid={ButtonIconSize.Lg}
|
||||
/>
|
||||
</>,
|
||||
);
|
||||
expect(getByTestId(ButtonIconSize.Sm)).toHaveClass(
|
||||
`mm-button-icon--size-${ButtonIconSize.Sm}`,
|
||||
);
|
||||
expect(getByTestId(ButtonIconSize.Lg)).toHaveClass(
|
||||
`mm-button-icon--size-${ButtonIconSize.Lg}`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should render with different colors', () => {
|
||||
const { getByTestId } = render(
|
||||
<>
|
||||
<ButtonIcon
|
||||
iconName={IconName.AddSquare}
|
||||
ariaLabel="add"
|
||||
color={IconColor.iconDefault}
|
||||
data-testid={IconColor.iconDefault}
|
||||
/>
|
||||
<ButtonIcon
|
||||
iconName={IconName.AddSquare}
|
||||
ariaLabel="add"
|
||||
color={IconColor.errorDefault}
|
||||
data-testid={IconColor.errorDefault}
|
||||
/>
|
||||
</>,
|
||||
);
|
||||
expect(getByTestId(IconColor.iconDefault)).toHaveClass(
|
||||
`box--color-${IconColor.iconDefault}`,
|
||||
);
|
||||
expect(getByTestId(IconColor.errorDefault)).toHaveClass(
|
||||
`box--color-${IconColor.errorDefault}`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should render with added classname', () => {
|
||||
const { getByTestId } = render(
|
||||
<ButtonIcon
|
||||
data-testid="classname"
|
||||
className="mm-button-icon--test"
|
||||
iconName={IconName.AddSquare}
|
||||
ariaLabel="add"
|
||||
/>,
|
||||
);
|
||||
expect(getByTestId('classname')).toHaveClass('mm-button-icon--test');
|
||||
});
|
||||
|
||||
it('should render with different button states', () => {
|
||||
const { getByTestId } = render(
|
||||
<>
|
||||
<ButtonIcon
|
||||
disabled
|
||||
data-testid="disabled"
|
||||
iconName={IconName.AddSquare}
|
||||
ariaLabel="add"
|
||||
/>
|
||||
</>,
|
||||
);
|
||||
|
||||
expect(getByTestId('disabled')).toHaveClass(`mm-button-icon--disabled`);
|
||||
expect(getByTestId('disabled')).toBeDisabled();
|
||||
});
|
||||
it('should render with icon', () => {
|
||||
const { getByTestId } = render(
|
||||
<ButtonIcon
|
||||
data-testid="icon"
|
||||
iconName={IconName.AddSquare}
|
||||
ariaLabel="add"
|
||||
iconProps={{ 'data-testid': 'button-icon' }}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(getByTestId('button-icon')).toBeDefined();
|
||||
});
|
||||
|
||||
it('should render with aria-label', () => {
|
||||
const { getByLabelText } = render(
|
||||
<ButtonIcon iconName={IconName.AddSquare} ariaLabel="add" />,
|
||||
);
|
||||
|
||||
expect(getByLabelText('add')).toBeDefined();
|
||||
});
|
||||
});
|
72
ui/components/component-library/button-icon/button-icon.tsx
Normal file
72
ui/components/component-library/button-icon/button-icon.tsx
Normal file
@ -0,0 +1,72 @@
|
||||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import {
|
||||
AlignItems,
|
||||
BackgroundColor,
|
||||
BorderRadius,
|
||||
DISPLAY,
|
||||
IconColor,
|
||||
JustifyContent,
|
||||
} from '../../../helpers/constants/design-system';
|
||||
|
||||
import Box from '../../ui/box';
|
||||
import { Icon, IconSize } from '../icon';
|
||||
|
||||
import { ButtonIconSize, ButtonIconProps } from './button-icon.types';
|
||||
|
||||
const buttonIconSizeToIconSize: Record<ButtonIconSize, IconSize> = {
|
||||
[ButtonIconSize.Sm]: IconSize.Sm,
|
||||
[ButtonIconSize.Lg]: IconSize.Lg,
|
||||
};
|
||||
|
||||
export const ButtonIcon = React.forwardRef(
|
||||
(
|
||||
{
|
||||
ariaLabel,
|
||||
as = 'button',
|
||||
className = '',
|
||||
color = IconColor.iconDefault,
|
||||
href,
|
||||
size = ButtonIconSize.Lg,
|
||||
iconName,
|
||||
disabled,
|
||||
iconProps,
|
||||
...props
|
||||
}: ButtonIconProps,
|
||||
ref: React.Ref<HTMLElement>,
|
||||
) => {
|
||||
const Tag = href ? 'a' : as;
|
||||
const isDisabled = disabled && Tag === 'button';
|
||||
return (
|
||||
<Box
|
||||
aria-label={ariaLabel}
|
||||
as={Tag}
|
||||
className={classnames(
|
||||
'mm-button-icon',
|
||||
`mm-button-icon--size-${String(size)}`,
|
||||
{
|
||||
'mm-button-icon--disabled': Boolean(disabled),
|
||||
},
|
||||
className,
|
||||
)}
|
||||
color={color}
|
||||
{...(isDisabled ? { disabled: true } : {})} // only allow disabled attribute to be passed down to the Box when the as prop is equal to a button element
|
||||
display={DISPLAY.INLINE_FLEX}
|
||||
justifyContent={JustifyContent.center}
|
||||
alignItems={AlignItems.center}
|
||||
borderRadius={BorderRadius.LG}
|
||||
backgroundColor={BackgroundColor.transparent}
|
||||
{...(href ? { href } : {})}
|
||||
ref={ref}
|
||||
{...props}
|
||||
>
|
||||
<Icon
|
||||
name={iconName}
|
||||
size={buttonIconSizeToIconSize[size]}
|
||||
{...iconProps}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
);
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user