mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-22 09:23:21 +01:00
Approval flow for add & switch network (#19656)
This commit is contained in:
parent
48465432d6
commit
a2dfe8d113
@ -22,6 +22,8 @@ const addEthereumChain = {
|
||||
findNetworkConfigurationBy: true,
|
||||
setActiveNetwork: true,
|
||||
requestUserApproval: true,
|
||||
startApprovalFlow: true,
|
||||
endApprovalFlow: true,
|
||||
},
|
||||
};
|
||||
export default addEthereumChain;
|
||||
@ -38,6 +40,8 @@ async function addEthereumChainHandler(
|
||||
findNetworkConfigurationBy,
|
||||
setActiveNetwork,
|
||||
requestUserApproval,
|
||||
startApprovalFlow,
|
||||
endApprovalFlow,
|
||||
},
|
||||
) {
|
||||
if (!req.params?.[0] || typeof req.params[0] !== 'object') {
|
||||
@ -242,6 +246,9 @@ async function addEthereumChainHandler(
|
||||
);
|
||||
}
|
||||
let networkConfigurationId;
|
||||
|
||||
const { id: approvalFlowId } = await startApprovalFlow();
|
||||
|
||||
try {
|
||||
await requestUserApproval({
|
||||
origin,
|
||||
@ -269,6 +276,7 @@ async function addEthereumChainHandler(
|
||||
// Once the network has been added, the requested is considered successful
|
||||
res.result = null;
|
||||
} catch (error) {
|
||||
endApprovalFlow({ id: approvalFlowId });
|
||||
return end(error);
|
||||
}
|
||||
|
||||
@ -285,14 +293,24 @@ async function addEthereumChainHandler(
|
||||
networkConfigurationId,
|
||||
},
|
||||
});
|
||||
await setActiveNetwork(networkConfigurationId);
|
||||
} catch (error) {
|
||||
// For the purposes of this method, it does not matter if the user
|
||||
// declines to switch the selected network. However, other errors indicate
|
||||
// that something is wrong.
|
||||
if (error.code !== errorCodes.provider.userRejectedRequest) {
|
||||
return end(error);
|
||||
}
|
||||
return end(
|
||||
error.code === errorCodes.provider.userRejectedRequest
|
||||
? undefined
|
||||
: error,
|
||||
);
|
||||
} finally {
|
||||
endApprovalFlow({ id: approvalFlowId });
|
||||
}
|
||||
|
||||
try {
|
||||
await setActiveNetwork(networkConfigurationId);
|
||||
} catch (error) {
|
||||
return end(error);
|
||||
}
|
||||
|
||||
return end();
|
||||
}
|
||||
|
@ -3909,6 +3909,16 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.approvalController.addAndShowApprovalRequest.bind(
|
||||
this.approvalController,
|
||||
),
|
||||
startApprovalFlow: this.approvalController.startFlow.bind(
|
||||
this.approvalController,
|
||||
),
|
||||
endApprovalFlow: this.approvalController.endFlow.bind(
|
||||
this.approvalController,
|
||||
),
|
||||
setApprovalFlowLoadingText:
|
||||
this.approvalController.setFlowLoadingText.bind(
|
||||
this.approvalController,
|
||||
),
|
||||
sendMetrics: this.metaMetricsController.trackEvent.bind(
|
||||
this.metaMetricsController,
|
||||
),
|
||||
|
@ -100,7 +100,7 @@
|
||||
"resolutions": {
|
||||
"@babel/core": "patch:@babel/core@npm%3A7.21.5#./.yarn/patches/@babel-core-npm-7.21.5-c72c337956.patch",
|
||||
"@babel/runtime": "patch:@babel/runtime@npm%3A7.18.9#./.yarn/patches/@babel-runtime-npm-7.18.9-28ca6b5f61.patch",
|
||||
"@metamask/approval-controller": "^3.0.0",
|
||||
"@metamask/approval-controller": "^3.3.0",
|
||||
"@types/react": "^16.9.53",
|
||||
"analytics-node/axios": "^0.21.2",
|
||||
"ganache-core/lodash": "^4.17.21",
|
||||
@ -224,7 +224,7 @@
|
||||
"@metamask-institutional/transaction-update": "^0.1.21",
|
||||
"@metamask/address-book-controller": "^3.0.0",
|
||||
"@metamask/announcement-controller": "^4.0.0",
|
||||
"@metamask/approval-controller": "^3.1.0",
|
||||
"@metamask/approval-controller": "^3.3.0",
|
||||
"@metamask/assets-controllers": "^9.2.0",
|
||||
"@metamask/base-controller": "^3.0.0",
|
||||
"@metamask/browser-passworder": "^4.1.0",
|
||||
|
@ -32,11 +32,14 @@ import {
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
getUnapprovedTemplatedConfirmations,
|
||||
getUnapprovedTxCount,
|
||||
getApprovalFlows,
|
||||
getTotalUnapprovedCount,
|
||||
} from '../../selectors';
|
||||
import NetworkDisplay from '../../components/app/network-display/network-display';
|
||||
import Callout from '../../components/ui/callout';
|
||||
import SiteOrigin from '../../components/ui/site-origin';
|
||||
import { Icon, IconName } from '../../components/component-library';
|
||||
import Loading from '../../components/ui/loading-screen';
|
||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||
import SnapAuthorshipHeader from '../../components/app/snaps/snap-authorship-header';
|
||||
import { getSnapName } from '../../helpers/utils/util';
|
||||
@ -176,6 +179,9 @@ export default function ConfirmationPage({
|
||||
isEqual,
|
||||
);
|
||||
const unapprovedTxsCount = useSelector(getUnapprovedTxCount);
|
||||
const approvalFlows = useSelector(getApprovalFlows, isEqual);
|
||||
const totalUnapprovedCount = useSelector(getTotalUnapprovedCount);
|
||||
const [approvalFlowLoadingText, setApprovalFlowLoadingText] = useState(null);
|
||||
const [currentPendingConfirmation, setCurrentPendingConfirmation] =
|
||||
useState(0);
|
||||
const pendingConfirmation = pendingConfirmations[currentPendingConfirmation];
|
||||
@ -256,20 +262,36 @@ export default function ConfirmationPage({
|
||||
// viewed index, reset the index.
|
||||
if (
|
||||
pendingConfirmations.length === 0 &&
|
||||
(approvalFlows.length === 0 || totalUnapprovedCount !== 0) &&
|
||||
redirectToHomeOnZeroConfirmations
|
||||
) {
|
||||
history.push(DEFAULT_ROUTE);
|
||||
} else if (pendingConfirmations.length <= currentPendingConfirmation) {
|
||||
} else if (
|
||||
pendingConfirmations.length &&
|
||||
pendingConfirmations.length <= currentPendingConfirmation
|
||||
) {
|
||||
setCurrentPendingConfirmation(pendingConfirmations.length - 1);
|
||||
}
|
||||
}, [
|
||||
pendingConfirmations,
|
||||
approvalFlows,
|
||||
totalUnapprovedCount,
|
||||
history,
|
||||
currentPendingConfirmation,
|
||||
redirectToHomeOnZeroConfirmations,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const childFlow = approvalFlows[approvalFlows.length - 1];
|
||||
|
||||
setApprovalFlowLoadingText(childFlow?.loadingText ?? null);
|
||||
}, [approvalFlows]);
|
||||
|
||||
if (!pendingConfirmation) {
|
||||
if (approvalFlows.length > 0) {
|
||||
return <Loading loadingMessage={approvalFlowLoadingText} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -33,6 +33,7 @@ const mockBaseStore = {
|
||||
pendingApprovals: {
|
||||
[mockApprovalId]: mockApproval,
|
||||
},
|
||||
approvalFlows: [{ id: mockApprovalId, loadingText: null }],
|
||||
subjectMetadata: {},
|
||||
providerConfig: {
|
||||
type: 'rpc',
|
||||
|
@ -33,6 +33,7 @@ const mockBaseStore = {
|
||||
pendingApprovals: {
|
||||
[mockApprovalId]: mockApproval,
|
||||
},
|
||||
approvalFlows: [],
|
||||
subjectMetadata: {},
|
||||
providerConfig: {
|
||||
type: 'rpc',
|
||||
|
@ -84,6 +84,7 @@ import FlaskHomeFooter from './flask/flask-home-footer.component';
|
||||
function shouldCloseNotificationPopup({
|
||||
isNotification,
|
||||
totalUnapprovedCount,
|
||||
hasApprovalFlows,
|
||||
isSigningQRHardwareTransaction,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||
waitForConfirmDeepLinkDialog,
|
||||
@ -93,6 +94,7 @@ function shouldCloseNotificationPopup({
|
||||
let shouldCLose =
|
||||
isNotification &&
|
||||
totalUnapprovedCount === 0 &&
|
||||
!hasApprovalFlows &&
|
||||
!isSigningQRHardwareTransaction;
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||
@ -139,6 +141,7 @@ export default class Home extends PureComponent {
|
||||
originOfCurrentTab: PropTypes.string,
|
||||
disableWeb3ShimUsageAlert: PropTypes.func.isRequired,
|
||||
pendingConfirmations: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
hasApprovalFlows: PropTypes.bool.isRequired,
|
||||
infuraBlocked: PropTypes.bool.isRequired,
|
||||
showWhatsNewPopup: PropTypes.bool.isRequired,
|
||||
hideWhatsNewPopup: PropTypes.func.isRequired,
|
||||
@ -287,6 +290,7 @@ export default class Home extends PureComponent {
|
||||
showAwaitingSwapScreen,
|
||||
swapsFetchParams,
|
||||
pendingConfirmations,
|
||||
hasApprovalFlows,
|
||||
} = this.props;
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||
@ -307,7 +311,7 @@ export default class Home extends PureComponent {
|
||||
history.push(CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE);
|
||||
} else if (hasWatchNftPendingApprovals) {
|
||||
history.push(CONFIRM_ADD_SUGGESTED_NFT_ROUTE);
|
||||
} else if (pendingConfirmations.length > 0) {
|
||||
} else if (pendingConfirmations.length > 0 || hasApprovalFlows) {
|
||||
history.push(CONFIRMATION_V_NEXT_ROUTE);
|
||||
}
|
||||
///: BEGIN:ONLY_INCLUDE_IN(build-mmi)
|
||||
|
@ -36,6 +36,7 @@ import {
|
||||
getRemoveNftMessage,
|
||||
getSuggestedTokens,
|
||||
getSuggestedNfts,
|
||||
getApprovalFlows,
|
||||
} from '../../selectors';
|
||||
|
||||
import {
|
||||
@ -137,6 +138,7 @@ const mapStateToProps = (state) => {
|
||||
selectedAddress,
|
||||
firstPermissionsRequestId,
|
||||
totalUnapprovedCount,
|
||||
hasApprovalFlows: getApprovalFlows(state).length > 0,
|
||||
connectedStatusPopoverHasBeenShown,
|
||||
defaultHomeActiveTabName,
|
||||
firstTimeFlowType,
|
||||
|
@ -106,6 +106,7 @@ describe('Routes Component', () => {
|
||||
swapsFeatureIsLive: true,
|
||||
},
|
||||
pendingApprovals: {},
|
||||
approvalFlows: [],
|
||||
announcements: {},
|
||||
},
|
||||
send: {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ApprovalType } from '@metamask/controller-utils';
|
||||
import { hasPendingApprovals } from './approvals';
|
||||
import { getApprovalFlows, hasPendingApprovals } from './approvals';
|
||||
|
||||
describe('approval selectors', () => {
|
||||
const mockedState = {
|
||||
@ -13,6 +13,7 @@ describe('approval selectors', () => {
|
||||
type: ApprovalType.WatchAsset,
|
||||
requestData: {},
|
||||
requestState: null,
|
||||
expectsResult: false,
|
||||
},
|
||||
'2': {
|
||||
id: '2',
|
||||
@ -21,13 +22,19 @@ describe('approval selectors', () => {
|
||||
type: ApprovalType.Transaction,
|
||||
requestData: {},
|
||||
requestState: null,
|
||||
expectsResult: false,
|
||||
},
|
||||
},
|
||||
unapprovedTxs: {
|
||||
'2': {
|
||||
approvalFlows: [
|
||||
{
|
||||
id: '1',
|
||||
loadingText: 'loadingText1',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
loadingText: 'loadingText2',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
@ -47,4 +54,12 @@ describe('approval selectors', () => {
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getApprovalFlows', () => {
|
||||
it('should return existing approval flows', () => {
|
||||
const result = getApprovalFlows(mockedState);
|
||||
|
||||
expect(result).toStrictEqual(mockedState.metamask.approvalFlows);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,13 +1,10 @@
|
||||
import { ApprovalControllerState } from '@metamask/approval-controller';
|
||||
import { ApprovalType } from '@metamask/controller-utils';
|
||||
import { TransactionMeta } from '../../shared/constants/transaction';
|
||||
|
||||
type ApprovalsMetaMaskState = {
|
||||
metamask: {
|
||||
pendingApprovals: ApprovalControllerState['pendingApprovals'];
|
||||
unapprovedTxs: {
|
||||
[transactionId: string]: TransactionMeta;
|
||||
};
|
||||
approvalFlows: ApprovalControllerState['approvalFlows'];
|
||||
};
|
||||
};
|
||||
|
||||
@ -46,3 +43,7 @@ export const getApprovalRequestsByType = (
|
||||
|
||||
return pendingApprovalRequests;
|
||||
};
|
||||
|
||||
export function getApprovalFlows(state: ApprovalsMetaMaskState) {
|
||||
return state.metamask.approvalFlows;
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ import {
|
||||
getPermittedAccountsForCurrentTab,
|
||||
getSelectedAddress,
|
||||
hasTransactionPendingApprovals,
|
||||
getApprovalFlows,
|
||||
///: BEGIN:ONLY_INCLUDE_IN(snaps)
|
||||
getNotifications,
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
@ -2382,9 +2383,12 @@ export function closeCurrentNotificationWindow(): ThunkAction<
|
||||
AnyAction
|
||||
> {
|
||||
return (_, getState) => {
|
||||
const state = getState();
|
||||
const approvalFlows = getApprovalFlows(state);
|
||||
if (
|
||||
getEnvironmentType() === ENVIRONMENT_TYPE_NOTIFICATION &&
|
||||
!hasTransactionPendingApprovals(getState())
|
||||
!hasTransactionPendingApprovals(state) &&
|
||||
approvalFlows.length === 0
|
||||
) {
|
||||
closeNotificationPopup();
|
||||
}
|
||||
|
@ -69,6 +69,7 @@ interface TemporaryBackgroundState {
|
||||
networkId: string | null;
|
||||
networkStatus: NetworkStatus;
|
||||
pendingApprovals: ApprovalControllerState['pendingApprovals'];
|
||||
approvalFlows: ApprovalControllerState['approvalFlows'];
|
||||
knownMethodData?: {
|
||||
[fourBytePrefix: string]: Record<string, unknown>;
|
||||
};
|
||||
|
10
yarn.lock
10
yarn.lock
@ -3857,16 +3857,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@metamask/approval-controller@npm:^3.0.0":
|
||||
version: 3.1.0
|
||||
resolution: "@metamask/approval-controller@npm:3.1.0"
|
||||
"@metamask/approval-controller@npm:^3.3.0":
|
||||
version: 3.3.0
|
||||
resolution: "@metamask/approval-controller@npm:3.3.0"
|
||||
dependencies:
|
||||
"@metamask/base-controller": ^3.0.0
|
||||
"@metamask/utils": ^5.0.2
|
||||
eth-rpc-errors: ^4.0.2
|
||||
immer: ^9.0.6
|
||||
nanoid: ^3.1.31
|
||||
checksum: 2043e62e8815a600e839617b4df26515fc33f655e21562dc230cd6dbfc4677e955e1e45a5df8fbb2def2122b3578f6a632acb939f8175419febb1471d0c48ce0
|
||||
checksum: 1fa6111a897d6f4aa369fd1a669fb5e16558277019f9d6c61449aea0d7b2672a62c189e5b3d9e84ab6e4d5826932c78d2bdb0f05aafb184a4ff07903c46abf2c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -24482,7 +24482,7 @@ __metadata:
|
||||
"@metamask-institutional/transaction-update": ^0.1.21
|
||||
"@metamask/address-book-controller": ^3.0.0
|
||||
"@metamask/announcement-controller": ^4.0.0
|
||||
"@metamask/approval-controller": ^3.1.0
|
||||
"@metamask/approval-controller": ^3.3.0
|
||||
"@metamask/assets-controllers": ^9.2.0
|
||||
"@metamask/auto-changelog": ^2.1.0
|
||||
"@metamask/base-controller": ^3.0.0
|
||||
|
Loading…
Reference in New Issue
Block a user