Skip to content

Commit 86e77a8

Browse files
authored
Merge branch 'main' into SOL-225-disable-buttons-for-solana
2 parents cc515c4 + e61196a commit 86e77a8

15 files changed

+157
-22
lines changed

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

+9
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ export function validateAddEthereumChainParams(params) {
168168
* @param {object} hooks - The hooks object.
169169
* @param {string} hooks.origin - The origin sending this request.
170170
* @param {boolean} hooks.isAddFlow - Variable to check if its add flow.
171+
* @param {boolean} hooks.isSwitchFlow - Variable to check if its switch flow.
171172
* @param {boolean} [hooks.autoApprove] - A boolean indicating whether the request should prompt the user or be automatically approved.
172173
* @param {Function} hooks.setActiveNetwork - The callback to change the current network for the origin.
173174
* @param {Function} hooks.getCaveat - The callback to get the CAIP-25 caveat for the origin.
@@ -188,6 +189,7 @@ export async function switchChain(
188189
{
189190
origin,
190191
isAddFlow,
192+
isSwitchFlow,
191193
autoApprove,
192194
setActiveNetwork,
193195
getCaveat,
@@ -210,9 +212,16 @@ export async function switchChain(
210212
const ethChainIds = getPermittedEthChainIds(caip25Caveat.value);
211213

212214
if (!ethChainIds.includes(chainId)) {
215+
let metadata;
216+
if (isSwitchFlow) {
217+
metadata = {
218+
isSwitchEthereumChain: true,
219+
};
220+
}
213221
await requestPermittedChainsPermissionIncrementalForOrigin({
214222
chainId,
215223
autoApprove,
224+
metadata,
216225
});
217226
} else if (hasApprovalRequestsForOrigin?.() && !isAddFlow) {
218227
await requestUserApproval({

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

+13-3
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ import { Hex } from '@metamask/utils';
88
import * as EthChainUtils from './ethereum-chain-utils';
99

1010
describe('Ethereum Chain Utils', () => {
11-
const createMockedSwitchChain = () => {
11+
const createMockedSwitchChain = (mks = {}) => {
1212
const end = jest.fn();
1313
const mocks = {
1414
origin: 'www.test.com',
1515
isAddFlow: false,
16+
isSwitchFlow: false,
1617
autoApprove: false,
1718
setActiveNetwork: jest.fn(),
1819
getCaveat: jest.fn(),
@@ -23,6 +24,7 @@ describe('Ethereum Chain Utils', () => {
2324
hasApprovalRequestsForOrigin: jest.fn(),
2425
toNetworkConfiguration: {},
2526
fromNetworkConfiguration: {},
27+
...mks,
2628
};
2729
const response: { result?: true } = {};
2830
const switchChain = (chainId: Hex, networkClientId: string) =>
@@ -177,7 +179,9 @@ describe('Ethereum Chain Utils', () => {
177179

178180
describe('with an existing CAIP-25 permission granted from the multichain flow (isMultichainOrigin: true) and the chainId is not already permissioned', () => {
179181
it('requests permittedChains approval', async () => {
180-
const { mocks, switchChain } = createMockedSwitchChain();
182+
const { mocks, switchChain } = createMockedSwitchChain({
183+
isSwitchFlow: true,
184+
});
181185
mocks.requestPermittedChainsPermissionIncrementalForOrigin.mockRejectedValue(
182186
new Error(
183187
"Cannot switch to or add permissions for chainId '0x1' because permissions were granted over the Multichain API.",
@@ -194,7 +198,13 @@ describe('Ethereum Chain Utils', () => {
194198

195199
expect(
196200
mocks.requestPermittedChainsPermissionIncrementalForOrigin,
197-
).toHaveBeenCalledWith({ chainId: '0x1', autoApprove: false });
201+
).toHaveBeenCalledWith({
202+
chainId: '0x1',
203+
autoApprove: false,
204+
metadata: {
205+
isSwitchEthereumChain: true,
206+
},
207+
});
198208
});
199209

200210
it('does not switch the active network', async () => {

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

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ async function switchEthereumChainHandler(
7777

7878
return switchChain(res, end, chainId, networkClientIdToSwitchTo, {
7979
origin,
80+
isSwitchFlow: true,
8081
setActiveNetwork,
8182
getCaveat,
8283
requestPermittedChainsPermissionIncrementalForOrigin,

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

+1
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ describe('switchEthereumChainHandler', () => {
180180
},
181181
getCaveat: mocks.getCaveat,
182182
hasApprovalRequestsForOrigin: mocks.hasApprovalRequestsForOrigin,
183+
isSwitchFlow: true,
183184
origin: 'example.com',
184185
requestPermittedChainsPermissionIncrementalForOrigin:
185186
mocks.requestPermittedChainsPermissionIncrementalForOrigin,

app/scripts/metamask-controller.js

+7
Original file line numberDiff line numberDiff line change
@@ -5461,11 +5461,13 @@ export default class MetamaskController extends EventEmitter {
54615461
* @param {string} options.origin - The origin to request approval for.
54625462
* @param {Hex} options.chainId - The chainId to add to the existing permittedChains.
54635463
* @param {boolean} options.autoApprove - If the chain should be granted without prompting for user approval.
5464+
* @param {object} options.metadata - Request data for the approval.
54645465
*/
54655466
async requestPermittedChainsPermissionIncremental({
54665467
origin,
54675468
chainId,
54685469
autoApprove,
5470+
metadata,
54695471
}) {
54705472
if (isSnapId(origin)) {
54715473
throw new Error(
@@ -5483,6 +5485,10 @@ export default class MetamaskController extends EventEmitter {
54835485
);
54845486

54855487
if (!autoApprove) {
5488+
let options;
5489+
if (metadata) {
5490+
options = { metadata };
5491+
}
54865492
await this.permissionController.requestPermissionsIncremental(
54875493
{ origin },
54885494
{
@@ -5495,6 +5501,7 @@ export default class MetamaskController extends EventEmitter {
54955501
],
54965502
},
54975503
},
5504+
options,
54985505
);
54995506
return;
55005507
}

app/scripts/metamask-controller.test.js

+1
Original file line numberDiff line numberDiff line change
@@ -1534,6 +1534,7 @@ describe('MetaMaskController', () => {
15341534
).toHaveBeenCalledWith(
15351535
{ origin: 'test.com' },
15361536
expectedCaip25Permission,
1537+
undefined,
15371538
);
15381539
});
15391540

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import React from 'react';
2+
import { useI18nContext } from '../../../hooks/useI18nContext';
3+
import { useTemplateAlertContext } from '../../../pages/confirmations/confirmation/alerts/TemplateAlertContext';
4+
import { Icon, IconName, IconSize } from '../../component-library';
5+
import { PageContainerFooter } from '../../ui/page-container';
6+
7+
export const PermissionPageContainerFooter = ({
8+
cancelText,
9+
disabled,
10+
onCancel,
11+
onSubmit,
12+
}: {
13+
cancelText: string;
14+
disabled: boolean;
15+
onCancel: () => void;
16+
onSubmit: () => void;
17+
}) => {
18+
const t = useI18nContext();
19+
const { hasAlerts, showAlertsModal } = useTemplateAlertContext();
20+
21+
return (
22+
<PageContainerFooter
23+
footerClassName="permission-page-container-footer"
24+
cancelButtonType="default"
25+
onCancel={onCancel}
26+
cancelText={cancelText}
27+
onSubmit={hasAlerts ? showAlertsModal : onSubmit}
28+
submitText={t('confirm')}
29+
buttonSizeLarge={false}
30+
disabled={disabled}
31+
submitButtonIcon={
32+
hasAlerts ? <Icon name={IconName.Info} size={IconSize.Sm} /> : undefined
33+
}
34+
/>
35+
);
36+
};

ui/components/app/permission-page-container/permission-page-container.component.js

+8-8
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
import { Caip25EndowmentPermissionName } from '@metamask/multichain';
88
import { SubjectType } from '@metamask/permission-controller';
99
import { MetaMetricsEventCategory } from '../../../../shared/constants/metametrics';
10-
import { PageContainerFooter } from '../../ui/page-container';
1110
import PermissionsConnectFooter from '../permissions-connect-footer';
1211
import { RestrictedMethods } from '../../../../shared/constants/permissions';
1312

@@ -24,7 +23,9 @@ import {
2423
getRequestedCaip25CaveatValue,
2524
getCaip25PermissionsResponse,
2625
} from '../../../pages/permissions-connect/connect-page/utils';
26+
import { TemplateAlertContextProvider } from '../../../pages/confirmations/confirmation/alerts/TemplateAlertContext';
2727
import { containsEthPermissionsAndNonEvmAccount } from '../../../helpers/utils/permissions';
28+
import { PermissionPageContainerFooter } from './permission-page-container-footer.component';
2829
import { PermissionPageContainerContent } from '.';
2930

3031
export default class PermissionPageContainer extends Component {
@@ -211,7 +212,10 @@ export default class PermissionPageContainer extends Component {
211212
: this.context.t('back');
212213

213214
return (
214-
<>
215+
<TemplateAlertContextProvider
216+
onSubmit={() => this.onSubmit()}
217+
confirmationId={request?.metadata?.id}
218+
>
215219
{this.state.isShowingSnapsPrivacyWarning && (
216220
<SnapPrivacyWarning
217221
onAccepted={() => confirmSnapsPrivacyWarning()}
@@ -235,21 +239,17 @@ export default class PermissionPageContainer extends Component {
235239
{targetSubjectMetadata?.subjectType !== SubjectType.Snap && (
236240
<PermissionsConnectFooter />
237241
)}
238-
<PageContainerFooter
239-
footerClassName="permission-page-container-footer"
240-
cancelButtonType="default"
242+
<PermissionPageContainerFooter
241243
onCancel={() => this.onLeftFooterClick()}
242244
cancelText={footerLeftActionText}
243245
onSubmit={() => this.onSubmit()}
244-
submitText={this.context.t('confirm')}
245-
buttonSizeLarge={false}
246246
disabled={containsEthPermissionsAndNonEvmAccount(
247247
selectedAccounts,
248248
requestedPermissions,
249249
)}
250250
/>
251251
</Box>
252-
</>
252+
</TemplateAlertContextProvider>
253253
);
254254
}
255255
}

ui/components/app/toast-master/toast-master.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
SEND_ROUTE,
2626
SWAPS_ROUTE,
2727
PREPARE_SWAP_ROUTE,
28+
CROSS_CHAIN_SWAP_ROUTE,
2829
} from '../../../helpers/constants/routes';
2930
import { getURLHost } from '../../../helpers/utils/util';
3031
import { useI18nContext } from '../../../hooks/useI18nContext';
@@ -79,6 +80,8 @@ export function ToastMaster() {
7980
const onSwapsScreen =
8081
location.pathname === SWAPS_ROUTE ||
8182
location.pathname === PREPARE_SWAP_ROUTE;
83+
const onBridgeScreen =
84+
location.pathname === `${CROSS_CHAIN_SWAP_ROUTE}${PREPARE_SWAP_ROUTE}`;
8285

8386
if (onHomeScreen) {
8487
return (
@@ -99,7 +102,7 @@ export function ToastMaster() {
99102
);
100103
}
101104

102-
if (onSendScreen || onSwapsScreen) {
105+
if (onSendScreen || onSwapsScreen || onBridgeScreen) {
103106
return (
104107
<ToastContainer>
105108
<SwitchedNetworkToast />

ui/components/ui/page-container/page-container-footer/page-container-footer.component.js

+3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export default class PageContainerFooter extends Component {
1717
buttonSizeLarge: PropTypes.bool,
1818
footerClassName: PropTypes.string,
1919
footerButtonClassName: PropTypes.string,
20+
submitButtonIcon: PropTypes.string,
2021
};
2122

2223
static contextTypes = {
@@ -37,6 +38,7 @@ export default class PageContainerFooter extends Component {
3738
buttonSizeLarge = false,
3839
footerClassName,
3940
footerButtonClassName,
41+
submitButtonIcon,
4042
} = this.props;
4143

4244
return (
@@ -68,6 +70,7 @@ export default class PageContainerFooter extends Component {
6870
disabled={disabled}
6971
onClick={(e) => onSubmit(e)}
7072
data-testid="page-container-footer-next"
73+
icon={submitButtonIcon}
7174
>
7275
{submitText || this.context.t('next')}
7376
</Button>

ui/components/ui/page-container/page-container-footer/page-container-footer.component.test.js

+15
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import React from 'react';
22
import { fireEvent } from '@testing-library/react';
3+
34
import { renderWithProvider } from '../../../../../test/lib/render-helpers';
5+
import { Icon, IconName } from '../../../component-library';
46
import PageFooter from '.';
57

68
describe('Page Footer', () => {
@@ -72,5 +74,18 @@ describe('Page Footer', () => {
7274
console.log(submitButton.className);
7375
expect(submitButton.className).toContain('danger-primary');
7476
});
77+
78+
it('renders submitButtonIcon if passed', () => {
79+
const { getByTestId } = renderWithProvider(
80+
<PageFooter
81+
{...props}
82+
submitButtonIcon={
83+
<Icon name={IconName.Add} data-testid="icon-test-id" />
84+
}
85+
/>,
86+
);
87+
88+
expect(getByTestId('icon-test-id')).toBeInTheDocument();
89+
});
7590
});
7691
});

ui/pages/confirmations/confirmation/alerts/TemplateAlertContext.tsx

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
import { ApprovalRequest } from '@metamask/approval-controller';
21
import React, {
32
ReactElement,
43
createContext,
54
useCallback,
65
useContext,
76
useState,
87
} from 'react';
8+
import { useSelector } from 'react-redux';
99

1010
import useAlerts from '../../../../hooks/useAlerts';
1111
import { AlertActionHandlerProvider } from '../../../../components/app/alert-system/contexts/alertActionHandler';
1212
import { AlertMetricsProvider } from '../../../../components/app/alert-system/contexts/alertMetricsContext';
1313
import { MultipleAlertModal } from '../../../../components/app/alert-system/multiple-alert-modal';
14+
import { getMemoizedUnapprovedConfirmations } from '../../../../selectors';
1415
import { useTemplateConfirmationAlerts } from './useTemplateConfirmationAlerts';
1516
import { useAlertsActions } from './useAlertsActions';
1617

@@ -27,9 +28,16 @@ export const TemplateAlertContext = createContext<
2728

2829
export const TemplateAlertContextProvider: React.FC<{
2930
children: ReactElement;
30-
pendingConfirmation: ApprovalRequest<{ id: string }>;
31+
confirmationId: string;
3132
onSubmit: () => void;
32-
}> = ({ children, pendingConfirmation, onSubmit }) => {
33+
}> = ({ children, confirmationId, onSubmit }) => {
34+
const pendingConfirmations = useSelector(getMemoizedUnapprovedConfirmations);
35+
36+
const pendingConfirmation =
37+
pendingConfirmations?.find(
38+
(confirmation) => confirmation.id === confirmationId,
39+
) ?? pendingConfirmations[0];
40+
3341
const [isAlertsModalVisible, setAlertsModalVisible] = useState(false);
3442
const alertOwnerId = pendingConfirmation?.id;
3543
useTemplateConfirmationAlerts(pendingConfirmation);

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

+33
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,18 @@ const PENDING_APPROVAL_MOCK = {
2222
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2323
} as ApprovalRequest<any>;
2424

25+
const PERMISSION_MOCK = {
26+
id: 'testApprovalId',
27+
requestData: {
28+
testProperty: 'testValue',
29+
metadata: { isSwitchEthereumChain: true },
30+
},
31+
type: ApprovalType.WalletRequestPermissions,
32+
origin: 'https://metamask.github.io',
33+
// TODO: Replace `any` with type
34+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
35+
} as unknown as ApprovalRequest<any>;
36+
2537
const ADD_ETH_CHAIN_ALERT = [
2638
{
2739
actions: [
@@ -83,6 +95,27 @@ describe('useUpdateEthereumChainAlerts', () => {
8395
expect(result.current).toStrictEqual(SWITCH_ETH_CHAIN_ALERT);
8496
});
8597

98+
it('returns alert for permission request with isLegacySwitchEthereumChain if there are pending confirmations', () => {
99+
const { result } = renderHookWithProvider(
100+
() =>
101+
useUpdateEthereumChainAlerts({
102+
...PERMISSION_MOCK,
103+
type: ApprovalType.SwitchEthereumChain,
104+
}),
105+
{
106+
...state,
107+
metamask: {
108+
...state.metamask,
109+
pendingApprovals: {
110+
...state.metamask.pendingApprovals,
111+
[PERMISSION_MOCK.id]: PERMISSION_MOCK,
112+
},
113+
},
114+
},
115+
);
116+
expect(result.current).toStrictEqual(SWITCH_ETH_CHAIN_ALERT);
117+
});
118+
86119
it('does not returns alert if there are no pending confirmations', () => {
87120
const { result } = renderHookWithProvider(
88121
() => useUpdateEthereumChainAlerts(PENDING_APPROVAL_MOCK),

0 commit comments

Comments
 (0)