Skip to content

Commit d2d7db9

Browse files
committed
When switching network alert the user and get approval if there are pending requests from origin.
1 parent 135d0d2 commit d2d7db9

15 files changed

+209
-14
lines changed

app/_locales/en/messages.json

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/_locales/en_GB/messages.json

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/scripts/lib/rpc-method-middleware/handlers/add-ethereum-chain.js

+1
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ async function addEthereumChainHandler(
186186
updatedNetwork.rpcEndpoints[updatedNetwork.defaultRpcEndpointIndex];
187187

188188
return switchChain(res, end, chainId, networkClientId, {
189+
isAddFlow: true,
189190
autoApprove: shouldAddOrUpdateNetwork,
190191
setActiveNetwork,
191192
getCaveat,

app/scripts/lib/rpc-method-middleware/handlers/add-ethereum-chain.test.js

+4
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ const createMockedHandler = () => {
6363
const next = jest.fn();
6464
const end = jest.fn();
6565
const mocks = {
66+
isAddFlow: true,
6667
getCurrentChainIdForDomain: jest.fn().mockReturnValue(NON_INFURA_CHAIN_ID),
6768
setNetworkClientIdForDomain: jest.fn(),
6869
getNetworkConfigurationByChainId: jest.fn(),
@@ -186,6 +187,7 @@ describe('addEthereumChainHandler', () => {
186187
{
187188
autoApprove: true,
188189
getCaveat: mocks.getCaveat,
190+
isAddFlow: true,
189191
setActiveNetwork: mocks.setActiveNetwork,
190192
requestPermittedChainsPermissionIncrementalForOrigin:
191193
mocks.requestPermittedChainsPermissionIncrementalForOrigin,
@@ -252,6 +254,7 @@ describe('addEthereumChainHandler', () => {
252254
{
253255
autoApprove: true,
254256
getCaveat: mocks.getCaveat,
257+
isAddFlow: true,
255258
setActiveNetwork: mocks.setActiveNetwork,
256259
requestPermittedChainsPermissionIncrementalForOrigin:
257260
mocks.requestPermittedChainsPermissionIncrementalForOrigin,
@@ -299,6 +302,7 @@ describe('addEthereumChainHandler', () => {
299302
{
300303
autoApprove: false,
301304
getCaveat: mocks.getCaveat,
305+
isAddFlow: true,
302306
setActiveNetwork: mocks.setActiveNetwork,
303307
requestPermittedChainsPermissionIncrementalForOrigin:
304308
mocks.requestPermittedChainsPermissionIncrementalForOrigin,

app/scripts/lib/rpc-method-middleware/handlers/ethereum-chain-utils.js

+22
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ApprovalType } from '@metamask/controller-utils';
12
import { rpcErrors } from '@metamask/rpc-errors';
23
import {
34
Caip25CaveatType,
@@ -165,12 +166,18 @@ export function validateAddEthereumChainParams(params) {
165166
* @param {string} chainId - The chainId being switched to.
166167
* @param {string} networkClientId - The network client being switched to.
167168
* @param {object} hooks - The hooks object.
169+
* @param {string} hooks.origin - The origin sending this request.
170+
* @param {boolean} hooks.isAddFlow - Variable to check if its add flow.
168171
* @param {boolean} [hooks.autoApprove] - A boolean indicating whether the request should prompt the user or be automatically approved.
169172
* @param {Function} hooks.setActiveNetwork - The callback to change the current network for the origin.
170173
* @param {Function} hooks.getCaveat - The callback to get the CAIP-25 caveat for the origin.
171174
* @param {Function} hooks.requestPermittedChainsPermissionIncrementalForOrigin - The callback to add a new chain to the permittedChains-equivalent CAIP-25 permission.
172175
* @param {Function} hooks.setTokenNetworkFilter - The callback to set the token network filter.
173176
* @param {Function} hooks.rejectApprovalRequestsForOrigin - The callback to reject all pending approval requests for the origin.
177+
* @param {Function} hooks.requestUserApproval - The callback to trigger user approval flow.
178+
* @param {Function} hooks.hasApprovalRequestsForOrigin - Function to check if there are pending approval requests from the origin.
179+
* @param {object} hooks.toNetworkConfiguration - Network configutation of network switching to.
180+
* @param {object} hooks.fromNetworkConfiguration - Network configutation of network switching from.
174181
* @returns a null response on success or an error if user rejects an approval when autoApprove is false or on unexpected errors.
175182
*/
176183
export async function switchChain(
@@ -179,12 +186,18 @@ export async function switchChain(
179186
chainId,
180187
networkClientId,
181188
{
189+
origin,
190+
isAddFlow,
182191
autoApprove,
183192
setActiveNetwork,
184193
getCaveat,
185194
requestPermittedChainsPermissionIncrementalForOrigin,
186195
setTokenNetworkFilter,
187196
rejectApprovalRequestsForOrigin,
197+
requestUserApproval,
198+
hasApprovalRequestsForOrigin,
199+
toNetworkConfiguration,
200+
fromNetworkConfiguration,
188201
},
189202
) {
190203
try {
@@ -201,6 +214,15 @@ export async function switchChain(
201214
chainId,
202215
autoApprove,
203216
});
217+
} else if (hasApprovalRequestsForOrigin?.() && !isAddFlow) {
218+
await requestUserApproval({
219+
origin,
220+
type: ApprovalType.SwitchEthereumChain,
221+
requestData: {
222+
toNetworkConfiguration,
223+
fromNetworkConfiguration,
224+
},
225+
});
204226
}
205227
} else {
206228
await requestPermittedChainsPermissionIncrementalForOrigin({

app/scripts/lib/rpc-method-middleware/handlers/ethereum-chain-utils.test.ts

+26
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import * as Multichain from '@metamask/multichain';
12
import { errorCodes, rpcErrors } from '@metamask/rpc-errors';
23
import {
34
Caip25CaveatType,
@@ -10,12 +11,18 @@ describe('Ethereum Chain Utils', () => {
1011
const createMockedSwitchChain = () => {
1112
const end = jest.fn();
1213
const mocks = {
14+
origin: 'www.test.com',
15+
isAddFlow: false,
1316
autoApprove: false,
1417
setActiveNetwork: jest.fn(),
1518
getCaveat: jest.fn(),
1619
requestPermittedChainsPermissionIncrementalForOrigin: jest.fn(),
1720
setTokenNetworkFilter: jest.fn(),
1821
rejectApprovalRequestsForOrigin: jest.fn(),
22+
requestUserApproval: jest.fn(),
23+
hasApprovalRequestsForOrigin: jest.fn(),
24+
toNetworkConfiguration: {},
25+
fromNetworkConfiguration: {},
1926
};
2027
const response: { result?: true } = {};
2128
const switchChain = (chainId: Hex, networkClientId: string) =>
@@ -125,6 +132,25 @@ describe('Ethereum Chain Utils', () => {
125132
expect(mocks.setTokenNetworkFilter).toHaveBeenCalledWith('0x1');
126133
});
127134

135+
it('check for user approval is user already has access on the chain', async () => {
136+
jest
137+
.spyOn(Multichain, 'getPermittedEthChainIds')
138+
.mockReturnValue(['0x1']);
139+
const { mocks, switchChain } = createMockedSwitchChain();
140+
mocks.hasApprovalRequestsForOrigin.mockReturnValue(true);
141+
mocks.autoApprove = false;
142+
mocks.getCaveat.mockReturnValue({
143+
value: {
144+
requiredScopes: {},
145+
optionalScopes: {},
146+
isMultichainOrigin: false,
147+
},
148+
});
149+
await switchChain('0x1', 'testnet');
150+
151+
expect(mocks.requestUserApproval).toHaveBeenCalledTimes(1);
152+
});
153+
128154
it('should throw errors if the permittedChains grant fails', async () => {
129155
const { mocks, end, switchChain } = createMockedSwitchChain();
130156
mocks.requestPermittedChainsPermissionIncrementalForOrigin.mockRejectedValueOnce(

app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.js

+16
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { providerErrors } from '@metamask/rpc-errors';
2+
23
import { MESSAGE_TYPE } from '../../../../../shared/constants/app';
34
import {
45
validateSwitchEthereumChainParams,
@@ -11,10 +12,12 @@ const switchEthereumChain = {
1112
hookNames: {
1213
getNetworkConfigurationByChainId: true,
1314
setActiveNetwork: true,
15+
requestUserApproval: true,
1416
getCaveat: true,
1517
getCurrentChainIdForDomain: true,
1618
requestPermittedChainsPermissionIncrementalForOrigin: true,
1719
setTokenNetworkFilter: true,
20+
hasApprovalRequestsForOrigin: true,
1821
},
1922
};
2023

@@ -28,10 +31,12 @@ async function switchEthereumChainHandler(
2831
{
2932
getNetworkConfigurationByChainId,
3033
setActiveNetwork,
34+
requestUserApproval,
3135
getCaveat,
3236
getCurrentChainIdForDomain,
3337
requestPermittedChainsPermissionIncrementalForOrigin,
3438
setTokenNetworkFilter,
39+
hasApprovalRequestsForOrigin,
3540
},
3641
) {
3742
let chainId;
@@ -64,10 +69,21 @@ async function switchEthereumChainHandler(
6469
);
6570
}
6671

72+
const fromNetworkConfiguration = getNetworkConfigurationByChainId(
73+
currentChainIdForOrigin,
74+
);
75+
76+
const toNetworkConfiguration = getNetworkConfigurationByChainId(chainId);
77+
6778
return switchChain(res, end, chainId, networkClientIdToSwitchTo, {
79+
origin,
6880
setActiveNetwork,
6981
getCaveat,
7082
requestPermittedChainsPermissionIncrementalForOrigin,
7183
setTokenNetworkFilter,
84+
requestUserApproval,
85+
hasApprovalRequestsForOrigin,
86+
toNetworkConfiguration,
87+
fromNetworkConfiguration,
7288
});
7389
}

app/scripts/lib/rpc-method-middleware/handlers/switch-ethereum-chain.test.js

+23
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const createMockedHandler = () => {
3939
const next = jest.fn();
4040
const end = jest.fn();
4141
const mocks = {
42+
hasApprovalRequestsForOrigin: () => false,
4243
getNetworkConfigurationByChainId: jest
4344
.fn()
4445
.mockReturnValue(createMockMainnetConfiguration()),
@@ -47,6 +48,7 @@ const createMockedHandler = () => {
4748
getCurrentChainIdForDomain: jest.fn().mockReturnValue(NON_INFURA_CHAIN_ID),
4849
requestPermittedChainsPermissionIncrementalForOrigin: jest.fn(),
4950
setTokenNetworkFilter: jest.fn(),
51+
requestUserApproval: jest.fn(),
5052
};
5153
const response = {};
5254
const handler = (request) =>
@@ -167,10 +169,31 @@ describe('switchEthereumChainHandler', () => {
167169
'mainnet',
168170
{
169171
setActiveNetwork: mocks.setActiveNetwork,
172+
fromNetworkConfiguration: {
173+
chainId: '0xe708',
174+
defaultRpcEndpointIndex: 0,
175+
rpcEndpoints: [
176+
{
177+
networkClientId: 'linea-mainnet',
178+
},
179+
],
180+
},
170181
getCaveat: mocks.getCaveat,
182+
hasApprovalRequestsForOrigin: mocks.hasApprovalRequestsForOrigin,
183+
origin: 'example.com',
171184
requestPermittedChainsPermissionIncrementalForOrigin:
172185
mocks.requestPermittedChainsPermissionIncrementalForOrigin,
186+
requestUserApproval: mocks.requestUserApproval,
173187
setTokenNetworkFilter: mocks.setTokenNetworkFilter,
188+
toNetworkConfiguration: {
189+
chainId: '0x1',
190+
defaultRpcEndpointIndex: 0,
191+
rpcEndpoints: [
192+
{
193+
networkClientId: 'mainnet',
194+
},
195+
],
196+
},
174197
},
175198
);
176199
});

app/scripts/metamask-controller.js

+2
Original file line numberDiff line numberDiff line change
@@ -6197,6 +6197,8 @@ export default class MetamaskController extends EventEmitter {
61976197
this.permissionController,
61986198
origin,
61996199
),
6200+
hasApprovalRequestsForOrigin: () =>
6201+
this.approvalController.has({ origin }),
62006202
rejectApprovalRequestsForOrigin: () =>
62016203
this.rejectOriginPendingApprovals(origin),
62026204

ui/pages/confirmations/confirmation/alerts/useTemplateConfirmationAlerts.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useDispatch } from 'react-redux';
44
import mockState from '../../../../../test/data/mock-state.json';
55
import { renderHookWithProvider } from '../../../../../test/lib/render-helpers';
66
import * as AlertActions from '../../../../ducks/confirm-alerts/confirm-alerts';
7-
import * as AddEthereumChainAlerts from './useAddEthereumChainAlerts';
7+
import * as UpdateEthereumChainAlerts from './useUpdateEthereumChainAlerts';
88

99
import { useTemplateConfirmationAlerts } from './useTemplateConfirmationAlerts';
1010

@@ -34,7 +34,7 @@ describe('updateConfirmationAlerts', () => {
3434
const mockDispatch = jest.fn();
3535
(useDispatch as jest.Mock).mockReturnValue(mockDispatch);
3636
jest
37-
.spyOn(AddEthereumChainAlerts, 'useAddEthereumChainAlerts')
37+
.spyOn(UpdateEthereumChainAlerts, 'useUpdateEthereumChainAlerts')
3838
.mockReturnValue(MOCK_ADD_ETH_CHAIN_ALERT);
3939
const mockUpdateAlerts = jest.spyOn(AlertActions, 'updateAlerts');
4040

ui/pages/confirmations/confirmation/alerts/useTemplateConfirmationAlerts.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ import {
77
clearAlerts,
88
updateAlerts,
99
} from '../../../../ducks/confirm-alerts/confirm-alerts';
10-
import { useAddEthereumChainAlerts } from './useAddEthereumChainAlerts';
10+
import { useUpdateEthereumChainAlerts } from './useUpdateEthereumChainAlerts';
1111

1212
export const useTemplateConfirmationAlerts = (
1313
pendingConfirmation: ApprovalRequest<{ id: string }>,
1414
) => {
1515
const dispatch = useDispatch();
16-
const addEthereumChainAlerts = useAddEthereumChainAlerts(pendingConfirmation);
16+
const addEthereumChainAlerts =
17+
useUpdateEthereumChainAlerts(pendingConfirmation);
1718
const alerts: Alert[] = useMemo(
1819
() => addEthereumChainAlerts,
1920
[addEthereumChainAlerts],

ui/pages/confirmations/confirmation/alerts/useAddEthereumChainAlerts.test.ts renamed to ui/pages/confirmations/confirmation/alerts/useUpdateEthereumChainAlerts.test.ts

+47-6
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { getMockPersonalSignConfirmState } from '../../../../../test/data/confir
66
import { renderHookWithProvider } from '../../../../../test/lib/render-helpers';
77
import * as AlertActions from '../../../../ducks/confirm-alerts/confirm-alerts';
88

9-
import { useAddEthereumChainAlerts } from './useAddEthereumChainAlerts';
9+
import { useUpdateEthereumChainAlerts } from './useUpdateEthereumChainAlerts';
1010

1111
jest.mock('react-redux', () => ({
1212
...jest.requireActual('react-redux'),
@@ -35,11 +35,19 @@ const ADD_ETH_CHAIN_ALERT = [
3535
},
3636
] as AlertActions.Alert[];
3737

38-
describe('useAddEthereumChainAlerts', () => {
39-
it('returns alert if there are pending confirmations', () => {
40-
const state = getMockPersonalSignConfirmState();
38+
const SWITCH_ETH_CHAIN_ALERT = [
39+
{
40+
...ADD_ETH_CHAIN_ALERT[0],
41+
message:
42+
'Switching network will cancel 1 pending transactions from this site.',
43+
},
44+
] as AlertActions.Alert[];
45+
46+
describe('useUpdateEthereumChainAlerts', () => {
47+
const state = getMockPersonalSignConfirmState();
48+
it('returns alert for approval type addEthereumChain if there are pending confirmations', () => {
4149
const { result } = renderHookWithProvider(
42-
() => useAddEthereumChainAlerts(PENDING_APPROVAL_MOCK),
50+
() => useUpdateEthereumChainAlerts(PENDING_APPROVAL_MOCK),
4351
{
4452
...state,
4553
metamask: {
@@ -54,9 +62,42 @@ describe('useAddEthereumChainAlerts', () => {
5462
expect(result.current).toStrictEqual(ADD_ETH_CHAIN_ALERT);
5563
});
5664

65+
it('returns alert for approval type switchEthereumChain if there are pending confirmations', () => {
66+
const { result } = renderHookWithProvider(
67+
() =>
68+
useUpdateEthereumChainAlerts({
69+
...PENDING_APPROVAL_MOCK,
70+
type: ApprovalType.SwitchEthereumChain,
71+
}),
72+
{
73+
...state,
74+
metamask: {
75+
...state.metamask,
76+
pendingApprovals: {
77+
...state.metamask.pendingApprovals,
78+
[PENDING_APPROVAL_MOCK.id]: PENDING_APPROVAL_MOCK,
79+
},
80+
},
81+
},
82+
);
83+
expect(result.current).toStrictEqual(SWITCH_ETH_CHAIN_ALERT);
84+
});
85+
5786
it('does not returns alert if there are no pending confirmations', () => {
5887
const { result } = renderHookWithProvider(
59-
() => useAddEthereumChainAlerts(PENDING_APPROVAL_MOCK),
88+
() => useUpdateEthereumChainAlerts(PENDING_APPROVAL_MOCK),
89+
mockState,
90+
);
91+
expect(result.current).toStrictEqual([]);
92+
});
93+
94+
it('does not returns alert for un-supported pending confirmation type', () => {
95+
const { result } = renderHookWithProvider(
96+
() =>
97+
useUpdateEthereumChainAlerts({
98+
...PENDING_APPROVAL_MOCK,
99+
type: ApprovalType.PersonalSign,
100+
}),
60101
mockState,
61102
);
62103
expect(result.current).toStrictEqual([]);

0 commit comments

Comments
 (0)