From 758d1480fed1f0496545f0a5fa23f095818d96c6 Mon Sep 17 00:00:00 2001 From: Owen Craston Date: Wed, 25 Sep 2024 16:52:41 -0700 Subject: [PATCH] scaffold remove snap keyring warning --- .../KeyringRemovalSnapWarning.tsx | 45 +++++++++++++++++ .../KeyringSnapRemovalWarning/index.ts | 1 + .../Views/Snaps/SnapSettings/SnapSettings.tsx | 48 +++++++++++++++++-- app/core/Engine.ts | 24 ++++++---- .../SnapKeyring/utils/getAccountsBySnapId.ts | 16 +++++++ ios/MetaMask.xcodeproj/project.pbxproj | 12 ++--- 6 files changed, 125 insertions(+), 21 deletions(-) create mode 100644 app/components/Views/Snaps/SnapSettings/KeyringSnapRemovalWarning/KeyringRemovalSnapWarning.tsx create mode 100644 app/components/Views/Snaps/SnapSettings/KeyringSnapRemovalWarning/index.ts create mode 100644 app/core/SnapKeyring/utils/getAccountsBySnapId.ts diff --git a/app/components/Views/Snaps/SnapSettings/KeyringSnapRemovalWarning/KeyringRemovalSnapWarning.tsx b/app/components/Views/Snaps/SnapSettings/KeyringSnapRemovalWarning/KeyringRemovalSnapWarning.tsx new file mode 100644 index 000000000000..ca189479676b --- /dev/null +++ b/app/components/Views/Snaps/SnapSettings/KeyringSnapRemovalWarning/KeyringRemovalSnapWarning.tsx @@ -0,0 +1,45 @@ +import React, { useEffect, useRef, useState } from 'react'; + +import { Snap } from '@metamask/snaps-utils'; +import BottomSheet, { BottomSheetRef } from 'app/component-library/components/BottomSheets/BottomSheet'; +import { selectProviderConfig } from 'app/selectors/networkController'; +import { useSelector } from 'react-redux'; +import Text, { TextVariant } from 'app/component-library/components/Texts/Text'; + +interface KeyringRemovalSnapWarningProps { + snap: Snap; + keyringAccounts: { name: string; address: string }[]; + onCancel: () => void; + onClose: () => void; + onSubmit: () => void; + onBack: () => void; +} + +export default function KeyringRemovalSnapWarning({ snap, + keyringAccounts, + onCancel, + onClose, + onSubmit, + onBack, +}: KeyringRemovalSnapWarningProps) { + + + const [showConfirmation, setShowConfirmation] = useState(false); + const [confirmedRemoval, setConfirmedRemoval] = useState(false); + const [confirmationInput, setConfirmationInput] = useState(''); + const [error, setError] = useState(false); + const { chainId } = useSelector(selectProviderConfig); + + const bottomSheetRef = useRef(null); + + useEffect(() => { + setShowConfirmation(keyringAccounts.length === 0); + }, [keyringAccounts]); + + return ( + + This is a warning; + + ); + +} diff --git a/app/components/Views/Snaps/SnapSettings/KeyringSnapRemovalWarning/index.ts b/app/components/Views/Snaps/SnapSettings/KeyringSnapRemovalWarning/index.ts new file mode 100644 index 000000000000..29541e40038e --- /dev/null +++ b/app/components/Views/Snaps/SnapSettings/KeyringSnapRemovalWarning/index.ts @@ -0,0 +1 @@ +export { default } from './KeyringRemovalSnapWarning'; diff --git a/app/components/Views/Snaps/SnapSettings/SnapSettings.tsx b/app/components/Views/Snaps/SnapSettings/SnapSettings.tsx index 19f739400ebb..baacd93773dc 100644 --- a/app/components/Views/Snaps/SnapSettings/SnapSettings.tsx +++ b/app/components/Views/Snaps/SnapSettings/SnapSettings.tsx @@ -1,5 +1,5 @@ ///: BEGIN:ONLY_INCLUDE_IF(external-snaps) -import React, { useCallback, useEffect } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { View, ScrollView, SafeAreaView } from 'react-native'; import Engine from '../../../../core/Engine'; @@ -28,6 +28,9 @@ import { useStyles } from '../../../hooks/useStyles'; import { useSelector } from 'react-redux'; import SNAP_SETTINGS_REMOVE_BUTTON from './SnapSettings.constants'; import { selectPermissionControllerState } from '../../../../selectors/snaps/permissionController'; +import KeyringRemovalSnapWarning from './KeyringSnapRemovalWarning/KeyringRemovalSnapWarning'; +import { getAccountsBySnapId } from '../../../../core/SnapKeyring/utils/getAccountsBySnapId'; +import { selectInternalAccounts } from 'app/selectors/accountsController'; interface SnapSettingsProps { snap: Snap; @@ -42,9 +45,15 @@ const SnapSettings = () => { const navigation = useNavigation(); const { snap } = useParams(); - const permissionsState = useSelector(selectPermissionControllerState); + + // eslint-disable-next-line no-unused-vars -- Main build does not use setKeyringAccounts + const [keyringAccounts, setKeyringAccounts] = useState([]); + ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) + const internalAccounts = useSelector(selectInternalAccounts); + ///: END:ONLY_INCLUDE_IF + // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any function getPermissionSubjects(state: any) { @@ -71,13 +80,29 @@ const SnapSettings = () => { }, [colors, navigation, snap.manifest.proposedName]); const removeSnap = useCallback(async () => { - // TODO: Replace "any" with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const { SnapController } = Engine.context as any; + const { SnapController } = Engine.context; await SnapController.removeSnap(snap.id); navigation.goBack(); }, [navigation, snap.id]); + + let isKeyringSnap = false; + ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) + isKeyringSnap = Boolean(permissionsFromController?.snap_manageAccounts); + + useEffect(() => { + if (isKeyringSnap) { + (async () => { + const addresses = await getAccountsBySnapId(snap.id); + const snapIdentities = Object.values(internalAccounts).filter( + (internalAccount) => + addresses.includes(internalAccount.address.toLowerCase()), + ); + setKeyringAccounts(snapIdentities); + })(); + } + }, [snap?.id, internalAccounts, isKeyringSnap]); + return ( @@ -115,6 +140,19 @@ const SnapSettings = () => { onPress={removeSnap} /> + + { + ///: BEGIN:ONLY_INCLUDE_IF(keyring-snaps) + { }} + snap={snap} + onBack={() => { }} + onCancel={() => { }} + onSubmit={() => { }} + keyringAccounts={keyringAccounts} + /> + ///: END:ONLY_INCLUDE_IF + } ); diff --git a/app/core/Engine.ts b/app/core/Engine.ts index 04b9a2cb2a68..bb8435e90dca 100644 --- a/app/core/Engine.ts +++ b/app/core/Engine.ts @@ -1363,7 +1363,7 @@ class Engine { return Boolean( hasProperty(showIncomingTransactions, currentChainId) && - showIncomingTransactions?.[currentHexChainId], + showIncomingTransactions?.[currentHexChainId], ); }, updateTransactions: true, @@ -1716,7 +1716,7 @@ class Engine { (state: NetworkState) => { if ( state.networksMetadata[state.selectedNetworkClientId].status === - NetworkStatus.Available && + NetworkStatus.Available && networkController.getNetworkClientById( networkController?.state.selectedNetworkClientId, ).configuration.chainId !== currentChainId @@ -1741,10 +1741,9 @@ class Engine { } catch (error) { console.error( error, - `Network ID not changed, current chainId: ${ - networkController.getNetworkClientById( - networkController?.state.selectedNetworkClientId, - ).configuration.chainId + `Network ID not changed, current chainId: ${networkController.getNetworkClientById( + networkController?.state.selectedNetworkClientId, + ).configuration.chainId }`, ); } @@ -1876,7 +1875,7 @@ class Engine { const decimalsToShow = (currentCurrency === 'usd' && 2) || undefined; if ( accountsByChainId?.[toHexadecimal(chainId)]?.[ - selectSelectedInternalAccountChecksummedAddress + selectSelectedInternalAccountChecksummedAddress ] ) { ethFiat = weiToFiatNumber( @@ -1908,9 +1907,9 @@ class Engine { item.balance || (item.address in tokenBalances ? renderFromTokenMinimalUnit( - tokenBalances[item.address], - item.decimals, - ) + tokenBalances[item.address], + item.decimals, + ) : undefined); const tokenBalanceFiat = balanceToFiatNumber( // TODO: Fix this by handling or eliminating the undefined case @@ -2327,4 +2326,9 @@ export default { assertEngineExists(instance); return instance.getGlobalEthQuery(); }, + + getSnapKeyring: () => { + assertEngineExists(instance); + return instance.getSnapKeyring(); + } }; diff --git a/app/core/SnapKeyring/utils/getAccountsBySnapId.ts b/app/core/SnapKeyring/utils/getAccountsBySnapId.ts new file mode 100644 index 000000000000..2563d0159394 --- /dev/null +++ b/app/core/SnapKeyring/utils/getAccountsBySnapId.ts @@ -0,0 +1,16 @@ +import { SnapKeyring } from '@metamask/eth-snap-keyring'; +import { SnapId } from '@metamask/snaps-sdk'; +import Engine from 'app/core/Engine'; + +/** + * Get the addresses of the accounts managed by a given Snap. + * + * @param snapId - Snap ID to get accounts for. + * @returns The addresses of the accounts. + */ +export const getAccountsBySnapId = async ( + snapId: SnapId, +) => { + const snapKeyring: SnapKeyring = await Engine.getSnapKeyring() as SnapKeyring; + return await snapKeyring.getAccountsBySnapId(snapId); +}; diff --git a/ios/MetaMask.xcodeproj/project.pbxproj b/ios/MetaMask.xcodeproj/project.pbxproj index 33f3d02a0fe4..71b5f0148352 100644 --- a/ios/MetaMask.xcodeproj/project.pbxproj +++ b/ios/MetaMask.xcodeproj/project.pbxproj @@ -9,7 +9,7 @@ /* Begin PBXBuildFile section */ 07CBADD9D4B441008304F8D3 /* EuclidCircularB-Light.otf in Resources */ = {isa = PBXBuildFile; fileRef = A98029A3662F4C1391489A6B /* EuclidCircularB-Light.otf */; }; 08B7A641467C4723B98328E9 /* CentraNo1-Medium.otf in Resources */ = {isa = PBXBuildFile; fileRef = F97653CAD1D04E1B8713C428 /* CentraNo1-Medium.otf */; }; - 0FD509E0336BF221F6527B24 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; }; + 0FD509E0336BF221F6527B24 /* (null) in Frameworks */ = {isa = PBXBuildFile; }; 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; @@ -134,7 +134,7 @@ B339FF2E289ABD70001B89FB /* EuclidCircularB-SemiboldItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = 9499B01ECAC44DA29AC44E80 /* EuclidCircularB-SemiboldItalic.otf */; }; B339FF32289ABD70001B89FB /* Branch.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 153F84C92319B8DB00C19B63 /* Branch.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; B339FF3C289ABF2C001B89FB /* MetaMask-QA-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B339FEA72899852C001B89FB /* MetaMask-QA-Info.plist */; }; - B638844E306CAE9147B52C85 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; }; + B638844E306CAE9147B52C85 /* (null) in Frameworks */ = {isa = PBXBuildFile; }; BF39E5BAE0F34F9091FF6AC0 /* EuclidCircularB-Semibold.otf in Resources */ = {isa = PBXBuildFile; fileRef = A8DE9C5BC0714D648276E123 /* EuclidCircularB-Semibold.otf */; }; CD13D926E1E84D9ABFE672C0 /* Roboto-BlackItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 3E2492C67CF345CABD7B8601 /* Roboto-BlackItalic.ttf */; }; CF9895772A3B49BE00B4C9B5 /* RCTMinimizer.m in Sources */ = {isa = PBXBuildFile; fileRef = CF9895762A3B49BE00B4C9B5 /* RCTMinimizer.m */; }; @@ -142,7 +142,7 @@ CF98DA9C28D9FEB700096782 /* RCTScreenshotDetect.m in Sources */ = {isa = PBXBuildFile; fileRef = CF98DA9B28D9FEB700096782 /* RCTScreenshotDetect.m */; }; CFD8DFC828EDD4C800CC75F6 /* RCTScreenshotDetect.m in Sources */ = {isa = PBXBuildFile; fileRef = CF98DA9B28D9FEB700096782 /* RCTScreenshotDetect.m */; }; D171C39A8BD44DBEB6B68480 /* EuclidCircularB-MediumItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = 42CBA652072F4BE2A8B815C1 /* EuclidCircularB-MediumItalic.otf */; }; - D45BF85DECACCB74EDCBE88A /* BuildFile in Frameworks */ = {isa = PBXBuildFile; }; + D45BF85DECACCB74EDCBE88A /* (null) in Frameworks */ = {isa = PBXBuildFile; }; D5BA0E32DFAA451781D5093E /* CentraNo1-BoldItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = 4560812198A247039A1CF5A5 /* CentraNo1-BoldItalic.otf */; }; DADE8F39CE81410A98B9B805 /* MMSans-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = 2EBD310362314C3ABFF40AD1 /* MMSans-Regular.otf */; }; DC6A024F56DD43E1A83B47B1 /* Roboto-MediumItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D5FF0FF1DFB74B3C8BB99E09 /* Roboto-MediumItalic.ttf */; }; @@ -337,9 +337,9 @@ 650F2B9D24DC5FF200C3B9C4 /* libRCTAesForked.a in Frameworks */, 153C1ABB2217BCDC0088EFE0 /* JavaScriptCore.framework in Frameworks */, 153F84CA2319B8FD00C19B63 /* Branch.framework in Frameworks */, - 0FD509E0336BF221F6527B24 /* BuildFile in Frameworks */, - D45BF85DECACCB74EDCBE88A /* BuildFile in Frameworks */, - B638844E306CAE9147B52C85 /* BuildFile in Frameworks */, + 0FD509E0336BF221F6527B24 /* (null) in Frameworks */, + D45BF85DECACCB74EDCBE88A /* (null) in Frameworks */, + B638844E306CAE9147B52C85 /* (null) in Frameworks */, ED2E8FE6D71BE9319F3B27D3 /* libPods-MetaMask.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0;