diff --git a/.ruby-version b/.ruby-version
index b5021469305..75a22a26ac4 100644
--- a/.ruby-version
+++ b/.ruby-version
@@ -1 +1 @@
-3.0.2
+3.0.3
diff --git a/android/.project b/android/.project
index 3cf8618bf4c..1efd2443546 100644
--- a/android/.project
+++ b/android/.project
@@ -14,4 +14,15 @@
org.eclipse.buildship.core.gradleprojectnature
+
+
+ 1684448150858
+
+ 30
+
+ org.eclipse.core.resources.regexFilterMatcher
+ node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__
+
+
+
diff --git a/android/.settings/org.eclipse.buildship.core.prefs b/android/.settings/org.eclipse.buildship.core.prefs
index e8895216fd3..e479558406c 100644
--- a/android/.settings/org.eclipse.buildship.core.prefs
+++ b/android/.settings/org.eclipse.buildship.core.prefs
@@ -1,2 +1,13 @@
+arguments=
+auto.sync=false
+build.scans.enabled=false
+connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
connection.project.dir=
eclipse.preferences.version=1
+gradle.user.home=
+java.home=
+jvm.arguments=
+offline.mode=false
+override.workspace.settings=false
+show.console.view=false
+show.executions.view=false
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 998c8114719..4e2abb24ca9 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -202,6 +202,12 @@ android {
storePassword 'android'
keyAlias 'androiddebugkey'
keyPassword 'android'
+ }
+ flask {
+ storeFile file('../keystores/flaskRelease.keystore')
+ storePassword System.getenv("BITRISEIO_ANDROID_FLASK_KEYSTORE_PASSWORD")
+ keyAlias System.getenv("BITRISEIO_ANDROID_FLASK_KEYSTORE_ALIAS")
+ keyPassword System.getenv("BITRISEIO_ANDROID_FLASK_KEYSTORE_PRIVATE_KEY_PASSWORD")
}
}
@@ -246,6 +252,14 @@ android {
applicationId "io.metamask"
signingConfig signingConfigs.release
}
+ flask {
+ dimension "version"
+ versionName "0.0.3"
+ versionCode 1128
+ applicationIdSuffix ".flask"
+ applicationId "io.metamask"
+ signingConfig signingConfigs.flask
+ }
}
buildTypes.each {
@@ -288,6 +302,10 @@ dependencies {
androidTestImplementation 'junit:junit:4.12'
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
implementation "com.android.installreferrer:installreferrer:2.2"
+ implementation 'org.apache.commons:commons-compress:1.22'
+ androidTestImplementation 'org.mockito:mockito-android:4.2.0'
+ androidTestImplementation 'androidx.test:core:1.5.0'
+ androidTestImplementation 'androidx.test:core-ktx:1.5.0'
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
exclude group:'com.facebook.fbjni'
diff --git a/android/app/src/androidTest/java/com/metamask/nativeModules/RNTarTest/RNTarTest.java b/android/app/src/androidTest/java/com/metamask/nativeModules/RNTarTest/RNTarTest.java
new file mode 100644
index 00000000000..710854be4ca
--- /dev/null
+++ b/android/app/src/androidTest/java/com/metamask/nativeModules/RNTarTest/RNTarTest.java
@@ -0,0 +1,52 @@
+package com.metamask.nativeModules.RNTarTest;
+
+import androidx.test.core.app.ApplicationProvider;
+import com.facebook.react.bridge.Promise;
+import com.facebook.react.bridge.ReactApplicationContext;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import java.nio.file.StandardCopyOption;
+
+import io.metamask.nativeModules.RNTar.RNTar;
+
+@RunWith(JUnit4.class)
+public class RNTarTest {
+ private RNTar tar;
+ private ReactApplicationContext reactContext;
+ private Promise promise;
+
+ @Before
+ public void setUp() {
+ reactContext = new ReactApplicationContext(ApplicationProvider.getApplicationContext());
+ tar = new RNTar(reactContext);
+ promise = mock(Promise.class);
+ }
+
+ @Test
+ public void testUnTar_validTgzFile() throws IOException {
+ // Prepare a sample .tgz file
+ InputStream tgzResource = Thread.currentThread().getContextClassLoader().getResourceAsStream("validTgzFile.tgz");
+ try {
+ File tgzFile = new File(reactContext.getCacheDir(), "validTgzFile.tgz");
+ Files.copy(tgzResource, tgzFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ String outputPath = reactContext.getCacheDir().getAbsolutePath() + "/output";
+ // Call unTar method
+ tar.unTar(tgzFile.getAbsolutePath(), outputPath, promise);
+ // Verify the promise was resolved
+ Path expectedDecompressedPath = Paths.get(outputPath, "package");
+ verify(promise).resolve(expectedDecompressedPath.toString());
+ } finally {
+ tgzResource.close();
+ }
+ }
+}
diff --git a/android/app/src/androidTest/resources/validTgzFile.tgz b/android/app/src/androidTest/resources/validTgzFile.tgz
new file mode 100644
index 00000000000..c68944080bf
Binary files /dev/null and b/android/app/src/androidTest/resources/validTgzFile.tgz differ
diff --git a/android/app/src/flask/res/ic_launcher-playstore.png b/android/app/src/flask/res/ic_launcher-playstore.png
new file mode 100644
index 00000000000..e25f87835cf
Binary files /dev/null and b/android/app/src/flask/res/ic_launcher-playstore.png differ
diff --git a/android/app/src/flask/res/mipmap-hdpi/ic_launcher.png b/android/app/src/flask/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000000..cc78c59ed29
Binary files /dev/null and b/android/app/src/flask/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/android/app/src/flask/res/mipmap-hdpi/ic_launcher_foreground.png b/android/app/src/flask/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000000..66375b3c3c8
Binary files /dev/null and b/android/app/src/flask/res/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/flask/res/mipmap-hdpi/ic_launcher_round.png b/android/app/src/flask/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 00000000000..890cd7b4e02
Binary files /dev/null and b/android/app/src/flask/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/android/app/src/flask/res/mipmap-mdpi/ic_launcher.png b/android/app/src/flask/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000000..5fb9067a09b
Binary files /dev/null and b/android/app/src/flask/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/android/app/src/flask/res/mipmap-mdpi/ic_launcher_foreground.png b/android/app/src/flask/res/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000000..fba595c81da
Binary files /dev/null and b/android/app/src/flask/res/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/flask/res/mipmap-mdpi/ic_launcher_round.png b/android/app/src/flask/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 00000000000..13963b80f64
Binary files /dev/null and b/android/app/src/flask/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/android/app/src/flask/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/flask/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000000..322085a030f
Binary files /dev/null and b/android/app/src/flask/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/android/app/src/flask/res/mipmap-xhdpi/ic_launcher_foreground.png b/android/app/src/flask/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000000..34fc0b75cd6
Binary files /dev/null and b/android/app/src/flask/res/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/flask/res/mipmap-xhdpi/ic_launcher_round.png b/android/app/src/flask/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 00000000000..83dded7f3d4
Binary files /dev/null and b/android/app/src/flask/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/android/app/src/flask/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/flask/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000000..a6d7b9dc6a2
Binary files /dev/null and b/android/app/src/flask/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/android/app/src/flask/res/mipmap-xxhdpi/ic_launcher_foreground.png b/android/app/src/flask/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000000..6424bfea2ff
Binary files /dev/null and b/android/app/src/flask/res/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/flask/res/mipmap-xxhdpi/ic_launcher_round.png b/android/app/src/flask/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000000..702f488f15f
Binary files /dev/null and b/android/app/src/flask/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/android/app/src/flask/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/flask/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000000..09319a8823a
Binary files /dev/null and b/android/app/src/flask/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/android/app/src/flask/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/android/app/src/flask/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000000..37e20fd5424
Binary files /dev/null and b/android/app/src/flask/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/flask/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/app/src/flask/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000000..b591d53a15a
Binary files /dev/null and b/android/app/src/flask/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/android/app/src/flask/res/values/strings.xml b/android/app/src/flask/res/values/strings.xml
new file mode 100644
index 00000000000..94ca908ad97
--- /dev/null
+++ b/android/app/src/flask/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ MetaMask Flask
+
diff --git a/android/app/src/main/java/io/metamask/MainApplication.java b/android/app/src/main/java/io/metamask/MainApplication.java
index fbeafe10a93..e5e9b2e1c3a 100644
--- a/android/app/src/main/java/io/metamask/MainApplication.java
+++ b/android/app/src/main/java/io/metamask/MainApplication.java
@@ -1,7 +1,6 @@
package io.metamask;
import com.facebook.react.ReactApplication;
-import com.cmcewen.blurview.BlurViewPackage;
import com.brentvatne.react.ReactVideoPackage;
import android.content.Context;
import com.facebook.react.PackageList;
@@ -25,6 +24,7 @@
import java.lang.reflect.Field;
import com.facebook.react.bridge.JSIModulePackage;
import com.swmansion.reanimated.ReanimatedJSIModulePackage;
+import io.metamask.nativeModules.RNTar.RNTarPackage;
public class MainApplication extends MultiDexApplication implements ShareApplication, ReactApplication {
@@ -43,7 +43,7 @@ protected List getPackages() {
packages.add(new RCTAnalyticsPackage());
packages.add(new PreventScreenshotPackage());
packages.add(new ReactVideoPackage());
-
+ packages.add(new RNTarPackage());
return packages;
}
diff --git a/android/app/src/main/java/io/metamask/nativeModules/RNTar/RNTar.java b/android/app/src/main/java/io/metamask/nativeModules/RNTar/RNTar.java
new file mode 100644
index 00000000000..1c5a4fbf297
--- /dev/null
+++ b/android/app/src/main/java/io/metamask/nativeModules/RNTar/RNTar.java
@@ -0,0 +1,138 @@
+package io.metamask.nativeModules.RNTar;
+
+import androidx.annotation.NonNull;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.ReactContextBaseJavaModule;
+import com.facebook.react.bridge.ReactMethod;
+import android.util.Log;
+import com.facebook.react.bridge.Promise;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.zip.GZIPInputStream;
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.Path;
+import android.os.Build;
+
+public class RNTar extends ReactContextBaseJavaModule {
+ private static String MODULE_NAME = "RNTar";
+
+ public RNTar(ReactApplicationContext context) {
+ super(context);
+ }
+
+ @NonNull
+ @Override
+ public String getName() {
+ return MODULE_NAME;
+ }
+
+ private void createDirectories(String path) throws IOException {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ Files.createDirectories(Paths.get(path));
+ } else {
+ File dir = new File(path);
+ if (!dir.exists()) {
+ dir.mkdirs();
+ }
+ }
+ }
+
+ private boolean isReadableWritable(String path) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ Path dirPath = Paths.get(path);
+ return Files.isReadable(dirPath) && Files.isWritable(dirPath);
+ } else {
+ File dir = new File(path);
+ return dir.canRead() && dir.canWrite();
+ }
+ }
+
+ private boolean exists(String path) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ return Files.exists(Paths.get(path));
+ } else {
+ return new File(path).exists();
+ }
+ }
+
+ private String extractTgzFile(String tgzPath, String outputPath) throws IOException {
+ try {
+ // Check if .tgz file exists
+ if (!exists(tgzPath)) {
+ throw new IOException("The specified .tgz file does not exist.");
+ }
+
+ // Create output directory if it doesn't exist
+ createDirectories(outputPath);
+
+ // Check if the output directory is readable and writable
+ if (!isReadableWritable(outputPath)) {
+ throw new IOException("The output directory is not readable and/or writable.");
+ }
+
+ // Set up the input streams for reading the .tgz file
+ try (FileInputStream fileInputStream = new FileInputStream(tgzPath);
+ GZIPInputStream gzipInputStream = new GZIPInputStream(fileInputStream);
+ TarArchiveInputStream tarInputStream = new TarArchiveInputStream(new BufferedInputStream(gzipInputStream))) {
+
+ TarArchiveEntry entry;
+
+ // Loop through the entries in the .tgz file
+ while ((entry = (TarArchiveEntry) tarInputStream.getNextEntry()) != null) {
+ File outputFile = new File(outputPath, entry.getName());
+
+ // If it is a directory, create the output directory
+ if (entry.isDirectory()) {
+ createDirectories(outputFile.getAbsolutePath());
+ } else {
+ // Create parent directories if they don't exist
+ createDirectories(outputFile.getParent());
+
+ // Set up the output streams for writing the file
+ try (FileOutputStream fos = new FileOutputStream(outputFile);
+ BufferedWriter dest = new BufferedWriter(new OutputStreamWriter(fos, StandardCharsets.UTF_8))) {
+
+ // Set up a BufferedReader for reading the file from the .tgz file
+ BufferedReader tarReader = new BufferedReader(new InputStreamReader(tarInputStream, StandardCharsets.UTF_8));
+
+ // Read the file line by line and convert line endings to the system default
+ String line;
+ while ((line = tarReader.readLine()) != null) {
+ dest.write(line);
+ dest.newLine();
+ }
+ }
+ }
+ }
+ }
+ // Return the output directory path
+ return new File(outputPath, "package").getAbsolutePath();
+ } catch (IOException e) {
+ Log.e("DecompressTgzFile", "Error decompressing tgz file", e);
+ throw new IOException("Error decompressing tgz file: " + e.getMessage(), e);
+ }
+ }
+
+ @ReactMethod
+ public void unTar(String pathToRead, String pathToWrite, final Promise promise) {
+ Log.d(MODULE_NAME, "Create event called with name: " + pathToRead
+ + " and location: " + pathToWrite);
+ try {
+ String decompressedPath = extractTgzFile(pathToRead, pathToWrite);
+ promise.resolve(decompressedPath);
+ } catch(Exception e) {
+ promise.reject("Error uncompressing file:", e);
+ }
+ }
+}
diff --git a/android/app/src/main/java/io/metamask/nativeModules/RNTar/RNTarPackage.java b/android/app/src/main/java/io/metamask/nativeModules/RNTar/RNTarPackage.java
new file mode 100644
index 00000000000..b121a633631
--- /dev/null
+++ b/android/app/src/main/java/io/metamask/nativeModules/RNTar/RNTarPackage.java
@@ -0,0 +1,28 @@
+package io.metamask.nativeModules.RNTar;
+import com.facebook.react.ReactPackage;
+import com.facebook.react.bridge.NativeModule;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.uimanager.ViewManager;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class RNTarPackage implements ReactPackage {
+
+ @Override
+ public List createViewManagers(ReactApplicationContext reactContext) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List createNativeModules(
+ ReactApplicationContext reactContext) {
+ List modules = new ArrayList<>();
+
+ modules.add(new RNTar(reactContext));
+
+ return modules;
+ }
+
+}
diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
index 4ae7d12378f..64c9c4a3bbe 100644
--- a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -1,5 +1,4 @@
-
-
\ No newline at end of file
+
diff --git a/app/components/Nav/App/index.js b/app/components/Nav/App/index.js
index e7dc3164eda..2a7aa50bd54 100644
--- a/app/components/Nav/App/index.js
+++ b/app/components/Nav/App/index.js
@@ -32,7 +32,7 @@ import branch from 'react-native-branch';
import AppConstants from '../../../core/AppConstants';
import Logger from '../../../util/Logger';
import { trackErrorAsAnalytics } from '../../../util/analyticsV2';
-import { routingInstrumentation } from '../../../util/sentryUtils';
+import { routingInstrumentation } from '../../../util/sentry/sentryUtils';
import Analytics from '../../../core/Analytics/Analytics';
import { connect, useSelector, useDispatch } from 'react-redux';
import {
diff --git a/app/components/Nav/Main/MainNavigator.js b/app/components/Nav/Main/MainNavigator.js
index a5f1488be7c..9311e8e8c7f 100644
--- a/app/components/Nav/Main/MainNavigator.js
+++ b/app/components/Nav/Main/MainNavigator.js
@@ -59,6 +59,8 @@ import { colors as importedColors } from '../../../styles/common';
import OrderDetails from '../../UI/FiatOnRampAggregator/Views/OrderDetails';
import TabBar from '../../../component-library/components/Navigation/TabBar';
import BrowserUrlModal from '../../Views/BrowserUrlModal';
+import { SnapsSettingsList } from '../../Views/Snaps/SnapsSettingsList';
+import { SnapSettings } from '../../Views/Snaps/SnapSettings';
import Routes from '../../../constants/navigation/Routes';
import AnalyticsV2 from '../../../util/analyticsV2';
import { MetaMetricsEvents } from '../../../core/Analytics';
@@ -198,6 +200,21 @@ const BrowserFlow = () => (
export const DrawerContext = React.createContext({ drawerRef: null });
+const SnapsSettingsStack = () => (
+
+
+
+
+);
+
const SettingsFlow = () => (
(
component={EnterPasswordSimple}
options={EnterPasswordSimple.navigationOptions}
/>
+
);
diff --git a/app/components/Nav/Main/RootRPCMethodsUI.js b/app/components/Nav/Main/RootRPCMethodsUI.js
index daf884a945c..a4707d43422 100644
--- a/app/components/Nav/Main/RootRPCMethodsUI.js
+++ b/app/components/Nav/Main/RootRPCMethodsUI.js
@@ -57,6 +57,7 @@ import {
selectProviderType,
} from '../../../selectors/networkController';
import { createAccountConnectNavDetails } from '../../Views/AccountConnect';
+import { InstallSnapApprovalFlow } from '../../UI/InstallSnapApprovalFlow';
const hstInterface = new ethers.utils.Interface(abi);
@@ -85,6 +86,8 @@ const RootRPCMethodsUI = (props) => {
const [signMessageParams, setSignMessageParams] = useState(undefined);
+ const [installSnap, setInstallSnap] = useState(false);
+
const setTransactionObject = props.setTransactionObject;
const setEtherTransaction = props.setEtherTransaction;
@@ -665,6 +668,47 @@ const RootRPCMethodsUI = (props) => {
/>
);
+ const onInstallSnapConfirm = () => {
+ acceptPendingApproval(hostToApprove.id, hostToApprove.requestData);
+ };
+
+ const onInstallSnapFinished = () => {
+ setShowPendingApproval(false);
+ setInstallSnap(false);
+ };
+
+ const onInstallSnapReject = () => {
+ rejectPendingApproval(hostToApprove.id, hostToApprove.requestData);
+ setShowPendingApproval(false);
+ setInstallSnap(false);
+ };
+
+ /**
+ * Render the modal that asks the user to approve/reject connections to a dapp using the MetaMask SDK.
+ */
+ const renderInstallSnapApprovalModal = () => (
+
+
+
+ );
+
// unapprovedTransaction effect
useEffect(() => {
Engine.context.TransactionController.hub.on(
@@ -691,26 +735,38 @@ const RootRPCMethodsUI = (props) => {
}
switch (request.type) {
+ case ApprovalTypes.INSTALL_SNAP:
+ setHostToApprove({ requestData, id: request.id });
+ showPendingApprovalModal({
+ type: ApprovalTypes.INSTALL_SNAP,
+ origin: request.origin,
+ });
+ setInstallSnap(true);
+ break;
+ case ApprovalTypes.UPDATE_SNAP:
+ // eslint-disable-next-line no-console
+ console.log('Update Snap');
+ break;
case ApprovalTypes.REQUEST_PERMISSIONS:
- if (requestData?.permissions?.eth_accounts) {
- const {
- metadata: { id },
- } = requestData;
-
- const totalAccounts = props.accountsLength;
-
- AnalyticsV2.trackEvent(MetaMetricsEvents.CONNECT_REQUEST_STARTED, {
- number_of_accounts: totalAccounts,
- source: 'PERMISSION SYSTEM',
- });
-
- props.navigation.navigate(
- ...createAccountConnectNavDetails({
- hostInfo: requestData,
- permissionRequestId: id,
- }),
- );
- }
+ // eslint-disable-next-line no-case-declarations
+ const {
+ metadata: { id },
+ } = requestData;
+
+ // eslint-disable-next-line no-case-declarations
+ const totalAccounts = props.accountsLength;
+
+ AnalyticsV2.trackEvent(MetaMetricsEvents.CONNECT_REQUEST_STARTED, {
+ number_of_accounts: totalAccounts,
+ source: 'PERMISSION SYSTEM',
+ });
+
+ props.navigation.navigate(
+ ...createAccountConnectNavDetails({
+ hostInfo: requestData,
+ permissionRequestId: id,
+ }),
+ );
break;
case ApprovalTypes.CONNECT_ACCOUNTS:
setHostToApprove({ data: requestData, id: request.id });
@@ -800,6 +856,7 @@ const RootRPCMethodsUI = (props) => {
{renderWatchAssetModal()}
{renderQRSigningModal()}
{renderAccountsApprovalModal()}
+ {renderInstallSnapApprovalModal()}
);
};
diff --git a/app/components/Nav/Main/index.js b/app/components/Nav/Main/index.js
index 2839c2a276b..b5b3d9aef71 100644
--- a/app/components/Nav/Main/index.js
+++ b/app/components/Nav/Main/index.js
@@ -68,6 +68,7 @@ import {
selectProviderConfig,
selectProviderType,
} from '../../../selectors/networkController';
+import { SnapsExecutionWebView } from '../../UI/SnapsExecutionWebView';
const Stack = createStackNavigator();
@@ -335,6 +336,9 @@ const Main = (props) => {
) : (
renderLoader()
)}
+
+
+
diff --git a/app/components/UI/InstallSnapApprovalFlow/InstallSnapApprovalFlow.tsx b/app/components/UI/InstallSnapApprovalFlow/InstallSnapApprovalFlow.tsx
new file mode 100644
index 00000000000..4b2ce175874
--- /dev/null
+++ b/app/components/UI/InstallSnapApprovalFlow/InstallSnapApprovalFlow.tsx
@@ -0,0 +1,99 @@
+import React, { useCallback, useState } from 'react';
+import {
+ InstallSnapApprovalArgs,
+ SnapInstallState,
+} from './InstallSnapApprovalFlow.types';
+import { InstallSnapConnectionRequest } from './components/InstallSnapConnectionRequest';
+import { View } from 'react-native';
+import { InstallSnapPermissionsRequest } from './components/InstallSnapPermissionsRequest';
+import { InstallSnapSuccess } from './components/InstallSnapSuccess';
+import { InstallSnapError } from './components/InstallSnapError';
+import { SNAP_INSTALL_FLOW } from '../../../constants/test-ids';
+import Logger from '../../../util/Logger';
+
+const InstallSnapApprovalFlow = ({
+ requestData,
+ onConfirm,
+ onFinish,
+ onCancel,
+}: InstallSnapApprovalArgs) => {
+ const [installState, setInstallState] = useState(
+ SnapInstallState.Confirm,
+ );
+
+ const [installError, setInstallError] = useState(
+ undefined,
+ );
+
+ const onConfirmNext = useCallback(() => {
+ setInstallState(SnapInstallState.AcceptPermissions);
+ }, []);
+
+ const onPermissionsConfirm = useCallback(() => {
+ try {
+ onConfirm();
+ } catch (error) {
+ Logger.error(
+ error as Error,
+ `${SNAP_INSTALL_FLOW} Failed to install snap`,
+ );
+ setInstallError(error as Error);
+ setInstallState(SnapInstallState.SnapInstallError);
+ }
+ setInstallState(SnapInstallState.SnapInstalled);
+ }, [onConfirm]);
+
+ const onSnapInstalled = useCallback(() => {
+ onFinish();
+ }, [onFinish]);
+
+ const renderInstallStep = useCallback(() => {
+ switch (installState) {
+ case SnapInstallState.Confirm:
+ return (
+
+ );
+ case SnapInstallState.AcceptPermissions:
+ return (
+
+ );
+ case SnapInstallState.SnapInstalled:
+ return (
+
+ );
+ case SnapInstallState.SnapInstallError:
+ return (
+
+ );
+ }
+ }, [
+ installError,
+ installState,
+ onCancel,
+ onConfirmNext,
+ onPermissionsConfirm,
+ onSnapInstalled,
+ requestData,
+ ]);
+
+ return {renderInstallStep()};
+};
+
+export default InstallSnapApprovalFlow;
diff --git a/app/components/UI/InstallSnapApprovalFlow/InstallSnapApprovalFlow.types.ts b/app/components/UI/InstallSnapApprovalFlow/InstallSnapApprovalFlow.types.ts
new file mode 100644
index 00000000000..24be7bc307f
--- /dev/null
+++ b/app/components/UI/InstallSnapApprovalFlow/InstallSnapApprovalFlow.types.ts
@@ -0,0 +1,25 @@
+interface InstallSnapApprovalArgs {
+ requestData: any;
+ onConfirm: () => void;
+ onFinish: () => void;
+ onCancel: () => void;
+ chainId?: string;
+}
+
+interface InstallSnapFlowProps {
+ requestData: any;
+ onConfirm: () => void;
+ onCancel: () => void;
+ chainId?: string;
+ error?: Error;
+}
+
+export enum SnapInstallState {
+ Confirm = 'Confirm',
+ AcceptPermissions = 'AcceptPermissions',
+ SnapInstalled = 'SnapInstalled',
+ SnapInstallError = 'SnapInstallError',
+}
+
+// eslint-disable-next-line import/prefer-default-export
+export type { InstallSnapApprovalArgs, InstallSnapFlowProps };
diff --git a/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapConnectionRequest/InstallSnapConnectionRequest.styles.ts b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapConnectionRequest/InstallSnapConnectionRequest.styles.ts
new file mode 100644
index 00000000000..3d71b9b0214
--- /dev/null
+++ b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapConnectionRequest/InstallSnapConnectionRequest.styles.ts
@@ -0,0 +1,35 @@
+import { StyleSheet } from 'react-native';
+import Device from '../../../../../util/device';
+import { Theme } from '../../../../../util/theme/models';
+
+const styleSheet = (params: { theme: Theme }) => {
+ const { theme } = params;
+ const { colors } = theme;
+ return StyleSheet.create({
+ root: {
+ backgroundColor: colors.background.default,
+ paddingTop: 24,
+ borderTopLeftRadius: 20,
+ borderTopRightRadius: 20,
+ minHeight: 200,
+ paddingBottom: Device.isIphoneX() ? 20 : 0,
+ },
+ accountCardWrapper: {
+ paddingHorizontal: 24,
+ },
+ actionContainer: {
+ flex: 0,
+ paddingVertical: 16,
+ justifyContent: 'center',
+ },
+ snapCell: {
+ marginVertical: 16,
+ },
+ description: {
+ textAlign: 'center',
+ paddingBottom: 16,
+ },
+ });
+};
+
+export default styleSheet;
diff --git a/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapConnectionRequest/InstallSnapConnectionRequest.tsx b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapConnectionRequest/InstallSnapConnectionRequest.tsx
new file mode 100644
index 00000000000..b555926d6c9
--- /dev/null
+++ b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapConnectionRequest/InstallSnapConnectionRequest.tsx
@@ -0,0 +1,118 @@
+import React, { useMemo } from 'react';
+import { ImageSourcePropType, View } from 'react-native';
+import { InstallSnapFlowProps } from '../../InstallSnapApprovalFlow.types';
+import styleSheet from './InstallSnapConnectionRequest.styles';
+import { strings } from '../../../../../../locales/i18n';
+import {
+ SNAP_INSTALL_CANCEL,
+ SNAP_INSTALL_CONNECT,
+ SNAP_INSTALL_CONNECTION_REQUEST,
+} from '../../../../../constants/test-ids';
+import SheetHeader from '../../../../../component-library/components/Sheet/SheetHeader';
+import Text, {
+ TextVariant,
+} from '../../../../../component-library/components/Texts/Text';
+import TagUrl from '../../../../../component-library/components/Tags/TagUrl';
+import { getUrlObj, prefixUrlWithProtocol } from '../../../../../util/browser';
+import { IconName } from '../../../../../component-library/components/Icons/Icon';
+import Cell, {
+ CellVariants,
+} from '../../../../../component-library/components/Cells/Cell';
+import { AvatarVariants } from '../../../../../component-library/components/Avatars/Avatar';
+import {
+ ButtonSize,
+ ButtonVariants,
+} from '../../../../../component-library/components/Buttons/Button';
+import BottomSheetFooter, {
+ ButtonsAlignment,
+} from '../../../../../component-library/components/BottomSheets/BottomSheetFooter';
+import { ButtonProps } from '../../../../../component-library/components/Buttons/Button/Button.types';
+import { useStyles } from '../../../../hooks/useStyles';
+
+const InstallSnapConnectionRequest = ({
+ requestData,
+ onConfirm,
+ onCancel,
+}: InstallSnapFlowProps) => {
+ const { styles } = useStyles(styleSheet, {});
+
+ const snapName = useMemo(() => {
+ const colonIndex = requestData.snapId.indexOf(':');
+ if (colonIndex !== -1) {
+ return requestData.snapId.substring(colonIndex + 1);
+ }
+ return requestData.snapId;
+ }, [requestData.snapId]);
+
+ const dappOrigin = useMemo(
+ () => requestData.metadata.dappOrigin,
+ [requestData.metadata.dappOrigin],
+ );
+
+ const favicon: ImageSourcePropType = useMemo(() => {
+ const iconUrl = `https://api.faviconkit.com/${dappOrigin}/50`;
+ return { uri: iconUrl };
+ }, [dappOrigin]);
+
+ const urlWithProtocol = prefixUrlWithProtocol(dappOrigin);
+
+ const secureIcon = useMemo(
+ () =>
+ (getUrlObj(dappOrigin) as URL).protocol === 'https:'
+ ? IconName.Lock
+ : IconName.LockSlash,
+ [dappOrigin],
+ );
+
+ const cancelButtonProps: ButtonProps = {
+ variant: ButtonVariants.Secondary,
+ label: strings('accountApproval.cancel'),
+ size: ButtonSize.Lg,
+ onPress: onCancel,
+ testID: SNAP_INSTALL_CANCEL,
+ };
+
+ const connectButtonProps: ButtonProps = {
+ variant: ButtonVariants.Primary,
+ label: strings('accountApproval.connect'),
+ size: ButtonSize.Lg,
+ onPress: onConfirm,
+ testID: SNAP_INSTALL_CONNECT,
+ };
+
+ return (
+
+
+
+
+
+ {strings('install_snap.description', {
+ origin: dappOrigin,
+ snap: snapName,
+ })}
+
+ |
+
+
+
+
+
+ );
+};
+
+export default InstallSnapConnectionRequest;
diff --git a/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapConnectionRequest/index.ts b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapConnectionRequest/index.ts
new file mode 100644
index 00000000000..69c1912f1a4
--- /dev/null
+++ b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapConnectionRequest/index.ts
@@ -0,0 +1,4 @@
+/* eslint-disable import/prefer-default-export */
+import InstallSnapConnectionRequest from './InstallSnapConnectionRequest';
+
+export { InstallSnapConnectionRequest };
diff --git a/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapConnectionRequest/test/InstallSnapConnectionRequest.test.tsx b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapConnectionRequest/test/InstallSnapConnectionRequest.test.tsx
new file mode 100644
index 00000000000..5352b6990cd
--- /dev/null
+++ b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapConnectionRequest/test/InstallSnapConnectionRequest.test.tsx
@@ -0,0 +1,90 @@
+import React from 'react';
+import { render, fireEvent } from '@testing-library/react-native';
+import InstallSnapConnectionRequest from '../InstallSnapConnectionRequest';
+import {
+ SNAP_INSTALL_CANCEL,
+ SNAP_INSTALL_CONNECT,
+ SNAP_INSTALL_CONNECTION_REQUEST,
+} from '../../../../../../constants/test-ids';
+
+describe('InstallSnapConnectionRequest', () => {
+ const requestData = {
+ metadata: {
+ id: 'uNadWHqPnwOM4NER3mERI',
+ origin: 'npm:@lavamoat/tss-snap',
+ dappOrigin: 'tss.ac',
+ },
+ permissions: {
+ snap_manageState: {},
+ 'endowment:rpc': {
+ caveats: [
+ {
+ type: 'rpcOrigin',
+ value: {
+ dapps: true,
+ snaps: true,
+ },
+ },
+ ],
+ },
+ },
+ snapId: 'npm:@lavamoat/tss-snap',
+ };
+
+ const onConfirm = jest.fn();
+ const onCancel = jest.fn();
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('renders correctly', () => {
+ const { getByTestId } = render(
+ ,
+ );
+ expect(getByTestId(SNAP_INSTALL_CONNECTION_REQUEST)).toBeDefined();
+ });
+
+ it('calls onConfirm when the connect button is pressed', () => {
+ const { getByTestId } = render(
+ ,
+ );
+
+ fireEvent.press(getByTestId(SNAP_INSTALL_CONNECT));
+ expect(onConfirm).toHaveBeenCalledTimes(1);
+ });
+
+ it('calls onCancel when the cancel button is pressed', () => {
+ const { getByTestId } = render(
+ ,
+ );
+
+ fireEvent.press(getByTestId(SNAP_INSTALL_CANCEL));
+ expect(onCancel).toHaveBeenCalledTimes(1);
+ });
+
+ it('correctly prefixes dappOrigin with protocol', () => {
+ const { getByText } = render(
+ ,
+ );
+
+ const expectedUrl = 'https://tss.ac';
+ expect(getByText(expectedUrl)).toBeTruthy();
+ });
+});
diff --git a/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapError/InstallSnapError.styles.ts b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapError/InstallSnapError.styles.ts
new file mode 100644
index 00000000000..a1a77544a57
--- /dev/null
+++ b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapError/InstallSnapError.styles.ts
@@ -0,0 +1,60 @@
+import { StyleSheet } from 'react-native';
+import { Theme } from '../../../../../util/theme/models';
+import Device from '../../../../../util/device';
+
+/**
+ *
+ * @param params Style sheet params.
+ * @param params.theme App theme from ThemeContext.
+ * @param params.vars Inputs that the style sheet depends on.
+ * @returns StyleSheet object.
+ */
+const styleSheet = (params: { theme: Theme }) => {
+ const { theme } = params;
+ const { colors } = theme;
+ return StyleSheet.create({
+ root: {
+ backgroundColor: colors.background.default,
+ paddingTop: 24,
+ borderTopLeftRadius: 20,
+ borderTopRightRadius: 20,
+ minHeight: 200,
+ paddingBottom: Device.isIphoneX() ? 20 : 0,
+ },
+ accountCardWrapper: {
+ paddingHorizontal: 24,
+ },
+ actionContainer: {
+ flex: 0,
+ paddingVertical: 16,
+ justifyContent: 'center',
+ },
+ description: {
+ textAlign: 'center',
+ paddingBottom: 16,
+ },
+ snapCell: {
+ marginVertical: 16,
+ },
+ snapPermissionContainer: {
+ maxHeight: 300,
+ borderWidth: 1,
+ borderRadius: 8,
+ borderColor: colors.border.muted,
+ },
+ iconContainer: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ iconWrapper: {
+ width: 48,
+ height: 48,
+ borderRadius: 24,
+ backgroundColor: colors.success.muted,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ });
+};
+
+export default styleSheet;
diff --git a/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapError/InstallSnapError.tsx b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapError/InstallSnapError.tsx
new file mode 100644
index 00000000000..4f8f01eb6ba
--- /dev/null
+++ b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapError/InstallSnapError.tsx
@@ -0,0 +1,100 @@
+import React, { useMemo } from 'react';
+import { View } from 'react-native';
+import stylesheet from './InstallSnapError.styles';
+import { strings } from '../../../../../../locales/i18n';
+import {
+ SNAP_INSTALL_ERROR,
+ SNAP_INSTALL_OK,
+} from '../../../../../constants/test-ids';
+import SheetHeader from '../../../../../component-library/components/Sheet/SheetHeader';
+import Text, {
+ TextVariant,
+} from '../../../../../component-library/components/Texts/Text';
+import Icon, {
+ IconColor,
+ IconName,
+ IconSize,
+} from '../../../../../component-library/components/Icons/Icon';
+import Cell, {
+ CellVariants,
+} from '../../../../../component-library/components/Cells/Cell';
+import { AvatarVariants } from '../../../../../component-library/components/Avatars/Avatar';
+import {
+ ButtonSize,
+ ButtonVariants,
+} from '../../../../../component-library/components/Buttons/Button';
+import BottomSheetFooter, {
+ ButtonsAlignment,
+} from '../../../../../component-library/components/BottomSheets/BottomSheetFooter';
+import { ButtonProps } from '../../../../../component-library/components/Buttons/Button/Button.types';
+import { useStyles } from '../../../../hooks/useStyles';
+import { InstallSnapFlowProps } from '../../InstallSnapApprovalFlow.types';
+
+const InstallSnapError = ({
+ requestData,
+ onConfirm,
+ error,
+}: InstallSnapFlowProps) => {
+ const { styles } = useStyles(stylesheet, {});
+
+ const snapName = useMemo(() => {
+ const colonIndex = requestData.snapId.indexOf(':');
+ if (colonIndex !== -1) {
+ return requestData.snapId.substring(colonIndex + 1);
+ }
+ return requestData.snapId;
+ }, [requestData.snapId]);
+
+ const okButtonProps: ButtonProps = {
+ variant: ButtonVariants.Primary,
+ label: strings('install_snap.okay_action'),
+ size: ButtonSize.Lg,
+ onPress: onConfirm,
+ testID: SNAP_INSTALL_OK,
+ };
+
+ const errorTitle = useMemo(
+ () =>
+ error?.message ? error?.message : strings('install_snap.error_title'),
+ [error],
+ );
+
+ return (
+
+
+ |
+
+
+
+
+
+
+
+ {strings('install_snap.error_description', {
+ snap: snapName,
+ })}
+
+
+
+
+
+
+ );
+};
+
+export default InstallSnapError;
diff --git a/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapError/index.ts b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapError/index.ts
new file mode 100644
index 00000000000..bb74e27bb3e
--- /dev/null
+++ b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapError/index.ts
@@ -0,0 +1,4 @@
+/* eslint-disable import/prefer-default-export */
+import InstallSnapError from './InstallSnapError';
+
+export { InstallSnapError };
diff --git a/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapError/test/InstallSnapError.test.tsx b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapError/test/InstallSnapError.test.tsx
new file mode 100644
index 00000000000..313275c7668
--- /dev/null
+++ b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapError/test/InstallSnapError.test.tsx
@@ -0,0 +1,94 @@
+import React from 'react';
+import { render, fireEvent } from '@testing-library/react-native';
+import InstallSnapError from '../InstallSnapError';
+import {
+ SNAP_INSTALL_ERROR,
+ SNAP_INSTALL_OK,
+} from '../../../../../../constants/test-ids';
+
+describe('InstallSnapError', () => {
+ const requestData = {
+ metadata: {
+ id: 'uNadWHqPnwOM4NER3mERI',
+ origin: 'npm:@lavamoat/tss-snap',
+ dappOrigin: 'tss.ac',
+ },
+ permissions: {
+ snap_manageState: {},
+ 'endowment:rpc': {
+ caveats: [
+ {
+ type: 'rpcOrigin',
+ value: {
+ dapps: true,
+ snaps: true,
+ },
+ },
+ ],
+ },
+ },
+ snapId: 'npm:@lavamoat/tss-snap',
+ };
+
+ const onConfirm = jest.fn();
+ const onCancel = jest.fn();
+ const error = new Error('Installation failed');
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('renders correctly', () => {
+ const { getByTestId } = render(
+ ,
+ );
+
+ expect(getByTestId(SNAP_INSTALL_ERROR)).toBeTruthy();
+ });
+
+ it('calls onConfirm when the OK button is pressed', () => {
+ const { getByTestId } = render(
+ ,
+ );
+
+ fireEvent.press(getByTestId(SNAP_INSTALL_OK));
+ expect(onConfirm).toHaveBeenCalledTimes(1);
+ });
+
+ it('displays the correct snap name', () => {
+ const { getByText } = render(
+ ,
+ );
+
+ const expectedSnapName = '@lavamoat/tss-snap';
+ expect(getByText(expectedSnapName)).toBeTruthy();
+ });
+
+ it('displays the correct error title', () => {
+ const { getByText } = render(
+ ,
+ );
+
+ expect(getByText('Installation failed')).toBeTruthy();
+ });
+});
diff --git a/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapPermissionsRequest/InstallSnapPermissionRequest.styles.ts b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapPermissionsRequest/InstallSnapPermissionRequest.styles.ts
new file mode 100644
index 00000000000..dcbcc3054db
--- /dev/null
+++ b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapPermissionsRequest/InstallSnapPermissionRequest.styles.ts
@@ -0,0 +1,48 @@
+import { StyleSheet } from 'react-native';
+import { Theme } from '../../../../../util/theme/models';
+import Device from '../../../../../util/device';
+
+/**
+ *
+ * @param params Style sheet params.
+ * @param params.theme App theme from ThemeContext.
+ * @param params.vars Inputs that the style sheet depends on.
+ * @returns StyleSheet object.
+ */
+const styleSheet = (params: { theme: Theme }) => {
+ const { theme } = params;
+ const { colors } = theme;
+ return StyleSheet.create({
+ root: {
+ backgroundColor: colors.background.default,
+ paddingTop: 24,
+ borderTopLeftRadius: 20,
+ borderTopRightRadius: 20,
+ minHeight: 200,
+ paddingBottom: Device.isIphoneX() ? 20 : 0,
+ },
+ accountCardWrapper: {
+ paddingHorizontal: 24,
+ },
+ actionContainer: {
+ flex: 0,
+ paddingVertical: 16,
+ justifyContent: 'center',
+ },
+ description: {
+ textAlign: 'center',
+ paddingBottom: 16,
+ },
+ snapCell: {
+ marginVertical: 16,
+ },
+ snapPermissionContainer: {
+ maxHeight: 300,
+ borderWidth: 1,
+ borderRadius: 8,
+ borderColor: colors.border.muted,
+ },
+ });
+};
+
+export default styleSheet;
diff --git a/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapPermissionsRequest/InstallSnapPermissionsRequest.tsx b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapPermissionsRequest/InstallSnapPermissionsRequest.tsx
new file mode 100644
index 00000000000..d6014c9ff9f
--- /dev/null
+++ b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapPermissionsRequest/InstallSnapPermissionsRequest.tsx
@@ -0,0 +1,104 @@
+import React, { useMemo } from 'react';
+import { ScrollView, View } from 'react-native';
+import stylesheet from './InstallSnapPermissionRequest.styles';
+import { strings } from '../../../../../../locales/i18n';
+import {
+ SNAP_INSTALL_CANCEL,
+ SNAP_INSTALL_PERMISSIONS_REQUEST,
+ SNAP_INSTALL_PERMISSIONS_REQUEST_APPROVE,
+} from '../../../../../constants/test-ids';
+import SheetHeader from '../../../../../component-library/components/Sheet/SheetHeader';
+import Text, {
+ TextVariant,
+} from '../../../../../component-library/components/Texts/Text';
+import { IconName } from '../../../../../component-library/components/Icons/Icon';
+import Cell, {
+ CellVariants,
+} from '../../../../../component-library/components/Cells/Cell';
+import { AvatarVariants } from '../../../../../component-library/components/Avatars/Avatar';
+import {
+ ButtonSize,
+ ButtonVariants,
+} from '../../../../../component-library/components/Buttons/Button';
+import BottomSheetFooter, {
+ ButtonsAlignment,
+} from '../../../../../component-library/components/BottomSheets/BottomSheetFooter';
+import { ButtonProps } from '../../../../../component-library/components/Buttons/Button/Button.types';
+import { useStyles } from '../../../../hooks/useStyles';
+import { InstallSnapFlowProps } from '../../InstallSnapApprovalFlow.types';
+import { SnapPermissions } from '../../../../Views/Snaps/components/SnapPermissions';
+
+const InstallSnapPermissionsRequest = ({
+ requestData,
+ onConfirm,
+ onCancel,
+}: InstallSnapFlowProps) => {
+ const { styles } = useStyles(stylesheet, {});
+ const snapName = useMemo(() => {
+ const colonIndex = requestData.snapId.indexOf(':');
+ if (colonIndex !== -1) {
+ return requestData.snapId.substring(colonIndex + 1);
+ }
+ return requestData.snapId;
+ }, [requestData.snapId]);
+
+ const dappOrigin = useMemo(
+ () => requestData.metadata.dappOrigin,
+ [requestData.metadata.dappOrigin],
+ );
+
+ const cancelButtonProps: ButtonProps = {
+ variant: ButtonVariants.Secondary,
+ label: strings('accountApproval.cancel'),
+ size: ButtonSize.Lg,
+ onPress: onCancel,
+ testID: SNAP_INSTALL_CANCEL,
+ };
+
+ const connectButtonProps: ButtonProps = {
+ variant: ButtonVariants.Primary,
+ label: strings('install_snap.approve_permissions'),
+ size: ButtonSize.Lg,
+ onPress: onConfirm,
+ testID: SNAP_INSTALL_PERMISSIONS_REQUEST_APPROVE,
+ };
+
+ return (
+
+
+ |
+
+
+ {strings('install_snap.permissions_request_description', {
+ origin: dappOrigin,
+ snap: snapName,
+ })}
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default InstallSnapPermissionsRequest;
diff --git a/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapPermissionsRequest/index.ts b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapPermissionsRequest/index.ts
new file mode 100644
index 00000000000..a61745f8de8
--- /dev/null
+++ b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapPermissionsRequest/index.ts
@@ -0,0 +1,4 @@
+/* eslint-disable import/prefer-default-export */
+import InstallSnapPermissionsRequest from './InstallSnapPermissionsRequest';
+
+export { InstallSnapPermissionsRequest };
diff --git a/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapPermissionsRequest/test/InstallSnapPermissionsRequest.test.tsx b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapPermissionsRequest/test/InstallSnapPermissionsRequest.test.tsx
new file mode 100644
index 00000000000..7671bad8e4e
--- /dev/null
+++ b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapPermissionsRequest/test/InstallSnapPermissionsRequest.test.tsx
@@ -0,0 +1,48 @@
+import React from 'react';
+import { render } from '@testing-library/react-native';
+import InstallSnapPermissionsRequest from '../InstallSnapPermissionsRequest';
+import { SNAP_PERMISSION_CELL } from '../../../../../../constants/test-ids';
+
+describe('InstallSnapPermissionsRequest', () => {
+ const requestData = {
+ metadata: {
+ id: 'uNadWHqPnwOM4NER3mERI',
+ origin: 'npm:@lavamoat/tss-snap',
+ dappOrigin: 'tss.ac',
+ },
+ permissions: {
+ snap_manageState: {},
+ 'endowment:rpc': {
+ caveats: [
+ {
+ type: 'rpcOrigin',
+ value: {
+ dapps: true,
+ snaps: true,
+ },
+ },
+ ],
+ },
+ },
+ snapId: 'npm:@lavamoat/tss-snap',
+ };
+
+ const onConfirm = jest.fn();
+ const onCancel = jest.fn();
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('renders the correct number of permission cells', () => {
+ const { getAllByTestId } = render(
+ ,
+ );
+ const permissionCells = getAllByTestId(SNAP_PERMISSION_CELL);
+ expect(permissionCells).toHaveLength(3);
+ });
+});
diff --git a/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapSuccess/InstallSnapSuccess.styles.ts b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapSuccess/InstallSnapSuccess.styles.ts
new file mode 100644
index 00000000000..a1a77544a57
--- /dev/null
+++ b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapSuccess/InstallSnapSuccess.styles.ts
@@ -0,0 +1,60 @@
+import { StyleSheet } from 'react-native';
+import { Theme } from '../../../../../util/theme/models';
+import Device from '../../../../../util/device';
+
+/**
+ *
+ * @param params Style sheet params.
+ * @param params.theme App theme from ThemeContext.
+ * @param params.vars Inputs that the style sheet depends on.
+ * @returns StyleSheet object.
+ */
+const styleSheet = (params: { theme: Theme }) => {
+ const { theme } = params;
+ const { colors } = theme;
+ return StyleSheet.create({
+ root: {
+ backgroundColor: colors.background.default,
+ paddingTop: 24,
+ borderTopLeftRadius: 20,
+ borderTopRightRadius: 20,
+ minHeight: 200,
+ paddingBottom: Device.isIphoneX() ? 20 : 0,
+ },
+ accountCardWrapper: {
+ paddingHorizontal: 24,
+ },
+ actionContainer: {
+ flex: 0,
+ paddingVertical: 16,
+ justifyContent: 'center',
+ },
+ description: {
+ textAlign: 'center',
+ paddingBottom: 16,
+ },
+ snapCell: {
+ marginVertical: 16,
+ },
+ snapPermissionContainer: {
+ maxHeight: 300,
+ borderWidth: 1,
+ borderRadius: 8,
+ borderColor: colors.border.muted,
+ },
+ iconContainer: {
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ iconWrapper: {
+ width: 48,
+ height: 48,
+ borderRadius: 24,
+ backgroundColor: colors.success.muted,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ });
+};
+
+export default styleSheet;
diff --git a/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapSuccess/InstallSnapSuccess.tsx b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapSuccess/InstallSnapSuccess.tsx
new file mode 100644
index 00000000000..2a3dcd772b2
--- /dev/null
+++ b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapSuccess/InstallSnapSuccess.tsx
@@ -0,0 +1,93 @@
+import React, { useMemo } from 'react';
+import { View } from 'react-native';
+import stylesheet from './InstallSnapSuccess.styles';
+import { strings } from '../../../../../../locales/i18n';
+import {
+ SNAP_INSTALL_OK,
+ SNAP_INSTALL_SUCCESS,
+} from '../../../../../constants/test-ids';
+import SheetHeader from '../../../../../component-library/components/Sheet/SheetHeader';
+import Text, {
+ TextVariant,
+} from '../../../../../component-library/components/Texts/Text';
+import Icon, {
+ IconColor,
+ IconName,
+ IconSize,
+} from '../../../../../component-library/components/Icons/Icon';
+import Cell, {
+ CellVariants,
+} from '../../../../../component-library/components/Cells/Cell';
+import { AvatarVariants } from '../../../../../component-library/components/Avatars/Avatar';
+import {
+ ButtonSize,
+ ButtonVariants,
+} from '../../../../../component-library/components/Buttons/Button';
+import BottomSheetFooter, {
+ ButtonsAlignment,
+} from '../../../../../component-library/components/BottomSheets/BottomSheetFooter';
+import { ButtonProps } from '../../../../../component-library/components/Buttons/Button/Button.types';
+import { useStyles } from '../../../../hooks/useStyles';
+import { InstallSnapFlowProps } from '../../InstallSnapApprovalFlow.types';
+
+const InstallSnapSuccess = ({
+ requestData,
+ onConfirm,
+}: InstallSnapFlowProps) => {
+ const { styles } = useStyles(stylesheet, {});
+
+ const snapName = useMemo(() => {
+ const colonIndex = requestData.snapId.indexOf(':');
+ if (colonIndex !== -1) {
+ return requestData.snapId.substring(colonIndex + 1);
+ }
+ return requestData.snapId;
+ }, [requestData.snapId]);
+
+ const okButtonProps: ButtonProps = {
+ variant: ButtonVariants.Primary,
+ label: strings('install_snap.okay_action'),
+ size: ButtonSize.Lg,
+ onPress: onConfirm,
+ testID: SNAP_INSTALL_OK,
+ };
+
+ return (
+
+
+ |
+
+
+
+
+
+
+
+ {strings('install_snap.install_successful', {
+ snap: snapName,
+ })}
+
+
+
+
+
+
+ );
+};
+
+export default InstallSnapSuccess;
diff --git a/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapSuccess/index.ts b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapSuccess/index.ts
new file mode 100644
index 00000000000..a8bf2beafdd
--- /dev/null
+++ b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapSuccess/index.ts
@@ -0,0 +1,4 @@
+/* eslint-disable import/prefer-default-export */
+import InstallSnapSuccess from './InstallSnapSuccess';
+
+export { InstallSnapSuccess };
diff --git a/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapSuccess/test/InstallSnapSuccess.test.tsx b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapSuccess/test/InstallSnapSuccess.test.tsx
new file mode 100644
index 00000000000..5ef37ca9584
--- /dev/null
+++ b/app/components/UI/InstallSnapApprovalFlow/components/InstallSnapSuccess/test/InstallSnapSuccess.test.tsx
@@ -0,0 +1,77 @@
+import React from 'react';
+import { render, fireEvent } from '@testing-library/react-native';
+import InstallSnapSuccess from '../InstallSnapSuccess';
+import {
+ SNAP_INSTALL_OK,
+ SNAP_INSTALL_SUCCESS,
+} from '../../../../../../constants/test-ids';
+
+describe('InstallSnapSuccess', () => {
+ const requestData = {
+ metadata: {
+ id: 'uNadWHqPnwOM4NER3mERI',
+ origin: 'npm:@lavamoat/tss-snap',
+ dappOrigin: 'tss.ac',
+ },
+ permissions: {
+ snap_manageState: {},
+ 'endowment:rpc': {
+ caveats: [
+ {
+ type: 'rpcOrigin',
+ value: {
+ dapps: true,
+ snaps: true,
+ },
+ },
+ ],
+ },
+ },
+ snapId: 'npm:@lavamoat/tss-snap',
+ };
+
+ const onConfirm = jest.fn();
+ const onCancel = jest.fn();
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('renders correctly', () => {
+ const { getByTestId } = render(
+ ,
+ );
+
+ expect(getByTestId(SNAP_INSTALL_SUCCESS)).toBeDefined();
+ });
+
+ it('calls onConfirm when the OK button is pressed', () => {
+ const { getByTestId } = render(
+ ,
+ );
+
+ fireEvent.press(getByTestId(SNAP_INSTALL_OK));
+ expect(onConfirm).toHaveBeenCalledTimes(1);
+ });
+
+ it('displays the correct snap name', () => {
+ const { getByText } = render(
+ ,
+ );
+
+ const expectedSnapName = '@lavamoat/tss-snap';
+ expect(getByText(expectedSnapName)).toBeTruthy();
+ });
+});
diff --git a/app/components/UI/InstallSnapApprovalFlow/index.ts b/app/components/UI/InstallSnapApprovalFlow/index.ts
new file mode 100644
index 00000000000..c7d294ce6f7
--- /dev/null
+++ b/app/components/UI/InstallSnapApprovalFlow/index.ts
@@ -0,0 +1,5 @@
+/* eslint-disable import/prefer-default-export */
+import { InstallSnapApprovalArgs } from './InstallSnapApprovalFlow.types';
+import InstallSnapApprovalFlow from './InstallSnapApprovalFlow';
+export { InstallSnapApprovalFlow };
+export type { InstallSnapApprovalArgs };
diff --git a/app/components/UI/InstallSnapApprovalFlow/test/InstallSnapApprovalFlow.test.tsx b/app/components/UI/InstallSnapApprovalFlow/test/InstallSnapApprovalFlow.test.tsx
new file mode 100644
index 00000000000..fa553ea9fc2
--- /dev/null
+++ b/app/components/UI/InstallSnapApprovalFlow/test/InstallSnapApprovalFlow.test.tsx
@@ -0,0 +1,159 @@
+import React from 'react';
+import { render, fireEvent, waitFor } from '@testing-library/react-native';
+import InstallSnapApprovalFlow from '../InstallSnapApprovalFlow';
+import {
+ SNAP_INSTALL_CANCEL,
+ SNAP_INSTALL_CONNECT,
+ SNAP_INSTALL_CONNECTION_REQUEST,
+ SNAP_INSTALL_ERROR,
+ SNAP_INSTALL_PERMISSIONS_REQUEST,
+ SNAP_INSTALL_PERMISSIONS_REQUEST_APPROVE,
+ SNAP_INSTALL_SUCCESS,
+} from '../../../../constants/test-ids';
+
+describe('InstallSnapApprovalFlow', () => {
+ const requestData = {
+ requestData: {
+ metadata: {
+ id: 'uNadWHqPnwOM4NER3mERI',
+ origin: 'npm:@lavamoat/tss-snap',
+ dappOrigin: 'tss.ac',
+ },
+ permissions: {
+ snap_manageState: {},
+ 'endowment:rpc': {
+ caveats: [
+ {
+ type: 'rpcOrigin',
+ value: {
+ dapps: true,
+ snaps: false,
+ },
+ },
+ ],
+ },
+ },
+ snapId: 'npm:@lavamoat/tss-snap',
+ },
+ id: 'uNadWHqPnwOM4NER3mERI',
+ };
+
+ const onConfirm = jest.fn();
+ const onFinish = jest.fn();
+ const onCancel = jest.fn();
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('renders InstallSnapConnectionRequest component initially', () => {
+ const { getByTestId } = render(
+ ,
+ );
+ const connectionRequest = getByTestId(SNAP_INSTALL_CONNECTION_REQUEST);
+ expect(connectionRequest).toBeDefined();
+ });
+
+ it('switches to InstallSnapPermissionsRequest on confirmation', async () => {
+ const { getByTestId, findByTestId } = render(
+ ,
+ );
+ const confirmButton = getByTestId(SNAP_INSTALL_CONNECT);
+ fireEvent.press(confirmButton);
+ const permissionsRequest = await findByTestId(
+ SNAP_INSTALL_PERMISSIONS_REQUEST,
+ );
+ expect(permissionsRequest).toBeDefined();
+ });
+
+ it('calls onConfirm when Approve button is pressed in InstallSnapPermissionsRequest', async () => {
+ const { getByTestId } = render(
+ ,
+ );
+ const confirmButton = getByTestId(SNAP_INSTALL_CONNECT);
+ fireEvent.press(confirmButton);
+ const permissionsApproveButton = getByTestId(
+ SNAP_INSTALL_PERMISSIONS_REQUEST_APPROVE,
+ );
+ fireEvent.press(permissionsApproveButton);
+ expect(onConfirm).toHaveBeenCalledTimes(1);
+ });
+
+ it('renders InstallSnapSuccess on successful installation', async () => {
+ const { getByTestId, findByTestId } = render(
+ ,
+ );
+ const confirmButton = getByTestId(SNAP_INSTALL_CONNECT);
+ fireEvent.press(confirmButton);
+ const permissionsRequest = await findByTestId(
+ SNAP_INSTALL_PERMISSIONS_REQUEST,
+ );
+ expect(permissionsRequest).toBeDefined();
+ const permissionsApproveButton = getByTestId(
+ SNAP_INSTALL_PERMISSIONS_REQUEST_APPROVE,
+ );
+ fireEvent.press(permissionsApproveButton);
+ const installSuccess = await findByTestId(SNAP_INSTALL_SUCCESS);
+ expect(installSuccess).toBeDefined();
+ });
+
+ it('renders InstallSnapError on error during installation', async () => {
+ onConfirm.mockImplementation(() => {
+ throw new Error('Installation error');
+ });
+
+ const { getByTestId, findByTestId } = render(
+ ,
+ );
+ const confirmButton = getByTestId(SNAP_INSTALL_CONNECT);
+ fireEvent.press(confirmButton);
+ const permissionsRequest = getByTestId(SNAP_INSTALL_PERMISSIONS_REQUEST);
+ expect(permissionsRequest).toBeDefined();
+ const permissionsConfirmButton = getByTestId(
+ SNAP_INSTALL_PERMISSIONS_REQUEST_APPROVE,
+ );
+ fireEvent.press(permissionsConfirmButton);
+ await waitFor(() => {
+ expect(findByTestId(SNAP_INSTALL_ERROR)).toBeDefined();
+ });
+ });
+
+ it('calls onCancel on cancel button click', () => {
+ const { getByTestId } = render(
+ ,
+ );
+ const cancelButton = getByTestId(SNAP_INSTALL_CANCEL);
+ fireEvent.press(cancelButton);
+ expect(onCancel).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/app/components/UI/SnapsExecutionWebView/SnapsExecutionWebView.tsx b/app/components/UI/SnapsExecutionWebView/SnapsExecutionWebView.tsx
new file mode 100644
index 00000000000..e6b5d62745e
--- /dev/null
+++ b/app/components/UI/SnapsExecutionWebView/SnapsExecutionWebView.tsx
@@ -0,0 +1,62 @@
+import React, { useRef } from 'react';
+import { View, ScrollView } from 'react-native';
+import WebView from 'react-native-webview';
+import { snapsState, WebviewPostMessageStream } from '../../../core/Snaps';
+import { createStyles } from './styles';
+
+let stream: any;
+
+const SnapsExecutionWebView = () => {
+ const styles = createStyles();
+
+ const webviewRef = useRef();
+
+ const setWebviewPostMessage = () => {
+ stream = new WebviewPostMessageStream({
+ name: 'rnside',
+ target: 'webview',
+ targetOrigin: '*',
+ targetWindow: webviewRef.current,
+ });
+
+ // eslint-disable-next-line no-console
+ stream.on('data', (data: any) =>
+ // eslint-disable-next-line no-console
+ console.log(
+ '[APP LOG] setWebviewPostMessage: Message from Webview ' + data,
+ ),
+ );
+
+ snapsState.stream = stream;
+ snapsState.webview = webviewRef.current;
+ };
+
+ const messageFromWebview = (data: any) => {
+ stream?._onMessage(data);
+ };
+
+ const envURI = {
+ prod: 'https://gantunesr.github.io/mobile-execution-environment/',
+ localIOS: 'http://localhost:3001/mobile-execution-environment',
+ localAndroid: 'http://10.0.2.2:3001/mobile-execution-environment',
+ };
+
+ return (
+
+
+
+
+
+ );
+};
+
+export default SnapsExecutionWebView;
diff --git a/app/components/UI/SnapsExecutionWebView/index.ts b/app/components/UI/SnapsExecutionWebView/index.ts
new file mode 100644
index 00000000000..5e8c0f374a9
--- /dev/null
+++ b/app/components/UI/SnapsExecutionWebView/index.ts
@@ -0,0 +1,4 @@
+import SnapsExecutionWebView from './SnapsExecutionWebView';
+
+// eslint-disable-next-line import/prefer-default-export
+export { SnapsExecutionWebView };
diff --git a/app/components/UI/SnapsExecutionWebView/styles.ts b/app/components/UI/SnapsExecutionWebView/styles.ts
new file mode 100644
index 00000000000..659b2428b60
--- /dev/null
+++ b/app/components/UI/SnapsExecutionWebView/styles.ts
@@ -0,0 +1,20 @@
+/* eslint-disable react-native/no-color-literals */
+import { StyleSheet } from 'react-native';
+
+// eslint-disable-next-line import/prefer-default-export
+export const createStyles = () =>
+ StyleSheet.create({
+ webview: {
+ height: 0,
+ // marginBottom: 50,
+ // borderWidth: 1,
+ // borderStyle: 'dashed',
+ // borderColor: 'red',
+ },
+ container: {
+ // flex: 1,
+ // borderWidth: 1,
+ // borderStyle: 'dashed',
+ // borderColor: 'green',
+ },
+ });
diff --git a/app/components/Views/AccountConnect/AccountConnect.tsx b/app/components/Views/AccountConnect/AccountConnect.tsx
index a027349b2bf..47a20278fe9 100644
--- a/app/components/Views/AccountConnect/AccountConnect.tsx
+++ b/app/components/Views/AccountConnect/AccountConnect.tsx
@@ -35,7 +35,7 @@ import { getUrlObj, prefixUrlWithProtocol } from '../../../util/browser';
import { strings } from '../../../../locales/i18n';
import { AvatarAccountType } from '../../../component-library/components/Avatars/Avatar/variants/AvatarAccount';
import { safeToChecksumAddress } from '../../../util/address';
-import USER_INTENT from '../../../constants/permissions';
+import { USER_INTENT } from '../../../constants/permissions';
// Internal dependencies.
import {
diff --git a/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx b/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx
index e9cb31f0070..d7def3fcb64 100644
--- a/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx
+++ b/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.tsx
@@ -14,7 +14,7 @@ import Button, {
ButtonWidthTypes,
} from '../../../../component-library/components/Buttons/Button';
import AccountSelectorList from '../../../UI/AccountSelectorList';
-import USER_INTENT from '../../../../constants/permissions';
+import { USER_INTENT } from '../../../../constants/permissions';
import generateTestId from '../../../../../wdio/utils/generateTestId';
import { ACCOUNT_APPROVAL_SELECT_ALL_BUTTON } from '../../../../../wdio/screen-objects/testIDs/Components/AccountApprovalModal.testIds';
diff --git a/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.types.ts b/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.types.ts
index 7a4420f58c8..e0db8a7c2c4 100644
--- a/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.types.ts
+++ b/app/components/Views/AccountConnect/AccountConnectMultiSelector/AccountConnectMultiSelector.types.ts
@@ -12,7 +12,7 @@ export enum AccountConnectMultiSelectorScreens {
// External dependencies.
import { UseAccounts } from '../../../hooks/useAccounts';
import { IconName } from '../../../../component-library/components/Icons/Icon';
-import USER_INTENT from '../../../../constants/permissions';
+import { USER_INTENT } from '../../../../constants/permissions';
/**
* AccountConnectMultiSelector props.
diff --git a/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx b/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx
index 0a864dc50ff..7ff499d6732 100644
--- a/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx
+++ b/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.tsx
@@ -29,7 +29,7 @@ import { AccountConnectScreens } from '../AccountConnect.types';
// Internal dependencies.
import { AccountConnectSingleProps } from './AccountConnectSingle.types';
import styleSheet from './AccountConnectSingle.styles';
-import USER_INTENT from '../../../../constants/permissions';
+import { USER_INTENT } from '../../../../constants/permissions';
import {
ACCOUNT_APROVAL_MODAL_CONTAINER_ID,
diff --git a/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.types.ts b/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.types.ts
index 702a2928bea..197398910ec 100644
--- a/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.types.ts
+++ b/app/components/Views/AccountConnect/AccountConnectSingle/AccountConnectSingle.types.ts
@@ -5,7 +5,7 @@ import { ImageSourcePropType } from 'react-native';
import { AccountConnectScreens } from '../AccountConnect.types';
import { Account } from '../../../hooks/useAccounts';
import { IconName } from '../../../../component-library/components/Icons/Icon';
-import USER_INTENT from '../../../../constants/permissions';
+import { USER_INTENT } from '../../../../constants/permissions';
/**
* AccountConnectSingle props.
diff --git a/app/components/Views/AccountConnect/AccountConnectSingleSelector/AccountConnectSingleSelector.tsx b/app/components/Views/AccountConnect/AccountConnectSingleSelector/AccountConnectSingleSelector.tsx
index fdc06822918..152aef7761a 100644
--- a/app/components/Views/AccountConnect/AccountConnectSingleSelector/AccountConnectSingleSelector.tsx
+++ b/app/components/Views/AccountConnect/AccountConnectSingleSelector/AccountConnectSingleSelector.tsx
@@ -12,7 +12,7 @@ import { AccountConnectScreens } from '../AccountConnect.types';
// Internal dependencies.
import { AccountConnectSingleSelectorProps } from './AccountConnectSingleSelector.types';
import styles from './AccountConnectSingleSelector.styles';
-import USER_INTENT from '../../../../constants/permissions';
+import { USER_INTENT } from '../../../../constants/permissions';
const AccountConnectSingleSelector = ({
accounts,
diff --git a/app/components/Views/AccountConnect/AccountConnectSingleSelector/AccountConnectSingleSelector.types.ts b/app/components/Views/AccountConnect/AccountConnectSingleSelector/AccountConnectSingleSelector.types.ts
index a07edeb7466..839bbf19eeb 100644
--- a/app/components/Views/AccountConnect/AccountConnectSingleSelector/AccountConnectSingleSelector.types.ts
+++ b/app/components/Views/AccountConnect/AccountConnectSingleSelector/AccountConnectSingleSelector.types.ts
@@ -1,6 +1,6 @@
// External dependencies.
import { UseAccounts } from '../../../hooks/useAccounts';
-import USER_INTENT from '../../../../constants/permissions';
+import { USER_INTENT } from '../../../../constants/permissions';
import { AccountConnectScreens } from '../AccountConnect.types';
/**
diff --git a/app/components/Views/AccountPermissions/AccountPermissions.tsx b/app/components/Views/AccountPermissions/AccountPermissions.tsx
index b220895e4b3..761e0ca6567 100755
--- a/app/components/Views/AccountPermissions/AccountPermissions.tsx
+++ b/app/components/Views/AccountPermissions/AccountPermissions.tsx
@@ -45,7 +45,7 @@ import {
} from './AccountPermissions.types';
import AccountPermissionsConnected from './AccountPermissionsConnected';
import AccountPermissionsRevoke from './AccountPermissionsRevoke';
-import USER_INTENT from '../../../constants/permissions';
+import { USER_INTENT } from '../../../constants/permissions';
const AccountPermissions = (props: AccountPermissionsProps) => {
const navigation = useNavigation();
diff --git a/app/components/Views/Settings/index.js b/app/components/Views/Settings/index.js
index 846402c9512..f07187f4b45 100644
--- a/app/components/Views/Settings/index.js
+++ b/app/components/Views/Settings/index.js
@@ -9,6 +9,7 @@ import { MetaMetricsEvents } from '../../../core/Analytics';
import { connect } from 'react-redux';
import { ThemeContext, mockTheme } from '../../../util/theme';
import Routes from '../../../constants/navigation/Routes';
+import { createSnapsSettingsListNavDetails } from '../Snaps/SnapsSettingsList/SnapsSettingsList';
const createStyles = (colors) =>
StyleSheet.create({
@@ -102,6 +103,10 @@ class Settings extends PureComponent {
this.props.navigation.navigate('ContactsSettings');
};
+ onPressSnaps = () => {
+ this.props.navigation.navigate(...createSnapsSettingsListNavDetails());
+ };
+
render = () => {
const { seedphraseBackedUp } = this.props;
const colors = this.context.colors || mockTheme.colors;
@@ -135,6 +140,11 @@ class Settings extends PureComponent {
description={strings('app_settings.networks_desc')}
onPress={this.onPressNetworks}
/>
+
+ StyleSheet.create({
+ snapSettingsContainer: {
+ flex: 1,
+ marginHorizontal: 16,
+ },
+ itemPaddedContainer: {
+ paddingVertical: 16,
+ },
+ removeSection: {
+ paddingTop: 32,
+ },
+ removeButton: {
+ marginVertical: 16,
+ },
+ });
+
+export default styleSheet;
diff --git a/app/components/Views/Snaps/SnapSettings/SnapSettings.tsx b/app/components/Views/Snaps/SnapSettings/SnapSettings.tsx
new file mode 100644
index 00000000000..66db6285e96
--- /dev/null
+++ b/app/components/Views/Snaps/SnapSettings/SnapSettings.tsx
@@ -0,0 +1,118 @@
+import React, { useCallback, useEffect } from 'react';
+import { View, ScrollView, SafeAreaView } from 'react-native';
+
+import Engine from '../../../../core/Engine';
+import Text, {
+ TextVariant,
+} from '../../../../component-library/components/Texts/Text';
+import Button, {
+ ButtonVariants,
+ ButtonWidthTypes,
+} from '../../../../component-library/components/Buttons/Button';
+
+import stylesheet from './SnapSettings.styles';
+import {
+ createNavigationDetails,
+ useParams,
+} from '../../../../util/navigation/navUtils';
+import Routes from '../../../../constants/navigation/Routes';
+import { Snap } from '@metamask/snaps-utils';
+import { getNavigationOptionsTitle } from '../../../UI/Navbar';
+import { useNavigation } from '@react-navigation/native';
+import { SnapDetails } from '../components/SnapDetails';
+import { SnapDescription } from '../components/SnapDescription';
+import { SnapPermissions } from '../components/SnapPermissions';
+import { SNAP_SETTINGS_REMOVE_BUTTON } from '../../../../constants/test-ids';
+import { strings } from '../../../../../locales/i18n';
+import { useStyles } from '../../../hooks/useStyles';
+import { useSelector } from 'react-redux';
+
+interface SnapSettingsProps {
+ snap: Snap;
+}
+
+export const createSnapSettingsNavDetails =
+ createNavigationDetails(Routes.SNAPS.SNAP_SETTINGS);
+
+const SnapSettings = () => {
+ const { styles, theme } = useStyles(stylesheet, {});
+ const { colors } = theme;
+ const navigation = useNavigation();
+
+ const { snap } = useParams();
+
+ const permissionsState = useSelector(
+ (state: any) => state.engine.backgroundState.PermissionController,
+ );
+
+ function getPermissionSubjects(state: any) {
+ return state.subjects || {};
+ }
+
+ function getPermissions(state: any, origin: any) {
+ return getPermissionSubjects(state)[origin]?.permissions;
+ }
+
+ const permissionsFromController = getPermissions(permissionsState, snap.id);
+
+ useEffect(() => {
+ navigation.setOptions(
+ getNavigationOptionsTitle(
+ `${snap.manifest.proposedName}`,
+ navigation,
+ false,
+ colors,
+ false,
+ ),
+ );
+ }, [colors, navigation, snap.manifest.proposedName]);
+
+ const removeSnap = useCallback(async () => {
+ const { SnapController } = Engine.context as any;
+ await SnapController.removeSnap(snap.id);
+ navigation.goBack();
+ }, [navigation, snap.id]);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {strings(
+ 'app_settings.snaps.snap_settings.remove_snap_section_title',
+ )}
+
+
+ {strings(
+ 'app_settings.snaps.snap_settings.remove_snap_section_description',
+ )}
+
+
+
+
+
+ );
+};
+
+export default React.memo(SnapSettings);
diff --git a/app/components/Views/Snaps/SnapSettings/index.ts b/app/components/Views/Snaps/SnapSettings/index.ts
new file mode 100644
index 00000000000..6089bef1956
--- /dev/null
+++ b/app/components/Views/Snaps/SnapSettings/index.ts
@@ -0,0 +1,4 @@
+/* eslint-disable import/prefer-default-export */
+import SnapSettings from './SnapSettings';
+
+export { SnapSettings };
diff --git a/app/components/Views/Snaps/SnapSettings/test/SnapSettings.test.tsx b/app/components/Views/Snaps/SnapSettings/test/SnapSettings.test.tsx
new file mode 100644
index 00000000000..e82daf1b0a3
--- /dev/null
+++ b/app/components/Views/Snaps/SnapSettings/test/SnapSettings.test.tsx
@@ -0,0 +1,224 @@
+import React from 'react';
+import { fireEvent, waitFor } from '@testing-library/react-native';
+import { SemVerVersion, Status } from '@metamask/snaps-utils';
+import SnapSettings from '../SnapSettings';
+import {
+ SNAP_DETAILS_CELL,
+ SNAP_PERMISSIONS,
+ SNAP_PERMISSION_CELL,
+ SNAP_SETTINGS_REMOVE_BUTTON,
+} from '../../../../../constants/test-ids';
+import Engine from '../../../../../core/Engine';
+import renderWithProvider from '../../../../../util/test/renderWithProvider';
+import {
+ PermissionConstraint,
+ SubjectPermissions,
+} from '@metamask/permission-controller';
+
+jest.mock('../../../../../core/Engine', () => ({
+ context: {
+ SnapController: {
+ removeSnap: jest.fn(),
+ },
+ },
+}));
+
+jest.mock('../../../../../util/navigation/navUtils', () => ({
+ useParams: () => ({
+ snap: {
+ blocked: false,
+ enabled: true,
+ permissionName: 'wallet_snap_npm:@chainsafe/filsnap',
+ id: 'npm:@chainsafe/filsnap',
+ initialPermissions: {
+ 'endowment:network-access': {},
+ 'endowment:rpc': {
+ dapps: true,
+ snaps: true,
+ },
+ snap_confirm: {},
+ snap_getBip44Entropy: [
+ {
+ coinType: 1,
+ },
+ {
+ coinType: 461,
+ },
+ ],
+ snap_manageState: {},
+ },
+ manifest: {
+ version: '2.3.13' as SemVerVersion,
+ proposedName: 'Filsnap',
+ description: 'The Filecoin snap.',
+ repository: {
+ type: 'git',
+ url: 'https://github.com/Chainsafe/filsnap.git',
+ },
+ source: {
+ shasum: 'Z7lh6iD1yjfKES/WutUyxepg5Dgp8Xjo3kivsz9vpwc=',
+ location: {
+ npm: {
+ filePath: 'dist/bundle.js',
+ packageName: '@chainsafe/filsnap',
+ registry: 'https://registry.npmjs.org/',
+ },
+ },
+ },
+ initialPermissions: {
+ 'endowment:network-access': {},
+ 'endowment:rpc': {
+ dapps: true,
+ snaps: true,
+ },
+ snap_confirm: {},
+ snap_getBip44Entropy: [
+ {
+ coinType: 1,
+ },
+ {
+ coinType: 461,
+ },
+ ],
+ snap_manageState: {},
+ },
+ manifestVersion: '0.1',
+ },
+ status: 'runing' as Status,
+ version: '2.3.13' as SemVerVersion,
+ versionHistory: [
+ {
+ version: '2.3.13',
+ date: 1684964145490,
+ origin: 'metamask-mobile',
+ },
+ ],
+ },
+ }),
+ createNavigationDetails: jest.fn(),
+}));
+
+const mockDate = 1684964145490;
+const mockDate2 = 1686081721987;
+
+const mockPermissions: SubjectPermissions = {
+ 'endowment:network-access': {
+ id: 'Bjj3InYtb6U4ak-uja0f_',
+ parentCapability: 'endowment:network-access',
+ invoker: 'npm:@chainsafe/filsnap',
+ caveats: null,
+ date: mockDate,
+ },
+ 'endowment:rpc': {
+ id: 'Zma-vejrSvLtHmLrbSBAX',
+ parentCapability: 'endowment:rpc',
+ invoker: 'npm:@chainsafe/filsnap',
+ caveats: [
+ {
+ type: 'rpcOrigin',
+ value: {
+ dapps: true,
+ snaps: true,
+ },
+ },
+ ],
+ date: mockDate2,
+ },
+ snap_confirm: {
+ id: 'tVtSEUjc48Ab-gF6UI7X3',
+ parentCapability: 'snap_confirm',
+ invoker: 'npm:@chainsafe/filsnap',
+ caveats: null,
+ date: mockDate2,
+ },
+ snap_manageState: {
+ id: 'BKbg3uDSHHu0D1fCUTOmS',
+ parentCapability: 'snap_manageState',
+ invoker: 'npm:@chainsafe/filsnap',
+ caveats: null,
+ date: mockDate2,
+ },
+ snap_getBip44Entropy: {
+ id: 'MuqnOW-7BRg94sRDmVnDK',
+ parentCapability: 'snap_getBip44Entropy',
+ invoker: 'npm:@chainsafe/filsnap',
+ caveats: [
+ {
+ type: 'permittedCoinTypes',
+ value: [
+ {
+ coinType: 1,
+ },
+ {
+ coinType: 461,
+ },
+ ],
+ },
+ ],
+ date: mockDate2,
+ },
+};
+
+const mockGoBack = jest.fn();
+jest.mock('@react-navigation/native', () => {
+ const actualReactNavigation = jest.requireActual('@react-navigation/native');
+ return {
+ ...actualReactNavigation,
+ useNavigation: () => ({
+ setOptions: jest.fn(),
+ goBack: mockGoBack,
+ }),
+ };
+});
+
+const engineState = {
+ engine: {
+ backgroundState: {
+ PermissionController: {
+ subjects: {
+ 'npm:@chainsafe/filsnap': {
+ permissions: mockPermissions,
+ },
+ },
+ },
+ },
+ },
+};
+
+describe('SnapSettings', () => {
+ it('renders correctly', () => {
+ const { getAllByTestId, getByTestId } = renderWithProvider(
+ ,
+ {
+ state: engineState,
+ },
+ );
+
+ const removeButton = getByTestId(SNAP_SETTINGS_REMOVE_BUTTON);
+ const description = getByTestId(SNAP_DETAILS_CELL);
+ const permissionContainer = getByTestId(SNAP_PERMISSIONS);
+ const permissions = getAllByTestId(SNAP_PERMISSION_CELL);
+ expect(removeButton).toBeTruthy();
+ expect(description).toBeTruthy();
+ expect(permissionContainer).toBeTruthy();
+ expect(permissions.length).toBe(7);
+ expect(removeButton.props.children[1].props.children).toBe(
+ 'Remove Filsnap',
+ );
+ });
+
+ it('remove snap and goes back when Remove button is pressed', async () => {
+ const { getByTestId } = renderWithProvider(, {
+ state: engineState,
+ });
+
+ const removeButton = getByTestId(SNAP_SETTINGS_REMOVE_BUTTON);
+ fireEvent(removeButton, 'onPress');
+ expect(Engine.context.SnapController.removeSnap).toHaveBeenCalledWith(
+ 'npm:@chainsafe/filsnap',
+ );
+ await waitFor(() => {
+ expect(mockGoBack).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/app/components/Views/Snaps/SnapsSettingsList/SnapsSettingsList.styles.ts b/app/components/Views/Snaps/SnapsSettingsList/SnapsSettingsList.styles.ts
new file mode 100644
index 00000000000..7496c508e3f
--- /dev/null
+++ b/app/components/Views/Snaps/SnapsSettingsList/SnapsSettingsList.styles.ts
@@ -0,0 +1,38 @@
+import { StyleSheet } from 'react-native';
+import { Theme } from '../../../../util/theme/models';
+
+/**
+ *
+ * @param params Style sheet params.
+ * @param params.theme App theme from ThemeContext.
+ * @param params.vars Inputs that the style sheet depends on.
+ * @returns StyleSheet object.
+ */
+const styleSheet = (params: { theme: Theme }) => {
+ const { theme } = params;
+ const { colors } = theme;
+ return StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: colors.background.default,
+ },
+ webviewContainer: {
+ flex: 0.1,
+ },
+ input: {
+ height: 30,
+ borderColor: colors.border.default,
+ borderWidth: 1,
+ margin: 10,
+ padding: 5,
+ borderRadius: 5,
+ },
+ installBtn: {
+ marginHorizontal: 10,
+ width: '60%',
+ alignSelf: 'center',
+ },
+ });
+};
+
+export default styleSheet;
diff --git a/app/components/Views/Snaps/SnapsSettingsList/SnapsSettingsList.tsx b/app/components/Views/Snaps/SnapsSettingsList/SnapsSettingsList.tsx
new file mode 100644
index 00000000000..daae3006c8d
--- /dev/null
+++ b/app/components/Views/Snaps/SnapsSettingsList/SnapsSettingsList.tsx
@@ -0,0 +1,115 @@
+import React, { useState, useEffect } from 'react';
+import { View, Alert, ScrollView, TextInput } from 'react-native';
+import { useSelector } from 'react-redux';
+import { useNavigation } from '@react-navigation/native';
+
+import { SnapElement } from '../components/SnapElement';
+import Button, {
+ ButtonVariants,
+ ButtonSize,
+} from '../../../../component-library/components/Buttons/Button';
+import { getNavigationOptionsTitle } from '../../../UI/Navbar';
+import Engine from '../../../../core/Engine';
+import { createNavigationDetails } from '../../../../util/navigation/navUtils';
+import Routes from '../../../../constants/navigation/Routes';
+import { strings } from '../../../../../locales/i18n';
+import { Snap } from '@metamask/snaps-utils';
+import { useStyles } from '../../../../component-library/hooks';
+import stylesheet from './SnapsSettingsList.styles';
+
+const testSnaps = {
+ iOSLocalSnap: 'local:http://localhost:3000/snap/',
+ iOSLocalHelloWorldSnap: 'local:http://localhost:3000/helloworldsnap/',
+ androidLocalSnap: 'local:http://10.0.2.2:3000/snap/',
+ starknetSnap: 'npm:@consensys/starknet-snap',
+ filSnap: 'npm:@chainsafe/filsnap',
+};
+
+export const createSnapsSettingsListNavDetails = createNavigationDetails(
+ Routes.SNAPS.SNAPS_SETTINGS_LIST,
+);
+
+const SnapsSettingsList = () => {
+ const navigation = useNavigation();
+ const { styles, theme } = useStyles(stylesheet, {});
+ const { colors } = theme;
+
+ const url = testSnaps.filSnap;
+
+ const [snapInput, setSnapInput] = useState(url);
+ const snaps = useSelector(
+ (state: any) => state.engine.backgroundState.SnapController.snaps,
+ );
+
+ useEffect(() => {
+ navigation.setOptions(
+ getNavigationOptionsTitle(
+ strings('app_settings.snaps.title'),
+ navigation,
+ false,
+ colors,
+ false,
+ ),
+ );
+ }, [colors, navigation, snaps]);
+
+ const installSuccessMsg = (id: string) => `Snap ${id} installed\n\n🎉🎉🎉`;
+ const installFailedMsg = (id: string, e?: string) =>
+ `Snap ${id} failed to install\n\n💀💀💀\n\n${e}`;
+
+ const installSnap = async (snapId: string, origin: string): Promise => {
+ const { SnapController } = Engine.context as any;
+ let message: string;
+ try {
+ const result = await SnapController.processRequestedSnap(
+ origin,
+ snapId,
+ '',
+ );
+ if (result.error) {
+ message = installFailedMsg(snapId, result.error);
+ } else {
+ message = installSuccessMsg(snapId);
+ setSnapInput('');
+ }
+ } catch (e: any) {
+ message = installFailedMsg(snapId, JSON.stringify(e));
+ }
+ Alert.alert('Snap Alert', message, [
+ {
+ text: 'Ok',
+ onPress: () => null,
+ style: 'cancel',
+ },
+ ]);
+ };
+
+ return (
+
+ {__DEV__ ? (
+
+
+
+ ) : null}
+
+ {(Object.values(snaps) as Snap[]).map((snap: Snap) => (
+
+ ))}
+
+
+ );
+};
+
+export default React.memo(SnapsSettingsList);
diff --git a/app/components/Views/Snaps/SnapsSettingsList/index.ts b/app/components/Views/Snaps/SnapsSettingsList/index.ts
new file mode 100644
index 00000000000..10f3ff92508
--- /dev/null
+++ b/app/components/Views/Snaps/SnapsSettingsList/index.ts
@@ -0,0 +1,4 @@
+/* eslint-disable import/prefer-default-export */
+import SnapsSettingsList from './SnapsSettingsList';
+
+export { SnapsSettingsList };
diff --git a/app/components/Views/Snaps/SnapsSettingsList/test/SnapSettingsList.test.tsx b/app/components/Views/Snaps/SnapsSettingsList/test/SnapSettingsList.test.tsx
new file mode 100644
index 00000000000..615ee0a0993
--- /dev/null
+++ b/app/components/Views/Snaps/SnapsSettingsList/test/SnapSettingsList.test.tsx
@@ -0,0 +1,210 @@
+import React from 'react';
+import { fireEvent } from '@testing-library/react-native';
+import SnapsSettingsList from '../SnapsSettingsList';
+import { SemVerVersion, Snap, Status } from '@metamask/snaps-utils';
+import renderWithProvider from '../../../../../util/test/renderWithProvider';
+import { SNAP_ElEMENT } from '../../../../../constants/test-ids';
+import { createSnapSettingsNavDetails } from '../../SnapSettings/SnapSettings';
+
+const mockNavigate = jest.fn();
+jest.mock('@react-navigation/native', () => ({
+ ...jest.requireActual('@react-navigation/native'),
+ useNavigation: () => ({
+ navigate: mockNavigate,
+ setOptions: jest.fn(),
+ }),
+}));
+
+describe('SnapsSettingsList', () => {
+ const mockSnap: Snap = {
+ blocked: false,
+ enabled: true,
+ permissionName: 'wallet_snap_npm:@chainsafe/filsnap',
+ id: 'npm:@chainsafe/filsnap',
+ initialPermissions: {
+ 'endowment:network-access': {},
+ 'endowment:rpc': {
+ dapps: true,
+ snaps: true,
+ },
+ snap_confirm: {},
+ snap_getBip44Entropy: [
+ {
+ coinType: 1,
+ },
+ {
+ coinType: 461,
+ },
+ ],
+ snap_manageState: {},
+ },
+ manifest: {
+ version: '2.3.13' as SemVerVersion,
+ proposedName: 'Filsnap',
+ description: 'The Filecoin snap.',
+ repository: {
+ type: 'git',
+ url: 'https://github.com/Chainsafe/filsnap.git',
+ },
+ source: {
+ shasum: 'Z7lh6iD1yjfKES/WutUyxepg5Dgp8Xjo3kivsz9vpwc=',
+ location: {
+ npm: {
+ filePath: 'dist/bundle.js',
+ packageName: '@chainsafe/filsnap',
+ registry: 'https://registry.npmjs.org/',
+ },
+ },
+ },
+ initialPermissions: {
+ 'endowment:network-access': {},
+ 'endowment:rpc': {
+ dapps: true,
+ snaps: true,
+ },
+ snap_confirm: {},
+ snap_getBip44Entropy: [
+ {
+ coinType: 1,
+ },
+ {
+ coinType: 461,
+ },
+ ],
+ snap_manageState: {},
+ },
+ manifestVersion: '0.1',
+ },
+ status: 'runing' as Status,
+ version: '2.3.13' as SemVerVersion,
+ versionHistory: [
+ {
+ version: '2.3.13',
+ date: 1684964145490,
+ origin: 'metamask-mobile',
+ },
+ ],
+ };
+
+ const mockSnap2: Snap = {
+ blocked: false,
+ enabled: true,
+ permissionName: 'wallet_snap_npm:@lavamoat/tss-snap',
+ id: 'npm:@lavamoat/tss-snap',
+ initialPermissions: {
+ snap_manageState: {},
+ 'endowment:rpc': {
+ dapps: true,
+ snaps: false,
+ },
+ },
+ manifest: {
+ version: '0.2.0' as SemVerVersion,
+ description:
+ 'Snap using threshold signatures to sign messages and transactions.',
+ proposedName: 'Threshold Signatures',
+ repository: {
+ type: 'git',
+ url: 'https://github.com/LavaMoat/tss-snap',
+ },
+ source: {
+ shasum: 'cXhvie+xy84HqQFW+1dae44e0EV/kr8PUzt+6u+09eE=',
+ location: {
+ npm: {
+ filePath: 'bundle.js',
+ iconPath: 'images/icon.svg',
+ packageName: '@lavamoat/tss-snap',
+ registry: 'https://registry.npmjs.org/',
+ },
+ },
+ },
+ initialPermissions: {
+ snap_manageState: {},
+ 'endowment:rpc': {
+ dapps: true,
+ snaps: false,
+ },
+ },
+ manifestVersion: '0.1',
+ },
+ status: 'stopped' as Status,
+ version: '0.2.0' as SemVerVersion,
+ versionHistory: [
+ {
+ version: '0.2.0',
+ date: 1684872159230,
+ origin: 'tss.ac',
+ },
+ ],
+ };
+ const mockSnaps: Snap[] = [mockSnap, mockSnap2];
+
+ const engineState = {
+ engine: {
+ backgroundState: {
+ SnapController: {
+ snaps: mockSnaps,
+ processRequestedSnap: jest.fn(),
+ },
+ },
+ },
+ };
+
+ it('renders correctly', () => {
+ const { getByPlaceholderText, getByText } = renderWithProvider(
+ ,
+ { state: engineState },
+ );
+ expect(getByPlaceholderText('Snap to install')).toBeTruthy();
+ expect(getByText('Install Snap')).toBeTruthy();
+ });
+
+ it('renders the dev settings when __DEV__ is true', () => {
+ // Mock the __DEV__ flag
+ global.__DEV__ = true;
+ const { queryByText } = renderWithProvider(, {
+ state: engineState,
+ });
+
+ const installButton = queryByText('Install Snap');
+ expect(installButton).toBeTruthy();
+ });
+
+ it('does not render the dev settings when __DEV__ is false (production)', () => {
+ // Mock the __DEV__ flag
+ global.__DEV__ = false;
+
+ const { queryByText } = renderWithProvider(, {
+ state: engineState,
+ });
+
+ const installButton = queryByText('Install Snap');
+ const textInput = queryByText('Snap to install');
+ expect(installButton).toBeNull();
+ expect(textInput).toBeNull();
+ });
+
+ it('renders the list of SnapElements correctly', () => {
+ const { getAllByTestId } = renderWithProvider(, {
+ state: engineState,
+ });
+ const snapElements = getAllByTestId(SNAP_ElEMENT);
+ expect(snapElements.length).toBe(2);
+ });
+
+ it('navigates to SnapSettings when SnapElement is tapped', () => {
+ const { getAllByTestId } = renderWithProvider(, {
+ state: engineState,
+ });
+
+ const snapElement = getAllByTestId(SNAP_ElEMENT); // adjust to get specific SnapElement if there are multiple
+ fireEvent.press(snapElement[0]);
+ expect(mockNavigate).toHaveBeenCalledWith(
+ ...createSnapSettingsNavDetails({ snap: mockSnap }),
+ );
+ fireEvent.press(snapElement[1]);
+ expect(mockNavigate).toHaveBeenCalledWith(
+ ...createSnapSettingsNavDetails({ snap: mockSnap2 }),
+ );
+ });
+});
diff --git a/app/components/Views/Snaps/components/SnapDescription/SnapDescription.styles.tsx b/app/components/Views/Snaps/components/SnapDescription/SnapDescription.styles.tsx
new file mode 100644
index 00000000000..5b525320235
--- /dev/null
+++ b/app/components/Views/Snaps/components/SnapDescription/SnapDescription.styles.tsx
@@ -0,0 +1,44 @@
+import { StyleSheet } from 'react-native';
+import { Theme } from '../../../../../util/theme/models';
+
+/**
+ * @param params Style sheet params.
+ * @param params.theme App theme from ThemeContext.
+ * @param params.vars Inputs that the style sheet depends on.
+ * @returns StyleSheet object.
+ */
+const styleSheet = (params: { theme: Theme }) => {
+ const { theme } = params;
+ const { colors } = theme;
+ return StyleSheet.create({
+ snapInfoContainer: {
+ backgroundColor: colors.background.default,
+ borderRadius: 10,
+ borderWidth: 1,
+ borderColor: colors.border.default,
+ },
+ titleContainer: {
+ alignItems: 'center',
+ padding: 4,
+ flexDirection: 'row',
+ },
+ iconContainer: {
+ paddingHorizontal: 8,
+ flexDirection: 'row',
+ },
+ snapCell: {
+ borderRadius: 8,
+ borderWidth: 0,
+ },
+ detailsContainerWithBorder: {
+ padding: 16,
+ borderColor: colors.border.default,
+ borderTopWidth: 1,
+ alignItems: 'center',
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ },
+ });
+};
+
+export default styleSheet;
diff --git a/app/components/Views/Snaps/components/SnapDescription/SnapDescription.tsx b/app/components/Views/Snaps/components/SnapDescription/SnapDescription.tsx
new file mode 100644
index 00000000000..be9dad123f2
--- /dev/null
+++ b/app/components/Views/Snaps/components/SnapDescription/SnapDescription.tsx
@@ -0,0 +1,56 @@
+import React from 'react';
+import { View } from 'react-native';
+import Text, {
+ TextVariant,
+} from '../../../../../component-library/components/Texts/Text';
+import Icon, {
+ IconColor,
+ IconName,
+ IconSize,
+} from '../../../../../component-library/components/Icons/Icon';
+import stylesheet from './SnapDescription.styles';
+import {
+ SNAP_DESCRIPTION,
+ SNAP_DESCRIPTION_TITLE,
+} from '../../../../../constants/test-ids';
+import { useStyles } from '../../../../../component-library/hooks';
+
+interface SnapDescriptionProps {
+ snapName: string;
+ snapDescription: string;
+}
+
+const SnapDescription = ({
+ snapName,
+ snapDescription,
+}: SnapDescriptionProps) => {
+ const { styles } = useStyles(stylesheet, {});
+
+ return (
+
+
+
+
+
+
+ {snapName}
+
+
+
+
+ {snapDescription}
+
+
+
+ );
+};
+
+export default React.memo(SnapDescription);
diff --git a/app/components/Views/Snaps/components/SnapDescription/index.ts b/app/components/Views/Snaps/components/SnapDescription/index.ts
new file mode 100644
index 00000000000..d8617d16944
--- /dev/null
+++ b/app/components/Views/Snaps/components/SnapDescription/index.ts
@@ -0,0 +1,4 @@
+/* eslint-disable import/prefer-default-export */
+import SnapDescription from './SnapDescription';
+
+export { SnapDescription };
diff --git a/app/components/Views/Snaps/components/SnapDescription/test/SnapDescription.test.tsx b/app/components/Views/Snaps/components/SnapDescription/test/SnapDescription.test.tsx
new file mode 100644
index 00000000000..71cb6a6a987
--- /dev/null
+++ b/app/components/Views/Snaps/components/SnapDescription/test/SnapDescription.test.tsx
@@ -0,0 +1,22 @@
+import React from 'react';
+import { render } from '@testing-library/react-native';
+import SnapDescription from '../SnapDescription';
+import {
+ SNAP_DESCRIPTION_TITLE,
+ SNAP_DESCRIPTION,
+} from '../../../../../../constants/test-ids';
+
+describe('SnapDescription', () => {
+ it('renders correctly', async () => {
+ const { getByTestId } = render(
+ ,
+ );
+ const title = await getByTestId(SNAP_DESCRIPTION_TITLE);
+ const description = await getByTestId(SNAP_DESCRIPTION);
+ expect(title.props.children).toBe('test snap');
+ expect(description.props.children).toBe('Test snap description');
+ });
+});
diff --git a/app/components/Views/Snaps/components/SnapDetails/SnapDetails.styles.ts b/app/components/Views/Snaps/components/SnapDetails/SnapDetails.styles.ts
new file mode 100644
index 00000000000..23123619fcf
--- /dev/null
+++ b/app/components/Views/Snaps/components/SnapDetails/SnapDetails.styles.ts
@@ -0,0 +1,43 @@
+import { StyleSheet } from 'react-native';
+import { Theme } from '../../../../../util/theme/models';
+
+/**
+ *
+ * @param params Style sheet params.
+ * @param params.theme App theme from ThemeContext.
+ * @param params.vars Inputs that the style sheet depends on.
+ * @returns StyleSheet object.
+ */
+const styleSheet = (params: { theme: Theme }) => {
+ const { theme } = params;
+ const { colors } = theme;
+ return StyleSheet.create({
+ snapInfoContainer: {
+ backgroundColor: colors.background.default,
+ borderRadius: 8,
+ borderWidth: 1,
+ borderColor: colors.border.default,
+ },
+ snapCell: {
+ borderRadius: 8,
+ borderWidth: 0,
+ },
+ detailsContainerWithBorder: {
+ padding: 16,
+ borderColor: colors.border.default,
+ borderTopWidth: 1,
+ borderBottomWidth: 1,
+ alignItems: 'center',
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ },
+ detailsContainer: {
+ padding: 16,
+ alignItems: 'center',
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ },
+ });
+};
+
+export default styleSheet;
diff --git a/app/components/Views/Snaps/components/SnapDetails/SnapDetails.tsx b/app/components/Views/Snaps/components/SnapDetails/SnapDetails.tsx
new file mode 100644
index 00000000000..15502a2669d
--- /dev/null
+++ b/app/components/Views/Snaps/components/SnapDetails/SnapDetails.tsx
@@ -0,0 +1,122 @@
+import React, { useCallback, useMemo, useState } from 'react';
+import { View, Switch } from 'react-native';
+
+import Engine from '../../../../../core/Engine';
+import Text, {
+ TextVariant,
+ TextColor,
+} from '../../../../../component-library/components/Texts/Text';
+import Cell, {
+ CellVariants,
+} from '../../../../../component-library/components/Cells/Cell';
+import { AvatarVariants } from '../../../../../component-library/components/Avatars/Avatar/Avatar.types';
+import { IconName } from '../../../../../component-library/components/Icons/Icon';
+import { Snap } from '@metamask/snaps-utils';
+import stylesheet from './SnapDetails.styles';
+import { SnapVersionBadge } from '../SnapVersionTag';
+import { toDateFormat } from '../../../../../util/date';
+import {
+ SNAP_DETAILS_CELL,
+ SNAP_DETAILS_INSTALL_DATE,
+ SNAP_DETAILS_INSTALL_ORIGIN,
+ SNAP_DETAILS_SWITCH,
+} from '../../../../../constants/test-ids';
+import { strings } from '../../../../../../locales/i18n';
+import { useStyles } from '../../../../../component-library/hooks';
+import Label from '../../../../../component-library/components/Form/Label';
+
+interface SnapDetailsProps {
+ snap: Snap;
+}
+
+const SnapDetails = ({ snap }: SnapDetailsProps) => {
+ const { styles, theme } = useStyles(stylesheet, {});
+ const { colors } = theme;
+ const [enabled, setEnabled] = useState(snap.enabled);
+
+ const enableSnap = useCallback(async () => {
+ const { SnapController } = Engine.context as any;
+ await SnapController.enableSnap(snap.id);
+ }, [snap.id]);
+
+ const disableSnap = useCallback(async () => {
+ const { SnapController } = Engine.context as any;
+ await SnapController.disableSnap(snap.id);
+ }, [snap.id]);
+
+ const handleOnValueChange = useCallback(
+ (newValue) => {
+ setEnabled(newValue);
+ if (newValue) {
+ enableSnap();
+ } else {
+ disableSnap();
+ }
+ },
+ [disableSnap, enableSnap],
+ );
+
+ const snapInstalledDate: string = useMemo(
+ () =>
+ strings('app_settings.snaps.snap_details.install_date', {
+ date: toDateFormat(snap.versionHistory[0].date),
+ }),
+ [snap.versionHistory],
+ );
+
+ return (
+
+ |
+
+
+
+
+
+
+
+
+ {snap.versionHistory[0].origin}
+
+
+ {snapInstalledDate}
+
+
+
+
+
+
+
+
+ );
+};
+
+export default React.memo(SnapDetails);
diff --git a/app/components/Views/Snaps/components/SnapDetails/index.ts b/app/components/Views/Snaps/components/SnapDetails/index.ts
new file mode 100644
index 00000000000..0a77ce8dcc8
--- /dev/null
+++ b/app/components/Views/Snaps/components/SnapDetails/index.ts
@@ -0,0 +1,4 @@
+/* eslint-disable import/prefer-default-export */
+import SnapDetails from './SnapDetails';
+
+export { SnapDetails };
diff --git a/app/components/Views/Snaps/components/SnapDetails/test/SnapDetails.test.tsx b/app/components/Views/Snaps/components/SnapDetails/test/SnapDetails.test.tsx
new file mode 100644
index 00000000000..35cffe16c7c
--- /dev/null
+++ b/app/components/Views/Snaps/components/SnapDetails/test/SnapDetails.test.tsx
@@ -0,0 +1,144 @@
+import React from 'react';
+import { render, fireEvent } from '@testing-library/react-native';
+import { Snap, SemVerVersion, Status } from '@metamask/snaps-utils';
+import SnapDetails from '../SnapDetails';
+import Engine from '../../../../../../core/Engine';
+import {
+ SNAP_DETAILS_CELL,
+ SNAP_DETAILS_INSTALL_DATE,
+ SNAP_DETAILS_INSTALL_ORIGIN,
+ SNAP_DETAILS_SWITCH,
+ SNAP_VERSION_BADGE,
+} from '../../../../../../constants/test-ids';
+
+jest.mock('../../../../../../core/Engine', () => ({
+ context: {
+ SnapController: {
+ enableSnap: jest.fn(),
+ disableSnap: jest.fn(),
+ },
+ },
+}));
+
+describe('SnapDetails', () => {
+ const mockSnap: Snap = {
+ blocked: false,
+ enabled: true,
+ permissionName: 'wallet_snap_npm:@chainsafe/filsnap',
+ id: 'npm:@chainsafe/filsnap',
+ initialPermissions: {
+ 'endowment:network-access': {},
+ 'endowment:rpc': {
+ dapps: true,
+ snaps: true,
+ },
+ snap_confirm: {},
+ snap_getBip44Entropy: [
+ {
+ coinType: 1,
+ },
+ {
+ coinType: 461,
+ },
+ ],
+ snap_manageState: {},
+ },
+ manifest: {
+ version: '2.3.13' as SemVerVersion,
+ proposedName: 'Filsnap',
+ description: 'The Filecoin snap.',
+ repository: {
+ type: 'git',
+ url: 'https://github.com/Chainsafe/filsnap.git',
+ },
+ source: {
+ shasum: 'Z7lh6iD1yjfKES/WutUyxepg5Dgp8Xjo3kivsz9vpwc=',
+ location: {
+ npm: {
+ filePath: 'dist/bundle.js',
+ packageName: '@chainsafe/filsnap',
+ registry: 'https://registry.npmjs.org/',
+ },
+ },
+ },
+ initialPermissions: {
+ 'endowment:network-access': {},
+ 'endowment:rpc': {
+ dapps: true,
+ snaps: true,
+ },
+ snap_confirm: {},
+ snap_getBip44Entropy: [
+ {
+ coinType: 1,
+ },
+ {
+ coinType: 461,
+ },
+ ],
+ snap_manageState: {},
+ },
+ manifestVersion: '0.1',
+ },
+ status: 'runing' as Status,
+ version: '2.3.13' as SemVerVersion,
+ versionHistory: [
+ {
+ version: '2.3.13',
+ date: 1684964145490,
+ origin: 'metamask-mobile',
+ },
+ ],
+ };
+
+ const installDateString = 'Installed on May 24 at 5:35 pm';
+
+ it('renders the correct snap details', async () => {
+ const { getByTestId } = render();
+
+ const cell = await getByTestId(SNAP_DETAILS_CELL);
+ const switchElement = await getByTestId(SNAP_DETAILS_SWITCH);
+ const installOrigin = await getByTestId(SNAP_DETAILS_INSTALL_ORIGIN);
+ const installDate = await getByTestId(SNAP_DETAILS_INSTALL_DATE);
+ const versionBadge = await getByTestId(SNAP_VERSION_BADGE);
+
+ expect(cell).toBeTruthy();
+ expect(switchElement).toBeTruthy();
+ expect(installOrigin).toBeTruthy();
+ expect(installDate).toBeTruthy();
+ expect(versionBadge).toBeTruthy();
+
+ expect(cell.props.children.props.title).toEqual(
+ mockSnap.manifest.proposedName,
+ );
+ expect(cell.props.children.props.secondaryText).toEqual(mockSnap.id);
+
+ expect(switchElement.props.value).toEqual(true);
+
+ expect(installOrigin.props.children).toEqual('metamask-mobile');
+
+ expect(installDate.props.children).toEqual(installDateString);
+ });
+
+ it('handles snap enable and disable', async () => {
+ const { getByTestId } = render();
+
+ const switchElement = await getByTestId(SNAP_DETAILS_SWITCH);
+
+ fireEvent(switchElement, 'onValueChange', false);
+
+ expect(Engine.context.SnapController.disableSnap).toHaveBeenCalledWith(
+ mockSnap.id,
+ );
+
+ expect(switchElement.props.value).toEqual(false);
+
+ fireEvent(switchElement, 'onValueChange', true);
+
+ expect(Engine.context.SnapController.enableSnap).toHaveBeenCalledWith(
+ mockSnap.id,
+ );
+
+ expect(switchElement.props.value).toEqual(true);
+ });
+});
diff --git a/app/components/Views/Snaps/components/SnapElement/SnapElement.styles.ts b/app/components/Views/Snaps/components/SnapElement/SnapElement.styles.ts
new file mode 100644
index 00000000000..c372855fe81
--- /dev/null
+++ b/app/components/Views/Snaps/components/SnapElement/SnapElement.styles.ts
@@ -0,0 +1,10 @@
+import { StyleSheet } from 'react-native';
+
+const styleSheet = () =>
+ StyleSheet.create({
+ snapCell: {
+ borderWidth: 0,
+ },
+ arrowContainer: { justifyContent: 'center', flex: 1 },
+ });
+export default styleSheet;
diff --git a/app/components/Views/Snaps/components/SnapElement/SnapElement.tsx b/app/components/Views/Snaps/components/SnapElement/SnapElement.tsx
new file mode 100644
index 00000000000..cdedfd5ffeb
--- /dev/null
+++ b/app/components/Views/Snaps/components/SnapElement/SnapElement.tsx
@@ -0,0 +1,46 @@
+import React from 'react';
+import { View } from 'react-native';
+import Cell, {
+ CellVariants,
+} from '../../../../../component-library/components/Cells/Cell';
+import { AvatarVariants } from '../../../../../component-library/components/Avatars/Avatar/Avatar.types';
+import Icon, {
+ IconName,
+} from '../../../../../component-library/components/Icons/Icon';
+import { Snap } from '@metamask/snaps-utils';
+
+import stylesheet from './SnapElement.styles';
+import { createSnapSettingsNavDetails } from '../../SnapSettings/SnapSettings';
+import { useNavigation } from '@react-navigation/native';
+import { SNAP_ElEMENT } from '../../../../../constants/test-ids';
+import { useStyles } from '../../../../../component-library/hooks';
+
+const SnapElement = (snap: Snap) => {
+ const { styles } = useStyles(stylesheet, {});
+ const { navigate } = useNavigation();
+
+ const onPress = () => {
+ navigate(...createSnapSettingsNavDetails({ snap }));
+ };
+
+ return (
+
+
+
+
+ |
+ );
+};
+
+export default SnapElement;
diff --git a/app/components/Views/Snaps/components/SnapElement/index.ts b/app/components/Views/Snaps/components/SnapElement/index.ts
new file mode 100644
index 00000000000..c4bd805c590
--- /dev/null
+++ b/app/components/Views/Snaps/components/SnapElement/index.ts
@@ -0,0 +1,4 @@
+/* eslint-disable import/prefer-default-export */
+import SnapElement from './SnapElement';
+
+export { SnapElement };
diff --git a/app/components/Views/Snaps/components/SnapElement/test/SnapElement.test.tsx b/app/components/Views/Snaps/components/SnapElement/test/SnapElement.test.tsx
new file mode 100644
index 00000000000..cb4925d1ffc
--- /dev/null
+++ b/app/components/Views/Snaps/components/SnapElement/test/SnapElement.test.tsx
@@ -0,0 +1,109 @@
+import React from 'react';
+import { render, fireEvent } from '@testing-library/react-native';
+import { SemVerVersion, Snap, Status } from '@metamask/snaps-utils';
+import SnapElement from '../SnapElement';
+import { SNAP_ElEMENT } from '../../../../../../constants/test-ids';
+import { createSnapSettingsNavDetails } from '../../../SnapSettings/SnapSettings';
+
+const mockNavigate = jest.fn();
+jest.mock('@react-navigation/native', () => {
+ const actualReactNavigation = jest.requireActual('@react-navigation/native');
+ return {
+ ...actualReactNavigation,
+ useNavigation: () => ({
+ navigate: mockNavigate,
+ }),
+ };
+});
+
+describe('SnapElement', () => {
+ const mockSnap: Snap = {
+ blocked: false,
+ enabled: true,
+ permissionName: 'wallet_snap_npm:@chainsafe/filsnap',
+ id: 'npm:@chainsafe/filsnap',
+ initialPermissions: {
+ 'endowment:network-access': {},
+ 'endowment:rpc': {
+ dapps: true,
+ snaps: true,
+ },
+ snap_confirm: {},
+ snap_getBip44Entropy: [
+ {
+ coinType: 1,
+ },
+ {
+ coinType: 461,
+ },
+ ],
+ snap_manageState: {},
+ },
+ manifest: {
+ version: '2.3.13' as SemVerVersion,
+ proposedName: 'Filsnap',
+ description: 'The Filecoin snap.',
+ repository: {
+ type: 'git',
+ url: 'https://github.com/Chainsafe/filsnap.git',
+ },
+ source: {
+ shasum: 'Z7lh6iD1yjfKES/WutUyxepg5Dgp8Xjo3kivsz9vpwc=',
+ location: {
+ npm: {
+ filePath: 'dist/bundle.js',
+ packageName: '@chainsafe/filsnap',
+ registry: 'https://registry.npmjs.org/',
+ },
+ },
+ },
+ initialPermissions: {
+ 'endowment:network-access': {},
+ 'endowment:rpc': {
+ dapps: true,
+ snaps: true,
+ },
+ snap_confirm: {},
+ snap_getBip44Entropy: [
+ {
+ coinType: 1,
+ },
+ {
+ coinType: 461,
+ },
+ ],
+ snap_manageState: {},
+ },
+ manifestVersion: '0.1',
+ },
+ status: 'runing' as Status,
+ version: '2.3.13' as SemVerVersion,
+ versionHistory: [
+ {
+ version: '2.3.13',
+ date: 1684964145490,
+ origin: 'metamask-mobile',
+ },
+ ],
+ };
+
+ it('renders correctly', () => {
+ const { getByTestId } = render();
+
+ const cell = getByTestId(SNAP_ElEMENT);
+ expect(cell.props.children.props.title).toEqual(
+ mockSnap.manifest.proposedName,
+ );
+ expect(cell.props.children.props.secondaryText).toEqual(mockSnap.id);
+ });
+
+ it('navigates when pressed', () => {
+ const { getByTestId } = render();
+
+ const cell = getByTestId(SNAP_ElEMENT);
+ fireEvent.press(cell);
+ expect(mockNavigate).toHaveBeenCalledWith(
+ ...createSnapSettingsNavDetails({ snap: mockSnap }),
+ );
+ });
+});
diff --git a/app/components/Views/Snaps/components/SnapPermissionCell/SnapPermissionCell.styles.ts b/app/components/Views/Snaps/components/SnapPermissionCell/SnapPermissionCell.styles.ts
new file mode 100644
index 00000000000..f192d5ac9b9
--- /dev/null
+++ b/app/components/Views/Snaps/components/SnapPermissionCell/SnapPermissionCell.styles.ts
@@ -0,0 +1,41 @@
+import { StyleSheet } from 'react-native';
+import { Theme } from '../../../../../util/theme/models';
+
+/**
+ *
+ * @param params Style sheet params.
+ * @param params.theme App theme from ThemeContext.
+ * @param params.vars Inputs that the style sheet depends on.
+ * @returns StyleSheet object.
+ */
+const styleSheet = (params: { theme: Theme }) => {
+ const { theme } = params;
+ const { colors } = theme;
+ return StyleSheet.create({
+ permissionCell: {
+ borderRadius: 10,
+ borderWidth: 0,
+ },
+ cellBase: {
+ flexDirection: 'row',
+ },
+ cellBaseInfo: {
+ flex: 1,
+ alignItems: 'flex-start',
+ },
+ secondaryText: {
+ color: colors.text.alternative,
+ },
+ iconWrapper: {
+ marginTop: 16,
+ marginRight: 16,
+ width: 32,
+ height: 32,
+ borderRadius: 16,
+ backgroundColor: colors.background.alternative,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ });
+};
+export default styleSheet;
diff --git a/app/components/Views/Snaps/components/SnapPermissionCell/SnapPermissionCell.tsx b/app/components/Views/Snaps/components/SnapPermissionCell/SnapPermissionCell.tsx
new file mode 100644
index 00000000000..1f2a810e89d
--- /dev/null
+++ b/app/components/Views/Snaps/components/SnapPermissionCell/SnapPermissionCell.tsx
@@ -0,0 +1,73 @@
+import React, { useMemo } from 'react';
+import { View } from 'react-native';
+import { useStyles } from '../../../../hooks/useStyles';
+import stylesheet from './SnapPermissionCell.styles';
+import {
+ SNAP_PERMISSIONS_DATE,
+ SNAP_PERMISSIONS_TITLE,
+ SNAP_PERMISSION_CELL,
+} from '../../../../../constants/test-ids';
+import Icon, {
+ IconColor,
+ IconName,
+ IconSize,
+} from '../../../../../component-library/components/Icons/Icon';
+import Card from '../../../../../component-library/components/Cards/Card';
+import Text, {
+ TextVariant,
+} from '../../../../../component-library/components/Texts/Text';
+import { strings } from '../../../../../../locales/i18n';
+import { toDateFormat } from '../../../../../util/date';
+
+export interface SnapPermissionCellProps {
+ title: string;
+ date?: number;
+}
+
+const SnapPermissionCell = ({ title, date }: SnapPermissionCellProps) => {
+ const snapInstalledDate: string = useMemo(
+ () =>
+ date
+ ? strings('app_settings.snaps.snap_permissions.approved_date', {
+ date: toDateFormat(date),
+ })
+ : strings(
+ 'app_settings.snaps.snap_permissions.permission_requested_now',
+ ),
+ [date],
+ );
+
+ const { styles } = useStyles(stylesheet, {});
+ return (
+
+
+
+
+
+
+
+ {title}
+
+
+ {snapInstalledDate}
+
+
+
+
+ );
+};
+
+export default React.memo(SnapPermissionCell);
diff --git a/app/components/Views/Snaps/components/SnapPermissionCell/index.ts b/app/components/Views/Snaps/components/SnapPermissionCell/index.ts
new file mode 100644
index 00000000000..8514a4b9546
--- /dev/null
+++ b/app/components/Views/Snaps/components/SnapPermissionCell/index.ts
@@ -0,0 +1,4 @@
+/* eslint-disable import/prefer-default-export */
+import SnapPermissionCell from './SnapPermissionCell';
+
+export { SnapPermissionCell };
diff --git a/app/components/Views/Snaps/components/SnapPermissionCell/test/SnapPermissionCell.test.tsx b/app/components/Views/Snaps/components/SnapPermissionCell/test/SnapPermissionCell.test.tsx
new file mode 100644
index 00000000000..8f9ea01d3e3
--- /dev/null
+++ b/app/components/Views/Snaps/components/SnapPermissionCell/test/SnapPermissionCell.test.tsx
@@ -0,0 +1,65 @@
+import React from 'react';
+import { render } from '@testing-library/react-native';
+import SnapPermissionCell, {
+ SnapPermissionCellProps,
+} from '../SnapPermissionCell';
+import {
+ SNAP_PERMISSIONS_DATE,
+ SNAP_PERMISSIONS_TITLE,
+ SNAP_PERMISSION_CELL,
+} from '../../../../../../constants/test-ids';
+
+describe('SnapPermissionCell', () => {
+ const defaultProps = {
+ title: 'Permission Title',
+ date: 1686005090788,
+ };
+
+ const setup = (props: SnapPermissionCellProps = defaultProps) => {
+ const utils = render();
+ const permissionCell = utils.getByTestId(SNAP_PERMISSION_CELL);
+ const permissionTitle = utils.getByTestId(SNAP_PERMISSIONS_TITLE);
+ const permissionDate = utils.getByTestId(SNAP_PERMISSIONS_DATE);
+
+ return {
+ ...utils,
+ permissionCell,
+ permissionTitle,
+ permissionDate,
+ };
+ };
+
+ test('renders correctly', () => {
+ const { permissionCell, permissionTitle, permissionDate } = setup();
+
+ const expectedDate = 'Approved on Jun 5 at 6:44 pm';
+
+ expect(permissionCell).toBeDefined();
+ expect(permissionTitle.props.children).toEqual(defaultProps.title);
+ expect(permissionDate.props.children).toEqual(expectedDate);
+ });
+
+ test('displays custom title and secondary text', () => {
+ const customProps = {
+ title: 'Custom Title',
+ date: 1686005090788,
+ };
+ const expectedDate = 'Approved on Jun 5 at 6:44 pm';
+ const { permissionTitle, permissionDate } = setup(customProps);
+
+ expect(permissionTitle.props.children).toEqual(customProps.title);
+ expect(permissionDate.props.children).toEqual(expectedDate);
+ });
+
+ test('displays "Requested now" as secondary text when date is undefined', () => {
+ const customProps = {
+ title: 'Custom Title',
+ };
+ const expectedDate = 'Requested now';
+ const { permissionCell, permissionTitle, permissionDate } =
+ setup(customProps);
+ expect(permissionCell).toBeDefined();
+ expect(permissionTitle.props.children).toEqual(customProps.title);
+ expect(permissionDate.props.children).toEqual(expectedDate);
+ });
+});
diff --git a/app/components/Views/Snaps/components/SnapPermissions/README.md b/app/components/Views/Snaps/components/SnapPermissions/README.md
new file mode 100644
index 00000000000..ac1390ab3b8
--- /dev/null
+++ b/app/components/Views/Snaps/components/SnapPermissions/README.md
@@ -0,0 +1,142 @@
+# SnapPermissions
+
+The SnapPermissions component is used to display permissions granted to a certain Snap. All permissions will be rendered as a list with each permission displayed as a Card with a title and the installation date of the Snap.
+
+## Props
+
+
+The SnapPermissions component expects one prop:
+
+1. `permissions: RequestedPermissions`: An object of type [RequestedPermissions](https://github.com/MetaMask/core/blob/43dfaf348c1af150a9e2b688ac23c2ab7c318fee/packages/permission-controller/src/Permission.ts#L199). This object contains the permission names as keys and [RequestedPermission](https://github.com/MetaMask/core/blob/43dfaf348c1af150a9e2b688ac23c2ab7c318fee/packages/permission-controller/src/Permission.ts#L194) as the value. The component will parse this object to render the correct titles.
+
+## Functionality
+This component derives human-readable titles for the provided permissions list and maps them to their corresponding cards. The snap methods/permissions and their human-readable counterparts can be found in [this document](https://www.notion.so/bac3299d2c5241c599d2e5e7986e72f7?v=ef742a61bd844435b7171bd2e90b447e).
+
+### Protocol Derivation
+There are a few snap permissions that are protocol specific. Each of these protocols are displayed as a separate permission in the list and contain the human-readable protocol name in the title.
+
+#### These permissions are:
+
+- `snap_getBip44Entropy`
+
+- `snap_getBip32Entropy'`
+
+- `snap_getBip32PublicKey`
+
+
+
+#### SLIP-0044
+
+The `Bip44` permission follows the [slip-0044](https://github.com/satoshilabs/slips/blob/master/slip-0044.md#registered-coin-types) standard and maps `coin type` as a number to human-readable symbols and names. These mapping can be found [here](https://github.com/satoshilabs/slips/blob/master/slip-0044.md#registered-coin-types). MetaMask uses the [@metamask/slip44](https://github.com/MetaMask/slip44) utility library to maintain these mappings.
+
+##### Example
+
+The permission...
+
+```JSON
+{
+ "snap_getBip44Entropy": {
+ "id": "TDDl8FTScrkgzs-sQ4ep0",
+ "parentCapability": "snap_getBip44Entropy",
+ "invoker": "npm:@chainsafe/filsnap",
+ "caveats": [
+ {
+ "type": "permittedCoinTypes",
+ "value": [
+ {
+ "coinType": 1
+ },
+ {
+ "coinType": 2
+ }
+ ]
+ }
+ ],
+ "date": 1686015956781
+ }
+}
+```
+
+renders the titles `Control your Bitcoin accounts and assets` and `Control your Litecoin accounts and assets` respectively.
+
+
+
+#### BIP 32
+
+The `snap_getBip32Entropy` and `snap_getBip32PublicKey` rely on a different mapping to derive their protocols. For these permissions, we compare their `path` and `curve` to the [SNAPS_DERIVATION_PATHS](https://github.com/MetaMask/metamask-extension/blob/49f8052b157374370ac71373708933c6e639944e/shared/constants/snaps.ts#L52). The mobile app uses a copy of this object that can be found in `app/constants/snaps.ts`.
+
+##### Example
+
+The permission...
+
+```json
+
+{
+ "snap_getBip32Entropy": {
+ "id": "j8TJuxqEtJZbIqjd2bqsq",
+ "parentCapability": "snap_getBip32Entropy",
+ "invoker": "npm:@metamask/test-snap-bip32",
+ "caveats": [
+ {
+ "type": "permittedDerivationPaths",
+ "value": [
+ {
+ "path": [
+ "m",
+ "44'",
+ "0'"
+ ],
+ "curve": "secp256k1"
+ },
+ {
+ "path": [
+ "m",
+ "44'",
+ "0'"
+ ],
+ "curve": "ed25519"
+ }
+ ]
+ }
+ ],
+ "date": 1686083278257
+ }
+}
+```
+
+renders the titles `Control your Bitcoin Legacy accounts and assets` and `Control your Test BIP-32 Path (ed25519) accounts and assets` respectively.
+
+
+
+
+
+## Usage
+
+
+
+The `SnapPermissions` component can be used as a regular React component in JSX:
+
+```jsx
+const mockPermissions: RequestedPermissions = {
+ snap_manageState: {},
+ 'endowment:rpc': {
+ caveats: [
+ {
+ type: 'rpcOrigin',
+ value: {
+ dapps: true,
+ snaps: true,
+ },
+ },
+ ],
+ },
+};
+
+
+
+```
+
+
+## Testing
+
+All of this complex logic is tested in the `SnapPermissions/test/SnapPermission.test.tsx` file and can be run with the command `yarn jest SnapPermissions/test/SnapPermission.test.tsx` from the root of the mobile directory.
\ No newline at end of file
diff --git a/app/components/Views/Snaps/components/SnapPermissions/SnapPermissions.styles.ts b/app/components/Views/Snaps/components/SnapPermissions/SnapPermissions.styles.ts
new file mode 100644
index 00000000000..460585a0336
--- /dev/null
+++ b/app/components/Views/Snaps/components/SnapPermissions/SnapPermissions.styles.ts
@@ -0,0 +1,16 @@
+import { StyleSheet } from 'react-native';
+
+/**
+ *
+ * @param params Style sheet params.
+ * @param params.theme App theme from ThemeContext.
+ * @param params.vars Inputs that the style sheet depends on.
+ * @returns StyleSheet object.
+ */
+const styleSheet = () =>
+ StyleSheet.create({
+ section: {
+ paddingTop: 32,
+ },
+ });
+export default styleSheet;
diff --git a/app/components/Views/Snaps/components/SnapPermissions/SnapPermissions.tsx b/app/components/Views/Snaps/components/SnapPermissions/SnapPermissions.tsx
new file mode 100644
index 00000000000..062f7578075
--- /dev/null
+++ b/app/components/Views/Snaps/components/SnapPermissions/SnapPermissions.tsx
@@ -0,0 +1,253 @@
+import React, { useCallback, useMemo } from 'react';
+import { View } from 'react-native';
+import slip44 from '@metamask/slip44';
+import styleSheet from './SnapPermissions.styles';
+import type { SupportedCurve } from '@metamask/key-tree';
+import { SNAP_PERMISSIONS } from '../../../../../constants/test-ids';
+import { strings } from '../../../../../../locales/i18n';
+import Text, {
+ TextVariant,
+} from '../../../../../component-library/components/Texts/Text';
+import {
+ SNAPS_DERIVATION_PATHS,
+ SnapsDerivationPath,
+ SnapsDerivationPathType,
+} from '../../../../../constants/snaps';
+import lodash from 'lodash';
+import { useStyles } from '../../../../../component-library/hooks';
+import { SnapPermissionCell } from '../SnapPermissionCell';
+import { RequestedPermissions } from '@metamask/permission-controller';
+import { RestrictedMethods } from '../../../../../core/Permissions/constants';
+import { EndowmentPermissions } from '../../../../../constants/permissions';
+
+interface SnapPermissionsProps {
+ permissions: RequestedPermissions;
+ showLabel?: boolean;
+}
+
+const SnapPermissions = ({
+ permissions,
+ showLabel = true,
+}: SnapPermissionsProps) => {
+ const { styles } = useStyles(styleSheet, {});
+
+ /**
+ * Gets the name of the SLIP-44 protocol corresponding to the specified
+ * `coin_type`.
+ * Copy of coinTypeToProtocolName from extension
+ * Source: https://github.com/MetaMask/metamask-extension/blob/49f8052b157374370ac71373708933c6e639944e/ui/helpers/utils/util.js#L524
+ * @param { number} coinType - The SLIP-44 `coin_type` value whose name
+ * to retrieve.
+ * @returns {string | undefined} The name of the protocol if found.
+ */
+ const coinTypeToProtocolName = (coinType: number): string | undefined => {
+ if (coinType === 1) {
+ return 'Test Networks';
+ }
+ return slip44[coinType]?.name;
+ };
+
+ /**
+ * Copy of getSnapDerivationPathName from extension
+ * source: https://github.com/MetaMask/metamask-extension/blob/49f8052b157374370ac71373708933c6e639944e/ui/helpers/utils/util.js#L548
+ * @param {string[]} path
+ * @param {string} curve
+ * @returns {string | null}
+ */
+ const getSnapDerivationPathName = (
+ path: SnapsDerivationPathType,
+ curve: SupportedCurve,
+ ) => {
+ const pathMetadata = SNAPS_DERIVATION_PATHS.find(
+ (derivationPath) =>
+ derivationPath.curve === curve &&
+ lodash.isEqual(derivationPath.path, path),
+ );
+ return pathMetadata?.name ?? null;
+ };
+
+ interface SnapPermissionData {
+ label: string;
+ date?: number;
+ }
+
+ const handleRPCPermissionTitles = useCallback(
+ (
+ permissionsList: RequestedPermissions,
+ key: typeof EndowmentPermissions['endowment:rpc'],
+ ) => {
+ const rpcPermissionsData: SnapPermissionData[] = [];
+ const rpcPermissionsCaveats = permissionsList[key].caveats;
+ const date = permissionsList[key].date;
+ if (rpcPermissionsCaveats) {
+ for (const caveat of rpcPermissionsCaveats) {
+ const rpcPermissions = caveat.value as Record;
+ for (const rpcKey in rpcPermissions) {
+ if (rpcPermissions[rpcKey] === true) {
+ const title = strings(
+ `app_settings.snaps.snap_permissions.human_readable_permission_titles.endowment:rpc.${rpcKey}`,
+ );
+ rpcPermissionsData.push({ label: title, date });
+ }
+ }
+ }
+ }
+ return rpcPermissionsData;
+ },
+ [],
+ );
+
+ const handleBip44EntropyPermissionTitles = useCallback(
+ (
+ permissionsList: RequestedPermissions,
+ key: typeof RestrictedMethods.snap_getBip44Entropy,
+ ) => {
+ const bip44EntropyData: SnapPermissionData[] = [];
+ const coinTypeCaveats = permissionsList[key].caveats;
+ const date = permissionsList[key].date;
+ if (coinTypeCaveats) {
+ for (const caveat of coinTypeCaveats) {
+ const coinTypes = caveat.value as { coinType: number }[];
+ for (const coinType of coinTypes) {
+ const protocolName = coinTypeToProtocolName(coinType.coinType);
+ if (protocolName) {
+ const title = strings(
+ 'app_settings.snaps.snap_permissions.human_readable_permission_titles.snap_getBip44Entropy',
+ { protocol: protocolName },
+ );
+ bip44EntropyData.push({ label: title, date });
+ }
+ }
+ }
+ }
+ return bip44EntropyData;
+ },
+ [],
+ );
+
+ const isSnapsDerivationPath = (object: any): object is SnapsDerivationPath =>
+ typeof object === 'object' &&
+ object !== null &&
+ 'path' in object &&
+ 'curve' in object;
+
+ const handleBip32PermissionTitles = useCallback(
+ (
+ permissionsList: RequestedPermissions,
+ key:
+ | typeof RestrictedMethods.snap_getBip32Entropy
+ | typeof RestrictedMethods.snap_getBip32PublicKey,
+ ) => {
+ const bip32Data: SnapPermissionData[] = [];
+ const permittedDerivationPaths = permissionsList[key].caveats?.[0].value;
+ const date = permissionsList[key].date;
+ if (permittedDerivationPaths && Array.isArray(permittedDerivationPaths)) {
+ for (const permittedPath of permittedDerivationPaths) {
+ if (isSnapsDerivationPath(permittedPath)) {
+ const derivedProtocolName = getSnapDerivationPathName(
+ permittedPath.path,
+ permittedPath.curve,
+ );
+ const protocolName =
+ derivedProtocolName ??
+ `${permittedPath.path.join('/')} (${permittedPath.curve})`;
+
+ const title = strings(
+ `app_settings.snaps.snap_permissions.human_readable_permission_titles.${key}`,
+ { protocol: protocolName },
+ );
+ bip32Data.push({ label: title, date });
+ }
+ }
+ }
+ return bip32Data;
+ },
+ [],
+ );
+
+ /**
+ * Derives human-readable titles for the provided permissions list.
+ * The derived titles are based on the permission key and specific permission scenarios.
+ *
+ * @param permissionsList - An object of permissions, where the key is the permission name and the value is the permission details.
+ *
+ * The function handles the following permission keys:
+ * 1. 'endowment:rpc': RPC permissions are processed to find RPC methods that are set to true, and the corresponding titles are added.
+ * 2. 'snap_getBip44Entropy': For each coin type in the permissions list, the corresponding protocol name is found and its title is added.
+ * 3. 'snap_getBip32Entropy': For each BIP32 entropy permission in the permissions list, the corresponding protocol name or derivation path and curve are found and its title is added.
+ * 4. 'snap_getBip32PublicKey': For each BIP32 public key permission in the permissions list, the corresponding protocol name or derivation path and curve are found and its title is added.
+ *
+ * For any other permission key, a default title is derived based on the permission key.
+ *
+ * @returns An array of strings, where each string is a human-readable title for a permission.
+ */
+
+ const derivePermissionsTitles: (
+ permissionsList: RequestedPermissions,
+ ) => SnapPermissionData[] = useCallback(
+ (permissionsList: RequestedPermissions) => {
+ const permissionsData: SnapPermissionData[] = [];
+
+ for (const key in permissionsList) {
+ const date = permissionsList[key].date;
+
+ switch (key) {
+ case EndowmentPermissions['endowment:rpc']: {
+ permissionsData.push(
+ ...handleRPCPermissionTitles(permissionsList, key),
+ );
+ break;
+ }
+ case RestrictedMethods.snap_getBip44Entropy: {
+ permissionsData.push(
+ ...handleBip44EntropyPermissionTitles(permissionsList, key),
+ );
+ break;
+ }
+ case RestrictedMethods.snap_getBip32Entropy:
+ case RestrictedMethods.snap_getBip32PublicKey: {
+ permissionsData.push(
+ ...handleBip32PermissionTitles(permissionsList, key),
+ );
+ break;
+ }
+ default: {
+ const title = strings(
+ `app_settings.snaps.snap_permissions.human_readable_permission_titles.${key}`,
+ );
+ permissionsData.push({ label: title, date });
+ }
+ }
+ }
+
+ return permissionsData;
+ },
+ [
+ handleBip32PermissionTitles,
+ handleBip44EntropyPermissionTitles,
+ handleRPCPermissionTitles,
+ ],
+ );
+
+ const permissionsToRender: SnapPermissionData[] = useMemo(
+ () => derivePermissionsTitles(permissions),
+ [derivePermissionsTitles, permissions],
+ );
+
+ return (
+
+ {showLabel ? (
+
+ {strings(
+ 'app_settings.snaps.snap_permissions.permission_section_title',
+ )}
+
+ ) : null}
+ {permissionsToRender.map((item, index) => (
+
+ ))}
+
+ );
+};
+
+export default React.memo(SnapPermissions);
diff --git a/app/components/Views/Snaps/components/SnapPermissions/index.ts b/app/components/Views/Snaps/components/SnapPermissions/index.ts
new file mode 100644
index 00000000000..553845d5375
--- /dev/null
+++ b/app/components/Views/Snaps/components/SnapPermissions/index.ts
@@ -0,0 +1,4 @@
+/* eslint-disable import/prefer-default-export */
+import SnapPermissions from './SnapPermissions';
+
+export { SnapPermissions };
diff --git a/app/components/Views/Snaps/components/SnapPermissions/test/SnapPermissions.test.tsx b/app/components/Views/Snaps/components/SnapPermissions/test/SnapPermissions.test.tsx
new file mode 100644
index 00000000000..9c5aeb83ba3
--- /dev/null
+++ b/app/components/Views/Snaps/components/SnapPermissions/test/SnapPermissions.test.tsx
@@ -0,0 +1,1023 @@
+import React from 'react';
+import SnapPermissions from '../SnapPermissions';
+import {
+ SNAP_PERMISSIONS_DATE,
+ SNAP_PERMISSIONS_TITLE,
+ SNAP_PERMISSION_CELL,
+} from '../../../../../../constants/test-ids';
+import { SnapPermissions as SnapPermissionsType } from '@metamask/snaps-utils';
+import {
+ SubjectPermissions,
+ PermissionConstraint,
+ RequestedPermissions,
+} from '@metamask/permission-controller';
+import { render } from '@testing-library/react-native';
+
+describe('SnapPermissions', () => {
+ const mockDate = 1684964145490;
+ const mockDate2 = 1686081721987;
+ const longRunningTitle = 'Run indefinitely';
+ const networkAccessTitle = 'Access the internet';
+ const transactionInsightTitle = 'Display transaction insights';
+ const cronjobTitle = 'Schedule and run periodic actions';
+ const rpcSnapsTitle =
+ 'Allow other snaps to communicate directly with this snap';
+ const rpcDappsTitle = 'Allow dapps to communicate directly with this snap';
+ const snapConfirmTitle = 'Display custom dialogs';
+ const snapManageStateTitle = 'Store and manage data on your device';
+ const snapNotifyTitle = 'Show notifications';
+ const snapGetBip32EntropyTitle = (protocol: string) =>
+ `Control your ${protocol} accounts and assets`;
+ const snapGetBip32PublicKeyTitle = (protocol: string) =>
+ `View your public key for ${protocol}`;
+ const snapGetBip44EntropyTitle = (protocol: string) =>
+ `Control your ${protocol} accounts and assets`;
+ const snapGetEntropyTitle = 'Derive arbitrary keys unique to this snap';
+ const endowmentKeyringTitle = 'endowment:keyring';
+
+ const mockCoinTypes = [
+ { coinType: 0 }, // Bitcoin
+ { coinType: 1 }, // Testnet (all coins)
+ { coinType: 2 }, // Litecoin
+ { coinType: 3 }, // Dogecoin
+ { coinType: 4 }, // Reddcoin
+ { coinType: 5 }, // Dash
+ { coinType: 6 }, // Peercoin
+ { coinType: 7 }, // Namecoin
+ { coinType: 8 }, // Feathercoin
+ { coinType: 9 }, // Counterparty
+ { coinType: 10 }, // 0x8000000a BLK Blackcoin
+ { coinType: 11 }, // 0x8000000b NSR NuShares
+ { coinType: 12 }, // 0x8000000c NBT NuBits
+ { coinType: 13 }, // 0x8000000d MZC Mazacoin
+ { coinType: 14 }, // 0x8000000e VIA Viacoin
+ { coinType: 15 }, // 0x8000000f XCH ClearingHouse
+ { coinType: 16 }, // 0x80000010 RBY Rubycoin
+ { coinType: 17 }, // 0x80000011 GRS Groestlcoin
+ { coinType: 18 }, // 0x80000012 DGC Digitalcoin
+ { coinType: 19 }, // 0x80000013 CCN Cannacoin
+ { coinType: 20 }, // 0x80000014 DGB DigiByte
+ { coinType: 21 }, // 0x80000015 Open Assets
+ { coinType: 22 }, // 0x80000016 MONA Monacoin
+ { coinType: 23 }, // 0x80000017 CLAM Clams
+ { coinType: 24 }, // 0x80000018 XPM Primecoin
+ { coinType: 25 }, // 0x80000019 NEOS Neoscoin
+ { coinType: 26 }, // 0x8000001a JBS Jumbucks
+ { coinType: 27 }, // 0x8000001b ZRC ziftrCOIN
+ { coinType: 28 }, // 0x8000001c VTC Vertcoin
+ { coinType: 29 }, //0x8000001d NXT NXT
+ { coinType: 30 }, // 0x8000001e BURST Burst
+ { coinType: 31 }, // 0x8000001f MUE MonetaryUnit
+ { coinType: 32 }, // 0x80000020 ZOOM Zoom
+ { coinType: 33 }, // 0x80000021 VASH Virtual Cash
+ { coinType: 34 }, // 0x80000022 CDN Canada eCoin
+ { coinType: 35 }, // 0x80000023 SDC ShadowCash
+ { coinType: 36 }, // 0x80000024 PKB ParkByte
+ { coinType: 37 }, // 0x80000025 PND Pandacoin
+ { coinType: 38 }, // 0x80000026 START StartCOIN
+ { coinType: 39 }, // 0x80000027 MOIN MOIN
+ { coinType: 40 }, // 0x80000028 EXP Expanse
+ { coinType: 41 }, // 0x80000029 EMC2 Einsteinium
+ { coinType: 42 }, // 0x8000002a DCR Decred
+ { coinType: 43 }, // 0x8000002b XEM NEM
+ { coinType: 44 }, // 0x8000002c PART Particl
+ { coinType: 45 }, // 0x8000002d ARG Argentum (dead)
+ { coinType: 46 }, // 0x8000002e Libertas
+ { coinType: 47 }, // 0x8000002f Posw coin
+ { coinType: 48 }, // 0x80000030 SHR Shreeji
+ { coinType: 49 }, // 0x80000031 GCR Global Currency Reserve (GCRcoin)
+ ];
+
+ const mockCurves:
+ | SnapPermissionsType['snap_getBip32Entropy']
+ | SnapPermissionsType['snap_getBip32PublicKey'] = [
+ {
+ path: ['m', `44'`, `0'`],
+ curve: 'ed25519',
+ },
+ {
+ path: ['m', `44'`, `1'`],
+ curve: 'secp256k1',
+ },
+ {
+ path: ['m', `44'`, `0'`],
+ curve: 'secp256k1',
+ },
+ {
+ path: ['m', `49'`, `0'`],
+ curve: 'secp256k1',
+ },
+ {
+ path: ['m', `49'`, `1'`],
+ curve: 'secp256k1',
+ },
+ {
+ path: ['m', `84'`, `0'`],
+ curve: 'secp256k1',
+ },
+ {
+ path: ['m', `84'`, `1'`],
+ curve: 'secp256k1',
+ },
+ {
+ path: ['m', `44'`, `501'`],
+ curve: 'secp256k1',
+ },
+ {
+ path: ['m', `44'`, `2'`],
+ curve: 'secp256k1',
+ },
+ {
+ path: ['m', `44'`, `3'`],
+ curve: 'secp256k1',
+ },
+ {
+ path: ['m', `44'`, `60'`],
+ curve: 'secp256k1',
+ },
+ {
+ path: ['m', `44'`, `118'`],
+ curve: 'secp256k1',
+ },
+ {
+ path: ['m', `44'`, `145'`],
+ curve: 'secp256k1',
+ },
+ {
+ path: ['m', `44'`, `714'`],
+ curve: 'secp256k1',
+ },
+ {
+ path: ['m', `44'`, `931'`],
+ curve: 'secp256k1',
+ },
+ {
+ path: ['m', `44'`, `330'`],
+ curve: 'secp256k1',
+ },
+ {
+ path: ['m', `44'`, `459'`],
+ curve: 'secp256k1',
+ },
+ {
+ path: ['m', `44'`, `529'`],
+ curve: 'secp256k1',
+ },
+ {
+ path: ['m', `44'`, `397'`],
+ curve: 'ed25519',
+ },
+ {
+ path: ['m', `44'`, `1'`, `0'`],
+ curve: 'ed25519',
+ },
+ ];
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('renders permissions correctly', () => {
+ const mockPermissions: SubjectPermissions = {
+ 'endowment:long-running': {
+ id: 'Bjj3InYtb6U4ak-uja0f_',
+ parentCapability: 'endowment:long-running',
+ invoker: 'npm:@chainsafe/filsnap',
+ caveats: null,
+ date: mockDate,
+ },
+ 'endowment:network-access': {
+ id: 'Bjj3InYtb6U4ak-uja0f_',
+ parentCapability: 'endowment:network-access',
+ invoker: 'npm:@chainsafe/filsnap',
+ caveats: null,
+ date: mockDate,
+ },
+ 'endowment:transaction-insight': {
+ id: '_6zTUtmw1BQAF-Iospl_m',
+ parentCapability: 'endowment:transaction-insight',
+ invoker: 'npm:@metamask/test-snap-insights',
+ caveats: null,
+ date: mockDate,
+ },
+ 'endowment:cronjob': {
+ id: '_6zTUtmw1BQAF-Iospl_m',
+ parentCapability: 'endowment:cronjob',
+ invoker: 'npm:@metamask/test-snap-insights',
+ caveats: [
+ {
+ type: 'jobs',
+ value: {
+ jobs: [
+ {
+ expression: '* * * * *',
+ request: {
+ method: 'GET',
+ params: {},
+ id: '1',
+ jsonrpc: '2.0',
+ },
+ },
+ ],
+ },
+ },
+ ],
+ date: mockDate,
+ },
+ 'endowment:rpc': {
+ id: 'Zma-vejrSvLtHmLrbSBAX',
+ parentCapability: 'endowment:rpc',
+ invoker: 'npm:@chainsafe/filsnap',
+ caveats: [
+ {
+ type: 'rpcOrigin',
+ value: {
+ dapps: true,
+ snaps: true,
+ },
+ },
+ ],
+ date: mockDate2,
+ },
+ snap_confirm: {
+ id: 'tVtSEUjc48Ab-gF6UI7X3',
+ parentCapability: 'snap_confirm',
+ invoker: 'npm:@chainsafe/filsnap',
+ caveats: null,
+ date: mockDate2,
+ },
+ snap_manageState: {
+ id: 'BKbg3uDSHHu0D1fCUTOmS',
+ parentCapability: 'snap_manageState',
+ invoker: 'npm:@metamask/test-snap-managestate',
+ caveats: null,
+ date: mockDate2,
+ },
+ snap_notify: {
+ id: '2JfZ78WEwQT3qvB20EyJR',
+ parentCapability: 'snap_notify',
+ invoker: 'npm:@metamask/test-snap-notification',
+ caveats: null,
+ date: mockDate2,
+ },
+ snap_getBip32Entropy: {
+ id: 'j8TJuxqEtJZbIqjd2bqsq',
+ parentCapability: 'snap_getBip32Entropy',
+ invoker: 'npm:@metamask/test-snap-bip32',
+ caveats: [
+ {
+ type: 'permittedDerivationPaths',
+ value: [
+ {
+ path: ['m', "44'", "0'"],
+ curve: 'secp256k1',
+ },
+ ],
+ },
+ ],
+ date: mockDate2,
+ },
+ snap_getBip32PublicKey: {
+ id: 'BmILTjG7zK4YKzjq-JMYM',
+ parentCapability: 'snap_getBip32PublicKey',
+ invoker: 'npm:@metamask/test-snap-bip32',
+ caveats: [
+ {
+ type: 'permittedDerivationPaths',
+ value: [
+ {
+ path: ['m', "44'", "0'"],
+ curve: 'secp256k1',
+ },
+ ],
+ },
+ ],
+ date: mockDate2,
+ },
+ snap_getBip44Entropy: {
+ id: 'MuqnOW-7BRg94sRDmVnDK',
+ parentCapability: 'snap_getBip44Entropy',
+ invoker: 'npm:@metamask/test-snap-bip44',
+ caveats: [
+ {
+ type: 'permittedCoinTypes',
+ value: [
+ {
+ coinType: 0,
+ },
+ ],
+ },
+ ],
+ date: mockDate2,
+ },
+ snap_getEntropy: {
+ id: 'MuqnOW-7BRg94sRDmVnDK',
+ parentCapability: 'snap_getEntropy',
+ invoker: 'npm:@metamask/test-snap-bip44',
+ caveats: null,
+ date: mockDate2,
+ },
+ 'endowment:keyring': {
+ id: 'MuqnOW-7BRg94sRDmVnDK',
+ parentCapability: 'snap_getEntropy',
+ invoker: 'npm:@metamask/test-snap-bip44',
+ caveats: [
+ {
+ type: 'mockNamespace',
+ value: [
+ {
+ mockNamespace: {
+ chains: [
+ {
+ name: 'Mock Chain',
+ id: 'mock-chain-id',
+ },
+ ],
+ methods: ['mockMethod1', 'mockMethod2'],
+ events: ['mockEvent1', 'mockEvent2'],
+ },
+ },
+ ],
+ },
+ ],
+ date: mockDate2,
+ },
+ };
+
+ const { getAllByTestId } = render(
+ ,
+ );
+
+ const permissionCells = getAllByTestId(SNAP_PERMISSION_CELL);
+ const permissionCellTitles = getAllByTestId(SNAP_PERMISSIONS_TITLE);
+ const permissionCellDates = getAllByTestId(SNAP_PERMISSIONS_DATE);
+
+ expect(permissionCells.length).toBe(14);
+ expect(permissionCellTitles[0].props.children).toBe(longRunningTitle);
+ expect(permissionCellDates[0].props.children).toBe(
+ 'Approved on May 24 at 5:35 pm',
+ );
+ expect(permissionCellTitles[1].props.children).toBe(networkAccessTitle);
+ expect(permissionCellDates[1].props.children).toBe(
+ 'Approved on May 24 at 5:35 pm',
+ );
+ expect(permissionCellTitles[2].props.children).toBe(
+ transactionInsightTitle,
+ );
+ expect(permissionCellDates[2].props.children).toBe(
+ 'Approved on May 24 at 5:35 pm',
+ );
+ expect(permissionCellTitles[3].props.children).toBe(cronjobTitle);
+ expect(permissionCellDates[3].props.children).toBe(
+ 'Approved on May 24 at 5:35 pm',
+ );
+ expect(permissionCellTitles[4].props.children).toBe(rpcDappsTitle);
+ expect(permissionCellDates[4].props.children).toBe(
+ 'Approved on Jun 6 at 4:02 pm',
+ );
+ expect(permissionCellTitles[5].props.children).toBe(rpcSnapsTitle);
+ expect(permissionCellDates[5].props.children).toBe(
+ 'Approved on Jun 6 at 4:02 pm',
+ );
+ expect(permissionCellTitles[6].props.children).toBe(snapConfirmTitle);
+ expect(permissionCellDates[6].props.children).toBe(
+ 'Approved on Jun 6 at 4:02 pm',
+ );
+ expect(permissionCellTitles[7].props.children).toBe(snapManageStateTitle);
+ expect(permissionCellDates[7].props.children).toBe(
+ 'Approved on Jun 6 at 4:02 pm',
+ );
+ expect(permissionCellTitles[8].props.children).toBe(snapNotifyTitle);
+ expect(permissionCellDates[8].props.children).toBe(
+ 'Approved on Jun 6 at 4:02 pm',
+ );
+ expect(permissionCellTitles[9].props.children).toBe(
+ snapGetBip32EntropyTitle('Bitcoin Legacy'),
+ );
+ expect(permissionCellDates[9].props.children).toBe(
+ 'Approved on Jun 6 at 4:02 pm',
+ );
+ expect(permissionCellTitles[10].props.children).toBe(
+ snapGetBip32PublicKeyTitle('Bitcoin Legacy'),
+ );
+ expect(permissionCellDates[10].props.children).toBe(
+ 'Approved on Jun 6 at 4:02 pm',
+ );
+ expect(permissionCellTitles[11].props.children).toBe(
+ snapGetBip44EntropyTitle('Bitcoin'),
+ );
+ expect(permissionCellDates[11].props.children).toBe(
+ 'Approved on Jun 6 at 4:02 pm',
+ );
+ expect(permissionCellTitles[12].props.children).toBe(snapGetEntropyTitle);
+ expect(permissionCellDates[12].props.children).toBe(
+ 'Approved on Jun 6 at 4:02 pm',
+ );
+ expect(permissionCellTitles[13].props.children).toBe(endowmentKeyringTitle);
+ expect(permissionCellDates[13].props.children).toBe(
+ 'Approved on Jun 6 at 4:02 pm',
+ );
+ });
+
+ it('renders correct installed date', () => {
+ const mockPermissions: SubjectPermissions = {
+ 'endowment:long-running': {
+ id: 'Bjj3InYtb6U4ak-uja0f_',
+ parentCapability: 'endowment:long-running',
+ invoker: 'npm:@chainsafe/filsnap',
+ caveats: null,
+ date: mockDate,
+ },
+ 'endowment:rpc': {
+ id: 'Zma-vejrSvLtHmLrbSBAX',
+ parentCapability: 'endowment:rpc',
+ invoker: 'npm:@chainsafe/filsnap',
+ caveats: [
+ {
+ type: 'rpcOrigin',
+ value: {
+ dapps: true,
+ snaps: true,
+ },
+ },
+ ],
+ date: mockDate2,
+ },
+ };
+
+ const { getAllByTestId } = render(
+ ,
+ );
+ const permissionCellDates = getAllByTestId(SNAP_PERMISSIONS_DATE);
+
+ const expectedDate1 = 'Approved on May 24 at 5:35 pm';
+ const expectedDate2 = 'Approved on Jun 6 at 4:02 pm';
+
+ expect(permissionCellDates[0].props.children).toBe(expectedDate1);
+ expect(permissionCellDates[1].props.children).toBe(expectedDate2);
+ });
+
+ it('renders correct permissions cells for endowment:rpc when both dapps and snaps are permitted', () => {
+ const mockPermissions: SubjectPermissions = {
+ 'endowment:rpc': {
+ id: 'Zma-vejrSvLtHmLrbSBAX',
+ parentCapability: 'endowment:rpc',
+ invoker: 'npm:@chainsafe/filsnap',
+ caveats: [
+ {
+ type: 'rpcOrigin',
+ value: {
+ dapps: true,
+ snaps: true,
+ },
+ },
+ ],
+ date: mockDate2,
+ },
+ };
+ const { getAllByTestId } = render(
+ ,
+ );
+ const permissionCells = getAllByTestId(SNAP_PERMISSIONS_TITLE);
+
+ expect(permissionCells.length).toBe(2);
+ expect(permissionCells[0].props.children).toBe(rpcDappsTitle);
+ expect(permissionCells[1].props.children).toBe(rpcSnapsTitle);
+ });
+
+ it('only renders snap rpc permission when only snaps are permitted', () => {
+ const mockPermissions: SubjectPermissions = {
+ 'endowment:rpc': {
+ id: 'Zma-vejrSvLtHmLrbSBAX',
+ parentCapability: 'endowment:rpc',
+ invoker: 'npm:@chainsafe/filsnap',
+ caveats: [
+ {
+ type: 'rpcOrigin',
+ value: {
+ snaps: true,
+ },
+ },
+ ],
+ date: mockDate2,
+ },
+ };
+
+ const { getAllByTestId } = render(
+ ,
+ );
+ const permissionCells = getAllByTestId(SNAP_PERMISSIONS_TITLE);
+
+ expect(permissionCells.length).toBe(1);
+ expect(permissionCells[0].props.children).toBe(rpcSnapsTitle);
+ });
+
+ it('only renders dapps rpc permission when only dapps are permitted', () => {
+ const mockPermissions: SubjectPermissions = {
+ 'endowment:rpc': {
+ id: 'Zma-vejrSvLtHmLrbSBAX',
+ parentCapability: 'endowment:rpc',
+ invoker: 'npm:@chainsafe/filsnap',
+ caveats: [
+ {
+ type: 'rpcOrigin',
+ value: {
+ dapps: true,
+ },
+ },
+ ],
+ date: mockDate2,
+ },
+ };
+
+ const { getAllByTestId } = render(
+ ,
+ );
+ const permissionCells = getAllByTestId(SNAP_PERMISSIONS_TITLE);
+
+ expect(permissionCells.length).toBe(1);
+ expect(permissionCells[0].props.children).toBe(rpcDappsTitle);
+ });
+
+ it('does not render rpc permissions when both snaps and dapps are false', () => {
+ const mockPermissions: SubjectPermissions = {
+ 'endowment:rpc': {
+ id: 'Zma-vejrSvLtHmLrbSBAX',
+ parentCapability: 'endowment:rpc',
+ invoker: 'npm:@chainsafe/filsnap',
+ caveats: [
+ {
+ type: 'rpcOrigin',
+ value: {
+ dapps: false,
+ snaps: false,
+ },
+ },
+ ],
+ date: mockDate2,
+ },
+ };
+
+ const { queryAllByTestId } = render(
+ ,
+ );
+ const permissionCells = queryAllByTestId(SNAP_PERMISSIONS_TITLE);
+
+ expect(permissionCells.length).toBe(0);
+ });
+
+ it('renders the correct permissions snap_getBip44Entropy with specified protocols', () => {
+ const mockPermissions: SubjectPermissions = {
+ snap_getBip44Entropy: {
+ id: 'MuqnOW-7BRg94sRDmVnDK',
+ parentCapability: 'snap_getBip44Entropy',
+ invoker: 'npm:@metamask/test-snap-bip44',
+ caveats: [
+ {
+ type: 'permittedCoinTypes',
+ value: mockCoinTypes,
+ },
+ ],
+ date: mockDate2,
+ },
+ };
+
+ const { getAllByTestId } = render(
+ ,
+ );
+ const permissionCellTitles = getAllByTestId(SNAP_PERMISSIONS_TITLE);
+
+ expect(permissionCellTitles.length).toBe(50);
+ expect(permissionCellTitles[0].props.children).toBe(
+ snapGetBip44EntropyTitle('Bitcoin'),
+ );
+ expect(permissionCellTitles[1].props.children).toBe(
+ snapGetBip44EntropyTitle('Test Networks'),
+ );
+ expect(permissionCellTitles[2].props.children).toBe(
+ snapGetBip44EntropyTitle('Litecoin'),
+ );
+ expect(permissionCellTitles[3].props.children).toBe(
+ snapGetBip44EntropyTitle('Dogecoin'),
+ );
+ expect(permissionCellTitles[4].props.children).toBe(
+ snapGetBip44EntropyTitle('Reddcoin'),
+ );
+ expect(permissionCellTitles[5].props.children).toBe(
+ snapGetBip44EntropyTitle('Dash'),
+ );
+ expect(permissionCellTitles[6].props.children).toBe(
+ snapGetBip44EntropyTitle('Peercoin'),
+ );
+ expect(permissionCellTitles[7].props.children).toBe(
+ snapGetBip44EntropyTitle('Namecoin'),
+ );
+ expect(permissionCellTitles[8].props.children).toBe(
+ snapGetBip44EntropyTitle('Feathercoin'),
+ );
+ expect(permissionCellTitles[9].props.children).toBe(
+ snapGetBip44EntropyTitle('Counterparty'),
+ );
+ expect(permissionCellTitles[10].props.children).toBe(
+ snapGetBip44EntropyTitle('Blackcoin'),
+ );
+ expect(permissionCellTitles[11].props.children).toBe(
+ snapGetBip44EntropyTitle('NuShares'),
+ );
+ expect(permissionCellTitles[12].props.children).toBe(
+ snapGetBip44EntropyTitle('NuBits'),
+ );
+ expect(permissionCellTitles[13].props.children).toBe(
+ snapGetBip44EntropyTitle('Mazacoin'),
+ );
+ expect(permissionCellTitles[14].props.children).toBe(
+ snapGetBip44EntropyTitle('Viacoin'),
+ );
+ expect(permissionCellTitles[15].props.children).toBe(
+ snapGetBip44EntropyTitle('ClearingHouse'),
+ );
+ expect(permissionCellTitles[16].props.children).toBe(
+ snapGetBip44EntropyTitle('Rubycoin'),
+ );
+ expect(permissionCellTitles[17].props.children).toBe(
+ snapGetBip44EntropyTitle('Groestlcoin'),
+ );
+ expect(permissionCellTitles[18].props.children).toBe(
+ snapGetBip44EntropyTitle('Digitalcoin'),
+ );
+ expect(permissionCellTitles[19].props.children).toBe(
+ snapGetBip44EntropyTitle('Cannacoin'),
+ );
+ expect(permissionCellTitles[20].props.children).toBe(
+ snapGetBip44EntropyTitle('DigiByte'),
+ );
+ expect(permissionCellTitles[21].props.children).toBe(
+ snapGetBip44EntropyTitle('Open Assets'),
+ );
+ expect(permissionCellTitles[22].props.children).toBe(
+ snapGetBip44EntropyTitle('Monacoin'),
+ );
+ expect(permissionCellTitles[23].props.children).toBe(
+ snapGetBip44EntropyTitle('Clams'),
+ );
+ expect(permissionCellTitles[24].props.children).toBe(
+ snapGetBip44EntropyTitle('Primecoin'),
+ );
+ expect(permissionCellTitles[25].props.children).toBe(
+ snapGetBip44EntropyTitle('Neoscoin'),
+ );
+ expect(permissionCellTitles[26].props.children).toBe(
+ snapGetBip44EntropyTitle('Jumbucks'),
+ );
+ expect(permissionCellTitles[27].props.children).toBe(
+ snapGetBip44EntropyTitle('ziftrCOIN'),
+ );
+ expect(permissionCellTitles[28].props.children).toBe(
+ snapGetBip44EntropyTitle('Vertcoin'),
+ );
+ expect(permissionCellTitles[29].props.children).toBe(
+ snapGetBip44EntropyTitle('NXT'),
+ );
+ expect(permissionCellTitles[30].props.children).toBe(
+ snapGetBip44EntropyTitle('Burst'),
+ );
+ expect(permissionCellTitles[31].props.children).toBe(
+ snapGetBip44EntropyTitle('MonetaryUnit'),
+ );
+ expect(permissionCellTitles[32].props.children).toBe(
+ snapGetBip44EntropyTitle('Zoom'),
+ );
+ expect(permissionCellTitles[33].props.children).toBe(
+ snapGetBip44EntropyTitle('Virtual Cash'),
+ );
+ expect(permissionCellTitles[34].props.children).toBe(
+ snapGetBip44EntropyTitle('Canada eCoin'),
+ );
+ expect(permissionCellTitles[35].props.children).toBe(
+ snapGetBip44EntropyTitle('ShadowCash'),
+ );
+ expect(permissionCellTitles[36].props.children).toBe(
+ snapGetBip44EntropyTitle('ParkByte'),
+ );
+ expect(permissionCellTitles[37].props.children).toBe(
+ snapGetBip44EntropyTitle('Pandacoin'),
+ );
+ expect(permissionCellTitles[38].props.children).toBe(
+ snapGetBip44EntropyTitle('StartCOIN'),
+ );
+ expect(permissionCellTitles[39].props.children).toBe(
+ snapGetBip44EntropyTitle('MOIN'),
+ );
+ expect(permissionCellTitles[40].props.children).toBe(
+ snapGetBip44EntropyTitle('Expanse'),
+ );
+ expect(permissionCellTitles[41].props.children).toBe(
+ snapGetBip44EntropyTitle('Einsteinium'),
+ );
+ expect(permissionCellTitles[42].props.children).toBe(
+ snapGetBip44EntropyTitle('Decred'),
+ );
+ expect(permissionCellTitles[43].props.children).toBe(
+ snapGetBip44EntropyTitle('NEM'),
+ );
+ expect(permissionCellTitles[44].props.children).toBe(
+ snapGetBip44EntropyTitle('Particl'),
+ );
+ expect(permissionCellTitles[45].props.children).toBe(
+ snapGetBip44EntropyTitle('Argentum (dead)'),
+ );
+ expect(permissionCellTitles[46].props.children).toBe(
+ snapGetBip44EntropyTitle('Libertas'),
+ );
+ expect(permissionCellTitles[47].props.children).toBe(
+ snapGetBip44EntropyTitle('Posw coin'),
+ );
+ expect(permissionCellTitles[48].props.children).toBe(
+ snapGetBip44EntropyTitle('Shreeji'),
+ );
+ expect(permissionCellTitles[49].props.children).toBe(
+ snapGetBip44EntropyTitle('Global Currency Reserve (GCRcoin)'),
+ );
+ });
+
+ it('renders the correct permissions titles for snap_getBip32Entropy with specified protocols', () => {
+ const mockPermissions: SubjectPermissions = {
+ snap_getBip32Entropy: {
+ id: 'j8TJuxqEtJZbIqjd2bqsq',
+ parentCapability: 'snap_getBip32Entropy',
+ invoker: 'npm:@metamask/test-snap-bip32',
+ caveats: [
+ {
+ type: 'permittedDerivationPaths',
+ value: mockCurves,
+ },
+ ],
+ date: mockDate2,
+ },
+ };
+
+ const { getAllByTestId } = render(
+ ,
+ );
+ const permissionCellTitles = getAllByTestId(SNAP_PERMISSIONS_TITLE);
+
+ expect(permissionCellTitles.length).toBe(20);
+ expect(permissionCellTitles[0].props.children).toBe(
+ snapGetBip32EntropyTitle('Test BIP-32 Path (ed25519)'),
+ );
+ expect(permissionCellTitles[1].props.children).toBe(
+ snapGetBip32EntropyTitle('Test BIP-32 Path (secp256k1)'),
+ );
+ expect(permissionCellTitles[2].props.children).toBe(
+ snapGetBip32EntropyTitle('Bitcoin Legacy'),
+ );
+ expect(permissionCellTitles[3].props.children).toBe(
+ snapGetBip32EntropyTitle('Bitcoin Nested SegWit'),
+ );
+ expect(permissionCellTitles[4].props.children).toBe(
+ snapGetBip32EntropyTitle('Bitcoin Testnet Nested SegWit'),
+ );
+ expect(permissionCellTitles[5].props.children).toBe(
+ snapGetBip32EntropyTitle('Bitcoin Native SegWit'),
+ );
+ expect(permissionCellTitles[6].props.children).toBe(
+ snapGetBip32EntropyTitle('Bitcoin Testnet Native SegWit'),
+ );
+ expect(permissionCellTitles[7].props.children).toBe(
+ snapGetBip32EntropyTitle('Solana'),
+ );
+ expect(permissionCellTitles[8].props.children).toBe(
+ snapGetBip32EntropyTitle('Litecoin'),
+ );
+ expect(permissionCellTitles[9].props.children).toBe(
+ snapGetBip32EntropyTitle('Dogecoin'),
+ );
+ expect(permissionCellTitles[10].props.children).toBe(
+ snapGetBip32EntropyTitle('Ethereum'),
+ );
+ expect(permissionCellTitles[11].props.children).toBe(
+ snapGetBip32EntropyTitle('Atom'),
+ );
+ expect(permissionCellTitles[12].props.children).toBe(
+ snapGetBip32EntropyTitle('Bitcoin Cash'),
+ );
+ expect(permissionCellTitles[13].props.children).toBe(
+ snapGetBip32EntropyTitle('Binance (BNB)'),
+ );
+ expect(permissionCellTitles[14].props.children).toBe(
+ snapGetBip32EntropyTitle('THORChain (RUNE)'),
+ );
+ expect(permissionCellTitles[15].props.children).toBe(
+ snapGetBip32EntropyTitle('Terra (LUNA)'),
+ );
+ expect(permissionCellTitles[16].props.children).toBe(
+ snapGetBip32EntropyTitle('Kava'),
+ );
+ expect(permissionCellTitles[17].props.children).toBe(
+ snapGetBip32EntropyTitle('Secret Network'),
+ );
+ expect(permissionCellTitles[18].props.children).toBe(
+ snapGetBip32EntropyTitle('NEAR Protocol'),
+ );
+ expect(permissionCellTitles[19].props.children).toBe(
+ snapGetBip32EntropyTitle('NEAR Protocol Testnet'),
+ );
+ });
+
+ it('renders the correct default text for snap_getBip32Entropy with invalid curves', () => {
+ const invalidMockPermissions: SubjectPermissions = {
+ snap_getBip32Entropy: {
+ id: 'j8TJuxqEtJZbIqjd2bqsq',
+ parentCapability: 'snap_getBip32Entropy',
+ invoker: 'npm:@metamask/test-snap-bip32',
+ caveats: [
+ {
+ type: 'permittedDerivationPaths',
+ value: [
+ {
+ path: ['m', "44'", "0'", '0'],
+ curve: 'secp256k1',
+ },
+ {
+ path: ['m', "44'", "0'", '3'],
+ curve: 'ed25519',
+ },
+ ],
+ },
+ ],
+ date: mockDate2,
+ },
+ };
+
+ const { getAllByTestId } = render(
+ ,
+ );
+ const permissionCellTitles = getAllByTestId(SNAP_PERMISSIONS_TITLE);
+
+ expect(permissionCellTitles.length).toBe(2);
+ expect(permissionCellTitles[0].props.children).toBe(
+ snapGetBip32EntropyTitle("m/44'/0'/0 (secp256k1)"),
+ );
+
+ expect(permissionCellTitles[1].props.children).toBe(
+ snapGetBip32EntropyTitle("m/44'/0'/3 (ed25519)"),
+ );
+ });
+
+ it('renders the correct default text for snap_getBip32PublicKey with invalid curves', () => {
+ const mockPermissions: SubjectPermissions = {
+ snap_getBip32PublicKey: {
+ id: 'j8TJuxqEtJZbIqjd2bqsq',
+ parentCapability: 'snap_getBip32Entropy',
+ invoker: 'npm:@metamask/test-snap-bip32',
+ caveats: [
+ {
+ type: 'permittedDerivationPaths',
+ value: [
+ {
+ path: ['m', "44'", "0'", '0'],
+ curve: 'secp256k1',
+ },
+ {
+ path: ['m', "44'", "0'", '3'],
+ curve: 'ed25519',
+ },
+ ],
+ },
+ ],
+ date: mockDate2,
+ },
+ };
+
+ const { getAllByTestId } = render(
+ ,
+ );
+ const permissionCellTitles = getAllByTestId(SNAP_PERMISSIONS_TITLE);
+
+ expect(permissionCellTitles.length).toBe(2);
+ expect(permissionCellTitles[0].props.children).toBe(
+ snapGetBip32PublicKeyTitle("m/44'/0'/0 (secp256k1)"),
+ );
+
+ expect(permissionCellTitles[1].props.children).toBe(
+ snapGetBip32PublicKeyTitle("m/44'/0'/3 (ed25519)"),
+ );
+ });
+
+ it('renders the correct permissions titles for snap_getBip32PublicKey with specified protocols', () => {
+ const mockPermissions: SubjectPermissions = {
+ snap_getBip32PublicKey: {
+ id: 'j8TJuxqEtJZbIqjd2bqsq',
+ parentCapability: 'snap_getBip32Entropy',
+ invoker: 'npm:@metamask/test-snap-bip32',
+ caveats: [
+ {
+ type: 'permittedDerivationPaths',
+ value: mockCurves,
+ },
+ ],
+ date: mockDate2,
+ },
+ };
+
+ const { getAllByTestId } = render(
+ ,
+ );
+ const permissionCellTitles = getAllByTestId(SNAP_PERMISSIONS_TITLE);
+
+ expect(permissionCellTitles.length).toBe(20);
+ expect(permissionCellTitles[0].props.children).toBe(
+ snapGetBip32PublicKeyTitle('Test BIP-32 Path (ed25519)'),
+ );
+ expect(permissionCellTitles[1].props.children).toBe(
+ snapGetBip32PublicKeyTitle('Test BIP-32 Path (secp256k1)'),
+ );
+ expect(permissionCellTitles[2].props.children).toBe(
+ snapGetBip32PublicKeyTitle('Bitcoin Legacy'),
+ );
+ expect(permissionCellTitles[3].props.children).toBe(
+ snapGetBip32PublicKeyTitle('Bitcoin Nested SegWit'),
+ );
+ expect(permissionCellTitles[4].props.children).toBe(
+ snapGetBip32PublicKeyTitle('Bitcoin Testnet Nested SegWit'),
+ );
+ expect(permissionCellTitles[5].props.children).toBe(
+ snapGetBip32PublicKeyTitle('Bitcoin Native SegWit'),
+ );
+ expect(permissionCellTitles[6].props.children).toBe(
+ snapGetBip32PublicKeyTitle('Bitcoin Testnet Native SegWit'),
+ );
+ expect(permissionCellTitles[7].props.children).toBe(
+ snapGetBip32PublicKeyTitle('Solana'),
+ );
+ expect(permissionCellTitles[8].props.children).toBe(
+ snapGetBip32PublicKeyTitle('Litecoin'),
+ );
+ expect(permissionCellTitles[9].props.children).toBe(
+ snapGetBip32PublicKeyTitle('Dogecoin'),
+ );
+ expect(permissionCellTitles[10].props.children).toBe(
+ snapGetBip32PublicKeyTitle('Ethereum'),
+ );
+ expect(permissionCellTitles[11].props.children).toBe(
+ snapGetBip32PublicKeyTitle('Atom'),
+ );
+ expect(permissionCellTitles[12].props.children).toBe(
+ snapGetBip32PublicKeyTitle('Bitcoin Cash'),
+ );
+ expect(permissionCellTitles[13].props.children).toBe(
+ snapGetBip32PublicKeyTitle('Binance (BNB)'),
+ );
+ expect(permissionCellTitles[14].props.children).toBe(
+ snapGetBip32PublicKeyTitle('THORChain (RUNE)'),
+ );
+ expect(permissionCellTitles[15].props.children).toBe(
+ snapGetBip32PublicKeyTitle('Terra (LUNA)'),
+ );
+ expect(permissionCellTitles[16].props.children).toBe(
+ snapGetBip32PublicKeyTitle('Kava'),
+ );
+ expect(permissionCellTitles[17].props.children).toBe(
+ snapGetBip32PublicKeyTitle('Secret Network'),
+ );
+ expect(permissionCellTitles[18].props.children).toBe(
+ snapGetBip32PublicKeyTitle('NEAR Protocol'),
+ );
+ expect(permissionCellTitles[19].props.children).toBe(
+ snapGetBip32PublicKeyTitle('NEAR Protocol Testnet'),
+ );
+ });
+
+ it('renders correctly with no permissions', () => {
+ const { queryByTestId } = render();
+ expect(queryByTestId(SNAP_PERMISSION_CELL)).toBeNull();
+ });
+
+ it('renders "Requested now" as the secondary text when no date is found in the permissions object', () => {
+ const mockPermissions: RequestedPermissions = {
+ snap_manageState: {},
+ 'endowment:rpc': {
+ caveats: [
+ {
+ type: 'rpcOrigin',
+ value: {
+ dapps: true,
+ snaps: true,
+ },
+ },
+ ],
+ },
+ };
+ const { getAllByTestId } = render(
+ ,
+ );
+ const permissionCellDates = getAllByTestId(SNAP_PERMISSIONS_DATE);
+
+ const expectedDate = 'Requested now';
+ const permissionCellTitles = getAllByTestId(SNAP_PERMISSIONS_TITLE);
+ expect(permissionCellTitles.length).toBe(3);
+ expect(permissionCellDates[0].props.children).toBe(expectedDate);
+ expect(permissionCellDates[1].props.children).toBe(expectedDate);
+ expect(permissionCellDates[2].props.children).toBe(expectedDate);
+ });
+});
diff --git a/app/components/Views/Snaps/components/SnapVersionTag/SnapVersionTag.styles.ts b/app/components/Views/Snaps/components/SnapVersionTag/SnapVersionTag.styles.ts
new file mode 100644
index 00000000000..f5962d29db5
--- /dev/null
+++ b/app/components/Views/Snaps/components/SnapVersionTag/SnapVersionTag.styles.ts
@@ -0,0 +1,30 @@
+import { StyleSheet } from 'react-native';
+import { Theme } from '../../../../../util/theme/models';
+
+/**
+ *
+ * @param params Style sheet params.
+ * @param params.theme App theme from ThemeContext.
+ * @param params.vars Inputs that the style sheet depends on.
+ * @returns StyleSheet object.
+ */
+const styleSheet = (params: { theme: Theme }) => {
+ const { theme } = params;
+ const { colors } = theme;
+ return StyleSheet.create({
+ versionBadgeContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ backgroundColor: colors.background.alternative,
+ paddingVertical: 2,
+ paddingHorizontal: 8,
+ borderRadius: 16,
+ },
+ versionBadgeItem: {
+ padding: 2,
+ },
+ });
+};
+
+export default styleSheet;
diff --git a/app/components/Views/Snaps/components/SnapVersionTag/SnapVersionTag.tsx b/app/components/Views/Snaps/components/SnapVersionTag/SnapVersionTag.tsx
new file mode 100644
index 00000000000..0926440237f
--- /dev/null
+++ b/app/components/Views/Snaps/components/SnapVersionTag/SnapVersionTag.tsx
@@ -0,0 +1,46 @@
+import React from 'react';
+import { View } from 'react-native';
+import Text, {
+ TextVariant,
+ TextColor,
+} from '../../../../../component-library/components/Texts/Text';
+import Icon, {
+ IconName,
+ IconSize,
+} from '../../../../../component-library/components/Icons/Icon';
+import { SemVerVersion } from '@metamask/snaps-utils';
+import stylesheet from './SnapVersionTag.styles';
+import {
+ SNAP_VERSION_BADGE,
+ SNAP_VERSION_BADGE_VALUE,
+} from '../../../../../constants/test-ids';
+import { useStyles } from '../../../../../component-library/hooks';
+
+interface SnapVersionTagProps extends React.ComponentProps {
+ version: SemVerVersion;
+}
+
+const SnapVersionTag: React.FC = ({
+ version,
+}: SnapVersionTagProps) => {
+ const { styles } = useStyles(stylesheet, {});
+ return (
+
+
+ {`v${version}`}
+
+
+
+ );
+};
+
+export default React.memo(SnapVersionTag);
diff --git a/app/components/Views/Snaps/components/SnapVersionTag/index.ts b/app/components/Views/Snaps/components/SnapVersionTag/index.ts
new file mode 100644
index 00000000000..ac853176e6e
--- /dev/null
+++ b/app/components/Views/Snaps/components/SnapVersionTag/index.ts
@@ -0,0 +1,4 @@
+/* eslint-disable import/prefer-default-export */
+import SnapVersionBadge from './SnapVersionTag';
+
+export { SnapVersionBadge };
diff --git a/app/components/Views/Snaps/components/SnapVersionTag/test/SnapVersionTag.test.tsx b/app/components/Views/Snaps/components/SnapVersionTag/test/SnapVersionTag.test.tsx
new file mode 100644
index 00000000000..1e4a7595b78
--- /dev/null
+++ b/app/components/Views/Snaps/components/SnapVersionTag/test/SnapVersionTag.test.tsx
@@ -0,0 +1,20 @@
+import React from 'react';
+import { render } from '@testing-library/react-native';
+import { SemVerVersion } from '@metamask/snaps-utils';
+import SnapVersionBadge from '../SnapVersionTag';
+import {
+ SNAP_VERSION_BADGE,
+ SNAP_VERSION_BADGE_VALUE,
+} from '../../../../../../constants/test-ids';
+
+describe('SnapVersionBadge', () => {
+ it('renders the version in the correct format', async () => {
+ const { getByTestId } = render(
+ ,
+ );
+ const versionBadge = await getByTestId(SNAP_VERSION_BADGE);
+ const versionBadgeValue = await getByTestId(SNAP_VERSION_BADGE_VALUE);
+ expect(versionBadge).toBeTruthy();
+ expect(versionBadgeValue.props.children).toBe('v2.3.13');
+ });
+});
diff --git a/app/constants/navigation/Routes.ts b/app/constants/navigation/Routes.ts
index 67e3fd153f1..f7812eaabc6 100644
--- a/app/constants/navigation/Routes.ts
+++ b/app/constants/navigation/Routes.ts
@@ -80,6 +80,10 @@ const Routes = {
HOME: 'WalletTabHome',
TAB_STACK_FLOW: 'WalletTabStackFlow',
},
+ SNAPS: {
+ SNAPS_SETTINGS_LIST: 'SnapsSettingsList',
+ SNAP_SETTINGS: 'SnapSettings',
+ },
VAULT_RECOVERY: {
RESTORE_WALLET: 'RestoreWallet',
WALLET_RESTORED: 'WalletRestored',
diff --git a/app/constants/permissions.ts b/app/constants/permissions.ts
index 8f711e7c8bb..284f344602a 100644
--- a/app/constants/permissions.ts
+++ b/app/constants/permissions.ts
@@ -1,4 +1,13 @@
-enum USER_INTENT {
+export const EndowmentPermissions = Object.freeze({
+ 'endowment:network-access': 'endowment:network-access',
+ 'endowment:long-running': 'endowment:long-running',
+ 'endowment:transaction-insight': 'endowment:transaction-insight',
+ 'endowment:cronjob': 'endowment:cronjob',
+ 'endowment:ethereum-provider': 'endowment:ethereum-provider',
+ 'endowment:rpc': 'endowment:rpc',
+} as const);
+
+export enum USER_INTENT {
None,
Create,
CreateMultiple,
@@ -7,5 +16,3 @@ enum USER_INTENT {
Import,
ConnectHW,
}
-
-export default USER_INTENT;
diff --git a/app/constants/snaps.ts b/app/constants/snaps.ts
new file mode 100644
index 00000000000..a671ce3483f
--- /dev/null
+++ b/app/constants/snaps.ts
@@ -0,0 +1,114 @@
+/* eslint-disable import/prefer-default-export */
+import type { SupportedCurve } from '@metamask/key-tree';
+
+export type SnapsDerivationPathType = ['m', ...string[]];
+
+export interface SnapsDerivationPath {
+ path: SnapsDerivationPathType;
+ curve: SupportedCurve;
+ name: string;
+}
+
+// Copy of extension mapping: https://github.com/MetaMask/metamask-extension/blob/49f8052b157374370ac71373708933c6e639944e/shared/constants/snaps.ts#L52
+export const SNAPS_DERIVATION_PATHS: SnapsDerivationPath[] = [
+ {
+ path: ['m', `44'`, `0'`],
+ curve: 'ed25519',
+ name: 'Test BIP-32 Path (ed25519)',
+ },
+ {
+ path: ['m', `44'`, `1'`],
+ curve: 'secp256k1',
+ name: 'Test BIP-32 Path (secp256k1)',
+ },
+ {
+ path: ['m', `44'`, `0'`],
+ curve: 'secp256k1',
+ name: 'Bitcoin Legacy',
+ },
+ {
+ path: ['m', `49'`, `0'`],
+ curve: 'secp256k1',
+ name: 'Bitcoin Nested SegWit',
+ },
+ {
+ path: ['m', `49'`, `1'`],
+ curve: 'secp256k1',
+ name: 'Bitcoin Testnet Nested SegWit',
+ },
+ {
+ path: ['m', `84'`, `0'`],
+ curve: 'secp256k1',
+ name: 'Bitcoin Native SegWit',
+ },
+ {
+ path: ['m', `84'`, `1'`],
+ curve: 'secp256k1',
+ name: 'Bitcoin Testnet Native SegWit',
+ },
+ {
+ path: ['m', `44'`, `501'`],
+ curve: 'secp256k1',
+ name: 'Solana',
+ },
+ {
+ path: ['m', `44'`, `2'`],
+ curve: 'secp256k1',
+ name: 'Litecoin',
+ },
+ {
+ path: ['m', `44'`, `3'`],
+ curve: 'secp256k1',
+ name: 'Dogecoin',
+ },
+ {
+ path: ['m', `44'`, `60'`],
+ curve: 'secp256k1',
+ name: 'Ethereum',
+ },
+ {
+ path: ['m', `44'`, `118'`],
+ curve: 'secp256k1',
+ name: 'Atom',
+ },
+ {
+ path: ['m', `44'`, `145'`],
+ curve: 'secp256k1',
+ name: 'Bitcoin Cash',
+ },
+ {
+ path: ['m', `44'`, `714'`],
+ curve: 'secp256k1',
+ name: 'Binance (BNB)',
+ },
+ {
+ path: ['m', `44'`, `931'`],
+ curve: 'secp256k1',
+ name: 'THORChain (RUNE)',
+ },
+ {
+ path: ['m', `44'`, `330'`],
+ curve: 'secp256k1',
+ name: 'Terra (LUNA)',
+ },
+ {
+ path: ['m', `44'`, `459'`],
+ curve: 'secp256k1',
+ name: 'Kava',
+ },
+ {
+ path: ['m', `44'`, `529'`],
+ curve: 'secp256k1',
+ name: 'Secret Network',
+ },
+ {
+ path: ['m', `44'`, `397'`],
+ curve: 'ed25519',
+ name: 'NEAR Protocol',
+ },
+ {
+ path: ['m', `44'`, `1'`, `0'`],
+ curve: 'ed25519',
+ name: 'NEAR Protocol Testnet',
+ },
+];
diff --git a/app/constants/test-ids.js b/app/constants/test-ids.js
index 89167a62d4d..3bdf80c73f1 100644
--- a/app/constants/test-ids.js
+++ b/app/constants/test-ids.js
@@ -101,3 +101,32 @@ export const SIGNATURE_MODAL_CANCEL_BUTTON_ID =
// Advanced Settings
export const ADVANCED_SETTINGS_CONTAINER_ID = 'advanced-settings';
export const ETH_SIGN_SWITCH_ID = 'eth-sign-switch';
+// SNAPS
+export const SNAP_DETAILS_CELL = 'snap-details-cell';
+export const SNAP_DETAILS_SWITCH = 'snap-details-switch';
+export const SNAP_DETAILS_INSTALL_ORIGIN = 'snap-details-install-origin';
+export const SNAP_DETAILS_INSTALL_DATE = 'snap-details-install-date';
+export const SNAP_VERSION_BADGE = 'snap-version-badge';
+export const SNAP_VERSION_BADGE_VALUE = 'snap-version-badge-value';
+export const SNAP_DESCRIPTION_TITLE = 'snap-description-title';
+export const SNAP_DESCRIPTION = 'snap-description';
+export const SNAP_ElEMENT = 'snap-element';
+export const SNAP_SETTINGS_REMOVE_BUTTON = 'snap-settings-remove-button';
+export const SNAP_PERMISSION_CELL = 'snap-permission-cell';
+export const SNAP_PERMISSIONS = 'snap-permissions';
+export const SNAP_PERMISSIONS_TITLE = 'snap-permissions-title';
+export const SNAP_PERMISSIONS_DATE = 'snap-permissions-date';
+
+//Snaps Install
+export const SNAP_INSTALL_FLOW = 'snap-install-flow';
+export const SNAP_INSTALL_CANCEL = 'snap-install-cancel';
+export const SNAP_INSTALL_CONNECT = 'snap-install-connect';
+export const SNAP_INSTALL_OK = 'snap-install-ok';
+export const SNAP_INSTALL_CONNECTION_REQUEST =
+ 'snap-install-connection-request';
+export const SNAP_INSTALL_PERMISSIONS_REQUEST =
+ 'snap-install-connection-permissions-request';
+export const SNAP_INSTALL_PERMISSIONS_REQUEST_APPROVE =
+ 'snap-install-connection-permissions-request-approve';
+export const SNAP_INSTALL_SUCCESS = 'snap-install-success';
+export const SNAP_INSTALL_ERROR = 'snap-install-error';
diff --git a/app/core/BackgroundBridge/BackgroundBridge.js b/app/core/BackgroundBridge/BackgroundBridge.js
index 24a42bf863d..e0af9b164c6 100644
--- a/app/core/BackgroundBridge/BackgroundBridge.js
+++ b/app/core/BackgroundBridge/BackgroundBridge.js
@@ -21,6 +21,8 @@ import {
import RemotePort from './RemotePort';
import WalletConnectPort from './WalletConnectPort';
import Port from './Port';
+import { createSnapMethodMiddleware } from '../Snaps/createSnapMethodMiddleware';
+import { getPermittedAccounts } from '../Permissions';
const createFilterMiddleware = require('eth-json-rpc-filters');
const createSubscriptionManager = require('eth-json-rpc-filters/subscriptionManager');
@@ -334,6 +336,44 @@ export class BackgroundBridge extends EventEmitter {
engine.push(subscriptionManager.middleware);
// watch asset
+ // Snaps middleware
+ /*
+ from extension https://github.dev/MetaMask/metamask-extension/blob/1d5e8a78400d7aaaf2b3cbdb30cff9399061df34/app/scripts/metamask-controller.js#L3830-L3861
+ */
+ engine.push(
+ createSnapMethodMiddleware(true, {
+ getAppKey: async () =>
+ new Promise((resolve, reject) => {
+ resolve('mockAppKey');
+ }),
+ getUnlockPromise: () => Promise.resolve(),
+ getSnaps: Engine.controllerMessenger.call.bind(
+ Engine.controllerMessenger,
+ 'SnapController:getPermitted',
+ origin,
+ ),
+ requestPermissions: async (requestedPermissions) => {
+ const [approvedPermissions] =
+ await Engine.context.PermissionController.requestPermissions(
+ { origin },
+ requestedPermissions,
+ );
+
+ return Object.values(approvedPermissions);
+ },
+ getPermissions: Engine.context.PermissionController.getPermissions.bind(
+ Engine.context.PermissionController,
+ origin,
+ ),
+ getAccounts: (origin) => getPermittedAccounts(origin),
+ installSnaps: Engine.controllerMessenger.call.bind(
+ Engine.controllerMessenger,
+ 'SnapController:install',
+ origin,
+ ),
+ }),
+ );
+
// user-facing RPC methods
engine.push(
this.createMiddleware({
diff --git a/app/core/Engine.ts b/app/core/Engine.ts
index cd5ab540679..38ba139caff 100644
--- a/app/core/Engine.ts
+++ b/app/core/Engine.ts
@@ -11,6 +11,7 @@ import {
TokenDetectionController,
NftDetectionController,
} from '@metamask/assets-controllers';
+import { AppState } from 'react-native';
import { AddressBookController } from '@metamask/address-book-controller';
import { ControllerMessenger } from '@metamask/base-controller';
import { ComposableController } from '@metamask/composable-controller';
@@ -26,6 +27,8 @@ import { GasFeeController } from '@metamask/gas-fee-controller';
import { ApprovalController } from '@metamask/approval-controller';
import { PermissionController } from '@metamask/permission-controller';
import SwapsController, { swapsUtils } from '@metamask/swaps-controller';
+import { SnapController } from '@metamask/snaps-controllers';
+import { SubjectMetadataController } from '@metamask/subject-metadata-controller';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { MetaMaskKeyring as QRHardwareKeyring } from '@keystonehq/metamask-airgapped-keyring';
import Encryptor from './Encryptor';
@@ -44,9 +47,20 @@ import {
import NotificationManager from './NotificationManager';
import Logger from '../util/Logger';
import { LAST_INCOMING_TX_BLOCK_INFO } from '../constants/storage';
+import { EndowmentPermissions } from '../constants/permissions';
+import { SNAP_BLOCKLIST, checkSnapsBlockList } from '../util/snaps';
import { isZero } from '../util/lodash';
import { MetaMetricsEvents } from './Analytics';
import AnalyticsV2 from '../util/analyticsV2';
+import {
+ SnapBridge,
+ WebviewExecutionService,
+ buildSnapEndowmentSpecifications,
+ buildSnapRestrictedMethodSpecifications,
+ detectSnapLocation,
+ fetchFunction,
+} from './Snaps';
+import { getRpcMethodMiddleware } from './RPCMethods/RPCMethodMiddleware';
import {
getCaveatSpecifications,
getPermissionSpecifications,
@@ -268,6 +282,213 @@ class Engine {
keyringState,
);
+ /**
+ * Gets the mnemonic of the user's primary keyring.
+ */
+ const getPrimaryKeyringMnemonic = async () => {
+ try {
+ const mnemonic = await keyringController.exportMnemonic();
+ if (mnemonic) {
+ return mnemonic;
+ }
+ throw new Error('No mnemonic found');
+ } catch (error) {
+ console.error(error);
+ }
+ };
+
+ const getAppState = () => {
+ const state = AppState.currentState;
+ return state === 'active';
+ };
+
+ const getAppKeyForSubject = async (subject, requestedAccount) => {
+ let account;
+
+ if (requestedAccount) {
+ account = requestedAccount;
+ } else {
+ [account] = await keyringController.getAccounts();
+ }
+ const appKey = await keyringController.exportAppKeyForAddress(
+ account,
+ subject,
+ );
+ return appKey;
+ };
+
+ const getSnapPermissionSpecifications = () => ({
+ ...buildSnapEndowmentSpecifications(),
+ ...buildSnapRestrictedMethodSpecifications({
+ clearSnapState: this.controllerMessenger.call.bind(
+ this.controllerMessenger,
+ 'SnapController:clearSnapState',
+ ),
+ getMnemonic: getPrimaryKeyringMnemonic.bind(this),
+ getUnlockPromise: getAppState.bind(this),
+ getSnap: this.controllerMessenger.call.bind(
+ this.controllerMessenger,
+ 'SnapController:get',
+ ),
+ handleSnapRpcRequest: this.controllerMessenger.call.bind(
+ this.controllerMessenger,
+ 'SnapController:handleRequest',
+ ),
+ getSnapState: this.controllerMessenger.call.bind(
+ this.controllerMessenger,
+ 'SnapController:getSnapState',
+ ),
+ updateSnapState: this.controllerMessenger.call.bind(
+ this.controllerMessenger,
+ 'SnapController:updateSnapState',
+ ),
+ showConfirmation: (origin, confirmationData) =>
+ approvalController.addAndShowApprovalRequest({
+ origin,
+ type: 'snapConfirmation',
+ requestData: confirmationData,
+ }),
+ showDialog: (origin, type, content, placeholder) =>
+ approvalController.addAndShowApprovalRequest({
+ origin,
+ type,
+ requestData: { content, placeholder },
+ }),
+ showInAppNotification: (origin, args) => {
+ // eslint-disable-next-line no-console
+ console.log(
+ 'Snaps/ showInAppNotification called with args: ',
+ args,
+ ' and origin: ',
+ origin,
+ );
+ },
+ }),
+ });
+
+ const permissionController = new PermissionController({
+ messenger: this.controllerMessenger.getRestricted({
+ name: 'PermissionController',
+ allowedActions: [
+ `${approvalController.name}:addRequest`,
+ `${approvalController.name}:hasRequest`,
+ `${approvalController.name}:acceptRequest`,
+ `${approvalController.name}:rejectRequest`,
+ ],
+ }),
+ state: initialState.PermissionController,
+ caveatSpecifications: getCaveatSpecifications({ getIdentities }),
+ permissionSpecifications: {
+ ...getPermissionSpecifications({
+ getAllAccounts: () => keyringController.getAccounts(),
+ }),
+ ...getSnapPermissionSpecifications(),
+ },
+ unrestrictedMethods,
+ });
+
+ const subjectMetadataController = new SubjectMetadataController({
+ messenger: this.controllerMessenger.getRestricted({
+ name: 'SubjectMetadataController',
+ allowedActions: [`${permissionController.name}:hasPermissions`],
+ }),
+ state: initialState.SubjectMetadataController || {},
+ subjectCacheLimit: 100,
+ });
+
+ this.setupSnapProvider = (snapId, connectionStream) => {
+ // eslint-disable-next-line no-console
+ console.log(
+ '[ENGINE LOG] Engine+setupSnapProvider: Setup stream for Snap',
+ snapId,
+ );
+ // TO DO:
+ // Develop a simpler getRpcMethodMiddleware object for SnapBridge
+ // Consider developing an abstract class to derived custom implementations for each use case
+ const bridge = new SnapBridge({
+ snapId,
+ connectionStream,
+ getRPCMethodMiddleware: ({ hostname, getProviderState }) =>
+ getRpcMethodMiddleware({
+ hostname,
+ getProviderState,
+ navigation: null,
+ getApprovedHosts: () => null,
+ setApprovedHosts: () => null,
+ approveHost: () => null,
+ // Mock URL
+ url: 'https://www.google.com',
+ title: 'Snap',
+ icon: null,
+ isHomepage: false,
+ fromHomepage: false,
+ toggleUrlModal: () => null,
+ wizardScrollAdjusted: () => null,
+ tabId: false,
+ isWalletConnect: true,
+ }),
+ });
+
+ bridge.setupProviderConnection();
+ };
+
+ this.snapExecutionService = new WebviewExecutionService({
+ // iframeUrl: new URL(
+ // 'https://metamask.github.io/iframe-execution-environment/0.11.0',
+ // ),
+ messenger: this.controllerMessenger.getRestricted({
+ name: 'ExecutionService',
+ }),
+ setupSnapProvider: this.setupSnapProvider.bind(this),
+ });
+
+ const snapControllerMessenger = this.controllerMessenger.getRestricted({
+ name: 'SnapController',
+ allowedEvents: [
+ 'ExecutionService:unhandledError',
+ 'ExecutionService:outboundRequest',
+ 'ExecutionService:outboundResponse',
+ ],
+ allowedActions: [
+ `${approvalController.name}:addRequest`,
+ `${permissionController.name}:getEndowments`,
+ `${permissionController.name}:getPermissions`,
+ `${permissionController.name}:hasPermission`,
+ `${permissionController.name}:hasPermissions`,
+ `${permissionController.name}:requestPermissions`,
+ `${permissionController.name}:revokeAllPermissions`,
+ `${permissionController.name}:revokePermissions`,
+ `${permissionController.name}:revokePermissionForAllSubjects`,
+ `${permissionController.name}:grantPermissions`,
+ `${subjectMetadataController.name}:getSubjectMetadata`,
+ 'ExecutionService:executeSnap',
+ 'ExecutionService:getRpcRequestHandler',
+ 'ExecutionService:terminateSnap',
+ 'ExecutionService:terminateAllSnaps',
+ 'ExecutionService:handleRpcRequest',
+ ],
+ });
+
+ const snapController = new SnapController({
+ environmentEndowmentPermissions: Object.values(EndowmentPermissions),
+ featureFlags: { dappsCanUpdateSnaps: true },
+ // TO DO
+ getAppKey: async (subject, appKeyType) =>
+ getAppKeyForSubject(`${appKeyType}:${subject}`),
+ checkBlockList: async (snapsToCheck) =>
+ checkSnapsBlockList(snapsToCheck, SNAP_BLOCKLIST),
+ state: initialState.SnapController || {},
+ messenger: snapControllerMessenger,
+ // TO DO
+ closeAllConnections: () =>
+ // eslint-disable-next-line no-console
+ console.log(
+ 'TO DO: Create method to close all connections (Closes all connections for the given origin, and removes the references)',
+ ),
+ detectSnapLocation: (location, options) =>
+ detectSnapLocation(location, { ...options, fetch: fetchFunction }),
+ });
+
const controllers = [
keyringController,
new AccountTrackerController({
@@ -408,28 +629,7 @@ class Engine {
),
gasFeeController,
approvalController,
- new PermissionController({
- messenger: this.controllerMessenger.getRestricted({
- name: 'PermissionController',
- allowedActions: [
- `${approvalController.name}:addRequest`,
- `${approvalController.name}:hasRequest`,
- `${approvalController.name}:acceptRequest`,
- `${approvalController.name}:rejectRequest`,
- ],
- }),
- state: initialState.PermissionController,
- caveatSpecifications: getCaveatSpecifications({ getIdentities }),
- permissionSpecifications: {
- ...getPermissionSpecifications({
- getAllAccounts: () => keyringController.getAccounts(),
- }),
- /*
- ...this.getSnapPermissionSpecifications(),
- */
- },
- unrestrictedMethods,
- }),
+ permissionController,
new SignatureController({
messenger: this.controllerMessenger.getRestricted({
name: 'SignatureController',
@@ -458,6 +658,8 @@ class Engine {
),
},
}),
+ snapController,
+ subjectMetadataController,
];
// set initial state
@@ -821,7 +1023,9 @@ export default {
TokensController,
TokenDetectionController,
NftDetectionController,
+ SnapController,
PermissionController,
+ SubjectMetadataController,
} = instance.datamodel.state;
// normalize `null` currencyRate to `0`
@@ -853,7 +1057,9 @@ export default {
GasFeeController,
TokenDetectionController,
NftDetectionController,
+ SnapController,
PermissionController,
+ SubjectMetadataController,
};
},
get datamodel() {
diff --git a/app/core/EngineService/EngineService.ts b/app/core/EngineService/EngineService.ts
index 49ee0fbb25a..908b72e8c74 100644
--- a/app/core/EngineService/EngineService.ts
+++ b/app/core/EngineService/EngineService.ts
@@ -66,10 +66,18 @@ class EngineService {
name: 'ApprovalController',
key: `${engine.context.ApprovalController.name}:stateChange`,
},
+ {
+ name: 'SnapController',
+ key: `${engine.context.SnapController.name}:stateChange`,
+ },
{
name: 'PermissionController',
key: `${engine.context.PermissionController.name}:stateChange`,
},
+ {
+ name: 'subjectMetadataController',
+ key: `${engine.context.SubjectMetadataController.name}:stateChange`,
+ },
];
engine?.datamodel?.subscribe?.(() => {
diff --git a/app/core/Permissions/constants.js b/app/core/Permissions/constants.js
deleted file mode 100644
index 546945a4f6b..00000000000
--- a/app/core/Permissions/constants.js
+++ /dev/null
@@ -1,7 +0,0 @@
-export const CaveatTypes = Object.freeze({
- restrictReturnedAccounts: 'restrictReturnedAccounts',
-});
-
-export const RestrictedMethods = Object.freeze({
- eth_accounts: 'eth_accounts',
-});
diff --git a/app/core/Permissions/constants.ts b/app/core/Permissions/constants.ts
new file mode 100644
index 00000000000..752abe74b8b
--- /dev/null
+++ b/app/core/Permissions/constants.ts
@@ -0,0 +1,16 @@
+export const CaveatTypes = Object.freeze({
+ restrictReturnedAccounts: 'restrictReturnedAccounts',
+});
+
+export const RestrictedMethods = Object.freeze({
+ eth_accounts: 'eth_accounts',
+ // Snap Specific Restricted Methods
+ snap_confirm: 'snap_confirm',
+ snap_notify: 'snap_notify',
+ snap_manageState: 'snap_manageState',
+ snap_getBip32PublicKey: 'snap_getBip32PublicKey',
+ snap_getBip32Entropy: 'snap_getBip32Entropy',
+ snap_getBip44Entropy: 'snap_getBip44Entropy',
+ snap_getEntropy: 'snap_getEntropy',
+ wallet_snap: 'wallet_snap',
+});
diff --git a/app/core/Permissions/specifications.js b/app/core/Permissions/specifications.js
index c0255d85943..7ba8d2dc3ee 100644
--- a/app/core/Permissions/specifications.js
+++ b/app/core/Permissions/specifications.js
@@ -1,3 +1,5 @@
+import { endowmentCaveatSpecifications as snapsEndowmentCaveatSpecifications } from '@metamask/snaps-controllers';
+import { caveatSpecifications as snapsCaveatsSpecifications } from '@metamask/rpc-methods';
import {
constructPermission,
PermissionType,
@@ -8,7 +10,7 @@ import { CaveatTypes, RestrictedMethods } from './constants';
/**
* This file contains the specifications of the permissions and caveats
* that are recognized by our permission system. See the PermissionController
- * README in @metamask/snap-controllers for details.
+ * README in @metamask/snaps-controllers for details.
*/
/**
@@ -64,6 +66,8 @@ export const getCaveatSpecifications = ({ getIdentities }) => ({
validator: (caveat, _origin, _target) =>
validateCaveatAccounts(caveat.value, getIdentities),
},
+ ...snapsCaveatsSpecifications,
+ ...snapsEndowmentCaveatSpecifications,
});
/**
@@ -132,6 +136,14 @@ export const getPermissionSpecifications = ({ getAllAccounts }) => ({
}
},
},
+ [PermissionKeys.snap_confirm]: {
+ permissionType: PermissionType.RestrictedMethod,
+ targetKey: PermissionKeys.snap_confirm,
+ },
+ [PermissionKeys.snap_getBip44Entropy]: {
+ permissionType: PermissionType.RestrictedMethod,
+ targetKey: PermissionKeys.snap_getBip44Entropy,
+ },
});
/**
diff --git a/app/core/RPCMethods/RPCMethodMiddleware.ts b/app/core/RPCMethods/RPCMethodMiddleware.ts
index 3c146671ed7..7c1292c79ae 100644
--- a/app/core/RPCMethods/RPCMethodMiddleware.ts
+++ b/app/core/RPCMethods/RPCMethodMiddleware.ts
@@ -38,6 +38,8 @@ export enum ApprovalTypes {
ETH_SIGN_TYPED_DATA = 'eth_signTypedData',
WATCH_ASSET = 'wallet_watchAsset',
TRANSACTION = 'transaction',
+ INSTALL_SNAP = 'wallet_installSnap',
+ UPDATE_SNAP = 'wallet_updateSnap',
}
interface RPCMethodsMiddleParameters {
@@ -224,6 +226,12 @@ export const getRpcMethodMiddleware = ({
};
const rpcMethods: any = {
+ // SNAPS TEMPORAL METHODS
+ snap_confirm: () => {
+ // eslint-disable-next-line no-console
+ console.log('[RPCMethodsMiddleware LOG] snap_confirm executed');
+ res.result = {};
+ },
eth_getTransactionByHash: async () => {
res.result = await polyfillGasPrice('getTransactionByHash', req.params);
},
@@ -738,6 +746,8 @@ export const getRpcMethodMiddleware = ({
* initialization.
*/
metamask_getProviderState: async () => {
+ // eslint-disable-next-line no-console
+ console.log('[RpcMethodMiddleware LOG] metamask_getProviderState');
res.result = {
...getProviderState(),
accounts: isMMSDK
diff --git a/app/core/Snaps/SnapBridge.ts b/app/core/Snaps/SnapBridge.ts
new file mode 100644
index 00000000000..3265070d4ec
--- /dev/null
+++ b/app/core/Snaps/SnapBridge.ts
@@ -0,0 +1,269 @@
+/* eslint-disable import/no-commonjs */
+/* eslint-disable @typescript-eslint/no-require-imports */
+/* eslint-disable @typescript-eslint/no-var-requires */
+// eslint-disable-next-line import/no-nodejs-modules
+import { Duplex } from 'stream';
+import {
+ createSwappableProxy,
+ createEventEmitterProxy,
+} from 'swappable-obj-proxy';
+import { JsonRpcEngine } from 'json-rpc-engine';
+import { createEngineStream } from 'json-rpc-middleware-stream';
+import { NetworksChainId } from '@metamask/controller-utils';
+
+import Engine from '../Engine';
+import { setupMultiplex } from '../../util/streams';
+import Logger from '../../util/Logger';
+import { getAllNetworks } from '../../util/networks';
+import { createSnapMethodMiddleware } from './createSnapMethodMiddleware';
+
+const ObjectMultiplex = require('obj-multiplex');
+const createFilterMiddleware = require('eth-json-rpc-filters');
+const createSubscriptionManager = require('eth-json-rpc-filters/subscriptionManager');
+const providerAsMiddleware = require('eth-json-rpc-middleware/providerAsMiddleware');
+const pump = require('pump');
+
+interface ISnapBridgeProps {
+ snapId: string;
+ connectionStream: Duplex;
+ getRPCMethodMiddleware: (args: any) => any;
+}
+
+export default class SnapBridge {
+ snapId: string;
+ stream: Duplex;
+ getRPCMethodMiddleware: (args: any) => any;
+ provider: any;
+ blockTracker: any;
+
+ #mux: typeof ObjectMultiplex;
+ #providerProxy: any;
+ #blockTrackerProxy: any;
+
+ constructor({
+ snapId,
+ connectionStream,
+ getRPCMethodMiddleware,
+ }: ISnapBridgeProps) {
+ // eslint-disable-next-line no-console
+ console.log(
+ '[SNAP BRIDGE LOG] Engine+setupSnapProvider: Setup bridge for Snap',
+ snapId,
+ );
+ this.snapId = snapId;
+ this.stream = connectionStream;
+ this.getRPCMethodMiddleware = getRPCMethodMiddleware;
+
+ const { NetworkController } = Engine.context as any;
+
+ const provider = NetworkController.provider;
+ const blockTracker = provider._blockTracker;
+
+ this.#providerProxy = null;
+ this.#blockTrackerProxy = null;
+
+ this.#setProvider(provider);
+ this.#setBlockTracker(blockTracker);
+
+ this.#mux = setupMultiplex(this.stream);
+ }
+
+ #setProvider = (provider: any): void => {
+ if (this.#providerProxy) {
+ this.#providerProxy.setTarget(provider);
+ } else {
+ this.#providerProxy = createSwappableProxy(provider);
+ }
+ this.provider = provider;
+ };
+
+ #setBlockTracker = (blockTracker: any): void => {
+ if (this.#blockTrackerProxy) {
+ this.#blockTrackerProxy.setTarget(blockTracker);
+ } else {
+ this.#blockTrackerProxy = createEventEmitterProxy(blockTracker, {
+ eventFilter: 'skipInternal',
+ });
+ }
+ this.blockTracker = blockTracker;
+ };
+
+ getProviderState() {
+ const memState = this.getState();
+ return {
+ isUnlocked: this.isUnlocked(),
+ ...this.getProviderNetworkState(memState),
+ };
+ }
+
+ setupProviderConnection = () => {
+ // eslint-disable-next-line no-console
+ console.log('[SNAP BRIDGE LOG] Engine+setupProviderConnection');
+ const outStream = this.#mux.createStream('metamask-provider');
+ const engine = this.setupProviderEngine();
+ const providerStream = createEngineStream({ engine });
+ pump(outStream, providerStream, outStream, (err: any) => {
+ // handle any middleware cleanup
+ engine._middleware.forEach((mid: any) => {
+ if (mid.destroy && typeof mid.destroy === 'function') {
+ mid.destroy();
+ }
+ });
+ if (err) Logger.log('Error with provider stream conn', err);
+ });
+ };
+
+ setupProviderEngine = () => {
+ const engine = new JsonRpcEngine();
+
+ // create filter polyfill middleware
+ const filterMiddleware = createFilterMiddleware({
+ provider: this.#providerProxy,
+ blockTracker: this.#blockTrackerProxy,
+ });
+
+ // create subscription polyfill middleware
+ const subscriptionManager = createSubscriptionManager({
+ provider: this.#providerProxy,
+ blockTracker: this.#blockTrackerProxy,
+ });
+
+ subscriptionManager.events.on('notification', (message: any) =>
+ engine.emit('notification', message),
+ );
+
+ // engine.push(createOriginMiddleware({ origin: this.snapId }));
+ // engine.push(createLoggerMiddleware({ origin: this.snapId }));
+
+ // Filter and subscription polyfills
+ engine.push(filterMiddleware);
+ engine.push(subscriptionManager.middleware);
+
+ const { context, controllerMessenger } = Engine as any;
+ const { PermissionController } = context;
+
+ engine.push(
+ createSnapMethodMiddleware(true, {
+ // getAppKey: async () =>
+ // new Promise((resolve, reject) => {
+ // resolve('mockAppKey');
+ // }),
+ // getUnlockPromise: () => Promise.resolve(),
+
+ getSnaps: controllerMessenger.call.bind(
+ controllerMessenger,
+ 'SnapController:getPermitted',
+ this.snapId,
+ ),
+
+ requestPermissions: async (requestedPermissions: any) => {
+ const [approvedPermissions] =
+ await PermissionController.requestPermissions(
+ { origin: this.snapId },
+ requestedPermissions,
+ );
+
+ return Object.values(approvedPermissions);
+ },
+ getPermissions: PermissionController.getPermissions.bind(
+ PermissionController,
+ this.snapId,
+ ),
+ // getAccounts: (origin) => getPermittedAccounts(origin),
+ installSnaps: controllerMessenger.call.bind(
+ controllerMessenger,
+ 'SnapController:install',
+ this.snapId,
+ ),
+ }),
+ );
+
+ engine.push(
+ PermissionController.createPermissionMiddleware({
+ origin: this.snapId,
+ }),
+ );
+
+ // User-Facing RPC methods
+ engine.push(
+ this.getRPCMethodMiddleware({
+ hostname: this.snapId,
+ getProviderState: this.getProviderState.bind(this),
+ }),
+ );
+
+ // Forward to metamask primary provider
+ engine.push(providerAsMiddleware(this.#providerProxy));
+ return engine;
+ };
+
+ getNetworkState = ({ network }: { network: string }) => {
+ const { NetworkController } = Engine.context as any;
+ const networkType = NetworkController.state.providerConfig.type;
+ const networkProvider = NetworkController.state.providerConfig;
+
+ const isInitialNetwork =
+ networkType && getAllNetworks().includes(networkType);
+ let chainId;
+
+ if (isInitialNetwork) {
+ chainId = NetworksChainId[networkType];
+ } else if (networkType === 'rpc') {
+ chainId = networkProvider.chainId;
+ }
+ if (chainId && !chainId.startsWith('0x')) {
+ // Convert to hex
+ chainId = `0x${parseInt(chainId, 10).toString(16)}`;
+ }
+
+ const result = {
+ networkVersion: network,
+ chainId,
+ };
+ return result;
+ };
+
+ isUnlocked = (): boolean => {
+ const { KeyringController } = Engine.context as any;
+ return KeyringController.isUnlocked();
+ };
+
+ getState = () => {
+ const { context, datamodel } = Engine;
+ const { KeyringController } = context as any;
+ const vault = KeyringController.state.vault;
+ const { network, selectedAddress } = datamodel.flatState as any;
+ return {
+ isInitialized: !!vault,
+ isUnlocked: true,
+ network,
+ selectedAddress,
+ };
+ };
+
+ getProviderNetworkState({ network }: { network: string }) {
+ const { NetworkController } = Engine.context as any;
+ const networkType = NetworkController.state.providerConfig.type;
+ const networkProvider = NetworkController.state.providerConfig;
+
+ const isInitialNetwork =
+ networkType && getAllNetworks().includes(networkType);
+ let chainId;
+
+ if (isInitialNetwork) {
+ chainId = NetworksChainId[networkType];
+ } else if (networkType === 'rpc') {
+ chainId = networkProvider.chainId;
+ }
+ if (chainId && !chainId.startsWith('0x')) {
+ // Convert to hex
+ chainId = `0x${parseInt(chainId, 10).toString(16)}`;
+ }
+
+ const result = {
+ networkVersion: network,
+ chainId,
+ };
+ return result;
+ }
+}
diff --git a/app/core/Snaps/SnapDuplex.ts b/app/core/Snaps/SnapDuplex.ts
new file mode 100644
index 00000000000..4179a67b674
--- /dev/null
+++ b/app/core/Snaps/SnapDuplex.ts
@@ -0,0 +1,66 @@
+import { Duplex } from 'readable-stream';
+
+type StreamData = number | string | Record | unknown[];
+
+export interface PostMessageEvent {
+ origin: string;
+ source: typeof window;
+}
+
+/**
+ * Abstract base class for postMessage streams.
+ */
+export default abstract class SnapDuplex extends Duplex {
+ private _stream: any;
+ private _jobId: string;
+
+ constructor({ stream, jobId }: { stream: any; jobId: string }) {
+ super({
+ objectMode: true,
+ });
+
+ this._stream = stream;
+ this._jobId = jobId;
+
+ this._stream.on('data', (data) => this._onData(data));
+ }
+
+ protected _onData(data: StreamData): void {
+ // eslint-disable-next-line no-console
+ console.log(
+ '[SNAP DUPLEX LOG] SnapDuplex+_onData: Job',
+ this._jobId,
+ 'read data',
+ data,
+ );
+ if (data.jobId !== this._jobId) {
+ return;
+ }
+
+ this.push(data.data);
+ }
+
+ /**
+ * Child classes must implement this function.
+ */
+ protected abstract _postMessage(_data?: unknown): void;
+
+ _read(): void {
+ return undefined;
+ }
+
+ _write(data: StreamData, _encoding: string | null, cb: () => void): void {
+ // eslint-disable-next-line no-console
+ console.log('[SNAP DUPLEX LOG] SnapDuplex+_write: Job', this._jobId);
+ this._stream.write({ data, jobId: this._jobId });
+ cb();
+ }
+
+ destroy() {
+ // eslint-disable-next-line no-console
+ console.log(
+ '[SNAP DUPLEX LOG] SnapDuplex+destroy: Destroy stream from SnapDuplex',
+ );
+ this._stream.destroy();
+ }
+}
diff --git a/app/core/Snaps/SnapWebviewPostMessageStream.ts b/app/core/Snaps/SnapWebviewPostMessageStream.ts
new file mode 100644
index 00000000000..0d5def5fcdb
--- /dev/null
+++ b/app/core/Snaps/SnapWebviewPostMessageStream.ts
@@ -0,0 +1,63 @@
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-nocheck
+import { BasePostMessageStream } from '@metamask/post-message-stream';
+
+interface WebViewPostMessageStreamArgs {
+ name: string;
+ stream: any;
+ jobId: string;
+}
+
+/**
+ * A {@link Window.postMessage} stream.
+ */
+export default class SnapWebviewPostMessageStream extends BasePostMessageStream {
+ private _name: string;
+
+ private _stream: string;
+ private _jobId: string;
+ /**
+ * Creates a stream for communicating with other streams across the same or
+ * different `window` objects.
+ *
+ * @param args - Options bag.
+ * @param args.name - The name of the stream. Used to differentiate between
+ * multiple streams sharing the same window object.
+ * @param args.target - The name of the stream to exchange messages with.
+ * @param args.targetOrigin - The origin of the target. Defaults to
+ * `location.origin`, '*' is permitted.
+ * @param args.targetWindow - The window object of the target stream. Defaults
+ * to `window`.
+ */
+ constructor({ name, stream, jobId }: WebViewPostMessageStreamArgs) {
+ super();
+
+ this._name = name;
+ this._stream = stream;
+ this._jobId = jobId;
+ this._onMessage = this._onMessage.bind(this);
+ this._stream.on('data', (data) => this._onMessage(data));
+ }
+
+ protected _postMessage(data: unknown): void {
+ // eslint-disable-next-line no-console
+ console.log(
+ '[SNAP STREAM LOG] SnapWebviewPostMessageStream+_postMessage: Write data',
+ );
+ this._stream.write(data);
+ }
+
+ private _onMessage(data: any): void {
+ // eslint-disable-next-line no-console
+ console.log(
+ '[SNAP STREAM LOG] SnapWebviewPostMessageStream+_onMessage: Listen for data',
+ );
+ this._onData(data);
+ }
+
+ destroy(): void {
+ // eslint-disable-next-line no-console
+ console.log('TO DO: Destroy stream from SnapWebviewPostMessageStream');
+ this.destroyed = true;
+ }
+}
diff --git a/app/core/Snaps/SnapsState.ts b/app/core/Snaps/SnapsState.ts
new file mode 100644
index 00000000000..d53fc6f5da8
--- /dev/null
+++ b/app/core/Snaps/SnapsState.ts
@@ -0,0 +1,6 @@
+const snapsState = {
+ stream: undefined,
+ webview: undefined,
+};
+
+export default snapsState;
diff --git a/app/core/Snaps/WebviewExecutionService.ts b/app/core/Snaps/WebviewExecutionService.ts
new file mode 100644
index 00000000000..0ae67edfd14
--- /dev/null
+++ b/app/core/Snaps/WebviewExecutionService.ts
@@ -0,0 +1,66 @@
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-nocheck
+import {
+ BasePostMessageStream,
+ Job,
+ AbstractExecutionService,
+ ExecutionServiceArgs,
+} from '@metamask/snaps-controllers';
+import snapsState from './SnapsState';
+// import SnapWebviewPostMessageStream from './SnapWebviewPostMessageStream';
+import SnapDuplex from './SnapDuplex';
+
+// const ObjectMultiplex = require('obj-multiplex');
+
+// type IframeExecutionEnvironmentServiceArgs = {
+// iframeUrl: URL;
+// } & ExecutionServiceArgs;
+
+export default class WebviewExecutionService extends AbstractExecutionService {
+ #snapDuplexMap: SnapDuplex[];
+
+ constructor({ messenger, setupSnapProvider }: ExecutionServiceArgs) {
+ super({
+ messenger,
+ setupSnapProvider,
+ });
+ // this.iframeUrl = iframeUrl;
+ this.#snapDuplexMap = {};
+ }
+
+ protected async initEnvStream(jobId: string): Promise<{
+ worker;
+ stream: BasePostMessageStream;
+ }> {
+ // eslint-disable-next-line no-console
+ console.log(
+ '[EXEC SERVICE LOG] WebviewExecutionService+_initEnvStream: Init env stream for job',
+ jobId,
+ );
+ const iframeWindow = snapsState.webview;
+ const stream = snapsState.stream;
+
+ // The WebviewExecutionService wraps the stream into a Duplex
+ // to pass the jobId to the Proxy Service
+ const snapStream = new SnapDuplex({
+ stream,
+ jobId,
+ });
+
+ this.#snapDuplexMap[jobId] = snapStream;
+
+ return { worker: iframeWindow, stream: snapStream };
+ }
+
+ protected terminateJob(jobWrapper: Job): void {
+ this.#snapDuplexMap[jobWrapper.id].destroy();
+ delete this.#snapDuplexMap[jobWrapper.id];
+
+ // eslint-disable-next-line no-console
+ console.log(
+ '[EXEC SERVICE LOG] WebviewExecutionService+_terminate: Job',
+ jobWrapper.id,
+ 'SnapDuplex destroyed',
+ );
+ }
+}
diff --git a/app/core/Snaps/WebviewPostMessageStream.ts b/app/core/Snaps/WebviewPostMessageStream.ts
new file mode 100644
index 00000000000..9226f660eba
--- /dev/null
+++ b/app/core/Snaps/WebviewPostMessageStream.ts
@@ -0,0 +1,81 @@
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-nocheck
+import {
+ BasePostMessageStream,
+ PostMessageEvent,
+} from '@metamask/post-message-stream';
+
+interface WebViewPostMessageStreamArgs {
+ name: string;
+ target: string;
+ targetOrigin?: string;
+ targetWindow?: Window;
+}
+
+/**
+ * A {@link Window.postMessage} stream.
+ */
+export default class WebviewPostMessageStream extends BasePostMessageStream {
+ private _name: string;
+
+ private _target: string;
+
+ private _targetOrigin: string;
+
+ private _targetWindow: Window;
+
+ /**
+ * Creates a stream for communicating with other streams across the same or
+ * different `window` objects.
+ *
+ * @param args - Options bag.
+ * @param args.name - The name of the stream. Used to differentiate between
+ * multiple streams sharing the same window object.
+ * @param args.target - The name of the stream to exchange messages with.
+ * @param args.targetOrigin - The origin of the target. Defaults to
+ * `location.origin`, '*' is permitted.
+ * @param args.targetWindow - The window object of the target stream. Defaults
+ * to `window`.
+ */
+ constructor({
+ name,
+ target,
+ targetOrigin,
+ targetWindow,
+ }: WebViewPostMessageStreamArgs) {
+ super();
+
+ this._name = name;
+ this._target = target;
+ this._targetOrigin = targetOrigin;
+ this._targetWindow = targetWindow;
+ this._onMessage = this._onMessage.bind(this);
+
+ //this._targetWindow.onMessage = this._onMessage;
+
+ setTimeout(() => this._handshake(), 0);
+ }
+
+ protected _postMessage(data: unknown): void {
+ const message = {
+ target: this._target,
+ data,
+ };
+ this._targetWindow.injectJavaScript(
+ `window.postMessage(${JSON.stringify(message)})`,
+ );
+ }
+
+ private _onMessage(event: PostMessageEvent): void {
+ const message = event.nativeEvent;
+ const data = JSON.parse(message.data);
+
+ this._onData(data.data);
+ }
+
+ destroy(): void {
+ // Do nothing
+ // eslint-disable-next-line no-console
+ console.log('[WebviewPostMessageStream LOG]: Destroy stream');
+ }
+}
diff --git a/app/core/Snaps/createSnapMethodMiddleware.ts b/app/core/Snaps/createSnapMethodMiddleware.ts
new file mode 100644
index 00000000000..c9fb8ad6233
--- /dev/null
+++ b/app/core/Snaps/createSnapMethodMiddleware.ts
@@ -0,0 +1,47 @@
+import { handlers as permittedSnapMethods } from '@metamask/rpc-methods/dist/permitted';
+import { selectHooks } from '@metamask/rpc-methods/dist/utils';
+import { ethErrors } from 'eth-rpc-errors';
+
+/*
+ copied form extension
+ https://github.com/MetaMask/metamask-extension/blob/develop/app/scripts/lib/rpc-method-middleware/createMethodMiddleware.js#L83
+*/
+const snapHandlerMap = permittedSnapMethods.reduce((map, handler) => {
+ for (const methodName of handler.methodNames) {
+ map.set(methodName, handler);
+ }
+ return map;
+}, new Map());
+
+// eslint-disable-next-line import/prefer-default-export
+export function createSnapMethodMiddleware(isSnap: boolean, hooks: any) {
+ return async function methodMiddleware(
+ req: unknown,
+ res: unknown,
+ next: unknown,
+ end: unknown,
+ ) {
+ const handler = snapHandlerMap.get(req.method);
+ if (handler) {
+ if (/^snap_/iu.test(req.method) && !isSnap) {
+ return end(ethErrors.rpc.methodNotFound());
+ }
+
+ const { implementation, hookNames } = handler;
+ try {
+ // Implementations may or may not be async, so we must await them.
+ return await implementation(
+ req,
+ res,
+ next,
+ end,
+ selectHooks(hooks, hookNames),
+ );
+ } catch (error) {
+ console.error(error);
+ return end(error);
+ }
+ }
+ return next();
+ };
+}
diff --git a/app/core/Snaps/index.ts b/app/core/Snaps/index.ts
new file mode 100644
index 00000000000..8278fff1489
--- /dev/null
+++ b/app/core/Snaps/index.ts
@@ -0,0 +1,28 @@
+import SnapBridge from './SnapBridge';
+import SnapDuplex from './SnapDuplex';
+import WebviewExecutionService from './WebviewExecutionService';
+import WebviewPostMessageStream from './WebviewPostMessageStream';
+import SnapWebviewPostMessageStream from './SnapWebviewPostMessageStream';
+import snapsState from './SnapsState';
+import {
+ buildSnapEndowmentSpecifications,
+ buildSnapRestrictedMethodSpecifications,
+ ExcludedSnapPermissions,
+ ExcludedSnapEndowments,
+} from './permissions/permissions';
+import { detectSnapLocation, fetchFunction } from './location';
+
+export {
+ snapsState,
+ SnapDuplex,
+ SnapBridge,
+ WebviewExecutionService,
+ WebviewPostMessageStream,
+ SnapWebviewPostMessageStream,
+ buildSnapEndowmentSpecifications,
+ buildSnapRestrictedMethodSpecifications,
+ ExcludedSnapPermissions,
+ ExcludedSnapEndowments,
+ fetchFunction,
+ detectSnapLocation,
+};
diff --git a/app/core/Snaps/location/fetch.ts b/app/core/Snaps/location/fetch.ts
new file mode 100644
index 00000000000..d2da00c37e0
--- /dev/null
+++ b/app/core/Snaps/location/fetch.ts
@@ -0,0 +1,48 @@
+/* eslint-disable import/prefer-default-export */
+import RNFetchBlob, { FetchBlobResponse } from 'rn-fetch-blob';
+import Logger from '../../../util/Logger';
+
+const SNAPS_FETCH_LOG_TAG = 'Snaps/ fetch';
+
+/**
+ * Reads and parses file from ReactNativeBlobUtil response
+ * @param path The path to the file to read and parse.
+ * @returns The parsed file data.
+ */
+const readAndParseFile = async (path: string) => {
+ try {
+ const data = await RNFetchBlob.fs.readFile(path, 'utf8');
+ return data;
+ } catch (error) {
+ Logger.log(SNAPS_FETCH_LOG_TAG, 'readAndParseFile error', error);
+ }
+};
+
+/**
+ * Converts a FetchBlobResponse object to a React Native Response object.
+ * @param response The FetchBlobResponse object to convert.
+ * @returns A new Response object with the same data as the input object.
+ */
+const convertFetchBlobResponseToResponse = async (
+ fetchBlobResponse: FetchBlobResponse,
+): Promise => {
+ const headers = new Headers(fetchBlobResponse.respInfo.headers);
+ const status = fetchBlobResponse.respInfo.status;
+ const dataPath = fetchBlobResponse.data;
+ const data = await readAndParseFile(dataPath);
+ const response = new Response(data, { headers, status });
+ return response;
+};
+
+export const fetchFunction = async (
+ inputRequest: RequestInfo,
+): Promise => {
+ const { config } = RNFetchBlob;
+ const urlToFetch: string =
+ typeof inputRequest === 'string' ? inputRequest : inputRequest.url;
+ const response: FetchBlobResponse = await config({ fileCache: true }).fetch(
+ 'GET',
+ urlToFetch,
+ );
+ return await convertFetchBlobResponseToResponse(response);
+};
diff --git a/app/core/Snaps/location/http.ts b/app/core/Snaps/location/http.ts
new file mode 100644
index 00000000000..fcdb711856c
--- /dev/null
+++ b/app/core/Snaps/location/http.ts
@@ -0,0 +1,111 @@
+import {
+ SnapManifest,
+ VirtualFile,
+ NpmSnapFileNames,
+ createSnapManifest,
+ normalizeRelative,
+ HttpSnapIdStruct,
+} from '@metamask/snaps-utils';
+import { assert, assertStruct } from '@metamask/utils';
+
+import { SnapLocation } from './location';
+
+export interface HttpOptions {
+ /**
+ * @default fetch
+ */
+ fetch?: typeof fetch;
+ fetchOptions?: RequestInit;
+}
+
+export class HttpLocation implements SnapLocation {
+ // We keep contents separate because then we can use only one Blob in cache,
+ // which we convert to Uint8Array when actually returning the file.
+ //
+ // That avoids deepCloning file contents.
+ // I imagine ArrayBuffers are copy-on-write optimized, meaning
+ // in most often case we'll only have one file contents in common case.
+ private readonly cache = new Map<
+ string,
+ { file: VirtualFile; contents: Blob | string }
+ >();
+
+ private validatedManifest?: VirtualFile;
+
+ private readonly url: URL;
+
+ private readonly fetchFn: typeof fetch;
+
+ private readonly fetchOptions?: RequestInit;
+
+ constructor(url: URL, opts: HttpOptions = {}) {
+ assertStruct(url.toString(), HttpSnapIdStruct, 'Invalid Snap Id: ');
+ this.fetchFn = opts.fetch ?? globalThis.fetch.bind(globalThis);
+ this.fetchOptions = opts.fetchOptions;
+ this.url = url;
+ }
+
+ async manifest(): Promise> {
+ if (this.validatedManifest) {
+ return this.validatedManifest.clone();
+ }
+
+ // jest-fetch-mock doesn't handle new URL(), we need to convert .toString()
+ const canonicalPath = new URL(
+ NpmSnapFileNames.Manifest,
+ this.url.toString(),
+ ).toString();
+ const contents = await (
+ await this.fetchFn(canonicalPath, this.fetchOptions)
+ ).text();
+ const manifest = JSON.parse(contents);
+ const vfile = new VirtualFile({
+ value: contents,
+ result: createSnapManifest(manifest),
+ path: NpmSnapFileNames.Manifest,
+ data: { canonicalPath },
+ });
+ this.validatedManifest = vfile;
+
+ return this.manifest();
+ }
+
+ async fetch(path: string): Promise {
+ const relativePath = normalizeRelative(path);
+ const cached = this.cache.get(relativePath);
+ if (cached !== undefined) {
+ const { file, contents } = cached;
+ const value = contents.toString();
+ const vfile = file.clone();
+ vfile.value = value;
+ return vfile;
+ }
+
+ const canonicalPath = this.toCanonical(relativePath).toString();
+ const response = await this.fetchFn(canonicalPath, this.fetchOptions);
+ const vfile = new VirtualFile({
+ value: '',
+ path: relativePath,
+ data: { canonicalPath },
+ });
+
+ const blob = await response.text();
+ assert(
+ !this.cache.has(relativePath),
+ 'Corrupted cache, multiple files with same path.',
+ );
+
+ this.cache.set(relativePath, { file: vfile, contents: blob });
+
+ return this.fetch(relativePath);
+ }
+
+ get root(): URL {
+ return new URL(this.url.toString());
+ }
+
+ private toCanonical(path: string): URL {
+ assert(!path.startsWith('/'), 'Tried to parse absolute path.');
+ return new URL(path, this.url.toString());
+ }
+}
diff --git a/app/core/Snaps/location/index.ts b/app/core/Snaps/location/index.ts
new file mode 100644
index 00000000000..7ad3dc3e323
--- /dev/null
+++ b/app/core/Snaps/location/index.ts
@@ -0,0 +1,2 @@
+export * from './location';
+export * from './fetch';
diff --git a/app/core/Snaps/location/local.ts b/app/core/Snaps/location/local.ts
new file mode 100644
index 00000000000..b79c571551f
--- /dev/null
+++ b/app/core/Snaps/location/local.ts
@@ -0,0 +1,55 @@
+/* eslint-disable import/prefer-default-export */
+import {
+ SnapIdPrefixes,
+ SnapManifest,
+ VirtualFile,
+} from '@metamask/snaps-utils';
+import { HttpLocation, HttpOptions } from './http';
+import { SnapLocation } from './location';
+import { assert } from '@metamask/utils';
+
+export class LocalLocation implements SnapLocation {
+ readonly #http: HttpLocation;
+
+ constructor(url: URL, opts: HttpOptions = {}) {
+ // assertStruct(url.toString(), LocalSnapIdStruct, 'Invalid Snap Id');
+ // TODO(ritave): Write deepMerge() which merges fetchOptions.
+ assert(
+ opts.fetchOptions === undefined,
+ 'Currently adding fetch options to local: is unsupported.',
+ );
+
+ this.#http = new HttpLocation(
+ new URL(url.toString().slice(SnapIdPrefixes.local.length)),
+ opts,
+ );
+ }
+
+ async manifest(): Promise> {
+ const vfile = await this.#http.manifest();
+
+ return convertCanonical(vfile);
+ }
+
+ async fetch(path: string): Promise {
+ return convertCanonical(await this.#http.fetch(path));
+ }
+
+ get shouldAlwaysReload() {
+ return true;
+ }
+}
+
+/**
+ * Converts vfiles with canonical `http:` paths into `local:` paths.
+ *
+ * @param vfile - The {@link VirtualFile} to convert.
+ * @returns The same object with updated `.data.canonicalPath`.
+ */
+function convertCanonical(
+ vfile: VirtualFile,
+): VirtualFile {
+ assert(vfile.data.canonicalPath !== undefined);
+ vfile.data.canonicalPath = `local:${vfile.data.canonicalPath}`;
+ return vfile;
+}
diff --git a/app/core/Snaps/location/location.ts b/app/core/Snaps/location/location.ts
new file mode 100644
index 00000000000..97b88af6bdb
--- /dev/null
+++ b/app/core/Snaps/location/location.ts
@@ -0,0 +1,55 @@
+import { SnapManifest, VirtualFile } from '@metamask/snaps-utils';
+import { LocalLocation } from './local';
+import { NpmLocation, NpmOptions } from './npm';
+
+export type DetectSnapLocationOptions = NpmOptions & {
+ /**
+ * The function used to fetch data.
+ *
+ * @default globalThis.fetch
+ */
+ fetch?: typeof fetch;
+ /**
+ * @default false
+ */
+ allowHttp?: boolean;
+};
+
+/**
+ * This should be exported from the @metamask/snaps-contracts package
+ * for now we will define it ourselves
+ */
+export interface SnapLocation {
+ /**
+ * All files are relative to the manifest, except the manifest itself.
+ */
+ manifest(): Promise>;
+ fetch(path: string): Promise;
+
+ readonly shouldAlwaysReload?: boolean;
+}
+
+/**
+ * Auto-magically detects which SnapLocation object to create based on the provided {@link location}.
+ *
+ * @param location - A {@link https://github.com/MetaMask/SIPs/blob/main/SIPS/sip-8.md SIP-8} uri.
+ * @param opts - NPM options and feature flags.
+ * @returns SnapLocation based on url.
+ */
+
+export function detectSnapLocation(
+ location: string | URL,
+ opts?: DetectSnapLocationOptions,
+): SnapLocation {
+ const root = new URL(location.toString());
+ switch (root.protocol) {
+ case 'local:':
+ return new LocalLocation(root, opts);
+ case 'npm:':
+ return new NpmLocation(root, opts);
+ default:
+ throw new TypeError(
+ `Unrecognized "${root.protocol}" snap location protocol.`,
+ );
+ }
+}
diff --git a/app/core/Snaps/location/npm.ts b/app/core/Snaps/location/npm.ts
new file mode 100644
index 00000000000..81b5dc220ae
--- /dev/null
+++ b/app/core/Snaps/location/npm.ts
@@ -0,0 +1,441 @@
+import {
+ assertIsSemVerVersion,
+ createSnapManifest,
+ DEFAULT_REQUESTED_SNAP_VERSION,
+ getTargetVersion,
+ isValidUrl,
+ NpmSnapIdStruct,
+ SemVerRange,
+ SemVerVersion,
+ SnapManifest,
+ VirtualFile,
+ normalizeRelative,
+} from '@metamask/snaps-utils';
+import { assert, assertStruct, isObject } from '@metamask/utils';
+
+import { DetectSnapLocationOptions, SnapLocation } from './location';
+import { NativeModules } from 'react-native';
+import RNFetchBlob, { FetchBlobResponse } from 'rn-fetch-blob';
+import Logger from '../../../util/Logger';
+
+const { RNTar } = NativeModules;
+
+const DEFAULT_NPM_REGISTRY = 'https://registry.npmjs.org';
+
+export interface NpmOptions {
+ /**
+ * @default DEFAULT_REQUESTED_SNAP_VERSION
+ */
+ versionRange?: SemVerRange;
+ /**
+ * Whether to allow custom NPM registries outside of {@link DEFAULT_NPM_REGISTRY}.
+ *
+ * @default false
+ */
+ allowCustomRegistries?: boolean;
+}
+
+interface NpmMeta {
+ registry: string;
+ packageName: string;
+ requestedRange: SemVerRange;
+ version?: string;
+ fetch: typeof fetch;
+}
+export interface NpmOptions {
+ /**
+ * @default DEFAULT_REQUESTED_SNAP_VERSION
+ */
+ versionRange?: SemVerRange;
+ /**
+ * Whether to allow custom NPM registries outside of {@link DEFAULT_NPM_REGISTRY}.
+ *
+ * @default false
+ */
+ allowCustomRegistries?: boolean;
+}
+
+const SNAPS_NPM_LOG_TAG = 'snaps/ NPM';
+
+/**
+ * Reads and parses file from ReactNativeBlobUtil response
+ * @param path The path to the file to read and parse.
+ * @returns The parsed file data.
+ */
+
+const decompressFile = async (
+ path: string,
+ targetPath: string,
+): Promise => {
+ try {
+ const decompressedDataLocation = await RNTar.unTar(path, targetPath);
+ if (decompressedDataLocation) {
+ return decompressedDataLocation;
+ }
+ throw new Error('Was unable to decompress tgz file');
+ } catch (error) {
+ Logger.error(error as Error, `${SNAPS_NPM_LOG_TAG} 'decompressFile error`);
+ throw new Error(`${SNAPS_NPM_LOG_TAG} decompressFile error: ${error}`);
+ }
+};
+const readAndParseAt = async (path: string) => {
+ try {
+ return await RNFetchBlob.fs.readFile(path, 'utf8');
+ } catch (error) {
+ Logger.error(error as Error, `${SNAPS_NPM_LOG_TAG} readAndParseAt error`);
+ throw new Error(`${SNAPS_NPM_LOG_TAG} readAndParseAt error: ${error}`);
+ }
+};
+const fetchAndStoreNPMPackage = async (
+ inputRequest: RequestInfo,
+): Promise => {
+ const { config } = RNFetchBlob;
+ const targetDir = RNFetchBlob.fs.dirs.DocumentDir;
+ const filePath = `${targetDir}/archive.tgz`;
+ const urlToFetch: string =
+ typeof inputRequest === 'string' ? inputRequest : inputRequest.url;
+
+ try {
+ const response: FetchBlobResponse = await config({
+ fileCache: true,
+ path: filePath,
+ }).fetch('GET', urlToFetch);
+ const dataPath = response.data;
+ try {
+ const decompressedPath = await decompressFile(dataPath, targetDir);
+ // remove response file from cache
+ response.flush();
+ return decompressedPath;
+ } catch (error) {
+ Logger.error(
+ error as Error,
+ `${SNAPS_NPM_LOG_TAG} fetchAndStoreNPMPackage failed to decompress data`,
+ );
+ throw new Error(
+ `${SNAPS_NPM_LOG_TAG} fetchAndStoreNPMPackage failed to decompress data with error: ${error}`,
+ );
+ }
+ } catch (error) {
+ Logger.error(
+ error as Error,
+ `${SNAPS_NPM_LOG_TAG} fetchAndStoreNPMPackage failed to fetch`,
+ );
+ throw new Error(
+ `${SNAPS_NPM_LOG_TAG} fetchAndStoreNPMPackage failed to fetch with error: ${error}`,
+ );
+ }
+};
+
+const cleanupFileSystem = async (path: string) => {
+ RNFetchBlob.fs.unlink(path).catch((error) => {
+ throw new Error(
+ `${SNAPS_NPM_LOG_TAG} cleanupFileSystem failed to clean files at path with error: ${error}`,
+ );
+ });
+};
+
+/**
+ * The paths of files within npm tarballs appear to always be prefixed with
+ * "package/".
+ */
+export class NpmLocation implements SnapLocation {
+ private readonly meta: NpmMeta;
+
+ private validatedManifest?: VirtualFile;
+
+ private files?: Map;
+
+ constructor(url: URL, opts: DetectSnapLocationOptions = {}) {
+ const allowCustomRegistries = opts.allowCustomRegistries ?? false;
+ const fetchFunction = opts.fetch ?? globalThis.fetch.bind(globalThis);
+ const requestedRange = opts.versionRange ?? DEFAULT_REQUESTED_SNAP_VERSION;
+
+ assertStruct(url.toString(), NpmSnapIdStruct, 'Invalid Snap Id: ');
+
+ let registry: string | URL;
+ if (
+ url.host === '' &&
+ url.port === '' &&
+ url.username === '' &&
+ url.password === ''
+ ) {
+ registry = new URL(DEFAULT_NPM_REGISTRY);
+ } else {
+ registry = 'https://';
+ if (url.username) {
+ registry += url.username;
+ if (url.password) {
+ registry += `:${url.password}`;
+ }
+ registry += '@';
+ }
+ registry += url.host;
+ registry = new URL(registry);
+ assert(
+ allowCustomRegistries,
+ new TypeError(
+ `Custom NPM registries are disabled, tried to use "${registry.toString()}".`,
+ ),
+ );
+ }
+
+ assert(
+ registry.pathname === '/' &&
+ registry.search === '' &&
+ registry.hash === '',
+ );
+
+ assert(
+ url.pathname !== '' && url.pathname !== '/',
+ new TypeError('The package name in NPM location is empty.'),
+ );
+ let packageName = url.pathname;
+ if (packageName.startsWith('/')) {
+ packageName = packageName.slice(1);
+ }
+
+ this.meta = {
+ requestedRange,
+ registry: registry.toString(),
+ packageName,
+ fetch: fetchFunction,
+ };
+ }
+
+ async manifest(): Promise> {
+ if (this.validatedManifest) {
+ return this.validatedManifest.clone();
+ }
+
+ const vfile = await this.fetch('snap.manifest.json');
+ const result = JSON.parse(vfile.toString());
+ vfile.result = createSnapManifest(result);
+ this.validatedManifest = vfile as VirtualFile;
+
+ return this.manifest();
+ }
+
+ async fetch(path: string): Promise {
+ const relativePath = normalizeRelative(path);
+ if (!this.files) {
+ await this.#lazyInit();
+ assert(this.files !== undefined);
+ }
+ const vfile = this.files.get(relativePath);
+ assert(
+ vfile !== undefined,
+ new TypeError(`File "${path}" not found in package.`),
+ );
+ return vfile.clone();
+ }
+
+ get packageName(): string {
+ return this.meta.packageName;
+ }
+
+ get version(): string {
+ assert(
+ this.meta.version !== undefined,
+ 'Tried to access version without first fetching NPM package.',
+ );
+ return this.meta.version;
+ }
+
+ get registry(): string {
+ return this.meta.registry;
+ }
+
+ get versionRange(): SemVerRange {
+ return this.meta.requestedRange;
+ }
+
+ async #lazyInit() {
+ assert(this.files === undefined);
+ const [manifestData, sourceCodeData, iconData, actualVersion, pathToFiles] =
+ await fetchNpmTarball(
+ this.meta.packageName,
+ this.meta.requestedRange,
+ this.meta.registry,
+ this.meta.fetch,
+ );
+ this.meta.version = actualVersion;
+
+ let canonicalBase = 'npm://';
+ if (this.meta.registry.username !== '') {
+ canonicalBase += this.meta.registry.username;
+ if (this.meta.registry.password !== '') {
+ canonicalBase += `:${this.meta.registry.password}`;
+ }
+ canonicalBase += '@';
+ }
+ canonicalBase += this.meta.registry.host;
+
+ const manifestJSON = JSON.parse(manifestData.data);
+ const manifestVFile = new VirtualFile({
+ value: manifestData.data,
+ result: createSnapManifest(manifestJSON),
+ path: manifestData.filePath,
+ data: {
+ canonicalPath: `${canonicalBase}${manifestData.filePath}`,
+ },
+ });
+
+ const sourceCodeVFile = new VirtualFile({
+ value: sourceCodeData.data,
+ path: sourceCodeData.filePath,
+ data: { canonicalPath: `${canonicalBase}${sourceCodeData.filePath}` },
+ });
+
+ this.files = new Map();
+
+ if (iconData) {
+ const iconVFile = new VirtualFile({
+ value: iconData.data,
+ path: iconData.filePath,
+ data: { canonicalPath: `${canonicalBase}${iconData.filePath}` },
+ });
+ this.files.set(iconVFile.path, iconVFile);
+ }
+
+ this.files.set(manifestVFile.path, manifestVFile);
+ this.files.set(sourceCodeVFile.path, sourceCodeVFile);
+
+ // cleanup filesystem
+ await cleanupFileSystem(pathToFiles);
+ }
+}
+
+/**
+ * Fetches the tarball (`.tgz` file) of the specified package and version from
+ * the public npm registry. Throws an error if fetching fails.
+ *
+ * @param packageName - The name of the package whose tarball to fetch.
+ * @param versionRange - The SemVer range of the package to fetch. The highest
+ * version satisfying the range will be fetched.
+ * @param registryUrl - The URL of the npm registry to fetch the tarball from.
+ * @param fetchFunction - The fetch function to use. Defaults to the global
+ * {@link fetch}. Useful for Node.js compatibility.
+ * @returns A tuple of the {@link Response} for the package tarball and the
+ * actual version of the package.
+ */
+
+interface NPMTarBallData {
+ filePath: string;
+ data: string;
+}
+
+async function fetchNpmTarball(
+ packageName: string,
+ versionRange: SemVerRange,
+ registryUrl: string,
+ fetchFunction: typeof fetch,
+): Promise<
+ [
+ NPMTarBallData,
+ NPMTarBallData,
+ NPMTarBallData | undefined,
+ SemVerVersion,
+ string,
+ ]
+> {
+ const urlToFetch = new URL(packageName, registryUrl).toString();
+ const packageMetadata = await (await fetchFunction(urlToFetch)).json();
+
+ if (!isObject(packageMetadata)) {
+ throw new Error(
+ `${SNAPS_NPM_LOG_TAG} Failed to fetch package "${packageName}" metadata from npm.`,
+ );
+ }
+ const versions = Object.keys((packageMetadata as any)?.versions ?? {}).map(
+ (version) => {
+ assertIsSemVerVersion(version);
+ return version;
+ },
+ );
+
+ const targetVersion = getTargetVersion(versions, versionRange);
+
+ if (targetVersion === null) {
+ throw new Error(
+ `${SNAPS_NPM_LOG_TAG} Failed to find a matching version in npm metadata for package "${packageName}" and requested semver range "${versionRange}".`,
+ );
+ }
+
+ const tarballUrlString = (packageMetadata as any)?.versions?.[targetVersion]
+ ?.dist?.tarball;
+
+ if (
+ !isValidUrl(tarballUrlString) ||
+ !tarballUrlString.toString().endsWith('.tgz')
+ ) {
+ throw new Error(
+ `${SNAPS_NPM_LOG_TAG} Failed to find valid tarball URL in NPM metadata for package "${packageName}".`,
+ );
+ }
+
+ // Override the tarball hostname/protocol with registryUrl hostname/protocol
+ const newRegistryUrl = new URL(registryUrl);
+ const newTarballUrl = new URL(tarballUrlString.toString());
+ newTarballUrl.hostname = newRegistryUrl.hostname;
+ newTarballUrl.protocol = newRegistryUrl.protocol;
+
+ // Perform a raw fetch because we want the Response object itself.
+ const npmPackageDataLocation = await fetchAndStoreNPMPackage(
+ newTarballUrl.toString(),
+ );
+
+ // read and parse data from file
+ const manifestPath = `${npmPackageDataLocation}/snap.manifest.json`;
+ const manifest = await readAndParseAt(manifestPath);
+
+ if (!manifest) {
+ throw new Error(
+ `${SNAPS_NPM_LOG_TAG} Failed to fetch manifest from tarball for package "${packageName}".`,
+ );
+ }
+
+ const manifestData: NPMTarBallData = {
+ filePath: 'snap.manifest.json',
+ data: manifest,
+ };
+ const locations = JSON.parse(manifest).source.location.npm;
+
+ if (!locations && !locations.filePath) {
+ throw new Error(
+ `${SNAPS_NPM_LOG_TAG} No filePath location specified in manifest for "${packageName}".`,
+ );
+ }
+ const sourceCodePath = `${npmPackageDataLocation}/${locations.filePath}`;
+ const sourceCode = await readAndParseAt(sourceCodePath);
+
+ if (!sourceCode) {
+ throw new Error(
+ `${SNAPS_NPM_LOG_TAG} Failed to fetch source code from tarball for package "${packageName}".`,
+ );
+ }
+
+ const sourceCodeData: NPMTarBallData = {
+ filePath: locations.filePath,
+ data: sourceCode,
+ };
+ const icon: string | undefined = locations.iconPath
+ ? await readAndParseAt(
+ `${npmPackageDataLocation}/${locations.iconPath}`,
+ ).catch(() => undefined)
+ : undefined;
+
+ const iconData: NPMTarBallData | undefined = icon
+ ? {
+ filePath: locations.iconPath,
+ data: icon,
+ }
+ : undefined;
+
+ return [
+ manifestData,
+ sourceCodeData,
+ iconData,
+ targetVersion,
+ npmPackageDataLocation,
+ ];
+}
diff --git a/app/core/Snaps/permissions/permissions.ts b/app/core/Snaps/permissions/permissions.ts
new file mode 100644
index 00000000000..196f3984d50
--- /dev/null
+++ b/app/core/Snaps/permissions/permissions.ts
@@ -0,0 +1,41 @@
+import { endowmentPermissionBuilders } from '@metamask/snaps-controllers';
+import {
+ restrictedMethodPermissionBuilders,
+ selectHooks,
+} from '@metamask/rpc-methods';
+
+export const ExcludedSnapPermissions = new Set([]);
+export const ExcludedSnapEndowments = new Set(['endowment:keyring']);
+
+/**
+ * @returns {Record>} All endowment permission
+ * specifications.
+ */
+export const buildSnapEndowmentSpecifications = () =>
+ Object.values(endowmentPermissionBuilders).reduce(
+ (allSpecifications, { targetKey, specificationBuilder }) => {
+ if (!ExcludedSnapEndowments.has(targetKey)) {
+ allSpecifications[targetKey] = specificationBuilder();
+ }
+ return allSpecifications;
+ },
+ {},
+ );
+
+/**
+ * @param {Record} hooks - The hooks for the Snap
+ * restricted method implementations.
+ */
+export function buildSnapRestrictedMethodSpecifications(hooks: any) {
+ return Object.values(restrictedMethodPermissionBuilders).reduce(
+ (specifications, { targetKey, specificationBuilder, methodHooks }) => {
+ if (!ExcludedSnapPermissions.has(targetKey)) {
+ specifications[targetKey] = specificationBuilder({
+ methodHooks: selectHooks(hooks, methodHooks),
+ });
+ }
+ return specifications;
+ },
+ {},
+ );
+}
diff --git a/app/util/sentryUtils.js b/app/util/sentry/sentryUtils.js
similarity index 75%
rename from app/util/sentryUtils.js
rename to app/util/sentry/sentryUtils.js
index 9701c1083af..18395db1b91 100644
--- a/app/util/sentryUtils.js
+++ b/app/util/sentry/sentryUtils.js
@@ -1,11 +1,12 @@
/* eslint-disable import/no-namespace */
import * as Sentry from '@sentry/react-native';
import { Dedupe, ExtraErrorData } from '@sentry/integrations';
-import extractEthJsErrorMessage from './extractEthJsErrorMessage';
+import extractEthJsErrorMessage from '../extractEthJsErrorMessage';
import DefaultPreference from 'react-native-default-preference';
-import { AGREED, METRICS_OPT_IN } from '../constants/storage';
+import { AGREED, METRICS_OPT_IN } from '../../constants/storage';
const METAMASK_ENVIRONMENT = process.env['METAMASK_ENVIRONMENT'] || 'local'; // eslint-disable-line dot-notation
+const METAMASK_BUILD_TYPE = process.env['METAMASK_BUILD_TYPE'] || 'main'; // eslint-disable-line dot-notation
const ERROR_URL_ALLOWLIST = [
'cryptocompare.com',
@@ -149,17 +150,50 @@ function sanitizeAddressesFromErrorMessages(report) {
});
}
+/**
+ * Derives the Sentry environment based on input parameters.
+ * This function is similar to the environment logic used in MetaMask extension.
+ * - https://github.com/MetaMask/metamask-extension/blob/34375a57e558853aab95fe35d5f278aa52b66636/app/scripts/lib/setupSentry.js#L91
+ *
+ * @param {boolean} isDev - Represents if the current environment is development (__DEV__ global variable).
+ * @param {string} [metamaskEnvironment='local'] - The environment MetaMask is running in
+ * (process.env.METAMASK_ENVIRONMENT).
+ * It defaults to 'local' if not provided.
+ * @param {string} [metamaskBuildType='main'] - The build type of MetaMask
+ * (process.env.METAMASK_BUILD_TYPE).
+ * It defaults to 'main' if not provided.
+ *
+ * @returns {string} - "metamaskEnvironment-metamaskBuildType" or just "metamaskEnvironment" if the build type is "main".
+ */
+export function deriveSentryEnvironment(
+ isDev,
+ metamaskEnvironment = 'local',
+ metamaskBuildType = 'main',
+) {
+ if (isDev || !metamaskEnvironment) {
+ return 'development';
+ }
+
+ if (metamaskBuildType === 'main') {
+ return metamaskEnvironment;
+ }
+
+ return `${metamaskEnvironment}-${metamaskBuildType}`;
+}
+
// Setup sentry remote error reporting
export function setupSentry() {
const init = async () => {
const dsn = process.env.MM_SENTRY_DSN;
- const environment =
- __DEV__ || !METAMASK_ENVIRONMENT ? 'development' : METAMASK_ENVIRONMENT;
-
const metricsOptIn = await DefaultPreference.get(METRICS_OPT_IN);
const integrations = [new Dedupe(), new ExtraErrorData()];
+ const environment = deriveSentryEnvironment(
+ __DEV__,
+ METAMASK_ENVIRONMENT,
+ METAMASK_BUILD_TYPE,
+ );
Sentry.init({
dsn,
diff --git a/app/util/sentry/sentryUtils.test.ts b/app/util/sentry/sentryUtils.test.ts
new file mode 100644
index 00000000000..77a54a725b5
--- /dev/null
+++ b/app/util/sentry/sentryUtils.test.ts
@@ -0,0 +1,89 @@
+/* eslint-disable dot-notation */
+import { deriveSentryEnvironment } from './sentryUtils';
+
+describe('deriveSentryEnvironment', () => {
+ test('should return production-flask for environment if __DEV__ is false, METAMASK_ENVIRONMENT is production, and METAMASK_BUILD_TYPE is flask', async () => {
+ const METAMASK_ENVIRONMENT = 'production';
+ const METAMASK_BUILD_TYPE = 'flask';
+ const isDev = false;
+
+ const env = deriveSentryEnvironment(
+ isDev,
+ METAMASK_ENVIRONMENT,
+ METAMASK_BUILD_TYPE,
+ );
+ expect(env).toBe('production-flask');
+ });
+
+ test('should return local-flask for environment if __DEV__ is false, METAMASK_ENVIRONMENT is undefined, and METAMASK_BUILD_TYPE is flask', async () => {
+ const METAMASK_BUILD_TYPE = 'flask';
+ const isDev = false;
+
+ const env = deriveSentryEnvironment(isDev, undefined, METAMASK_BUILD_TYPE);
+ expect(env).toBe('local-flask');
+ });
+
+ test('should return debug-flask for environment if __DEV__ is false, METAMASK_ENVIRONMENT is debug, and METAMASK_BUILD_TYPE is flask', async () => {
+ const METAMASK_BUILD_TYPE = 'flask';
+ const METAMASK_ENVIRONMENT = 'debug';
+ const isDev = false;
+
+ const env = deriveSentryEnvironment(
+ isDev,
+ METAMASK_ENVIRONMENT,
+ METAMASK_BUILD_TYPE,
+ );
+ expect(env).toBe('debug-flask');
+ });
+
+ test('should return local for environment if __DEV__ is false, METAMASK_ENVIRONMENT is local, and METAMASK_BUILD_TYPE is undefined', async () => {
+ const isDev = false;
+ const METAMASK_ENVIRONMENT = 'local';
+
+ const env = deriveSentryEnvironment(isDev, METAMASK_ENVIRONMENT);
+ expect(env).toBe('local');
+ });
+
+ test('should return local for environment if __DEV__ is false, and both METAMASK_ENVIRONMENT and METAMASK_BUILD_TYPE are undefined', async () => {
+ const isDev = false;
+
+ const env = deriveSentryEnvironment(isDev);
+ expect(env).toBe('local');
+ });
+
+ test('should return production for environment if __DEV__ is false, METAMASK_ENVIRONMENT is production, and METAMASK_BUILD_TYPE is undefined', async () => {
+ const METAMASK_ENVIRONMENT = 'production';
+ const isDev = false;
+
+ const env = deriveSentryEnvironment(isDev, METAMASK_ENVIRONMENT, undefined);
+ expect(env).toBe('production');
+ });
+
+ test('should return development for environment if __DEV__ is true', async () => {
+ const isDev = true;
+
+ const env = deriveSentryEnvironment(isDev, '', '');
+ expect(env).toBe('development');
+ });
+
+ test('should return development for environment if __DEV__ is true, regardless of METAMASK_ENVIRONMENT and METAMASK_BUILD_TYPE', async () => {
+ const isDev = true;
+ const METAMASK_ENVIRONMENT = 'production';
+ const METAMASK_BUILD_TYPE = 'flask';
+
+ const env = deriveSentryEnvironment(
+ isDev,
+ METAMASK_ENVIRONMENT,
+ METAMASK_BUILD_TYPE,
+ );
+ expect(env).toBe('development');
+ });
+
+ test('should return local-flask for environment if __DEV__ is false, METAMASK_ENVIRONMENT is null, and METAMASK_BUILD_TYPE is flask', async () => {
+ const isDev = false;
+ const METAMASK_BUILD_TYPE = 'flask';
+
+ const env = deriveSentryEnvironment(isDev, undefined, METAMASK_BUILD_TYPE);
+ expect(env).toBe('local-flask');
+ });
+});
diff --git a/app/util/snaps/checkSnapsBlockList.js b/app/util/snaps/checkSnapsBlockList.js
new file mode 100644
index 00000000000..36bc337ccad
--- /dev/null
+++ b/app/util/snaps/checkSnapsBlockList.js
@@ -0,0 +1,34 @@
+import { satisfies as satisfiesSemver } from 'semver';
+
+/**
+ * Checks if provided snaps are on the block list.
+ *
+ * @param snapsToCheck - An object containing snap ids and other information.
+ * @param blocklist - An object containing snap ids, version or shasum of the blocked snaps.
+ * @returns An object structure containing snaps block information.
+ */
+const checkSnapsBlockList = async (snapsToCheck, blocklist) =>
+ Object.entries(snapsToCheck).reduce((acc, [snapId, snapInfo]) => {
+ const blockInfo = blocklist.find(
+ (blocked) =>
+ (blocked.id === snapId &&
+ satisfiesSemver(snapInfo.version, blocked.versionRange, {
+ includePrerelease: true,
+ })) ||
+ // Check for null/undefined for a case in which SnapController did not return
+ // a valid message. This will avoid blocking all snaps in the given case.
+ // Avoid having (undefined === undefined).
+ (blocked.shasum ? blocked.shasum === snapInfo.shasum : false),
+ );
+
+ acc[snapId] = blockInfo
+ ? {
+ blocked: true,
+ reason: blockInfo.reason,
+ infoUrl: blockInfo.infoUrl,
+ }
+ : { blocked: false };
+ return acc;
+ }, {});
+
+export default checkSnapsBlockList;
diff --git a/app/util/snaps/index.js b/app/util/snaps/index.js
new file mode 100644
index 00000000000..d98d474e9e3
--- /dev/null
+++ b/app/util/snaps/index.js
@@ -0,0 +1,4 @@
+import { SNAP_BLOCKLIST } from './snap-blocklist';
+import checkSnapsBlockList from './checkSnapsBlockList';
+
+export { checkSnapsBlockList, SNAP_BLOCKLIST };
diff --git a/app/util/snaps/snap-blocklist.ts b/app/util/snaps/snap-blocklist.ts
new file mode 100644
index 00000000000..c47c5e06183
--- /dev/null
+++ b/app/util/snaps/snap-blocklist.ts
@@ -0,0 +1,11 @@
+// eslint-disable-next-line import/prefer-default-export
+export const SNAP_BLOCKLIST = [
+ {
+ id: 'npm:@consensys/starknet-snap',
+ versionRange: '<0.1.11',
+ },
+ {
+ // @consensys/starknet-snap v:0.1.10
+ shasum: 'A83r5/ZIcKuKwuAnQHHByVFCuofj7jGK5hOStmHY6A0=',
+ },
+];
diff --git a/app/util/test/testSetup.js b/app/util/test/testSetup.js
index 9ab7064d16c..93cae34c470 100644
--- a/app/util/test/testSetup.js
+++ b/app/util/test/testSetup.js
@@ -284,6 +284,11 @@ jest.mock(
() => require('../../core/__mocks__/MockedEngine').default,
);
+// This should be removed once the snaps controller package is updated to the latest version
+jest.mock('@metamask/browser-passworder', () => ({
+ NativeModules: jest.fn(),
+}));
+
afterEach(() => {
jest.restoreAllMocks();
global.gc && global.gc(true);
diff --git a/bitrise.yml b/bitrise.yml
index 866459126f7..de02cb02ed2 100644
--- a/bitrise.yml
+++ b/bitrise.yml
@@ -53,6 +53,16 @@ pipelines:
stages:
- run_smoke_e2e_ios_android_stage: {}
- notify: {}
+ # Pipeline for Flask
+ create_flask_release_builds_pipeline:
+ stages:
+ - create_build_flask_release: {}
+ - notify: {}
+ release_flask_builds_to_store_pipeline:
+ stages:
+ - create_build_flask_release: {}
+ - deploy_flask_build_release: {}
+ - release_notify: {}
#Stages refrence workflows. Those workflows cannot but utility "_this-is-a-utility"
stages:
@@ -106,6 +116,17 @@ stages:
release_notify:
workflows:
- release_announcing_stores: {}
+ create_build_flask_release:
+ workflows:
+ - build_android_flask_release: {}
+ - build_ios_flask_release: {}
+ deploy_flask_build_release:
+ workflows:
+ - deploy_android_to_store:
+ envs:
+ - MM_ANDROID_PACKAGE_NAME: "io.metamask.flask"
+ - deploy_ios_to_store:
+
workflows:
# Code Setups
setup:
@@ -202,7 +223,7 @@ workflows:
- yarn@0:
inputs:
- command: build:announce
- title: Accouncing pre-release
+ title: Announcing pre-release
is_always_run: false
meta:
bitrise.io:
@@ -579,7 +600,7 @@ workflows:
- package_name: $MM_ANDROID_PACKAGE_NAME
envs:
- opts:
- is_expand: false
+ is_expand: true
MM_ANDROID_PACKAGE_NAME: io.metamask
deploy_ios_to_store:
steps:
@@ -671,6 +692,115 @@ workflows:
- pipeline_intermediate_files: sourcemaps/ios/index.js.map:BITRISE_APP_STORE_SOURCEMAP_PATH
- deploy_path: sourcemaps/ios/index.js.map
title: Deploy Source Map
+ build_ios_flask_release:
+ before_run:
+ - code_setup
+ after_run:
+ - _notify_failure_on_slack
+ steps:
+ - certificate-and-profile-installer@1: {}
+ - set-xcode-build-number@1:
+ inputs:
+ - build_short_version_string: $FLASK_VERSION_NAME
+ - build_version: $FLASK_VERSION_NUMBER
+ - plist_path: $PROJECT_LOCATION_IOS/MetaMask/MetaMask-Flask-Info.plist
+ - cocoapods-install@2: {}
+ - script@1:
+ inputs:
+ - content: |-
+ #!/usr/bin/env bash
+ node -v
+ METAMASK_BUILD_TYPE=flask METAMASK_ENVIRONMENT='production' yarn build:ios:pre-flask
+ title: iOS Sourcemaps & Build
+ is_always_run: false
+ - deploy-to-bitrise-io@2.2.3:
+ is_always_run: false
+ is_skippable: true
+ inputs:
+ - pipeline_intermediate_files: ios/build/output/MetaMask-Flask.ipa:BITRISE_APP_STORE_IPA_PATH
+ - deploy_path: ios/build/output/MetaMask-Flask.ipa
+ title: Deploy iOS IPA
+ - deploy-to-bitrise-io@1.6.1:
+ is_always_run: false
+ is_skippable: true
+ inputs:
+ - deploy_path: ios/build/MetaMask-Flask.xcarchive:BITRISE_APP_STORE_XCARCHIVE_PATH
+ title: Deploy Symbols File
+ - deploy-to-bitrise-io@2.2.3:
+ is_always_run: false
+ is_skippable: true
+ inputs:
+ - pipeline_intermediate_files: sourcemaps/ios/index.js.map:BITRISE_APP_STORE_SOURCEMAP_PATH
+ - deploy_path: sourcemaps/ios/index.js.map
+ title: Deploy Source Map
+ build_android_flask_release:
+ before_run:
+ - code_setup
+ after_run:
+ - _notify_failure_on_slack
+ steps:
+ - change-android-versioncode-and-versionname@1:
+ inputs:
+ - new_version_name: $FLASK_VERSION_NAME
+ - new_version_code: $FLASK_VERSION_NUMBER
+ - build_gradle_path: $PROJECT_LOCATION_ANDROID/app/build.gradle
+ - file-downloader@1:
+ inputs:
+ - source: $BITRISEIO_ANDROID_FLASK_KEYSTORE_URL_URL
+ - destination: android/keystores/flaskRelease.keystore
+ - restore-gradle-cache@1: {}
+ - install-missing-android-tools@2:
+ inputs:
+ - ndk_revision: $NDK_VERSION
+ - gradlew_path: $PROJECT_LOCATION/gradlew
+ - script@1:
+ inputs:
+ - content: |-
+ #!/usr/bin/env bash
+ node -v
+ METAMASK_BUILD_TYPE=flask METAMASK_ENVIRONMENT='production' yarn build:android:pre-release:bundle:flask
+ title: Build Android Pre-Release Bundle
+ is_always_run: false
+ - save-gradle-cache@1: {}
+ - deploy-to-bitrise-io@2.2.3:
+ is_always_run: false
+ is_skippable: true
+ inputs:
+ - pipeline_intermediate_files: $PROJECT_LOCATION/app/build/outputs/apk/flask/release/app-flask-release.apk:BITRISE_PLAY_STORE_APK_PATH
+ - deploy_path: $PROJECT_LOCATION/app/build/outputs/apk/flask/release/app-flask-release.apk
+ title: Bitrise Deploy APK
+ - deploy-to-bitrise-io@2.2.3:
+ is_always_run: false
+ is_skippable: true
+ inputs:
+ - pipeline_intermediate_files: $PROJECT_LOCATION/app/build/outputs/apk/flask/release/sha512sums.txt:BITRISE_PLAY_STORE_SHA512SUMS_PATH
+ - deploy_path: $PROJECT_LOCATION/app/build/outputs/apk/flask/release/sha512sums.txt
+ title: Bitrise Deploy Checksum
+ - deploy-to-bitrise-io@2.2.3:
+ is_always_run: false
+ is_skippable: true
+ inputs:
+ - pipeline_intermediate_files: $PROJECT_LOCATION/app/build/outputs/mapping/flaskRelease/mapping.txt:BITRISE_PLAY_STORE_MAPPING_PATH
+ - deploy_path: $PROJECT_LOCATION/app/build/outputs/mapping/flaskRelease/mapping.txt
+ title: Bitrise ProGuard Map Files
+ - deploy-to-bitrise-io@2.2.3:
+ is_always_run: false
+ is_skippable: true
+ inputs:
+ - pipeline_intermediate_files: $PROJECT_LOCATION/app/build/outputs/bundle/flaskRelease/app-flask-release.aab:BITRISE_PLAY_STORE_ABB_PATH
+ - deploy_path: $PROJECT_LOCATION/app/build/outputs/bundle/flaskRelease/app-flask-release.aab
+ title: Bitrise Deploy AAB
+ - deploy-to-bitrise-io@2.2.3:
+ is_always_run: false
+ is_skippable: true
+ inputs:
+ - pipeline_intermediate_files: /bitrise/src/sourcemaps/android/index.js.map:BITRISE_PLAY_STORE_SOURCEMAP_PATH
+ - deploy_path: sourcemaps/android/index.js.map
+ title: Bitrise Deploy Sourcemaps
+ meta:
+ bitrise.io:
+ stack: linux-docker-android-20.04
+ machine_type_id: elite-xl
app:
envs:
- opts:
@@ -702,10 +832,16 @@ app:
PROJECT_LOCATION_IOS: ios
- opts:
is_expand: false
- VERSION_NAME: 7.0.1
+ VERSION_NAME: 0.0.3
+ - opts:
+ is_expand: false
+ VERSION_NUMBER: 1128
+ - opts:
+ is_expand: false
+ FLASK_VERSION_NAME: 0.0.3
- opts:
is_expand: false
- VERSION_NUMBER: 1126
+ FLASK_VERSION_NUMBER: 1128
- opts:
is_expand: false
ANDROID_APK_LINK: ''
diff --git a/index.js b/index.js
index 37cf7150283..dfcddbdb1eb 100644
--- a/index.js
+++ b/index.js
@@ -10,7 +10,7 @@ import crypto from 'crypto'; // eslint-disable-line import/no-nodejs-modules, no
require('react-native-browser-polyfill'); // eslint-disable-line import/no-commonjs
import * as Sentry from '@sentry/react-native'; // eslint-disable-line import/no-namespace
-import { setupSentry } from './app/util/sentryUtils';
+import { setupSentry } from './app/util/sentry/sentryUtils';
setupSentry();
import { AppRegistry, LogBox } from 'react-native';
diff --git a/ios/Light-Swift-Untar-V2/Light-Swift-Untar.swift b/ios/Light-Swift-Untar-V2/Light-Swift-Untar.swift
new file mode 100644
index 00000000000..06468a7e99f
--- /dev/null
+++ b/ios/Light-Swift-Untar-V2/Light-Swift-Untar.swift
@@ -0,0 +1,175 @@
+//
+// Light-Swift-Untar.swift
+// MetaMask
+//
+// Created by Owen Craston on 2023-03-13.
+// Copyright © 2023 MetaMask. All rights reserved.
+//
+
+// https://github.com/UInt2048/Light-Swift-Untar
+
+import Foundation
+
+public typealias Closure = (Double) -> Void
+
+enum UntarError: Error, LocalizedError {
+ case notFound(file: String)
+ case corruptFile(type: UnicodeScalar)
+ public var errorDescription: String? {
+ switch self {
+ case let .notFound(file: file): return "Source file \(file) not found"
+ case let .corruptFile(type: type): return "Invalid block type \(type) found"
+ }
+ }
+}
+
+public extension FileManager {
+ // MARK: - Definitions
+ private static var tarBlockSize: UInt64 = 512
+ private static var tarTypePosition: UInt64 = 156
+ private static var tarNamePosition: UInt64 = 0
+ private static var tarNameSize: UInt64 = 100
+ private static var tarSizePosition: UInt64 = 124
+ private static var tarSizeSize: UInt64 = 12
+ private static var tarMaxBlockLoadInMemory: UInt64 = 100
+
+ // MARK: - Private Methods
+ private func createFilesAndDirectories(path: String, tarObject: Any, size: UInt64,
+ progress progressClosure: Closure?) throws -> Bool {
+ try! createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil)
+ var location: UInt64 = 0
+ while location < size {
+ var blockCount: UInt64 = 1
+ if let closure = progressClosure { closure(Double(location) / Double(size)) }
+
+ let type = self.type(object: tarObject, offset: location)
+ switch type {
+ case "0": // File
+ let name = self.name(object: tarObject, offset: location)
+ let filePath = URL(fileURLWithPath: path).appendingPathComponent(name).path
+ let size = self.size(object: tarObject, offset: location)
+ if size == 0 { try "".write(toFile: filePath, atomically: true, encoding: .utf8) } else {
+ blockCount += (size - 1) / FileManager.tarBlockSize + 1 // size / tarBlockSize rounded up
+ writeFileData(object: tarObject, location: location + FileManager.tarBlockSize,
+ length: size, path: filePath)
+ }
+ case "5": // Directory
+ let name = self.name(object: tarObject, offset: location)
+ let directoryPath = URL(fileURLWithPath: path).appendingPathComponent(name).path
+ try createDirectory(atPath: directoryPath, withIntermediateDirectories: true,
+ attributes: nil)
+ case "\0": break // Null block
+ case "x": blockCount += 1 // Extra header block
+ case "1": fallthrough
+ case "2": fallthrough
+ case "3": fallthrough
+ case "4": fallthrough
+ case "6": fallthrough
+ case "7": fallthrough
+ case "g": // Not a file nor directory
+ let size = self.size(object: tarObject, offset: location)
+ blockCount += UInt64(ceil(Double(size) / Double(FileManager.tarBlockSize)))
+ default: throw UntarError.corruptFile(type: type) // Not a tar type
+ }
+ location += blockCount * FileManager.tarBlockSize
+ }
+ return true
+ }
+
+ private func type(object: Any, offset: UInt64) -> UnicodeScalar {
+ let typeData = data(object: object, location: offset + FileManager.tarTypePosition, length: 1)!
+ return UnicodeScalar([UInt8](typeData)[0])
+ }
+
+ private func name(object: Any, offset: UInt64) -> String {
+ var nameSize = FileManager.tarNameSize
+ for i in 0...FileManager.tarNameSize {
+ let char = String(data: data(object: object, location: offset + FileManager.tarNamePosition + i, length: 1)!, encoding: .ascii)!
+ if char == "\0" {
+ nameSize = i
+ break
+ }
+ }
+ return String(data: data(object: object, location: offset + FileManager.tarNamePosition, length: nameSize)!, encoding: .utf8)!
+ }
+
+ private func size(object: Any, offset: UInt64) -> UInt64 {
+ let sizeData = data(object: object, location: offset + FileManager.tarSizePosition,
+ length: FileManager.tarSizeSize)!
+ let sizeString = String(data: sizeData, encoding: .ascii)!
+ return strtoull(sizeString, nil, 8) // Size is an octal number, convert to decimal
+ }
+
+ private func writeFileData(object: Any, location _loc: UInt64, length _len: UInt64,
+ path: String) {
+ let pathURL = URL(fileURLWithPath: path)
+ let directoryPathURL = pathURL.deletingLastPathComponent()
+ if let data = object as? Data {
+ if !fileExists(atPath: directoryPathURL.path) {
+ try! createDirectory(atPath: directoryPathURL.path, withIntermediateDirectories: true)
+ }
+ createFile(atPath: path, contents: data.subdata(in: Int(_loc) ..< Int(_loc + _len)),
+ attributes: nil)
+ } else if let fileHandle = object as? FileHandle {
+ if NSData().write(toFile: path, atomically: false) {
+ let destinationFile = FileHandle(forWritingAtPath: path)!
+ fileHandle.seek(toFileOffset: _loc)
+
+ let maxSize = FileManager.tarMaxBlockLoadInMemory * FileManager.tarBlockSize
+ var length = _len, location = _loc
+ while length > maxSize {
+ autoreleasepool { // Needed to prevent heap overflow when reading large files
+ destinationFile.write(fileHandle.readData(ofLength: Int(maxSize)))
+ }
+ location += maxSize
+ length -= maxSize
+ }
+ autoreleasepool { // Needed to prevent heap overflow when reading large files
+ destinationFile.write(fileHandle.readData(ofLength: Int(length)))
+ }
+ destinationFile.closeFile()
+ }
+ }
+ }
+
+ private func data(object: Any, location: UInt64, length: UInt64) -> Data? {
+ if let data = object as? Data {
+ return data.subdata(in: Int(location) ..< Int(location + length))
+ } else if let fileHandle = object as? FileHandle {
+ fileHandle.seek(toFileOffset: location)
+ return autoreleasepool { // Needed to prevent heap overflow when reading large files
+ fileHandle.readData(ofLength: Int(length))
+ }
+ }
+ return nil
+ }
+
+ // MARK: - Public Methods
+ // Return true when no error for convenience
+ @discardableResult func createFilesAndDirectories(path: String, tarData: Data,
+ progress: Closure? = nil) throws -> Bool {
+ try createFilesAndDirectories(path: path, tarObject: tarData, size: UInt64(tarData.count),
+ progress: progress)
+ }
+
+ @discardableResult func createFilesAndDirectories(url: URL, tarData: Data,
+ progress: Closure? = nil) throws -> Bool {
+ try createFilesAndDirectories(path: url.path, tarData: tarData, progress: progress)
+ }
+
+ @discardableResult func createFilesAndDirectories(path: String, tarPath: String,
+ progress: Closure? = nil) throws -> Bool {
+ let fileManager = FileManager.default
+ if fileManager.fileExists(atPath: tarPath) {
+ let attributes = try fileManager.attributesOfItem(atPath: tarPath)
+ let size = attributes[.size] as! UInt64
+ let fileHandle = FileHandle(forReadingAtPath: tarPath)!
+ let result = try createFilesAndDirectories(path: path, tarObject: fileHandle, size: size,
+ progress: progress)
+ fileHandle.closeFile()
+ return result
+ }
+
+ throw UntarError.notFound(file: tarPath)
+ }
+}
diff --git a/ios/MetaMask-Bridging-Header.h b/ios/MetaMask-Bridging-Header.h
index 1b2cb5d6d09..ed40b96141d 100644
--- a/ios/MetaMask-Bridging-Header.h
+++ b/ios/MetaMask-Bridging-Header.h
@@ -1,4 +1,5 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
+#import
diff --git a/ios/MetaMask.xcodeproj/project.pbxproj b/ios/MetaMask.xcodeproj/project.pbxproj
index 5e6fd2a1001..48ec16971f9 100644
--- a/ios/MetaMask.xcodeproj/project.pbxproj
+++ b/ios/MetaMask.xcodeproj/project.pbxproj
@@ -8,7 +8,7 @@
/* Begin PBXBuildFile section */
07CBADD9D4B441008304F8D3 /* EuclidCircularB-Light.otf in Resources */ = {isa = PBXBuildFile; fileRef = A98029A3662F4C1391489A6B /* EuclidCircularB-Light.otf */; };
- 0FD509E0336BF221F6527B24 /* libPods-MetaMask.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0FF04C201CE95ED7EEEA82 /* libPods-MetaMask.a */; };
+ 0FD509E0336BF221F6527B24 /* BuildFile 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 */; };
@@ -30,6 +30,59 @@
2A27FC9EEF1F4FD18E658544 /* config.json in Resources */ = {isa = PBXBuildFile; fileRef = EF1C01B7F08047F9B8ADCFBA /* config.json */; };
2CDF19FE9DEE4BF8B07154B1 /* EuclidCircularB-LightItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = F79EAC4A7BF74E458277AFA4 /* EuclidCircularB-LightItalic.otf */; };
2DB27BE39B164356A98A0FB1 /* Roboto-Italic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 5D7956F8525C4A45A2A555C3 /* Roboto-Italic.ttf */; };
+ 2E32B0B929C051E000C21DF9 /* Light-Swift-Untar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E32B0B829C051E000C21DF9 /* Light-Swift-Untar.swift */; };
+ 2E32B0BA29C051E000C21DF9 /* Light-Swift-Untar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E32B0B829C051E000C21DF9 /* Light-Swift-Untar.swift */; };
+ 2E74440729BC0B7D00F6125B /* RNTar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E74440629BC0B7D00F6125B /* RNTar.swift */; };
+ 2E74441029BC0CE100F6125B /* RNTar.m in Sources */ = {isa = PBXBuildFile; fileRef = 2E74440F29BC0CE100F6125B /* RNTar.m */; };
+ 2E7DBAD22A3BB07000150C1B /* MetaMask-Flask-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2E7DBABA2A3B7A9400150C1B /* MetaMask-Flask-Info.plist */; };
+ 2E85D2AB2A16E80B00AF164F /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
+ 2E85D2AC2A16E80B00AF164F /* RNTar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E74440629BC0B7D00F6125B /* RNTar.swift */; };
+ 2E85D2AD2A16E80B00AF164F /* File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 654378AF243E2ADC00571B9C /* File.swift */; };
+ 2E85D2AE2A16E80B00AF164F /* Light-Swift-Untar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E32B0B829C051E000C21DF9 /* Light-Swift-Untar.swift */; };
+ 2E85D2AF2A16E80B00AF164F /* RCTScreenshotDetect.m in Sources */ = {isa = PBXBuildFile; fileRef = CF98DA9B28D9FEB700096782 /* RCTScreenshotDetect.m */; };
+ 2E85D2B02A16E80B00AF164F /* RNTar.m in Sources */ = {isa = PBXBuildFile; fileRef = 2E74440F29BC0CE100F6125B /* RNTar.m */; };
+ 2E85D2B12A16E80B00AF164F /* RCTAnalytics.m in Sources */ = {isa = PBXBuildFile; fileRef = 15F7796422A1BC8C00B1DF8C /* RCTAnalytics.m */; };
+ 2E85D2B22A16E80B00AF164F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
+ 2E85D2B42A16E80B00AF164F /* LinkPresentation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F961A36A28105CF9007442B5 /* LinkPresentation.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
+ 2E85D2B52A16E80B00AF164F /* libRCTAesForked.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 650F2B9C24DC5FEC00C3B9C4 /* libRCTAesForked.a */; };
+ 2E85D2B62A16E80B00AF164F /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 153C1A742217BCDC0088EFE0 /* JavaScriptCore.framework */; };
+ 2E85D2B72A16E80B00AF164F /* Lottie.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 15723E2D225FD0B800A5B418 /* Lottie.framework */; };
+ 2E85D2B82A16E80B00AF164F /* Mixpanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 15F7795722A1B79400B1DF8C /* Mixpanel.framework */; };
+ 2E85D2B92A16E80B00AF164F /* Branch.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 153F84C92319B8DB00C19B63 /* Branch.framework */; };
+ 2E85D2BE2A16E80B00AF164F /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
+ 2E85D2BF2A16E80B00AF164F /* InpageBridgeWeb3.js in Resources */ = {isa = PBXBuildFile; fileRef = 158B0639211A72F500DF3C74 /* InpageBridgeWeb3.js */; };
+ 2E85D2C02A16E80B00AF164F /* Metamask.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 15D158EC210BD8C8006982B5 /* Metamask.ttf */; };
+ 2E85D2C12A16E80B00AF164F /* Roboto-Black.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 684F2C84313849199863B5FE /* Roboto-Black.ttf */; };
+ 2E85D2C22A16E80B00AF164F /* ThemeColors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B0EF7FA827BD16EA00D48B4E /* ThemeColors.xcassets */; };
+ 2E85D2C32A16E80B00AF164F /* Roboto-BlackItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 3E2492C67CF345CABD7B8601 /* Roboto-BlackItalic.ttf */; };
+ 2E85D2C42A16E80B00AF164F /* Roboto-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = A783D1CD7D27456796FE2E1B /* Roboto-Bold.ttf */; };
+ 2E85D2C52A16E80B00AF164F /* debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 15FDD82721B7642B006B7C35 /* debug.xcconfig */; };
+ 2E85D2C62A16E80B00AF164F /* Roboto-BoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C9FD3FB1258A41A5A0546C83 /* Roboto-BoldItalic.ttf */; };
+ 2E85D2C72A16E80B00AF164F /* Roboto-Italic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 5D7956F8525C4A45A2A555C3 /* Roboto-Italic.ttf */; };
+ 2E85D2C82A16E80B00AF164F /* Roboto-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = BB8BA2D3C0354D6090B56A8A /* Roboto-Light.ttf */; };
+ 2E85D2C92A16E80B00AF164F /* Roboto-LightItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E020F42F788744B3BCE17F05 /* Roboto-LightItalic.ttf */; };
+ 2E85D2CA2A16E80B00AF164F /* release.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 15FDD86021B76461006B7C35 /* release.xcconfig */; };
+ 2E85D2CB2A16E80B00AF164F /* Roboto-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C752564A28B44392AEE16BD5 /* Roboto-Medium.ttf */; };
+ 2E85D2CC2A16E80B00AF164F /* Roboto-MediumItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D5FF0FF1DFB74B3C8BB99E09 /* Roboto-MediumItalic.ttf */; };
+ 2E85D2CD2A16E80B00AF164F /* Roboto-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 459C4774EB724F2D8E12F088 /* Roboto-Regular.ttf */; };
+ 2E85D2CE2A16E80B00AF164F /* Roboto-Thin.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D9A37B5BF2914CF1B49EEF80 /* Roboto-Thin.ttf */; };
+ 2E85D2CF2A16E80B00AF164F /* Roboto-ThinItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = CF552F79C77A4184A690513A /* Roboto-ThinItalic.ttf */; };
+ 2E85D2D02A16E80B00AF164F /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; };
+ 2E85D2D12A16E80B00AF164F /* branch.json in Resources */ = {isa = PBXBuildFile; fileRef = FE3C9A2458A1416290DEDAD4 /* branch.json */; };
+ 2E85D2D22A16E80B00AF164F /* config.json in Resources */ = {isa = PBXBuildFile; fileRef = EF1C01B7F08047F9B8ADCFBA /* config.json */; };
+ 2E85D2D32A16E80B00AF164F /* EuclidCircularB-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = 67FBD519E04742E0AF191782 /* EuclidCircularB-Bold.otf */; };
+ 2E85D2D42A16E80B00AF164F /* EuclidCircularB-BoldItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = 13EE4910D3BD408A8FCCA5D7 /* EuclidCircularB-BoldItalic.otf */; };
+ 2E85D2D52A16E80B00AF164F /* EuclidCircularB-Light.otf in Resources */ = {isa = PBXBuildFile; fileRef = A98029A3662F4C1391489A6B /* EuclidCircularB-Light.otf */; };
+ 2E85D2D62A16E80B00AF164F /* EuclidCircularB-LightItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = F79EAC4A7BF74E458277AFA4 /* EuclidCircularB-LightItalic.otf */; };
+ 2E85D2D72A16E80B00AF164F /* EuclidCircularB-Medium.otf in Resources */ = {isa = PBXBuildFile; fileRef = CE0434C5FB7C4C6F9FEBDCE2 /* EuclidCircularB-Medium.otf */; };
+ 2E85D2D82A16E80B00AF164F /* EuclidCircularB-MediumItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = 42CBA652072F4BE2A8B815C1 /* EuclidCircularB-MediumItalic.otf */; };
+ 2E85D2D92A16E80B00AF164F /* EuclidCircularB-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = F564570593ED4F3FB10BD348 /* EuclidCircularB-Regular.otf */; };
+ 2E85D2DA2A16E80B00AF164F /* EuclidCircularB-RegularItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = 58572D81B5D54ED79A16A16D /* EuclidCircularB-RegularItalic.otf */; };
+ 2E85D2DB2A16E80B00AF164F /* EuclidCircularB-Semibold.otf in Resources */ = {isa = PBXBuildFile; fileRef = A8DE9C5BC0714D648276E123 /* EuclidCircularB-Semibold.otf */; };
+ 2E85D2DC2A16E80B00AF164F /* EuclidCircularB-SemiboldItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = 9499B01ECAC44DA29AC44E80 /* EuclidCircularB-SemiboldItalic.otf */; };
+ 2E85D2DF2A16E80B00AF164F /* Mixpanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 15F7795722A1B79400B1DF8C /* Mixpanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 2E85D2E02A16E80B00AF164F /* Branch.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 153F84C92319B8DB00C19B63 /* Branch.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 2E85D2E12A16E80B00AF164F /* Lottie.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 15723E2D225FD0B800A5B418 /* Lottie.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
34CEE49BC79D411687B42FA9 /* Roboto-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 459C4774EB724F2D8E12F088 /* Roboto-Regular.ttf */; };
373454C575C84C24B0BB24D4 /* EuclidCircularB-SemiboldItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = 9499B01ECAC44DA29AC44E80 /* EuclidCircularB-SemiboldItalic.otf */; };
39D0D096A0F340ABAC1A8565 /* EuclidCircularB-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = F564570593ED4F3FB10BD348 /* EuclidCircularB-Regular.otf */; };
@@ -38,6 +91,8 @@
4CEFC9E34A8D4288BFE2F85A /* Roboto-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = BB8BA2D3C0354D6090B56A8A /* Roboto-Light.ttf */; };
650F2B9D24DC5FF200C3B9C4 /* libRCTAesForked.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 650F2B9C24DC5FEC00C3B9C4 /* libRCTAesForked.a */; };
654378B0243E2ADC00571B9C /* File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 654378AF243E2ADC00571B9C /* File.swift */; };
+ 6E1FD38DC9243F4DF22F49B9 /* libPods-MetaMask-Flask.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4454F233F7494F04B6334FF6 /* libPods-MetaMask-Flask.a */; };
+ 782BB8912FB02775B458B3A2 /* libPods-MetaMask.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2CFCE553CC2CBA5D93E371AB /* libPods-MetaMask.a */; };
7C0226ABD9694AEDBAF3016F /* Roboto-ThinItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = CF552F79C77A4184A690513A /* Roboto-ThinItalic.ttf */; };
7E08FB90F3754D47994208B4 /* Roboto-Thin.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D9A37B5BF2914CF1B49EEF80 /* Roboto-Thin.ttf */; };
813214A2220E40C7BBB5ED9E /* Roboto-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = A783D1CD7D27456796FE2E1B /* Roboto-Bold.ttf */; };
@@ -89,13 +144,13 @@
B339FF32289ABD70001B89FB /* Branch.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 153F84C92319B8DB00C19B63 /* Branch.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
B339FF33289ABD70001B89FB /* Lottie.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 15723E2D225FD0B800A5B418 /* Lottie.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
B339FF3C289ABF2C001B89FB /* MetaMask-QA-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B339FEA72899852C001B89FB /* MetaMask-QA-Info.plist */; };
- B638844E306CAE9147B52C85 /* libPods-MetaMask.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0FF04C201CE95ED7EEEA82 /* libPods-MetaMask.a */; };
+ B638844E306CAE9147B52C85 /* BuildFile 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 */; };
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 /* libPods-MetaMask.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EA0FF04C201CE95ED7EEEA82 /* libPods-MetaMask.a */; };
+ D45BF85DECACCB74EDCBE88A /* BuildFile in Frameworks */ = {isa = PBXBuildFile; };
DC6A024F56DD43E1A83B47B1 /* Roboto-MediumItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D5FF0FF1DFB74B3C8BB99E09 /* Roboto-MediumItalic.ttf */; };
DDB2D8FF8BDA806A38D61B1B /* libPods-MetaMask-QA.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E0B857D61941CE8A3F59C662 /* libPods-MetaMask-QA.a */; };
E34DE917F6FC4438A6E88402 /* EuclidCircularB-BoldItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = 13EE4910D3BD408A8FCCA5D7 /* EuclidCircularB-BoldItalic.otf */; };
@@ -189,6 +244,27 @@
remoteGlobalIDString = 7C170C291A4A02F500D9E0F2;
remoteInfo = Mixpanel;
};
+ 2E85D2A32A16E80B00AF164F /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 4379F36F969347758D1A9F96 /* Lottie.xcodeproj */;
+ proxyType = 1;
+ remoteGlobalIDString = 62CA59B71E3C173B002D7188;
+ remoteInfo = Lottie_iOS;
+ };
+ 2E85D2A52A16E80B00AF164F /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 15F7794F22A1B79400B1DF8C /* Mixpanel.xcodeproj */;
+ proxyType = 1;
+ remoteGlobalIDString = 7C170C291A4A02F500D9E0F2;
+ remoteInfo = Mixpanel;
+ };
+ 2E85D2A72A16E80B00AF164F /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 153F84C42319B8DA00C19B63 /* BranchSDK.xcodeproj */;
+ proxyType = 1;
+ remoteGlobalIDString = E298D0511C73D1B800589D22;
+ remoteInfo = Branch;
+ };
650F2B9B24DC5FEC00C3B9C4 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 650F2B9724DC5FEB00C3B9C4 /* RCTAesForked.xcodeproj */;
@@ -233,6 +309,19 @@
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
+ 2E85D2DE2A16E80B00AF164F /* Embed Frameworks */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ 2E85D2DF2A16E80B00AF164F /* Mixpanel.framework in Embed Frameworks */,
+ 2E85D2E02A16E80B00AF164F /* Branch.framework in Embed Frameworks */,
+ 2E85D2E12A16E80B00AF164F /* Lottie.framework in Embed Frameworks */,
+ );
+ name = "Embed Frameworks";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
B339FF30289ABD70001B89FB /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
@@ -275,14 +364,21 @@
1C516951C09F43CB97129B66 /* Octicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Octicons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Octicons.ttf"; sourceTree = ""; };
1F06D56A2D2F41FB9345D16F /* Lottie.framework */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = wrapper.framework; name = Lottie.framework; path = System/Library/Frameworks/Lottie.framework; sourceTree = SDKROOT; };
278065D027394AD9B2906E38 /* libBVLinearGradient.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libBVLinearGradient.a; sourceTree = ""; };
+ 2CFCE553CC2CBA5D93E371AB /* libPods-MetaMask.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MetaMask.a"; sourceTree = BUILT_PRODUCTS_DIR; };
2D16E6891FA4F8E400B85C8A /* libReact.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libReact.a; sourceTree = BUILT_PRODUCTS_DIR; };
2D38A4BA1190B57818AFF2BC /* Pods-MetaMask.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MetaMask.debug.xcconfig"; path = "Target Support Files/Pods-MetaMask/Pods-MetaMask.debug.xcconfig"; sourceTree = ""; };
+ 2E32B0B829C051E000C21DF9 /* Light-Swift-Untar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Light-Swift-Untar.swift"; sourceTree = ""; };
+ 2E74440629BC0B7D00F6125B /* RNTar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNTar.swift; sourceTree = ""; };
+ 2E74440F29BC0CE100F6125B /* RNTar.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNTar.m; sourceTree = ""; };
+ 2E7DBABA2A3B7A9400150C1B /* MetaMask-Flask-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "MetaMask-Flask-Info.plist"; path = "MetaMask/MetaMask-Flask-Info.plist"; sourceTree = ""; };
+ 2E85D2E72A16E80B00AF164F /* MetaMask-Flask.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "MetaMask-Flask.app"; sourceTree = BUILT_PRODUCTS_DIR; };
3E2492C67CF345CABD7B8601 /* Roboto-BlackItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-BlackItalic.ttf"; path = "../app/fonts/Roboto-BlackItalic.ttf"; sourceTree = ""; };
42C239E9FAA64BD9A34B8D8A /* MaterialCommunityIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = MaterialCommunityIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf"; sourceTree = ""; };
42C6DDE3B80F47AFA9C9D4F5 /* Foundation.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Foundation.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Foundation.ttf"; sourceTree = ""; };
42CBA652072F4BE2A8B815C1 /* EuclidCircularB-MediumItalic.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "EuclidCircularB-MediumItalic.otf"; path = "../app/fonts/EuclidCircularB-MediumItalic.otf"; sourceTree = ""; };
4379F36F969347758D1A9F96 /* Lottie.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = Lottie.xcodeproj; path = "../node_modules/lottie-ios/Lottie.xcodeproj"; sourceTree = ""; };
4444176409EB42CB93AB03C5 /* SimpleLineIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = SimpleLineIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf"; sourceTree = ""; };
+ 4454F233F7494F04B6334FF6 /* libPods-MetaMask-Flask.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MetaMask-Flask.a"; sourceTree = BUILT_PRODUCTS_DIR; };
459C4774EB724F2D8E12F088 /* Roboto-Regular.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-Regular.ttf"; path = "../app/fonts/Roboto-Regular.ttf"; sourceTree = ""; };
4A2D27104599412CA00C35EF /* Ionicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Ionicons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf"; sourceTree = ""; };
57C103F40F394637B5A886FC /* FontAwesome5_Brands.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome5_Brands.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf"; sourceTree = ""; };
@@ -296,6 +392,7 @@
654378AF243E2ADC00571B9C /* File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = File.swift; sourceTree = ""; };
67FBD519E04742E0AF191782 /* EuclidCircularB-Bold.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "EuclidCircularB-Bold.otf"; path = "../app/fonts/EuclidCircularB-Bold.otf"; sourceTree = ""; };
684F2C84313849199863B5FE /* Roboto-Black.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Roboto-Black.ttf"; path = "../app/fonts/Roboto-Black.ttf"; sourceTree = ""; };
+ 686BD49BFE4458530BB14B75 /* Pods-MetaMask-Flask.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MetaMask-Flask.release.xcconfig"; path = "Target Support Files/Pods-MetaMask-Flask/Pods-MetaMask-Flask.release.xcconfig"; sourceTree = ""; };
7FF1597C0ACA4902B86140B2 /* Zocial.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Zocial.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Zocial.ttf"; sourceTree = ""; };
8E369AC13A2049B6B21E5120 /* libRCTSearchApi.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRCTSearchApi.a; sourceTree = ""; };
9499B01ECAC44DA29AC44E80 /* EuclidCircularB-SemiboldItalic.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "EuclidCircularB-SemiboldItalic.otf"; path = "../app/fonts/EuclidCircularB-SemiboldItalic.otf"; sourceTree = ""; };
@@ -323,7 +420,6 @@
E0B857D61941CE8A3F59C662 /* libPods-MetaMask-QA.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MetaMask-QA.a"; sourceTree = BUILT_PRODUCTS_DIR; };
E2CC0CA5C079854C6CC0D78C /* Pods-MetaMask-QA.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MetaMask-QA.debug.xcconfig"; path = "Target Support Files/Pods-MetaMask-QA/Pods-MetaMask-QA.debug.xcconfig"; sourceTree = ""; };
E9629905BA1940ADA4189921 /* Feather.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Feather.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Feather.ttf"; sourceTree = ""; };
- EA0FF04C201CE95ED7EEEA82 /* libPods-MetaMask.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MetaMask.a"; sourceTree = BUILT_PRODUCTS_DIR; };
EBC2B6371CD846D28B9FAADF /* FontAwesome5_Regular.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome5_Regular.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf"; sourceTree = ""; };
EF1C01B7F08047F9B8ADCFBA /* config.json */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = config.json; path = ../app/fonts/config.json; sourceTree = ""; };
F562CA6B28AA4A67AA29B61C /* MaterialIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = MaterialIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf"; sourceTree = ""; };
@@ -331,6 +427,7 @@
F79EAC4A7BF74E458277AFA4 /* EuclidCircularB-LightItalic.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "EuclidCircularB-LightItalic.otf"; path = "../app/fonts/EuclidCircularB-LightItalic.otf"; sourceTree = ""; };
F961A36A28105CF9007442B5 /* LinkPresentation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LinkPresentation.framework; path = System/Library/Frameworks/LinkPresentation.framework; sourceTree = SDKROOT; };
F9DFF7AC557B46B6BEFAA1C1 /* libRNShakeEvent.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNShakeEvent.a; sourceTree = ""; };
+ FC5E48C319F9B776E5380A67 /* Pods-MetaMask-Flask.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MetaMask-Flask.debug.xcconfig"; path = "Target Support Files/Pods-MetaMask-Flask/Pods-MetaMask-Flask.debug.xcconfig"; sourceTree = ""; };
FE3C9A2458A1416290DEDAD4 /* branch.json */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = branch.json; path = ../branch.json; sourceTree = ""; };
/* End PBXFileReference section */
@@ -345,9 +442,24 @@
15ACC9FC22655C3A0063978B /* Lottie.framework in Frameworks */,
15F7795E22A1B7B500B1DF8C /* Mixpanel.framework in Frameworks */,
153F84CA2319B8FD00C19B63 /* Branch.framework in Frameworks */,
- 0FD509E0336BF221F6527B24 /* libPods-MetaMask.a in Frameworks */,
- D45BF85DECACCB74EDCBE88A /* libPods-MetaMask.a in Frameworks */,
- B638844E306CAE9147B52C85 /* libPods-MetaMask.a in Frameworks */,
+ 0FD509E0336BF221F6527B24 /* BuildFile in Frameworks */,
+ D45BF85DECACCB74EDCBE88A /* BuildFile in Frameworks */,
+ B638844E306CAE9147B52C85 /* BuildFile in Frameworks */,
+ 782BB8912FB02775B458B3A2 /* libPods-MetaMask.a in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 2E85D2B32A16E80B00AF164F /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 2E85D2B42A16E80B00AF164F /* LinkPresentation.framework in Frameworks */,
+ 2E85D2B52A16E80B00AF164F /* libRCTAesForked.a in Frameworks */,
+ 2E85D2B62A16E80B00AF164F /* JavaScriptCore.framework in Frameworks */,
+ 2E85D2B72A16E80B00AF164F /* Lottie.framework in Frameworks */,
+ 2E85D2B82A16E80B00AF164F /* Mixpanel.framework in Frameworks */,
+ 2E85D2B92A16E80B00AF164F /* Branch.framework in Frameworks */,
+ 6E1FD38DC9243F4DF22F49B9 /* libPods-MetaMask-Flask.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -388,6 +500,7 @@
13B07FAE1A68108700A75B9A /* MetaMask */ = {
isa = PBXGroup;
children = (
+ 2E7DBABA2A3B7A9400150C1B /* MetaMask-Flask-Info.plist */,
B339FEA72899852C001B89FB /* MetaMask-QA-Info.plist */,
AA9EDF17249955C7005D89EE /* MetaMaskDebug.entitlements */,
15F7796222A1BC1E00B1DF8C /* NativeModules */,
@@ -473,12 +586,21 @@
F961A36A28105CF9007442B5 /* LinkPresentation.framework */,
153C1A742217BCDC0088EFE0 /* JavaScriptCore.framework */,
2D16E6891FA4F8E400B85C8A /* libReact.a */,
- EA0FF04C201CE95ED7EEEA82 /* libPods-MetaMask.a */,
E0B857D61941CE8A3F59C662 /* libPods-MetaMask-QA.a */,
+ 4454F233F7494F04B6334FF6 /* libPods-MetaMask-Flask.a */,
+ 2CFCE553CC2CBA5D93E371AB /* libPods-MetaMask.a */,
);
name = Frameworks;
sourceTree = "";
};
+ 2EC1321B29C51D6600E3DF48 /* Light-Swift-Untar-V2 */ = {
+ isa = PBXGroup;
+ children = (
+ 2E32B0B829C051E000C21DF9 /* Light-Swift-Untar.swift */,
+ );
+ path = "Light-Swift-Untar-V2";
+ sourceTree = "";
+ };
4A27949D046C4516B9653BBB /* Resources */ = {
isa = PBXGroup;
children = (
@@ -547,6 +669,9 @@
83CBB9F61A601CBA00E9B192 = {
isa = PBXGroup;
children = (
+ 2EC1321B29C51D6600E3DF48 /* Light-Swift-Untar-V2 */,
+ 2E74440629BC0B7D00F6125B /* RNTar.swift */,
+ 2E74440F29BC0CE100F6125B /* RNTar.m */,
654378AF243E2ADC00571B9C /* File.swift */,
15FDD86021B76461006B7C35 /* release.xcconfig */,
15FDD82721B7642B006B7C35 /* debug.xcconfig */,
@@ -570,6 +695,7 @@
children = (
13B07F961A680F5B00A75B9A /* MetaMask.app */,
B339FF39289ABD70001B89FB /* MetaMask-QA.app */,
+ 2E85D2E72A16E80B00AF164F /* MetaMask-Flask.app */,
);
name = Products;
sourceTree = "";
@@ -581,6 +707,8 @@
5B6B5F6E5C0A886D11EF7F6F /* Pods-MetaMask.release.xcconfig */,
E2CC0CA5C079854C6CC0D78C /* Pods-MetaMask-QA.debug.xcconfig */,
5D89472CA15F3091AE95E296 /* Pods-MetaMask-QA.release.xcconfig */,
+ FC5E48C319F9B776E5380A67 /* Pods-MetaMask-Flask.debug.xcconfig */,
+ 686BD49BFE4458530BB14B75 /* Pods-MetaMask-Flask.release.xcconfig */,
);
path = Pods;
sourceTree = "";
@@ -623,6 +751,32 @@
productReference = 13B07F961A680F5B00A75B9A /* MetaMask.app */;
productType = "com.apple.product-type.application";
};
+ 2E85D2A12A16E80B00AF164F /* MetaMask-Flask */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 2E85D2E42A16E80B00AF164F /* Build configuration list for PBXNativeTarget "MetaMask-Flask" */;
+ buildPhases = (
+ 2E85D2A82A16E80B00AF164F /* [CP] Check Pods Manifest.lock */,
+ 2E85D2A92A16E80B00AF164F /* Override xcconfig files */,
+ 2E85D2AA2A16E80B00AF164F /* Sources */,
+ 2E85D2B32A16E80B00AF164F /* Frameworks */,
+ 2E85D2BD2A16E80B00AF164F /* Resources */,
+ 2E85D2DD2A16E80B00AF164F /* Bundle React Native code and images */,
+ 2E85D2DE2A16E80B00AF164F /* Embed Frameworks */,
+ 2E85D2E22A16E80B00AF164F /* [CP] Embed Pods Frameworks */,
+ 2E85D2E32A16E80B00AF164F /* [CP] Copy Pods Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 2E85D2A22A16E80B00AF164F /* PBXTargetDependency */,
+ 2E85D2A42A16E80B00AF164F /* PBXTargetDependency */,
+ 2E85D2A62A16E80B00AF164F /* PBXTargetDependency */,
+ );
+ name = "MetaMask-Flask";
+ productName = "Hello World";
+ productReference = 2E85D2E72A16E80B00AF164F /* MetaMask-Flask.app */;
+ productType = "com.apple.product-type.application";
+ };
B339FEF8289ABD70001B89FB /* MetaMask-QA */ = {
isa = PBXNativeTarget;
buildConfigurationList = B339FF36289ABD70001B89FB /* Build configuration list for PBXNativeTarget "MetaMask-QA" */;
@@ -707,6 +861,7 @@
targets = (
13B07F861A680F5B00A75B9A /* MetaMask */,
B339FEF8289ABD70001B89FB /* MetaMask-QA */,
+ 2E85D2A12A16E80B00AF164F /* MetaMask-Flask */,
);
};
/* End PBXProject section */
@@ -823,6 +978,45 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 2E85D2BD2A16E80B00AF164F /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 2E85D2BE2A16E80B00AF164F /* Images.xcassets in Resources */,
+ 2E85D2BF2A16E80B00AF164F /* InpageBridgeWeb3.js in Resources */,
+ 2E85D2C02A16E80B00AF164F /* Metamask.ttf in Resources */,
+ 2E85D2C12A16E80B00AF164F /* Roboto-Black.ttf in Resources */,
+ 2E85D2C22A16E80B00AF164F /* ThemeColors.xcassets in Resources */,
+ 2E85D2C32A16E80B00AF164F /* Roboto-BlackItalic.ttf in Resources */,
+ 2E85D2C42A16E80B00AF164F /* Roboto-Bold.ttf in Resources */,
+ 2E85D2C52A16E80B00AF164F /* debug.xcconfig in Resources */,
+ 2E85D2C62A16E80B00AF164F /* Roboto-BoldItalic.ttf in Resources */,
+ 2E85D2C72A16E80B00AF164F /* Roboto-Italic.ttf in Resources */,
+ 2E85D2C82A16E80B00AF164F /* Roboto-Light.ttf in Resources */,
+ 2E85D2C92A16E80B00AF164F /* Roboto-LightItalic.ttf in Resources */,
+ 2E85D2CA2A16E80B00AF164F /* release.xcconfig in Resources */,
+ 2E85D2CB2A16E80B00AF164F /* Roboto-Medium.ttf in Resources */,
+ 2E85D2CC2A16E80B00AF164F /* Roboto-MediumItalic.ttf in Resources */,
+ 2E85D2CD2A16E80B00AF164F /* Roboto-Regular.ttf in Resources */,
+ 2E85D2CE2A16E80B00AF164F /* Roboto-Thin.ttf in Resources */,
+ 2E85D2CF2A16E80B00AF164F /* Roboto-ThinItalic.ttf in Resources */,
+ 2E85D2D02A16E80B00AF164F /* LaunchScreen.xib in Resources */,
+ 2E85D2D12A16E80B00AF164F /* branch.json in Resources */,
+ 2E85D2D22A16E80B00AF164F /* config.json in Resources */,
+ 2E85D2D32A16E80B00AF164F /* EuclidCircularB-Bold.otf in Resources */,
+ 2E85D2D42A16E80B00AF164F /* EuclidCircularB-BoldItalic.otf in Resources */,
+ 2E85D2D52A16E80B00AF164F /* EuclidCircularB-Light.otf in Resources */,
+ 2E85D2D62A16E80B00AF164F /* EuclidCircularB-LightItalic.otf in Resources */,
+ 2E85D2D72A16E80B00AF164F /* EuclidCircularB-Medium.otf in Resources */,
+ 2E85D2D82A16E80B00AF164F /* EuclidCircularB-MediumItalic.otf in Resources */,
+ 2E7DBAD22A3BB07000150C1B /* MetaMask-Flask-Info.plist in Resources */,
+ 2E85D2D92A16E80B00AF164F /* EuclidCircularB-Regular.otf in Resources */,
+ 2E85D2DA2A16E80B00AF164F /* EuclidCircularB-RegularItalic.otf in Resources */,
+ 2E85D2DB2A16E80B00AF164F /* EuclidCircularB-Semibold.otf in Resources */,
+ 2E85D2DC2A16E80B00AF164F /* EuclidCircularB-SemiboldItalic.otf in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
B339FF0F289ABD70001B89FB /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -900,6 +1094,97 @@
shellPath = /bin/sh;
shellScript = "if [ -e ../.ios.env ]\nthen\n cp -rf ../.ios.env debug.xcconfig\n cp -rf ../.ios.env release.xcconfig\nelse\n cp -rf ../.ios.env.example debug.xcconfig\n cp -rf ../.ios.env.example release.xcconfig\nfi\n\n";
};
+ 2E85D2A82A16E80B00AF164F /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-MetaMask-Flask-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 2E85D2A92A16E80B00AF164F /* Override xcconfig files */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ );
+ name = "Override xcconfig files";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "if [ -e ../.ios.env ]\nthen\n cp -rf ../.ios.env debug.xcconfig\n cp -rf ../.ios.env release.xcconfig\nelse\n cp -rf ../.ios.env.example debug.xcconfig\n cp -rf ../.ios.env.example release.xcconfig\nfi\n\n";
+ };
+ 2E85D2DD2A16E80B00AF164F /* Bundle React Native code and images */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-MetaMask/Pods-MetaMask-frameworks-${CONFIGURATION}-input-files.xcfilelist",
+ );
+ inputPaths = (
+ );
+ name = "Bundle React Native code and images";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "if [[ -s \"$HOME/.nvm/nvm.sh\" ]]; then\n. \"$HOME/.nvm/nvm.sh\"\nelif [[ -x \"$(command -v brew)\" && -s \"$(brew --prefix nvm)/nvm.sh\" ]]; then\n. \"$(brew --prefix nvm)/nvm.sh\"\nfi\n\nexport NODE_BINARY=$(which node)\n\nif [[ -s \"$HOME/.nvm/nvm.sh\" ]]; then\n. \"$HOME/.nvm/nvm.sh\"\nelif [[ -x \"$(command -v brew)\" && -s \"$(brew --prefix nvm)/nvm.sh\" ]]; then\n. \"$(brew --prefix nvm)/nvm.sh\"\nfi\n\nif [ ! -e \"${SENTRY_PROPERTIES}\" ]; then\n export SENTRY_PROPERTIES=../sentry.properties\nfi\n\n../node_modules/@sentry/cli/bin/sentry-cli react-native xcode \\\n ../node_modules/react-native/scripts/react-native-xcode.sh\n";
+ };
+ 2E85D2E22A16E80B00AF164F /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-MetaMask-Flask/Pods-MetaMask-Flask-frameworks-${CONFIGURATION}-input-files.xcfilelist",
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-MetaMask-Flask/Pods-MetaMask-Flask-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-MetaMask-Flask/Pods-MetaMask-Flask-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 2E85D2E32A16E80B00AF164F /* [CP] Copy Pods Resources */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-MetaMask-Flask/Pods-MetaMask-Flask-resources-${CONFIGURATION}-input-files.xcfilelist",
+ );
+ name = "[CP] Copy Pods Resources";
+ outputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-MetaMask-Flask/Pods-MetaMask-Flask-resources-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-MetaMask-Flask/Pods-MetaMask-Flask-resources.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
3263892F1BBB809723CB4024 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -1055,18 +1340,37 @@
buildActionMask = 2147483647;
files = (
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */,
+ 2E74440729BC0B7D00F6125B /* RNTar.swift in Sources */,
654378B0243E2ADC00571B9C /* File.swift in Sources */,
+ 2E32B0B929C051E000C21DF9 /* Light-Swift-Untar.swift in Sources */,
CF98DA9C28D9FEB700096782 /* RCTScreenshotDetect.m in Sources */,
+ 2E74441029BC0CE100F6125B /* RNTar.m in Sources */,
15F7796522A1BC8C00B1DF8C /* RCTAnalytics.m in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 2E85D2AA2A16E80B00AF164F /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 2E85D2AB2A16E80B00AF164F /* AppDelegate.m in Sources */,
+ 2E85D2AC2A16E80B00AF164F /* RNTar.swift in Sources */,
+ 2E85D2AD2A16E80B00AF164F /* File.swift in Sources */,
+ 2E85D2AE2A16E80B00AF164F /* Light-Swift-Untar.swift in Sources */,
+ 2E85D2AF2A16E80B00AF164F /* RCTScreenshotDetect.m in Sources */,
+ 2E85D2B02A16E80B00AF164F /* RNTar.m in Sources */,
+ 2E85D2B12A16E80B00AF164F /* RCTAnalytics.m in Sources */,
+ 2E85D2B22A16E80B00AF164F /* main.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
B339FF01289ABD70001B89FB /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
CFD8DFC828EDD4C800CC75F6 /* RCTScreenshotDetect.m in Sources */,
+ 2E32B0BA29C051E000C21DF9 /* Light-Swift-Untar.swift in Sources */,
B339FF02289ABD70001B89FB /* AppDelegate.m in Sources */,
B339FF03289ABD70001B89FB /* File.swift in Sources */,
B339FF04289ABD70001B89FB /* RCTAnalytics.m in Sources */,
@@ -1092,6 +1396,21 @@
name = Mixpanel;
targetProxy = 15F7796022A1B7B500B1DF8C /* PBXContainerItemProxy */;
};
+ 2E85D2A22A16E80B00AF164F /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ name = Lottie_iOS;
+ targetProxy = 2E85D2A32A16E80B00AF164F /* PBXContainerItemProxy */;
+ };
+ 2E85D2A42A16E80B00AF164F /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ name = Mixpanel;
+ targetProxy = 2E85D2A52A16E80B00AF164F /* PBXContainerItemProxy */;
+ };
+ 2E85D2A62A16E80B00AF164F /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ name = Branch;
+ targetProxy = 2E85D2A72A16E80B00AF164F /* PBXContainerItemProxy */;
+ };
B339FEF9289ABD70001B89FB /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
name = Lottie_iOS;
@@ -1131,11 +1450,13 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1126;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 48XVW22RCG;
+ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@@ -1182,6 +1503,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "io.metamask.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = MetaMask;
PROVISIONING_PROFILE_SPECIFIER = "match Development io.metamask.MetaMask";
+ "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Bitrise AppStore io.metamask.MetaMask";
SWIFT_OBJC_BRIDGING_HEADER = "MetaMask-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@@ -1255,6 +1577,146 @@
};
name = Release;
};
+ 2E85D2E52A16E80B00AF164F /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = FC5E48C319F9B776E5380A67 /* Pods-MetaMask-Flask.debug.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-Flask";
+ ASSETCATALOG_COMPILER_OPTIMIZATION = time;
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
+ CODE_SIGN_STYLE = Manual;
+ CURRENT_PROJECT_VERSION = 1128;
+ DEAD_CODE_STRIPPING = YES;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ DEVELOPMENT_TEAM = 48XVW22RCG;
+ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG;
+ ENABLE_BITCODE = NO;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)",
+ );
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(SRCROOT)/../node_modules/react-native-wkwebview-reborn/ios/RCTWKWebView",
+ "$(SRCROOT)/../node_modules/react-native-keychain/RNKeychainManager",
+ "$(SRCROOT)/../node_modules/react-native-share/ios",
+ "$(SRCROOT)/../node_modules/react-native-branch/ios/**",
+ "$(SRCROOT)/../node_modules/react-native-search-api/ios/RCTSearchApi",
+ "$(SRCROOT)/../node_modules/lottie-ios/lottie-ios/Classes/**",
+ "$(SRCROOT)/../node_modules/react-native-view-shot/ios",
+ "$(SRCROOT)/../node_modules/react-native-tcp/ios/**",
+ );
+ INFOPLIST_FILE = "MetaMask/MetaMask-Flask-Info.plist";
+ INFOPLIST_KEY_CFBundleDisplayName = "MetaMask Flask";
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ LIBRARY_SEARCH_PATHS = (
+ "$(inherited)",
+ "\"$(SRCROOT)/MetaMask/System/Library/Frameworks\"",
+ );
+ LLVM_LTO = YES;
+ MARKETING_VERSION = 0.0.3;
+ ONLY_ACTIVE_ARCH = YES;
+ OTHER_CFLAGS = (
+ "$(inherited)",
+ "-DFB_SONARKIT_ENABLED=1",
+ );
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ "-ObjC",
+ "-lc++",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = "io.metamask.MetaMask-Flask";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "match Development io.metamask.MetaMask";
+ "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Bitrise AppStore io.metamask.MetaMask-Flask";
+ SWIFT_OBJC_BRIDGING_HEADER = "MetaMask-Bridging-Header.h";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Debug;
+ };
+ 2E85D2E62A16E80B00AF164F /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 686BD49BFE4458530BB14B75 /* Pods-MetaMask-Flask.release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-Flask";
+ ASSETCATALOG_COMPILER_OPTIMIZATION = time;
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements;
+ CODE_SIGN_IDENTITY = "iPhone Distribution";
+ CODE_SIGN_STYLE = Manual;
+ CURRENT_PROJECT_VERSION = 1128;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ DEVELOPMENT_TEAM = 48XVW22RCG;
+ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG;
+ ENABLE_BITCODE = NO;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)",
+ );
+ GCC_PRECOMPILE_PREFIX_HEADER = YES;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DISABLE_MIXPANEL_AB_DESIGNER=1",
+ "$(inherited)",
+ );
+ GCC_UNROLL_LOOPS = YES;
+ HEADER_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(SRCROOT)/../node_modules/react-native-wkwebview-reborn/ios/RCTWKWebView",
+ "$(SRCROOT)/../node_modules/react-native-keychain/RNKeychainManager",
+ "$(SRCROOT)/../node_modules/react-native-share/ios",
+ "$(SRCROOT)/../node_modules/react-native-branch/ios/**",
+ "$(SRCROOT)/../node_modules/react-native-search-api/ios/RCTSearchApi",
+ "$(SRCROOT)/../node_modules/lottie-ios/lottie-ios/Classes/**",
+ "$(SRCROOT)/../node_modules/react-native-view-shot/ios",
+ "$(SRCROOT)/../node_modules/react-native-tcp/ios/**",
+ );
+ INFOPLIST_FILE = "MetaMask/MetaMask-Flask-Info.plist";
+ INFOPLIST_KEY_CFBundleDisplayName = "MetaMask Flask";
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ LIBRARY_SEARCH_PATHS = (
+ "$(inherited)",
+ "\"$(SRCROOT)/MetaMask/System/Library/Frameworks\"",
+ );
+ LLVM_LTO = YES;
+ MARKETING_VERSION = 0.0.3;
+ ONLY_ACTIVE_ARCH = NO;
+ OTHER_CFLAGS = (
+ "$(inherited)",
+ "-DFB_SONARKIT_ENABLED=1",
+ );
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ "-ObjC",
+ "-lc++",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = "io.metamask.$(PRODUCT_NAME:rfc1034identifier)";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "Bitrise AppStore io.metamask.MetaMask";
+ "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Bitrise AppStore io.metamask.MetaMask-Flask";
+ SWIFT_OBJC_BRIDGING_HEADER = "MetaMask-Bridging-Header.h";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Release;
+ };
83CBBA201A601CBA00E9B192 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 15FDD82721B7642B006B7C35 /* debug.xcconfig */;
@@ -1344,11 +1806,13 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1126;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 48XVW22RCG;
+ "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@@ -1395,6 +1859,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "io.metamask.MetaMask-QA";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "match Development io.metamask.MetaMask";
+ "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development io.metamask.MetaMask";
SWIFT_OBJC_BRIDGING_HEADER = "MetaMask-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@@ -1480,6 +1945,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Debug;
};
+ 2E85D2E42A16E80B00AF164F /* Build configuration list for PBXNativeTarget "MetaMask-Flask" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 2E85D2E52A16E80B00AF164F /* Debug */,
+ 2E85D2E62A16E80B00AF164F /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Debug;
+ };
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "MetaMask" */ = {
isa = XCConfigurationList;
buildConfigurations = (
diff --git a/ios/MetaMask.xcodeproj/xcshareddata/xcschemes/MetaMask-Flask.xcscheme b/ios/MetaMask.xcodeproj/xcshareddata/xcschemes/MetaMask-Flask.xcscheme
new file mode 100644
index 00000000000..78731f691c2
--- /dev/null
+++ b/ios/MetaMask.xcodeproj/xcshareddata/xcschemes/MetaMask-Flask.xcscheme
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/Contents.json b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/Contents.json
new file mode 100644
index 00000000000..f1bf95e2150
--- /dev/null
+++ b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/Contents.json
@@ -0,0 +1,119 @@
+{
+ "images" : [
+ {
+ "filename" : "iOS App Icon_20pt@2x.png",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "iOS App Icon_20pt@3x.png",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "scale" : "3x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "iOS App Icon_29pt@2x.png",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "iOS App Icon_29pt@3x.png",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "scale" : "3x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "iOS App Icon_38pt@2x.png",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "scale" : "2x",
+ "size" : "38x38"
+ },
+ {
+ "filename" : "iOS App Icon_38pt@3x.png",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "scale" : "3x",
+ "size" : "38x38"
+ },
+ {
+ "filename" : "iOS App Icon_40pt@2x.png",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "iOS App Icon_40pt@3x.png",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "scale" : "3x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "iOS App Icon_60pt@2x.png",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "scale" : "2x",
+ "size" : "60x60"
+ },
+ {
+ "filename" : "iOS App Icon_60pt@3x.png",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "scale" : "3x",
+ "size" : "60x60"
+ },
+ {
+ "filename" : "Flask iOS App Icon_64pt@2x.png",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "scale" : "2x",
+ "size" : "64x64"
+ },
+ {
+ "filename" : "Flask iOS App Icon_64pt@3x.png",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "scale" : "3x",
+ "size" : "64x64"
+ },
+ {
+ "filename" : "iOS App Icon_68@2x.png",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "scale" : "2x",
+ "size" : "68x68"
+ },
+ {
+ "filename" : "iOS App Icon_76pt@2x.png",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "scale" : "2x",
+ "size" : "76x76"
+ },
+ {
+ "filename" : "iOS App Icon_83.5@2x.png",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "scale" : "2x",
+ "size" : "83.5x83.5"
+ },
+ {
+ "filename" : "iOS App Icon_1024pt@1x.png",
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/Flask iOS App Icon_64pt@2x.png b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/Flask iOS App Icon_64pt@2x.png
new file mode 100644
index 00000000000..18d681202b7
Binary files /dev/null and b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/Flask iOS App Icon_64pt@2x.png differ
diff --git a/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/Flask iOS App Icon_64pt@3x.png b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/Flask iOS App Icon_64pt@3x.png
new file mode 100644
index 00000000000..a2d57a3be77
Binary files /dev/null and b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/Flask iOS App Icon_64pt@3x.png differ
diff --git a/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_1024pt@1x.png b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_1024pt@1x.png
new file mode 100644
index 00000000000..9a243552952
Binary files /dev/null and b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_1024pt@1x.png differ
diff --git a/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_20pt@2x.png b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_20pt@2x.png
new file mode 100644
index 00000000000..6a4ee810ffe
Binary files /dev/null and b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_20pt@2x.png differ
diff --git a/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_20pt@3x.png b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_20pt@3x.png
new file mode 100644
index 00000000000..ac440ec01f0
Binary files /dev/null and b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_20pt@3x.png differ
diff --git a/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_29pt@2x.png b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_29pt@2x.png
new file mode 100644
index 00000000000..dc2180de5c9
Binary files /dev/null and b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_29pt@2x.png differ
diff --git a/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_29pt@3x.png b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_29pt@3x.png
new file mode 100644
index 00000000000..1d1c81c0d1b
Binary files /dev/null and b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_29pt@3x.png differ
diff --git a/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_38pt@2x.png b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_38pt@2x.png
new file mode 100644
index 00000000000..9432219e6e3
Binary files /dev/null and b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_38pt@2x.png differ
diff --git a/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_38pt@3x.png b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_38pt@3x.png
new file mode 100644
index 00000000000..9da8cfe80af
Binary files /dev/null and b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_38pt@3x.png differ
diff --git a/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_40pt@2x.png b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_40pt@2x.png
new file mode 100644
index 00000000000..70c56afd5d9
Binary files /dev/null and b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_40pt@2x.png differ
diff --git a/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_40pt@3x.png b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_40pt@3x.png
new file mode 100644
index 00000000000..eac6b6d547e
Binary files /dev/null and b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_40pt@3x.png differ
diff --git a/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_60pt@2x.png b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_60pt@2x.png
new file mode 100644
index 00000000000..6beb7529c48
Binary files /dev/null and b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_60pt@2x.png differ
diff --git a/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_60pt@3x.png b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_60pt@3x.png
new file mode 100644
index 00000000000..8e872b9f857
Binary files /dev/null and b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_60pt@3x.png differ
diff --git a/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_68@2x.png b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_68@2x.png
new file mode 100644
index 00000000000..866aed6c64f
Binary files /dev/null and b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_68@2x.png differ
diff --git a/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_76pt@2x.png b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_76pt@2x.png
new file mode 100644
index 00000000000..3af33380f2f
Binary files /dev/null and b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_76pt@2x.png differ
diff --git a/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_83.5@2x.png b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_83.5@2x.png
new file mode 100644
index 00000000000..2017ba539ed
Binary files /dev/null and b/ios/MetaMask/Images.xcassets/AppIcon-Flask.appiconset/iOS App Icon_83.5@2x.png differ
diff --git a/ios/MetaMask/IosExportOptionsMetaMaskFlaskRelease.plist b/ios/MetaMask/IosExportOptionsMetaMaskFlaskRelease.plist
new file mode 100644
index 00000000000..131b7fe1b57
--- /dev/null
+++ b/ios/MetaMask/IosExportOptionsMetaMaskFlaskRelease.plist
@@ -0,0 +1,17 @@
+
+
+
+
+ provisioningProfiles
+
+ io.metamask.MetaMask-Flask
+ Bitrise AppStore io.metamask.MetaMask-Flask
+
+ uploadSymbols
+
+ teamID
+ 48XVW22RCG
+ method
+ app-store
+
+
diff --git a/ios/MetaMask/MetaMask-Flask-Info.plist b/ios/MetaMask/MetaMask-Flask-Info.plist
new file mode 100644
index 00000000000..fd1030c8628
--- /dev/null
+++ b/ios/MetaMask/MetaMask-Flask-Info.plist
@@ -0,0 +1,141 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleDisplayName
+ MetaMask Flask
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $(MARKETING_VERSION)
+ CFBundleSignature
+ ????
+ CFBundleURLTypes
+
+
+ CFBundleURLSchemes
+
+ ethereum
+ metamask
+ dapp
+ wc
+
+
+
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+ ITSAppUsesNonExemptEncryption
+
+ LSApplicationQueriesSchemes
+
+ twitter
+ itms-apps
+
+ LSRequiresIPhoneOS
+
+ NSAppTransportSecurity
+
+ NSAllowsArbitraryLoads
+
+ NSExceptionDomains
+
+ localhost
+
+ NSExceptionAllowsInsecureHTTPLoads
+
+
+
+
+ NSCameraUsageDescription
+ MetaMask needs camera access to scan QR codes
+ NSFaceIDUsageDescription
+ $(PRODUCT_NAME) needs to authenticate
+ NSLocationWhenInUseUsageDescription
+
+ NSPhotoLibraryAddUsageDescription
+ Allow MetaMask to save an image to your Photo Library
+ NSPhotoLibraryUsageDescription
+ Allow MetaMask to access a images from your Photo Library
+ UIAppFonts
+
+ Entypo.ttf
+ AntDesign.ttf
+ EvilIcons.ttf
+ Feather.ttf
+ FontAwesome.ttf
+ Foundation.ttf
+ Ionicons.ttf
+ MaterialCommunityIcons.ttf
+ MaterialIcons.ttf
+ Octicons.ttf
+ SimpleLineIcons.ttf
+ Zocial.ttf
+ Metamask.ttf
+ Roboto-Black.ttf
+ Roboto-BlackItalic.ttf
+ Roboto-Bold.ttf
+ Roboto-BoldItalic.ttf
+ Roboto-Italic.ttf
+ Roboto-Light.ttf
+ Roboto-LightItalic.ttf
+ Roboto-Medium.ttf
+ Roboto-MediumItalic.ttf
+ Roboto-Regular.ttf
+ Roboto-Thin.ttf
+ Roboto-ThinItalic.ttf
+ FontAwesome5_Brands.ttf
+ FontAwesome5_Regular.ttf
+ FontAwesome5_Solid.ttf
+ EuclidCircularB-Bold.otf
+ EuclidCircularB-BoldItalic.otf
+ EuclidCircularB-Light.otf
+ EuclidCircularB-LightItalic.otf
+ EuclidCircularB-Medium.otf
+ EuclidCircularB-MediumItalic.otf
+ EuclidCircularB-Regular.otf
+ EuclidCircularB-RegularItalic.otf
+ EuclidCircularB-Semibold.otf
+ EuclidCircularB-SemiboldItalic.otf
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIRequiredDeviceCapabilities
+
+ armv7
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+
+ UIViewControllerBasedStatusBarAppearance
+
+ branch_key
+
+ live
+ $(MM_BRANCH_KEY_LIVE)
+ test
+ $(MM_BRANCH_KEY_TEST)
+
+ branch_universal_link_domains
+
+ metamask.app.link
+ metamask-alternate.app.link
+ metamask.test.app.link
+ metamask-alternate.test.app.link
+
+ fox_code
+ $(MM_FOX_CODE)
+ mixpanel_token
+ $(MM_MIXPANEL_TOKEN)
+
+
diff --git a/ios/Podfile b/ios/Podfile
index fd768f67f67..047f5e2a6d7 100644
--- a/ios/Podfile
+++ b/ios/Podfile
@@ -46,6 +46,7 @@ def common_target_logic
# Pods for MetaMask
pod 'React-RCTPushNotification', :path => '../node_modules/react-native/Libraries/PushNotificationIOS'
+ pod 'GzipSwift'
# you should disable the next line.
use_flipper!({"Flipper" => "0.93.0", "Flipper-DoubleConversion" => "1.1.7"})
@@ -59,6 +60,10 @@ target 'MetaMask-QA' do
common_target_logic
end
+target 'MetaMask-Flask' do
+ common_target_logic
+end
+
post_install do |installer|
flipper_post_install(installer)
react_native_post_install(installer)
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 37df2961656..b504af7113d 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -75,6 +75,7 @@ PODS:
- FlipperKit/FlipperKitNetworkPlugin
- fmt (6.2.1)
- glog (0.3.5)
+ - GzipSwift (5.1.1)
- JitsiWebRTC (106.0.0)
- libevent (2.1.12)
- lottie-ios (2.5.3)
@@ -524,6 +525,7 @@ DEPENDENCIES:
- FlipperKit/FlipperKitUserDefaultsPlugin (= 0.93.0)
- FlipperKit/SKIOSNetworkPlugin (= 0.93.0)
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
+ - GzipSwift
- lottie-react-native (from `../node_modules/lottie-react-native`)
- RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
- RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`)
@@ -612,6 +614,7 @@ SPEC REPOS:
- Flipper-RSocket
- FlipperKit
- fmt
+ - GzipSwift
- JitsiWebRTC
- libevent
- lottie-ios
@@ -776,11 +779,11 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/yoga"
SPEC CHECKSUMS:
- boost: a7c83b31436843459a1961bfd74b96033dc77234
- Branch: 74cc856025984f691833c8fa332834ac38a0cf4e
+ boost: 57d2868c099736d80fcd648bf211b4431e51a558
+ Branch: 4ac024cb3c29b0ef628048694db3c4cfa679beb0
BVLinearGradient: e3aad03778a456d77928f594a649e96995f1c872
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
- DoubleConversion: 831926d9b8bf8166fd87886c4abab286c2422662
+ DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
FBLazyVector: a926a9aaa3596b181972abf0f47eff3dee796222
FBReactNativeSpec: f1141d5407f4a27c397bca5db03cc03919357b0d
Flipper: b1fddf9a17c32097b2b4c806ad158b2f36bb2692
@@ -793,7 +796,8 @@ SPEC CHECKSUMS:
Flipper-RSocket: d9d9ade67cbecf6ac10730304bf5607266dd2541
FlipperKit: aec2d931adeee48a07bab1ea8bcc8a6bb87dfce4
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
- glog: 5337263514dd6f09803962437687240c5dc39aa4
+ glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b
+ GzipSwift: 893f3e48e597a1a4f62fafcb6514220fcf8287fa
JitsiWebRTC: f441eb0e2d67f0588bf24e21c5162e97342714fb
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
lottie-ios: a50d5c0160425cd4b01b852bb9578963e6d92d31
@@ -872,6 +876,6 @@ SPEC CHECKSUMS:
Yoga: b316a990bb6d115afa1b436b5626ac7c61717d17
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
-PODFILE CHECKSUM: 74e503a275b4ed097cb01c171027882e9cdc699d
+PODFILE CHECKSUM: 73bae756f0addc5de6eb822b03f08b96e155664f
-COCOAPODS: 1.12.0
+COCOAPODS: 1.12.1
diff --git a/ios/RNTar.m b/ios/RNTar.m
new file mode 100644
index 00000000000..0f47b223602
--- /dev/null
+++ b/ios/RNTar.m
@@ -0,0 +1,17 @@
+//
+// RNTar.m
+// MetaMask
+//
+// Created by Owen Craston on 2023-03-10.
+// Copyright © 2023 MetaMask. All rights reserved.
+//
+
+#import
+#import "React/RCTBridgeModule.h"
+
+@interface RCT_EXTERN_MODULE(RNTar, NSObject)
+RCT_EXTERN_METHOD(unTar:(nonnull NSString *)pathToRead
+ pathToWrite:(nonnull NSString *)pathToWrite
+ resolver:(RCTPromiseResolveBlock)resolve
+ rejecter:(RCTPromiseRejectBlock)reject)
+@end
diff --git a/ios/RNTar.swift b/ios/RNTar.swift
new file mode 100644
index 00000000000..116d6b26a87
--- /dev/null
+++ b/ios/RNTar.swift
@@ -0,0 +1,74 @@
+//
+// RNTar.swift
+// MetaMask
+//
+// Created by Owen Craston on 2023-03-10.
+// Copyright © 2023 MetaMask. All rights reserved.
+//
+
+import Foundation
+import Gzip
+
+enum UnTarError: Error, LocalizedError {
+ case unableToDecompressFile(file: String, error: Error? = nil)
+ case sourceFileNotFound(file: String)
+ public var errorDescription: String? {
+ switch self {
+ case let .unableToDecompressFile(file: file, error: error):
+ return "RNTar failed to decompress file \(file) \(error as Optional)"
+ case let .sourceFileNotFound(file: file):
+ return "Source file \(file) not found"
+ }
+ }
+}
+
+@objc(RNTar)
+class RNTar: NSObject {
+
+ @objc
+ static func requiresMainQueueSetup() -> Bool {
+ return false
+ }
+
+ func extractTgzFile(atPath path: String, toDirectory directory: String) throws -> String {
+ let fileManager = FileManager.default
+ guard fileManager.fileExists(atPath: path) else {
+ throw UnTarError.sourceFileNotFound(file: path)
+ }
+ let sourceUrl = URL(fileURLWithPath: path)
+ let destinationUrl = URL(fileURLWithPath: directory, isDirectory: true)
+ // Read the compressed data from the file
+ guard
+ let data = try? Data(contentsOf: sourceUrl),
+ let decompressedData = data.isGzipped ? try? data.gunzipped() : data
+ else {
+ throw UnTarError.unableToDecompressFile(file: path)
+ }
+
+ if !fileManager.fileExists(atPath: sourceUrl.path) {
+ do {
+ try (fileManager.createDirectory(atPath: sourceUrl.path, withIntermediateDirectories: true))
+ } catch {
+ throw UnTarError.unableToDecompressFile(file: path)
+ }
+ }
+ let untarResponse = try FileManager.default
+ .createFilesAndDirectories(path: destinationUrl.path, tarData: decompressedData)
+
+ if untarResponse {
+ return "\(destinationUrl.path)/package"
+ }
+ throw UnTarError.unableToDecompressFile(file: destinationUrl.path)
+ }
+
+ @objc func unTar(_ pathToRead: String, pathToWrite: String,
+ resolver: @escaping RCTPromiseResolveBlock,
+ rejecter: @escaping RCTPromiseRejectBlock) {
+ do {
+ let uncompressedPath = try extractTgzFile(atPath: pathToRead, toDirectory: pathToWrite)
+ resolver(uncompressedPath)
+ } catch {
+ rejecter("Error uncompressing file:", error.localizedDescription, error)
+ }
+ }
+}
diff --git a/locales/languages/en.json b/locales/languages/en.json
index 4df12c9200f..d8552391a75 100644
--- a/locales/languages/en.json
+++ b/locales/languages/en.json
@@ -746,7 +746,46 @@
"cancel": "Cancel"
},
"request_feature": "Request a feature",
- "contact_support": "Contact support"
+ "contact_support": "Contact support",
+ "snaps": {
+ "title": "Snaps",
+ "description": "Overview and manage your snaps",
+ "snap_settings": {
+ "remove_snap_section_title": "Remove Snap",
+ "remove_snap_section_description": "This action will delete the snap, its data, and its granted permissions.",
+ "remove_button_label": "Remove {{snapName}}"
+ },
+ "snap_details": {
+ "install_date": "Installed on {{date}}",
+ "install_origin": "Install Origin",
+ "enabled": "Enabled",
+ "version": "Version"
+ },
+ "snap_permissions": {
+ "approved_date": "Approved on {{date}}",
+ "permission_section_title": "Permissions",
+ "permission_requested_now": "Requested now",
+ "human_readable_permission_titles": {
+ "endowment:long-running": "Run indefinitely",
+ "endowment:network-access": "Access the internet",
+ "endowment:transaction-insight": "Display transaction insights",
+ "endowment:cronjob": "Schedule and run periodic actions",
+ "endowment:rpc": {
+ "snaps": "Allow other snaps to communicate directly with this snap",
+ "dapps": "Allow dapps to communicate directly with this snap"
+ },
+ "snap_confirm": "Display custom dialogs",
+ "snap_manageState": "Store and manage data on your device",
+ "snap_notify": "Show notifications",
+ "snap_getBip32Entropy": "Control your {{protocol}} accounts and assets",
+ "snap_getBip32PublicKey": "View your public key for {{protocol}}",
+ "snap_getBip44Entropy": "Control your {{protocol}} accounts and assets",
+ "snap_getEntropy": "Derive arbitrary keys unique to this snap",
+ "endowment:keyring": "endowment:keyring",
+ "wallet_snap": "Connect to {{otherSnapName}}"
+ }
+ }
+ }
},
"sdk": {
"disconnect_title": "Disconnect all sites?",
@@ -2503,5 +2542,17 @@
"add_new_account": "Add new account",
"add_hardware_wallet": "Add hardware wallet",
"import_account": "Import account"
+ },
+ "install_snap": {
+ "title": "Connection request",
+ "description": "{{origin}} wants to download and connect with {{snap}}. Make sure you trust the authors before you proceed.",
+ "permissions_request_title": "Permissions request",
+ "permissions_request_description": "{{origin}} wants to install {{snap}}, which is requesting the following permissions.",
+ "approve_permissions": "Approve",
+ "installed": "Installed",
+ "install_successful": "{{snap}} was successfully installed.",
+ "okay_action": "OK",
+ "error_title": "Install failed",
+ "error_description": "Installation of {{snap}} failed."
}
}
diff --git a/package.json b/package.json
index 940153697ea..de21743472a 100644
--- a/package.json
+++ b/package.json
@@ -19,27 +19,34 @@
"setup:e2e": "cd wdio && yarn install",
"start:ios": "./scripts/build.sh ios debug",
"start:ios:qa": "./scripts/build.sh ios qaDebug",
+ "start:ios:flask": "./scripts/build.sh ios flaskDebug",
"start:ios:e2e": "./scripts/build.sh ios debugE2E",
"start:ios:device": "./scripts/build.sh ios debug --device",
"start:android": "./scripts/build.sh android debug",
"start:android:qa": "./scripts/build.sh android qaDebug",
"start:android:e2e": "./scripts/build.sh android debugE2E",
"build:static-logos": "node ./scripts/metamask-bot-build-generate-static-assets",
+ "start:android:flask": "./scripts/build.sh android debugFlask",
"build:announce": "node ./scripts/metamask-bot-build-announce-bitrise.js",
"build:android:release": "./scripts/build.sh android release",
+ "build:android:flask": "./scripts/build.sh android Flask",
"build:android:release:e2e": "./scripts/build.sh android releaseE2E",
"build:android:qa:e2e": "./scripts/build.sh android QAE2E",
"build:android:checksum": "./scripts/checksum.sh",
"build:android:checksum:qa": "./scripts/checksum.sh QA",
+ "build:android:checksum:flask": "./scripts/checksum.sh Flask",
"build:android:checksum:verify": "shasum -a 512 -c sha512sums.txt",
"build:android:pre-release": "./scripts/build.sh android release --pre",
"build:android:pre-release:bundle": "GENERATE_BUNDLE=true ./scripts/build.sh android release --pre",
"build:android:pre-release:bundle:qa": "GENERATE_BUNDLE=true ./scripts/build.sh android QA --pre",
+ "build:android:pre-release:bundle:flask": "GENERATE_BUNDLE=true ./scripts/build.sh android Flask --pre",
"build:ios:release": "./scripts/build.sh ios release",
"build:ios:release:e2e": "./scripts/build.sh ios releaseE2E",
"build:ios:pre-release": "./scripts/build.sh ios release --pre",
"build:ios:qa": "./scripts/build.sh ios QA",
"build:ios:pre-qa": "./scripts/build.sh ios QA --pre",
+ "build:ios:flask": "./scripts/build.sh ios Flask",
+ "build:ios:pre-flask": "./scripts/build.sh ios Flask --pre",
"build:attribution": "./scripts/generate-attributions.sh",
"release:android": "./scripts/build.sh android release && open android/app/build/outputs/apk/release/",
"release:ios": "./scripts/build.sh ios release",
@@ -134,7 +141,9 @@
"react-native-svg-asset-plugin/sharp": "^0.30.5",
"detox/**/moment": "^2.29.4",
"oss-attribution-generator/**/debug": "^2.6.9",
- "d3-color": "3.1.0"
+ "d3-color": "3.1.0",
+ "**/fast-xml-parser": "4.2.4",
+ "tough-cookie": "4.1.3"
},
"dependencies": {
"@consensys/on-ramp-sdk": "1.20.0",
@@ -166,6 +175,13 @@
"@metamask/signature-controller": "^2.0.0",
"@metamask/swaps-controller": "^6.8.0",
"@metamask/transaction-controller": "4.0.0",
+ "@metamask/post-message-stream": "6.0.0",
+ "@metamask/rpc-methods": "0.26.2",
+ "@metamask/slip44": "3.0.0",
+ "@metamask/snaps-controllers": "0.26.2",
+ "@metamask/snaps-utils": "0.26.2",
+ "@metamask/subject-metadata-controller": "^1.0.0",
+ "@metamask/utils": "^5.0.0",
"@ngraveio/bc-ur": "^1.1.6",
"@react-native-async-storage/async-storage": "1.17.10",
"@react-native-clipboard/clipboard": "^1.8.4",
@@ -205,6 +221,7 @@
"bignumber.js": "^9.0.1",
"buffer": "5.2.1",
"compare-versions": "^3.6.0",
+ "constants": "^0.0.2",
"content-hash": "2.5.2",
"d3-shape": "^3.2.0",
"dnode": "1.2.2",
@@ -272,7 +289,7 @@
"react-native-fade-in-image": "1.4.1",
"react-native-flash-message": "0.1.11",
"react-native-fs": "^2.16.6",
- "react-native-gesture-handler": "^1.10.3",
+ "react-native-gesture-handler": "1.10.3",
"react-native-get-random-values": "^1.8.0",
"react-native-i18n": "2.0.15",
"react-native-in-app-review": "^3.2.3",
@@ -325,6 +342,7 @@
"socket.io-client": "^4.5.3",
"stream-browserify": "1.0.0",
"swappable-obj-proxy": "^1.1.0",
+ "text-encoding": "^0.7.0",
"through2": "3.0.1",
"unicode-confusables": "^0.1.1",
"url": "0.11.0",
@@ -366,6 +384,7 @@
"@types/react-native-svg-charts": "^5.0.12",
"@types/react-native-vector-icons": "^6.4.8",
"@types/react-native-video": "^5.0.13",
+ "@types/readable-stream": "^2.3.15",
"@types/redux-mock-store": "^1.0.3",
"@types/url-parse": "^1.4.8",
"@typescript-eslint/eslint-plugin": "^4.20.0",
@@ -423,6 +442,7 @@
"react-test-renderer": "17.0.2",
"regenerator-runtime": "0.13.9",
"rn-nodeify": "10.0.1",
+ "semver": "^7.3.5",
"stack-beautifier": "1.0.2",
"typescript": "^4.4.2",
"wdio-cucumberjs-json-reporter": "^4.4.3",
diff --git a/patches/@metamask+browser-passworder+4.0.2.patch b/patches/@metamask+browser-passworder+4.0.2.patch
new file mode 100644
index 00000000000..366e8a6526c
--- /dev/null
+++ b/patches/@metamask+browser-passworder+4.0.2.patch
@@ -0,0 +1,103 @@
+diff --git a/node_modules/@metamask/browser-passworder/dist/index.js b/node_modules/@metamask/browser-passworder/dist/index.js
+index 6c719e7..0196363 100644
+--- a/node_modules/@metamask/browser-passworder/dist/index.js
++++ b/node_modules/@metamask/browser-passworder/dist/index.js
+@@ -4,6 +4,11 @@ exports.generateSalt = exports.serializeBufferForStorage = exports.serializeBuff
+ const EXPORT_FORMAT = 'jwk';
+ const DERIVED_KEY_FORMAT = 'AES-GCM';
+ const STRING_ENCODING = 'utf-8';
++
++import { NativeModules } from 'react-native';
++const Aes = NativeModules.Aes;
++const AesForked = NativeModules.AesForked;
++
+ /**
+ * Encrypts a data object that can be any serializable value using
+ * a provided password.
+@@ -14,11 +19,28 @@ const STRING_ENCODING = 'utf-8';
+ * @param salt - The salt to use to encrypt.
+ * @returns The encrypted vault.
+ */
+-async function encrypt(password, dataObj, key, salt = generateSalt()) {
+- const cryptoKey = key || (await keyFromPassword(password, salt));
+- const payload = await encryptWithKey(cryptoKey, dataObj);
+- payload.salt = salt;
+- return JSON.stringify(payload);
++
++
++generateSalt = (byteCount = 32) => {
++ const view = new Uint8Array(byteCount);
++ global.crypto.getRandomValues(view);
++ // eslint-disable-next-line no-undef
++ const b64encoded = btoa(String.fromCharCode.apply(null, view));
++ return b64encoded;
++ }
++
++encryptWithKey = async (text, keyBase64) => {
++ const iv = await Aes.randomKey(16);
++ return Aes.encrypt(text, keyBase64, iv).then((cipher) => ({ cipher, iv }));
++};
++
++async function encrypt(password, object) {
++ const salt = generateSalt(16);
++ const key = await Aes.pbkdf2(password, salt, 5000, 256);
++ const result = await encryptWithKey(JSON.stringify(object), key);
++ result.salt = salt;
++ result.lib = 'original';
++ return JSON.stringify(result);
+ }
+ exports.encrypt = encrypt;
+ /**
+@@ -75,12 +97,17 @@ exports.encryptWithKey = encryptWithKey;
+ * @param key - The key to decrypt with.
+ * @returns The decrypted data.
+ */
+-async function decrypt(password, text, key) {
+- const payload = JSON.parse(text);
+- const { salt } = payload;
+- const cryptoKey = key || (await keyFromPassword(password, salt));
+- const result = await decryptWithKey(cryptoKey, payload);
+- return result;
++
++decryptWithKey = (encryptedData, key, lib) =>
++lib === 'original'
++ ? Aes.decrypt(encryptedData.cipher, key, encryptedData.iv)
++ : AesForked.decrypt(encryptedData.cipher, key, encryptedData.iv);
++
++async function decrypt(password, encryptedString) {
++ const encryptedData = JSON.parse(encryptedString);
++ const key = await Aes.pbkdf2(password, encryptedData.salt, 5000, 256)
++ const data = await Aes.decrypt(encryptedData.cipher, key, encryptedData.iv)
++ return JSON.parse(data);
+ }
+ exports.decrypt = decrypt;
+ /**
+@@ -151,27 +178,7 @@ async function exportKey(key) {
+ return JSON.stringify(exportedKey);
+ }
+ exports.exportKey = exportKey;
+-/**
+- * Generate a CryptoKey from a password and random salt.
+- *
+- * @param password - The password to use to generate key.
+- * @param salt - The salt string to use in key derivation.
+- * @param exportable - Should the derived key be exportable.
+- * @returns A CryptoKey for encryption and decryption.
+- */
+-async function keyFromPassword(password, salt, exportable = false) {
+- const passBuffer = Buffer.from(password, STRING_ENCODING);
+- const saltBuffer = Buffer.from(salt, 'base64');
+- const key = await global.crypto.subtle.importKey('raw', passBuffer, { name: 'PBKDF2' }, false, ['deriveBits', 'deriveKey']);
+- const derivedKey = await global.crypto.subtle.deriveKey({
+- name: 'PBKDF2',
+- salt: saltBuffer,
+- iterations: 10000,
+- hash: 'SHA-256',
+- }, key, { name: DERIVED_KEY_FORMAT, length: 256 }, exportable, ['encrypt', 'decrypt']);
+- return derivedKey;
+-}
+-exports.keyFromPassword = keyFromPassword;
++
+ /**
+ * Converts a hex string into a buffer.
+ *
diff --git a/patches/@metamask+keyring-controller+1.0.1.patch b/patches/@metamask+keyring-controller+1.0.1.patch
index 601eb60d0bf..d222071b254 100644
--- a/patches/@metamask+keyring-controller+1.0.1.patch
+++ b/patches/@metamask+keyring-controller+1.0.1.patch
@@ -1,5 +1,5 @@
diff --git a/node_modules/@metamask/keyring-controller/dist/KeyringController.js b/node_modules/@metamask/keyring-controller/dist/KeyringController.js
-index a5e5af1..e2dbbd2 100644
+index a5e5af1..66d2d7c 100644
--- a/node_modules/@metamask/keyring-controller/dist/KeyringController.js
+++ b/node_modules/@metamask/keyring-controller/dist/KeyringController.js
@@ -125,6 +125,7 @@ class KeyringController extends base_controller_1.BaseController {
@@ -26,7 +26,39 @@ index a5e5af1..e2dbbd2 100644
});
}
/**
-@@ -313,8 +315,8 @@ class KeyringController extends base_controller_1.BaseController {
+@@ -241,6 +243,31 @@ class KeyringController extends base_controller_1.BaseController {
+ }
+ throw new Error('Invalid password');
+ }
++
++ /* Temporary function that gets the mnemonic of the HD keyring.
++ This method is not password protected
++
++ @returns Promise resolving to the seed phrase.
++ */
++ exportMnemonic() {
++ const mnemonic = __classPrivateFieldGet(this, _KeyringController_keyring, "f").keyrings[0].mnemonic;
++ if (mnemonic) {
++ return mnemonic;
++ }
++ throw new Error('No mnemonic found');
++ }
++
++ /* Temporary function that gets the mnemonic of the HD keyring.
++ This method is not password protected
++
++ @returns Promise resolving to the seed phrase.
++ */
++ exportAppKeyForAddress(account, subject) {
++ return __awaiter(this, void 0, void 0, function* () {
++ return yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").exportAppKeyForAddress(account, subject);
++ })
++ }
++
+ /**
+ * Gets the private key from the keyring controlling an address.
+ *
+@@ -313,8 +340,8 @@ class KeyringController extends base_controller_1.BaseController {
const accounts = yield newKeyring.getAccounts();
const allAccounts = yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").getAccounts();
this.updateIdentities(allAccounts);
@@ -37,7 +69,7 @@ index a5e5af1..e2dbbd2 100644
});
}
/**
-@@ -615,16 +617,19 @@ class KeyringController extends base_controller_1.BaseController {
+@@ -615,16 +642,19 @@ class KeyringController extends base_controller_1.BaseController {
yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").addNewAccount(keyring);
const newAccounts = yield __classPrivateFieldGet(this, _KeyringController_keyring, "f").getAccounts();
this.updateIdentities(newAccounts);
@@ -58,7 +90,7 @@ index a5e5af1..e2dbbd2 100644
});
}
getAccountKeyringType(account) {
-@@ -635,13 +640,15 @@ class KeyringController extends base_controller_1.BaseController {
+@@ -635,13 +665,15 @@ class KeyringController extends base_controller_1.BaseController {
forgetQRDevice() {
return __awaiter(this, void 0, void 0, function* () {
const keyring = yield this.getOrAddQRKeyring();
diff --git a/patches/@metamask+snaps-utils+0.26.2.patch b/patches/@metamask+snaps-utils+0.26.2.patch
new file mode 100644
index 00000000000..e3f717469aa
--- /dev/null
+++ b/patches/@metamask+snaps-utils+0.26.2.patch
@@ -0,0 +1,17 @@
+diff --git a/node_modules/@metamask/snaps-utils/dist/snaps.js b/node_modules/@metamask/snaps-utils/dist/snaps.js
+index 708d395..14e4397 100644
+--- a/node_modules/@metamask/snaps-utils/dist/snaps.js
++++ b/node_modules/@metamask/snaps-utils/dist/snaps.js
+@@ -67,9 +67,9 @@ exports.getSnapSourceShasum = getSnapSourceShasum;
+ * @param errorMessage - The error message to throw if validation fails.
+ */
+ function validateSnapShasum(manifest, sourceCode, errorMessage = 'Invalid Snap manifest: manifest shasum does not match computed shasum.') {
+- if (manifest.source.shasum !== getSnapSourceShasum(sourceCode)) {
+- throw new ProgrammaticallyFixableSnapError(errorMessage, types_1.SnapValidationFailureReason.ShasumMismatch);
+- }
++ // if (manifest.source.shasum !== getSnapSourceShasum(sourceCode)) {
++ // throw new ProgrammaticallyFixableSnapError(errorMessage, types_1.SnapValidationFailureReason.ShasumMismatch);
++ // }
+ }
+ exports.validateSnapShasum = validateSnapShasum;
+ exports.LOCALHOST_HOSTNAMES = ['localhost', '127.0.0.1', '[::1]'];
diff --git a/scripts/build.sh b/scripts/build.sh
index a3ad4e781b5..09f1108999c 100755
--- a/scripts/build.sh
+++ b/scripts/build.sh
@@ -106,6 +106,43 @@ checkParameters(){
fi
}
+remapEnvVariable() {
+ # Get the old and new variable names
+ old_var_name=$1
+ new_var_name=$2
+
+ # Check if the old variable exists
+ if [ -z "${!old_var_name}" ]; then
+ echo "Error: $old_var_name does not exist in the environment."
+ return 1
+ fi
+
+ # Remap the variable
+ export $new_var_name="${!old_var_name}"
+
+ unset $old_var_name
+
+ echo "Successfully remapped $old_var_name to $new_var_name."
+}
+
+remapFlaskEnvVariables() {
+ # remap flask env variables to match what the app expects
+
+ echo "Remapping flask env variable names to match production"
+
+ # js.env variables``
+ remapEnvVariable "FLASK_MOONPAY_API_KEY_STAGING" "MOONPAY_API_KEY_STAGING"
+ remapEnvVariable "SEGMENT_FLASK_DEV_KEY" "SEGMENT_DEV_KEY"
+ remapEnvVariable "SEGMENT_FLASK_PROD_KEY" "SEGMENT_PROD_KEY"
+ remapEnvVariable "MM_FLASK_SENTRY_DSN" "MM_SENTRY_DSN"
+
+ # ios.env/ios.env variables
+ remapEnvVariable "MM_FLASK_BRANCH_KEY_TEST" "MM_BRANCH_KEY_TEST"
+ remapEnvVariable "MM_FLASK_BRANCH_KEY_LIVE" "MM_BRANCH_KEY_LIVE"
+ remapEnvVariable "MM_FLASK_MIXPANEL_TOKEN" "MM_MIXPANEL_TOKEN"
+}
+
+
prebuild(){
# Import provider
@@ -160,6 +197,11 @@ buildAndroidRunQA(){
react-native run-android --variant=qaDebug
}
+buildAndroidRunFlask(){
+ prebuild_android
+ react-native run-android --variant=flaskDebug
+}
+
buildIosSimulator(){
prebuild_ios
SIM="${IOS_SIMULATOR:-"iPhone 12 Pro"}"
@@ -172,6 +214,12 @@ buildIosSimulatorQA(){
cd ios && xcodebuild -workspace MetaMask.xcworkspace -scheme MetaMask-QA -configuration Debug -sdk iphonesimulator -derivedDataPath build
}
+buildIosSimulatorFlask(){
+ prebuild_ios
+ SIM="${IOS_SIMULATOR:-"iPhone 12 Pro"}"
+ cd ios && xcodebuild -workspace MetaMask.xcworkspace -scheme MetaMask-Flask -configuration Debug -sdk iphonesimulator -derivedDataPath build
+}
+
buildIosSimulatorE2E(){
prebuild_ios
cd ios && xcodebuild -workspace MetaMask.xcworkspace -scheme MetaMask -configuration Debug -sdk iphonesimulator -derivedDataPath build
@@ -191,11 +239,18 @@ buildIosDeviceQA(){
react-native run-ios --device --scheme "MetaMask-QA"
}
+buildIosDeviceFlask(){
+ prebuild_ios
+ react-native run-ios --device --scheme "MetaMask-Flask"
+}
+
generateArchivePackages() {
scheme="$1"
if [ "$scheme" = "MetaMask-QA" ] ; then
exportOptionsPlist="MetaMask/IosExportOptionsMetaMaskQARelease.plist"
+ elif [ "$scheme" = "MetaMask-Flask" ] ; then
+ exportOptionsPlist="MetaMask/IosExportOptionsMetaMaskFlaskRelease.plist"
else
exportOptionsPlist="MetaMask/IosExportOptionsMetaMaskRelease.plist"
fi
@@ -228,6 +283,30 @@ buildIosRelease(){
fi
}
+buildIosFlaskRelease(){
+ # remap flask env variables to match what the app expects
+ remapFlaskEnvVariables
+
+ prebuild_ios
+
+ # Replace release.xcconfig with ENV vars
+ if [ "$PRE_RELEASE" = true ] ; then
+ echo "Setting up env vars...";
+ echo "$IOS_ENV" | tr "|" "\n" > $IOS_ENV_FILE
+ echo "Build started..."
+ brew install watchman
+ cd ios
+ generateArchivePackages "MetaMask-Flask"
+ # Generate sourcemaps
+ yarn sourcemaps:ios
+ else
+ if [ ! -f "ios/release.xcconfig" ] ; then
+ echo "$IOS_ENV" | tr "|" "\n" > ios/release.xcconfig
+ fi
+ ./node_modules/.bin/react-native run-ios --scheme "MetaMask-Flask" --configuration Release --simulator "iPhone 12 Pro"
+ fi
+}
+
buildIosReleaseE2E(){
prebuild_ios
@@ -328,6 +407,35 @@ buildAndroidRelease(){
fi
}
+buildAndroidFlaskRelease(){
+ # remap flask env variables to match what the app expects
+ remapFlaskEnvVariables
+
+ if [ "$PRE_RELEASE" = false ] ; then
+ adb uninstall io.metamask || true
+ fi
+ prebuild_android
+
+ # GENERATE APK
+ cd android && ./gradlew assembleFlaskRelease --no-daemon --max-workers 2
+
+ # GENERATE BUNDLE
+ if [ "$GENERATE_BUNDLE" = true ] ; then
+ ./gradlew bundleFlaskRelease
+ fi
+
+ if [ "$PRE_RELEASE" = true ] ; then
+ # Generate sourcemaps
+ yarn sourcemaps:android
+ # Generate checksum
+ yarn build:android:checksum:flask
+ fi
+
+ if [ "$PRE_RELEASE" = false ] ; then
+ adb install app/build/outputs/apk/flask/release/app-flask-release.apk
+ fi
+}
+
buildAndroidReleaseE2E(){
prebuild_android
cd android && ./gradlew assembleProdRelease assembleAndroidTest -PminSdkVersion=26 -DtestBuildType=release
@@ -341,6 +449,8 @@ buildAndroidQAE2E(){
buildAndroid() {
if [ "$MODE" == "release" ] ; then
buildAndroidRelease
+ elif [ "$MODE" == "Flask" ] ; then
+ buildAndroidFlaskRelease
elif [ "$MODE" == "QA" ] ; then
buildAndroidQA
elif [ "$MODE" == "releaseE2E" ] ; then
@@ -351,6 +461,8 @@ buildAndroid() {
buildAndroidRunE2E
elif [ "$MODE" == "qaDebug" ] ; then
buildAndroidRunQA
+ elif [ "$MODE" == "flaskDebug" ] ; then
+ buildAndroidRunFlask
else
buildAndroidRun
fi
@@ -370,6 +482,8 @@ buildIos() {
echo "Build iOS $MODE started..."
if [ "$MODE" == "release" ] ; then
buildIosRelease
+ elif [ "$MODE" == "Flask" ] ; then
+ buildIosFlaskRelease
elif [ "$MODE" == "releaseE2E" ] ; then
buildIosReleaseE2E
elif [ "$MODE" == "debugE2E" ] ; then
@@ -382,6 +496,12 @@ buildIos() {
else
buildIosSimulatorQA
fi
+ elif [ "$MODE" == "flaskDebug" ] ; then
+ if [ "$RUN_DEVICE" = true ] ; then
+ buildIosDeviceFlask
+ else
+ buildIosSimulatorFlask
+ fi
else
if [ "$RUN_DEVICE" = true ] ; then
buildIosDevice
@@ -428,7 +548,7 @@ checkParameters "$@"
printTitle
-if [ "$MODE" == "release" ] || [ "$MODE" == "releaseE2E" ] || [ "$MODE" == "QA" ]; then
+if [ "$MODE" == "release" ] || [ "$MODE" == "releaseE2E" ] || [ "$MODE" == "QA" ] || [ "$MODE" == "Flask" ]; then
if [ "$PRE_RELEASE" = false ]; then
echo "RELEASE SENTRY PROPS"
checkAuthToken 'sentry.release.properties'
diff --git a/scripts/checksum.sh b/scripts/checksum.sh
index f3bc92040a0..a9afb7f3e90 100755
--- a/scripts/checksum.sh
+++ b/scripts/checksum.sh
@@ -8,7 +8,12 @@ FILE=./android/app/build/outputs/apk/qa/release/app-qa-release.apk
if test -f "$FILE"; then
shasum -a 512 "$FILE" > ./android/app/build/outputs/apk/qa/release/sha512sums.txt
fi
+elif [ "$MODE" == "Flask" ]; then
+FILE=./android/app/build/outputs/apk/flask/release/app-flask-release.apk
+ if test -f "$FILE"; then
+ shasum -a 512 "$FILE" > ./android/app/build/outputs/apk/flask/release/sha512sums.txt
+ fi
else
FILE=./android/app/build/outputs/apk/prod/release/app-prod-release.apk
diff --git a/scripts/metamask-bot-build-announce-bitrise.js b/scripts/metamask-bot-build-announce-bitrise.js
index 318687a0abc..2f740741d08 100755
--- a/scripts/metamask-bot-build-announce-bitrise.js
+++ b/scripts/metamask-bot-build-announce-bitrise.js
@@ -15,6 +15,7 @@ async function start() {
text: `NEW BUILDS AVAILABLE! Commit `,
attachments: [
{
+ // TODO: update with testflight link for Flask
title_link: 'itms-beta://beta.itunes.apple.com/v1/app/1438144202',
title: 'iOS',
text: 'Install via TestFlight',
diff --git a/scripts/set-versions.sh b/scripts/set-versions.sh
index 68dda13c5c6..c4cc8015964 100755
--- a/scripts/set-versions.sh
+++ b/scripts/set-versions.sh
@@ -44,6 +44,9 @@ perform_updates () {
# update bitrise.yml
sed -i -e 's/VERSION_NAME: .*/VERSION_NAME: '"$SEMVER_VERSION"'/' bitrise.yml
sed -i -e 's/VERSION_NUMBER: [0-9]\+/VERSION_NUMBER: '"$VERSION_NUMBER"'/' bitrise.yml
+ # update flask version numbers in bitrise.yml
+ sed -i -e 's/FLASK_VERSION_NAME: .*/FLASK_VERSION_NAME: '"$SEMVER_VERSION"'/' bitrise.yml
+ sed -i -e 's/FLASK_VERSION_NUMBER: [0-9]\+/FLASK_VERSION_NUMBER: '"$VERSION_NUMBER"'/' bitrise.yml
# update ios/MetaMask.xcodeproj/project.pbxproj
sed -i -e 's/MARKETING_VERSION = .*/MARKETING_VERSION = '"$SEMVER_VERSION;"'/' ios/MetaMask.xcodeproj/project.pbxproj
diff --git a/shim.js b/shim.js
index 277f5936b17..82e4409059d 100644
--- a/shim.js
+++ b/shim.js
@@ -1,5 +1,12 @@
/* eslint-disable import/no-nodejs-modules */
import { decode, encode } from 'base-64';
+// eslint-disable-next-line import/no-commonjs
+const TextEncodingPolyfill = require('text-encoding');
+
+Object.assign(global, {
+ TextEncoder: TextEncodingPolyfill.TextEncoder,
+ TextDecoder: TextEncodingPolyfill.TextDecoder,
+});
if (!global.btoa) {
global.btoa = encode;
@@ -9,6 +16,8 @@ if (!global.atob) {
global.atob = decode;
}
+if (typeof BigInt === 'undefined') global.BigInt = require('big-integer');
+
// Fix for https://github.com/facebook/react-native/issues/5667
if (typeof global.self === 'undefined') {
global.self = global;
diff --git a/wdio/screen-objects/testIDs/Screens/EditGasFeeScreen.testids.js b/wdio/screen-objects/testIDs/Screens/EditGasFeeScreen.testids.js
index 42747c5bce5..982d214d9a7 100644
--- a/wdio/screen-objects/testIDs/Screens/EditGasFeeScreen.testids.js
+++ b/wdio/screen-objects/testIDs/Screens/EditGasFeeScreen.testids.js
@@ -1,2 +1,2 @@
export const EDIT_PRIOTIRY_SCREEN_TEST_ID = 'edit-priority-screen';
-export const MAX_PRIORITY_FEE_INPUT_TEST_ID = 'max-priority-fee-range-input';
\ No newline at end of file
+export const MAX_PRIORITY_FEE_INPUT_TEST_ID = 'max-priority-fee-range-input';
diff --git a/yarn.lock b/yarn.lock
index 52ec94f63e0..a0c1297d1d7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -160,6 +160,11 @@
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.4.tgz#457ffe647c480dff59c2be092fc3acf71195c87f"
integrity sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g==
+"@babel/compat-data@^7.21.5":
+ version "7.21.7"
+ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.7.tgz#61caffb60776e49a57ba61a88f02bedd8714f6bc"
+ integrity sha512-KYMqFYTaenzMK4yUtf4EW9wc4N9ef80FsbMtkwool5zpwl4YrT1SdWYSTRcT94KO4hannogdS+LxY7L+arP3gA==
+
"@babel/core@^7.0.0", "@babel/core@^7.1.6", "@babel/core@^7.12.9", "@babel/core@^7.14.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.16.0.tgz#c4ff44046f5fe310525cc9eb4ef5147f0c5374d4"
@@ -223,6 +228,27 @@
json5 "^2.2.1"
semver "^6.3.0"
+"@babel/core@^7.18.6":
+ version "7.20.2"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.2.tgz#8dc9b1620a673f92d3624bd926dc49a52cf25b92"
+ integrity sha512-w7DbG8DtMrJcFOi4VrLm+8QM4az8Mo+PuLBKLp2zrYRCow8W/f9xiXm5sN53C8HksCyDQwCKha9JiDoIyPjT2g==
+ dependencies:
+ "@ampproject/remapping" "^2.1.0"
+ "@babel/code-frame" "^7.18.6"
+ "@babel/generator" "^7.20.2"
+ "@babel/helper-compilation-targets" "^7.20.0"
+ "@babel/helper-module-transforms" "^7.20.2"
+ "@babel/helpers" "^7.20.1"
+ "@babel/parser" "^7.20.2"
+ "@babel/template" "^7.18.10"
+ "@babel/traverse" "^7.20.1"
+ "@babel/types" "^7.20.2"
+ convert-source-map "^1.7.0"
+ debug "^4.1.0"
+ gensync "^1.0.0-beta.2"
+ json5 "^2.2.1"
+ semver "^6.3.0"
+
"@babel/generator@^7.14.0", "@babel/generator@^7.16.0":
version "7.16.0"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.16.0.tgz#d40f3d1d5075e62d3500bccb67f3daa8a95265b2"
@@ -259,6 +285,16 @@
"@jridgewell/gen-mapping" "^0.3.2"
jsesc "^2.5.1"
+"@babel/generator@^7.20.2", "@babel/generator@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.5.tgz#c0c0e5449504c7b7de8236d99338c3e2a340745f"
+ integrity sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==
+ dependencies:
+ "@babel/types" "^7.21.5"
+ "@jridgewell/gen-mapping" "^0.3.2"
+ "@jridgewell/trace-mapping" "^0.3.17"
+ jsesc "^2.5.1"
+
"@babel/generator@^7.21.4", "@babel/generator@^7.7.2":
version "7.21.4"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.4.tgz#64a94b7448989f421f919d5239ef553b37bb26bc"
@@ -335,6 +371,17 @@
browserslist "^4.20.2"
semver "^6.3.0"
+"@babel/helper-compilation-targets@^7.20.0":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.5.tgz#631e6cc784c7b660417421349aac304c94115366"
+ integrity sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w==
+ dependencies:
+ "@babel/compat-data" "^7.21.5"
+ "@babel/helper-validator-option" "^7.21.0"
+ browserslist "^4.21.3"
+ lru-cache "^5.1.1"
+ semver "^6.3.0"
+
"@babel/helper-compilation-targets@^7.21.4":
version "7.21.4"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.4.tgz#770cd1ce0889097ceacb99418ee6934ef0572656"
@@ -423,6 +470,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be"
integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==
+"@babel/helper-environment-visitor@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz#c769afefd41d171836f7cb63e295bedf689d48ba"
+ integrity sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==
+
"@babel/helper-explode-assignable-expression@^7.14.5":
version "7.14.5"
resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.14.5.tgz#8aa72e708205c7bb643e45c73b4386cdf2a1f645"
@@ -549,7 +601,7 @@
dependencies:
"@babel/types" "^7.16.0"
-"@babel/helper-module-imports@^7.18.6":
+"@babel/helper-module-imports@^7.18.6", "@babel/helper-module-imports@^7.21.4":
version "7.21.4"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz#ac88b2f76093637489e718a90cec6cf8a9b029af"
integrity sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==
@@ -598,6 +650,20 @@
"@babel/traverse" "^7.18.0"
"@babel/types" "^7.18.0"
+"@babel/helper-module-transforms@^7.20.2":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.21.5.tgz#d937c82e9af68d31ab49039136a222b17ac0b420"
+ integrity sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw==
+ dependencies:
+ "@babel/helper-environment-visitor" "^7.21.5"
+ "@babel/helper-module-imports" "^7.21.4"
+ "@babel/helper-simple-access" "^7.21.5"
+ "@babel/helper-split-export-declaration" "^7.18.6"
+ "@babel/helper-validator-identifier" "^7.19.1"
+ "@babel/template" "^7.20.7"
+ "@babel/traverse" "^7.21.5"
+ "@babel/types" "^7.21.5"
+
"@babel/helper-module-transforms@^7.21.2":
version "7.21.2"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz#160caafa4978ac8c00ac66636cb0fa37b024e2d2"
@@ -730,6 +796,13 @@
dependencies:
"@babel/types" "^7.20.2"
+"@babel/helper-simple-access@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz#d697a7971a5c39eac32c7e63c0921c06c8a249ee"
+ integrity sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==
+ dependencies:
+ "@babel/types" "^7.21.5"
+
"@babel/helper-skip-transparent-expression-wrappers@^7.14.5":
version "7.14.5"
resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.14.5.tgz#96f486ac050ca9f44b009fbe5b7d394cab3a0ee4"
@@ -782,6 +855,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63"
integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==
+"@babel/helper-string-parser@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz#2b3eea65443c6bdc31c22d037c65f6d323b6b2bd"
+ integrity sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==
+
"@babel/helper-validator-identifier@^7.14.5":
version "7.14.5"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz#d0f0e277c512e0c938277faa85a3968c9a44c0e8"
@@ -860,6 +938,15 @@
"@babel/traverse" "^7.18.2"
"@babel/types" "^7.18.2"
+"@babel/helpers@^7.20.1":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.21.5.tgz#5bac66e084d7a4d2d9696bdf0175a93f7fb63c08"
+ integrity sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA==
+ dependencies:
+ "@babel/template" "^7.20.7"
+ "@babel/traverse" "^7.21.5"
+ "@babel/types" "^7.21.5"
+
"@babel/helpers@^7.21.0":
version "7.21.0"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.21.0.tgz#9dd184fb5599862037917cdc9eecb84577dc4e7e"
@@ -921,6 +1008,11 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.6.tgz#b923430cb94f58a7eae8facbffa9efd19130e7f8"
integrity sha512-h1IUp81s2JYJ3mRkdxJgs4UvmSsRvDrx5ICSJbPvtWYv5i1nTBGcBpnog+89rAFMwvvru6E5NUHdBe01UeSzYA==
+"@babel/parser@^7.20.2", "@babel/parser@^7.21.5":
+ version "7.21.8"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.8.tgz#642af7d0333eab9c0ad70b14ac5e76dbde7bfdf8"
+ integrity sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==
+
"@babel/parser@^7.20.7", "@babel/parser@^7.21.4":
version "7.21.4"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.4.tgz#94003fdfc520bbe2875d4ae557b43ddb6d880f17"
@@ -1657,6 +1749,22 @@
debug "^4.1.0"
globals "^11.1.0"
+"@babel/traverse@^7.20.1", "@babel/traverse@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.5.tgz#ad22361d352a5154b498299d523cf72998a4b133"
+ integrity sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==
+ dependencies:
+ "@babel/code-frame" "^7.21.4"
+ "@babel/generator" "^7.21.5"
+ "@babel/helper-environment-visitor" "^7.21.5"
+ "@babel/helper-function-name" "^7.21.0"
+ "@babel/helper-hoist-variables" "^7.18.6"
+ "@babel/helper-split-export-declaration" "^7.18.6"
+ "@babel/parser" "^7.21.5"
+ "@babel/types" "^7.21.5"
+ debug "^4.1.0"
+ globals "^11.1.0"
+
"@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2", "@babel/traverse@^7.21.4", "@babel/traverse@^7.7.2":
version "7.21.4"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.4.tgz#a836aca7b116634e97a6ed99976236b3282c9d36"
@@ -1714,6 +1822,15 @@
"@babel/helper-validator-identifier" "^7.18.6"
to-fast-properties "^2.0.0"
+"@babel/types@^7.18.7":
+ version "7.20.2"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.2.tgz#67ac09266606190f496322dbaff360fdaa5e7842"
+ integrity sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog==
+ dependencies:
+ "@babel/helper-string-parser" "^7.19.4"
+ "@babel/helper-validator-identifier" "^7.19.1"
+ to-fast-properties "^2.0.0"
+
"@babel/types@^7.18.9", "@babel/types@^7.19.4":
version "7.19.4"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.4.tgz#0dd5c91c573a202d600490a35b33246fed8a41c7"
@@ -1732,11 +1849,41 @@
"@babel/helper-validator-identifier" "^7.19.1"
to-fast-properties "^2.0.0"
+"@babel/types@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.5.tgz#18dfbd47c39d3904d5db3d3dc2cc80bedb60e5b6"
+ integrity sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==
+ dependencies:
+ "@babel/helper-string-parser" "^7.21.5"
+ "@babel/helper-validator-identifier" "^7.19.1"
+ to-fast-properties "^2.0.0"
+
"@bcoe/v8-coverage@^0.2.3":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
+"@chainsafe/as-sha256@^0.4.1":
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/@chainsafe/as-sha256/-/as-sha256-0.4.1.tgz#cfc0737e25f8c206767bdb6703e7943e5d44513e"
+ integrity sha512-IqeeGwQihK6Y2EYLFofqs2eY2ep1I2MvQXHzOAI+5iQN51OZlUkrLgyAugu2x86xZewDk5xas7lNczkzFzF62w==
+
+"@chainsafe/persistent-merkle-tree@^0.6.1":
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.6.1.tgz#37bde25cf6cbe1660ad84311aa73157dc86ec7f2"
+ integrity sha512-gcENLemRR13+1MED2NeZBMA7FRS0xQPM7L2vhMqvKkjqtFT4YfjSVADq5U0iLuQLhFUJEMVuA8fbv5v+TN6O9A==
+ dependencies:
+ "@chainsafe/as-sha256" "^0.4.1"
+ "@noble/hashes" "^1.3.0"
+
+"@chainsafe/ssz@^0.11.1":
+ version "0.11.1"
+ resolved "https://registry.yarnpkg.com/@chainsafe/ssz/-/ssz-0.11.1.tgz#d4aec883af2ec5196ae67b96242c467da20b2476"
+ integrity sha512-cB8dBkgGN6ZoeOKuk+rIRHKN0L5i9JLGeC0Lui71QX0TuLcQKwgbfkUexpyJxnGFatWf8yeJxlOjozMn/OTP0g==
+ dependencies:
+ "@chainsafe/as-sha256" "^0.4.1"
+ "@chainsafe/persistent-merkle-tree" "^0.6.1"
+
"@cnakazawa/watch@^1.0.3":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a"
@@ -2153,11 +2300,24 @@
crc-32 "^1.2.0"
ethereumjs-util "^7.1.5"
+"@ethereumjs/common@^3.1.2":
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/@ethereumjs/common/-/common-3.1.2.tgz#c810301b78bcb7526bd690c6d7eb3f4a3c70839d"
+ integrity sha512-YV+bZfRlFhAXg+FfwC5r4UQKVj4OG7vDP5/JvvNXLLbYpNplH5Vca9jD0L+ab8y0YlTYJMQM1ALyHFu3AE3eBA==
+ dependencies:
+ "@ethereumjs/util" "^8.0.6"
+ crc-32 "^1.2.0"
+
"@ethereumjs/rlp@^4.0.0-beta.2":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@ethereumjs/rlp/-/rlp-4.0.0.tgz#66719891bd727251a7f233f9ca80212d1994f8c8"
integrity sha512-LM4jS5n33bJN60fM5EC8VeyhUgga6/DjCPBV2vWjnfVtobqtOiNC4SQ1MRFqyBSmJGGdB533JZWewyvlcdJtkQ==
+"@ethereumjs/rlp@^4.0.1":
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/@ethereumjs/rlp/-/rlp-4.0.1.tgz#626fabfd9081baab3d0a3074b0c7ecaf674aaa41"
+ integrity sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw==
+
"@ethereumjs/tx@3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-3.0.0.tgz#8dfd91ed6e91e63996e37b3ddc340821ebd48c81"
@@ -2190,6 +2350,17 @@
"@ethereumjs/common" "^2.6.4"
ethereumjs-util "^7.1.5"
+"@ethereumjs/tx@^4.1.1":
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-4.1.2.tgz#10bc6741b74d2404331b82b87f9b2c26177b6f90"
+ integrity sha512-PWWyO9lAFOiLwk7nB9OQisoJUsuvMz2PN2v4/ILbBpzamC5Ug79OddVq9r4rKvIDLPY+bn4NFerxBJg29+sjaA==
+ dependencies:
+ "@chainsafe/ssz" "^0.11.1"
+ "@ethereumjs/common" "^3.1.2"
+ "@ethereumjs/rlp" "^4.0.1"
+ "@ethereumjs/util" "^8.0.6"
+ ethereum-cryptography "^2.0.0"
+
"@ethereumjs/util@^8.0.0":
version "8.0.2"
resolved "https://registry.yarnpkg.com/@ethereumjs/util/-/util-8.0.2.tgz#b7348fc7253649b0f00685a94546c6eee1fad819"
@@ -2199,6 +2370,16 @@
async "^3.2.4"
ethereum-cryptography "^1.1.2"
+"@ethereumjs/util@^8.0.6":
+ version "8.0.6"
+ resolved "https://registry.yarnpkg.com/@ethereumjs/util/-/util-8.0.6.tgz#f9716ed34235ea05eff8353bc5d483e5a6455989"
+ integrity sha512-zFLG/gXtF3QUC7iKFn4PT6HCr+DEnlCbwUGKGtXoqjA+64T+e0FuqMjlo4bQIY2ngRzk3EtudKdGYC4g31ehhg==
+ dependencies:
+ "@chainsafe/ssz" "^0.11.1"
+ "@ethereumjs/rlp" "^4.0.1"
+ ethereum-cryptography "^2.0.0"
+ micro-ftch "^0.3.1"
+
"@ethersproject/abi@5.4.1":
version "5.4.1"
resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.4.1.tgz#6ac28fafc9ef6f5a7a37e30356a2eb31fa05d39b"
@@ -4172,7 +4353,7 @@
"@metamask/base-controller" "^2.0.0"
"@metamask/controller-utils" "^3.0.0"
-"@metamask/approval-controller@2.1.0", "@metamask/approval-controller@^2.0.0", "@metamask/approval-controller@^2.1.1":
+"@metamask/approval-controller@2.1.0", "@metamask/approval-controller@^1.0.1", "@metamask/approval-controller@^1.1.0", "@metamask/approval-controller@^2.0.0", "@metamask/approval-controller@^2.1.1":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@metamask/approval-controller/-/approval-controller-2.1.0.tgz#4ef461243866029f3c564b2961329da90f8aaa39"
integrity sha512-mPDy4Nl3Qy4PhGhNyWfsy2WLTB1x5ffYq9PrNqoDJdQ2KRUM+Y7txTv0NUcfWf3sRhQIyRfxLh3SltTN/s1lxQ==
@@ -4219,6 +4400,14 @@
"@metamask/controller-utils" "^1.0.0"
immer "^9.0.6"
+"@metamask/base-controller@^1.1.2":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@metamask/base-controller/-/base-controller-1.1.2.tgz#92643d16a322664adae924cf45806c96c6704e30"
+ integrity sha512-lOV3dyaTw+dTZOYkpjFwKN4DfOlvRpALknUlOzoFg+ChLeva8T7E4/pyo52FOEtxhajsq9/77soGm729oaNGMA==
+ dependencies:
+ "@metamask/controller-utils" "^2.0.0"
+ immer "^9.0.6"
+
"@metamask/base-controller@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@metamask/base-controller/-/base-controller-2.0.0.tgz#8f9130df3edaa270ade00378cf57917545d44617"
@@ -4237,6 +4426,11 @@
pbkdf2 "^3.0.9"
randombytes "^2.0.1"
+"@metamask/browser-passworder@^4.0.2":
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/@metamask/browser-passworder/-/browser-passworder-4.1.0.tgz#d515db2ffd69ecab813a688e2b7553f2766c5e79"
+ integrity sha512-FBvah1mPte5HudQdkgqAh2+Zc75T9kYxey+dCtHIj9gKohkHDcIA1bTOPLk0bBH+6PnOzYNPG8devvH04GOmPA==
+
"@metamask/composable-controller@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@metamask/composable-controller/-/composable-controller-2.0.0.tgz#a3549c5ad150d2c74e575987968de4b8f8ae5d9b"
@@ -4249,7 +4443,7 @@
resolved "https://registry.yarnpkg.com/@metamask/contract-metadata/-/contract-metadata-2.2.0.tgz#277764d0d56e37180ae7644a9d11eb96295b36fc"
integrity sha512-SM6A4C7vXNbVpgMTX67kfW8QWvu3eSXxMZlY5PqZBTkvri1s9zgQ0uwRkK5r2VXNEoVmXCDnnEX/tX5EzzgNUQ==
-"@metamask/controller-utils@^1.0.0", "@metamask/controller-utils@^3.0.0", "@metamask/controller-utils@^3.1.0", "@metamask/controller-utils@^3.4.0", "@metamask/controller-utils@~3.0.0":
+"@metamask/controller-utils@^1.0.0", "@metamask/controller-utils@^2.0.0", "@metamask/controller-utils@^3.0.0", "@metamask/controller-utils@^3.1.0", "@metamask/controller-utils@^3.4.0", "@metamask/controller-utils@~3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@metamask/controller-utils/-/controller-utils-3.0.0.tgz#e0984cdab14280409297671b5858891527c5e4ee"
integrity sha512-JjFWBZnnh5DSX2tRsw5xtXxaqVkTzaW7mkSZ+lL3LoCAw47Cf8zGP1kGR6VKxcceKi+MpEFvZr7gf1OFnOoEjw==
@@ -4358,6 +4552,18 @@
immer "^9.0.6"
uuid "^8.3.2"
+"@metamask/key-tree@^6.0.0":
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/@metamask/key-tree/-/key-tree-6.1.0.tgz#e2bc9e617441c76f909f1638d8bb3ef9f255d54a"
+ integrity sha512-Z9quifwXPc/jImJwOQ1EZw4NamYq6Wm8wRMz0bilnN/u4VA4JdGil1L7DuGTkoO3zzgJScA056/ge8+gSs0HwQ==
+ dependencies:
+ "@metamask/scure-bip39" "^2.1.0"
+ "@metamask/utils" "^3.3.0"
+ "@noble/ed25519" "^1.6.0"
+ "@noble/hashes" "^1.0.0"
+ "@noble/secp256k1" "^1.5.5"
+ "@scure/base" "^1.0.0"
+
"@metamask/keyring-controller@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@metamask/keyring-controller/-/keyring-controller-1.0.1.tgz#c2836bbafaab8c2d1300f4e56aad3b32b3dd90c0"
@@ -4424,6 +4630,15 @@
immer "^9.0.6"
web3-provider-engine "^16.0.3"
+"@metamask/object-multiplex@^1.1.0", "@metamask/object-multiplex@^1.2.0":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@metamask/object-multiplex/-/object-multiplex-1.2.0.tgz#38fc15c142f61939391e1b9a8eed679696c7e4f4"
+ integrity sha512-hksV602d3NWE2Q30Mf2Np1WfVKaGqfJRy9vpHAmelbaD0OkDt06/0KQkRR6UVYdMbTbkuEu8xN5JDUU80inGwQ==
+ dependencies:
+ end-of-stream "^1.4.4"
+ once "^1.4.0"
+ readable-stream "^2.3.3"
+
"@metamask/obs-store@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@metamask/obs-store/-/obs-store-7.0.0.tgz#6cae5f28306bb3e83a381bc9ae22682316095bd3"
@@ -4432,6 +4647,38 @@
"@metamask/safe-event-emitter" "^2.0.0"
through2 "^2.0.3"
+"@metamask/permission-controller@^1.0.1":
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/@metamask/permission-controller/-/permission-controller-1.0.2.tgz#ca0f79d817ba2dab2ce831065506ab3658ce6b94"
+ integrity sha512-YW8OGCS5ZjG+BlgSG+HEi+mqOIDvZonOpFNLzAVasE1DCReJdumBELEsy+l05XVCf1Dp4kLZ3MfRsPSPfHl0YA==
+ dependencies:
+ "@metamask/approval-controller" "^1.0.1"
+ "@metamask/base-controller" "^1.1.1"
+ "@metamask/controller-utils" "^1.0.0"
+ "@metamask/types" "^1.1.0"
+ "@types/deep-freeze-strict" "^1.1.0"
+ deep-freeze-strict "^1.1.1"
+ eth-rpc-errors "^4.0.0"
+ immer "^9.0.6"
+ json-rpc-engine "^6.1.0"
+ nanoid "^3.1.31"
+
+"@metamask/permission-controller@^2.0.0":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/@metamask/permission-controller/-/permission-controller-2.0.0.tgz#04135b7164a13df71f0f9b70de0a9fbd09f8a15a"
+ integrity sha512-KKnRc1F+N7fI8AXNxS+X21E1X0yQrBoqCGDn1WX5QPPOZ/wjvDbrPxaCK/Vkd2TxDosJ2jmA5HSl2TFY5X7U0w==
+ dependencies:
+ "@metamask/approval-controller" "^1.1.0"
+ "@metamask/base-controller" "^1.1.2"
+ "@metamask/controller-utils" "^2.0.0"
+ "@metamask/types" "^1.1.0"
+ "@types/deep-freeze-strict" "^1.1.0"
+ deep-freeze-strict "^1.1.1"
+ eth-rpc-errors "^4.0.0"
+ immer "^9.0.6"
+ json-rpc-engine "^6.1.0"
+ nanoid "^3.1.31"
+
"@metamask/permission-controller@~3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@metamask/permission-controller/-/permission-controller-3.0.0.tgz#2c58f749c4fc192743fd2e5fad5ffa503ba1c068"
@@ -4459,6 +4706,22 @@
eth-phishing-detect "^1.2.0"
punycode "^2.1.1"
+"@metamask/post-message-stream@6.0.0":
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/@metamask/post-message-stream/-/post-message-stream-6.0.0.tgz#e8c26b1ef41452b2f1862004651ae3970b944868"
+ integrity sha512-z0l6UMW3z6W4qIhKSh48/6n2Q9AG1oOh9tus+Ncs9w6dAbkElKke+mXZ7tT6oAyE3T4JZLnDfGKbPWYoq+nrsA==
+ dependencies:
+ "@metamask/utils" "^2.0.0"
+ readable-stream "2.3.3"
+
+"@metamask/post-message-stream@^6.0.0":
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/@metamask/post-message-stream/-/post-message-stream-6.1.0.tgz#d36837ea02777e65c1151a8687d427f84bf7004f"
+ integrity sha512-vf2M1j0nv2Ya+ShHsdowtOWpVWFPSZY2OopAxHQ7LoTAgsigKrqwkM3jpcgHvQIv4OpO+Ad2FrjcvIkGXGv5kw==
+ dependencies:
+ "@metamask/utils" "^3.0.1"
+ readable-stream "2.3.3"
+
"@metamask/preferences-controller@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@metamask/preferences-controller/-/preferences-controller-1.0.1.tgz#20f2187be103a1e7f49df4021e39afb12ee422a0"
@@ -4475,11 +4738,52 @@
"@metamask/base-controller" "^2.0.0"
"@metamask/controller-utils" "^3.0.0"
+"@metamask/providers@^10.2.0":
+ version "10.2.1"
+ resolved "https://registry.yarnpkg.com/@metamask/providers/-/providers-10.2.1.tgz#61304940adeccc7421dcda30ffd1d834273cc77b"
+ integrity sha512-p2TXw2a1Nb8czntDGfeIYQnk4LLVbd5vlcb3GY//lylYlKdSqp+uUTegCvxiFblRDOT68jsY8Ib1VEEzVUOolA==
+ dependencies:
+ "@metamask/object-multiplex" "^1.1.0"
+ "@metamask/safe-event-emitter" "^2.0.0"
+ "@types/chrome" "^0.0.136"
+ detect-browser "^5.2.0"
+ eth-rpc-errors "^4.0.2"
+ extension-port-stream "^2.0.1"
+ fast-deep-equal "^2.0.1"
+ is-stream "^2.0.0"
+ json-rpc-engine "^6.1.0"
+ json-rpc-middleware-stream "^4.2.1"
+ pump "^3.0.0"
+ webextension-polyfill-ts "^0.25.0"
+
+"@metamask/rpc-methods@0.26.2", "@metamask/rpc-methods@^0.26.2":
+ version "0.26.2"
+ resolved "https://registry.yarnpkg.com/@metamask/rpc-methods/-/rpc-methods-0.26.2.tgz#c07bd2e7a7e59f88c72cf4e951852278b3115ab4"
+ integrity sha512-8fKU6dzn06YlIz5Ik2RjaFnpQ3QeM++uUODiw+zKfiSt2EtXJHa18RruRbGIc+DlMr+cPkzrf6kpz2OGBOgfeg==
+ dependencies:
+ "@metamask/key-tree" "^6.0.0"
+ "@metamask/permission-controller" "^1.0.1"
+ "@metamask/snaps-utils" "^0.26.2"
+ "@metamask/types" "^1.1.0"
+ "@metamask/utils" "^3.3.1"
+ "@noble/hashes" "^1.1.3"
+ eth-rpc-errors "^4.0.2"
+ nanoid "^3.1.31"
+ superstruct "^0.16.7"
+
"@metamask/safe-event-emitter@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@metamask/safe-event-emitter/-/safe-event-emitter-2.0.0.tgz#af577b477c683fad17c619a78208cede06f9605c"
integrity sha512-/kSXhY692qiV1MXu6EeOZvg5nECLclxNXcKCxJ3cXQgYuRymRHpdx/t7JXfsK+JLjwA1e1c1/SBrlQYpusC29Q==
+"@metamask/scure-bip39@^2.1.0":
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/@metamask/scure-bip39/-/scure-bip39-2.1.0.tgz#13456884736e56ede15e471bd93c0aa0acdedd0b"
+ integrity sha512-Ndwdnld0SI6YaftEUUVq20sdoWcWNXsJXxvQkbiY42FKmrA16U6WoSh9Eq+NpugpKKwK6f5uvaTDusjndiEDGQ==
+ dependencies:
+ "@noble/hashes" "~1.1.1"
+ "@scure/base" "~1.1.0"
+
"@metamask/sdk-communication-layer@0.3.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/@metamask/sdk-communication-layer/-/sdk-communication-layer-0.3.0.tgz#ee7eac4f108bcf5d2ea0edfb9fedd97d1bf10360"
@@ -4505,6 +4809,98 @@
ethereumjs-util "^7.0.10"
immer "^9.0.6"
+"@metamask/slip44@3.0.0":
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/@metamask/slip44/-/slip44-3.0.0.tgz#12aba82d65426d943bf3f1ed7954cd9d93c11a8d"
+ integrity sha512-sRVOJsgCviK+9gA5jg9gJUtIuuEudCekPrbL6PGyeXHVl0IcrohZQI769ILuOO4IscP7jAqT78fKc+r1FKr7Vw==
+
+"@metamask/snaps-controllers@0.26.2":
+ version "0.26.2"
+ resolved "https://registry.yarnpkg.com/@metamask/snaps-controllers/-/snaps-controllers-0.26.2.tgz#11435bafa56e5110f36b21b96c621aa0bc6e4f8a"
+ integrity sha512-QjI7BXdGdystM28oWYSAMTRLFCBtXklpKR+VYph4XyiexyEYilpfOY5f3hj31f0i1+yhMnN51WPX85tSbYFPLQ==
+ dependencies:
+ "@metamask/approval-controller" "^1.0.1"
+ "@metamask/base-controller" "^1.1.1"
+ "@metamask/browser-passworder" "^4.0.2"
+ "@metamask/object-multiplex" "^1.1.0"
+ "@metamask/permission-controller" "^1.0.1"
+ "@metamask/post-message-stream" "^6.0.0"
+ "@metamask/rpc-methods" "^0.26.2"
+ "@metamask/snaps-execution-environments" "^0.26.2"
+ "@metamask/snaps-types" "^0.26.2"
+ "@metamask/snaps-utils" "^0.26.2"
+ "@metamask/subject-metadata-controller" "^1.0.1"
+ "@metamask/utils" "^3.3.1"
+ "@xstate/fsm" "^2.0.0"
+ concat-stream "^2.0.0"
+ cron-parser "^4.5.0"
+ eth-rpc-errors "^4.0.2"
+ gunzip-maybe "^1.4.2"
+ immer "^9.0.6"
+ json-rpc-engine "^6.1.0"
+ json-rpc-middleware-stream "^4.2.0"
+ nanoid "^3.1.31"
+ pump "^3.0.0"
+ readable-web-to-node-stream "^3.0.2"
+ tar-stream "^2.2.0"
+
+"@metamask/snaps-execution-environments@^0.26.2":
+ version "0.26.2"
+ resolved "https://registry.yarnpkg.com/@metamask/snaps-execution-environments/-/snaps-execution-environments-0.26.2.tgz#048e0dd8e4890f8897309a8c559e5d159dd74c5b"
+ integrity sha512-4eRn3FJHvURQYDAMykNrSz3uwnoaObCxHLE0eewuS3OQPi2ZEIvv+W0a6QByFOLesCWqwECGM4OtifAxwdIu2w==
+ dependencies:
+ "@metamask/object-multiplex" "^1.2.0"
+ "@metamask/post-message-stream" "^6.0.0"
+ "@metamask/providers" "^10.2.0"
+ "@metamask/snaps-types" "^0.26.2"
+ "@metamask/snaps-utils" "^0.26.2"
+ "@metamask/utils" "^3.3.1"
+ eth-rpc-errors "^4.0.3"
+ json-rpc-engine "^6.1.0"
+ pump "^3.0.0"
+ ses "^0.17.0"
+ stream-browserify "^3.0.0"
+ superstruct "^0.16.7"
+
+"@metamask/snaps-types@^0.26.2":
+ version "0.26.2"
+ resolved "https://registry.yarnpkg.com/@metamask/snaps-types/-/snaps-types-0.26.2.tgz#f44ce3d0e8631e372caf165c56c1b78d33961f25"
+ integrity sha512-S+ljpsVrvRAFqXWSapUmd2x96c10rjjvQIPb5eLgVNvmsroBad+CGhFcjCm+xz92qafX+MMkq5XucdZhfzjLRg==
+ dependencies:
+ "@metamask/providers" "^10.2.0"
+ "@metamask/snaps-utils" "^0.26.2"
+ "@metamask/types" "^1.1.0"
+
+"@metamask/snaps-utils@0.26.2", "@metamask/snaps-utils@^0.26.2":
+ version "0.26.2"
+ resolved "https://registry.yarnpkg.com/@metamask/snaps-utils/-/snaps-utils-0.26.2.tgz#b3fb1ad14c350d165986e6e9febc1fcadce9bfe7"
+ integrity sha512-a/O1XwNQ+nxR6ODuB3cUd7iIjefNFklqsYlum+l6f1Si/MVkfQ12T6zcv2crdS58/H27wok4jJTJ5Nx8GmRKug==
+ dependencies:
+ "@babel/core" "^7.18.6"
+ "@babel/types" "^7.18.7"
+ "@metamask/snaps-types" "^0.26.2"
+ "@metamask/utils" "^3.3.1"
+ "@noble/hashes" "^1.1.3"
+ "@scure/base" "^1.1.1"
+ cron-parser "^4.5.0"
+ eth-rpc-errors "^4.0.3"
+ fast-deep-equal "^3.1.3"
+ rfdc "^1.3.0"
+ semver "^7.3.7"
+ ses "^0.17.0"
+ superstruct "^0.16.7"
+ validate-npm-package-name "^5.0.0"
+
+"@metamask/subject-metadata-controller@^1.0.0", "@metamask/subject-metadata-controller@^1.0.1":
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/@metamask/subject-metadata-controller/-/subject-metadata-controller-1.0.2.tgz#35f571231cf99889bd413d683f8b7459adc655fb"
+ integrity sha512-zOtgtS+NHnfhrzyaxlMi9iR6Wo4qm04TWp7c/bMFjCI8XY/Rs1VoJegxR/481ELwbzBNzGwnEc/TwSW2qBi39A==
+ dependencies:
+ "@metamask/base-controller" "^1.1.2"
+ "@metamask/permission-controller" "^2.0.0"
+ "@metamask/types" "^1.1.0"
+ immer "^9.0.6"
+
"@metamask/swaps-controller@^6.8.0":
version "6.8.0"
resolved "https://registry.yarnpkg.com/@metamask/swaps-controller/-/swaps-controller-6.8.0.tgz#c2c43173dc1101fab9ec918e7f7853f14072740b"
@@ -4547,7 +4943,14 @@
resolved "https://registry.yarnpkg.com/@metamask/types/-/types-1.1.0.tgz#9bd14b33427932833c50c9187298804a18c2e025"
integrity sha512-EEV/GjlYkOSfSPnYXfOosxa3TqYtIW3fhg6jdw+cok/OhMgNn4wCfbENFqjytrHMU2f7ZKtBAvtiP5V8H44sSw==
-"@metamask/utils@^3.3.1", "@metamask/utils@^3.4.1":
+"@metamask/utils@^2.0.0":
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/@metamask/utils/-/utils-2.1.0.tgz#a65eaa0932b863383844ec323e05e293d8e718ab"
+ integrity sha512-4PHdo5B1ifpw6GbsdlDpp8oqA++rddSmt2pWBHtIGGL2tQMvmfHdaDDSns4JP9iC+AbMogVcUpv5Vt8ow1zsRA==
+ dependencies:
+ fast-deep-equal "^3.1.3"
+
+"@metamask/utils@^3.0.1", "@metamask/utils@^3.3.0", "@metamask/utils@^3.3.1", "@metamask/utils@^3.4.1":
version "3.6.0"
resolved "https://registry.yarnpkg.com/@metamask/utils/-/utils-3.6.0.tgz#b218b969a05ca7a8093b5d1670f6625061de707d"
integrity sha512-9cIRrfkWvHblSiNDVXsjivqa9Ak0RYo/1H6tqTqTbAx+oBK2Sva0lWDHxGchOqA7bySGUJKAWSNJvH6gdHZ0gQ==
@@ -4557,6 +4960,17 @@
semver "^7.3.8"
superstruct "^1.0.3"
+"@metamask/utils@^5.0.0":
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/@metamask/utils/-/utils-5.0.1.tgz#8a60adcfeaa33f046dc66c0b9b02f2b172872d1b"
+ integrity sha512-8XCCYlLYoW4rZD2+QjEZ0UjOuvvS/EBmn89QwWW8A9+KVEO2B9HxXJFGZF/Rl+E4IoCxVczrjjIjXLg2kvkNPg==
+ dependencies:
+ "@ethereumjs/tx" "^4.1.1"
+ "@types/debug" "^4.1.7"
+ debug "^4.3.4"
+ semver "^7.3.8"
+ superstruct "^1.0.3"
+
"@ngraveio/bc-ur@^1.1.5", "@ngraveio/bc-ur@^1.1.6":
version "1.1.6"
resolved "https://registry.yarnpkg.com/@ngraveio/bc-ur/-/bc-ur-1.1.6.tgz#8f8c75fff22f6a5e4dfbc5a6b540d7fe8f42cd39"
@@ -4570,11 +4984,33 @@
jsbi "^3.1.5"
sha.js "^2.4.11"
+"@noble/curves@1.0.0", "@noble/curves@~1.0.0":
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.0.0.tgz#e40be8c7daf088aaf291887cbc73f43464a92932"
+ integrity sha512-2upgEu0iLiDVDZkNLeFV2+ht0BAVgQnEmCk6JsOch9Rp8xfkMCbvbAZlA2pBHQc73dbl+vFOXfqkf4uemdn0bw==
+ dependencies:
+ "@noble/hashes" "1.3.0"
+
+"@noble/ed25519@^1.6.0":
+ version "1.7.1"
+ resolved "https://registry.yarnpkg.com/@noble/ed25519/-/ed25519-1.7.1.tgz#6899660f6fbb97798a6fbd227227c4589a454724"
+ integrity sha512-Rk4SkJFaXZiznFyC/t77Q0NKS4FL7TLJJsVG2V2oiEq3kJVeTdxysEe/yRWSpnWMe808XRDJ+VFh5pt/FN5plw==
+
"@noble/hashes@1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.1.2.tgz#e9e035b9b166ca0af657a7848eb2718f0f22f183"
integrity sha512-KYRCASVTv6aeUi1tsF8/vpyR7zpfs3FUzy2Jqm+MU+LmUKhQ0y2FpfwqkCcxSg2ua4GALJd8k2R76WxwZGbQpA==
+"@noble/hashes@1.3.0", "@noble/hashes@^1.3.0", "@noble/hashes@~1.3.0":
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.0.tgz#085fd70f6d7d9d109671090ccae1d3bec62554a1"
+ integrity sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==
+
+"@noble/hashes@^1.0.0", "@noble/hashes@^1.1.3":
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.1.5.tgz#1a0377f3b9020efe2fae03290bd2a12140c95c11"
+ integrity sha512-LTMZiiLc+V4v1Yi16TD6aX2gmtKszNye0pQgbaLqkvhIqP7nVsSaJsWloGQjJfJ8offaoP5GtX3yY5swbcJxxQ==
+
"@noble/hashes@~1.1.1":
version "1.1.3"
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.1.3.tgz#360afc77610e0a61f3417e497dcf36862e4f8111"
@@ -4585,6 +5021,11 @@
resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.6.3.tgz#7eed12d9f4404b416999d0c87686836c4c5c9b94"
integrity sha512-T04e4iTurVy7I8Sw4+c5OSN9/RkPlo1uKxAomtxQNLq8j1uPAqnsqG1bqvY3Jv7c13gyr6dui0zmh/I3+f/JaQ==
+"@noble/secp256k1@^1.5.5":
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.0.tgz#d15357f7c227e751d90aa06b05a0e5cf993ba8c1"
+ integrity sha512-kbacwGSsH/CTout0ZnZWxnW1B+jH/7r/WAAKLBtrRJ/+CUH7lgmQzl3GTrQua3SGKWNSDsS6lmjnDpIJ5Dxyaw==
+
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
@@ -4938,7 +5379,7 @@
image-to-base64 "^2.2.0"
open "^8.2.0"
-"@scure/base@~1.1.0":
+"@scure/base@^1.0.0", "@scure/base@^1.1.1", "@scure/base@~1.1.0":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938"
integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==
@@ -4952,6 +5393,15 @@
"@noble/secp256k1" "~1.6.0"
"@scure/base" "~1.1.0"
+"@scure/bip32@1.3.0":
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.0.tgz#6c8d980ef3f290987736acd0ee2e0f0d50068d87"
+ integrity sha512-bcKpo1oj54hGholplGLpqPHRbIsnbixFtc06nwuNM5/dwSXOq/AAYoIBRsBmnZJSdfeNW5rnff7NTAz3ZCqR9Q==
+ dependencies:
+ "@noble/curves" "~1.0.0"
+ "@noble/hashes" "~1.3.0"
+ "@scure/base" "~1.1.0"
+
"@scure/bip39@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.1.0.tgz#92f11d095bae025f166bef3defcc5bf4945d419a"
@@ -4960,6 +5410,14 @@
"@noble/hashes" "~1.1.1"
"@scure/base" "~1.1.0"
+"@scure/bip39@1.2.0":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.0.tgz#a207e2ef96de354de7d0002292ba1503538fc77b"
+ integrity sha512-SX/uKq52cuxm4YFXWFaVByaSHJh2w3BnokVSeUJVCv6K7WulT9u2BuNRBhuFl8vAuYnzx9bEu9WgpcNYTrYieg==
+ dependencies:
+ "@noble/hashes" "~1.3.0"
+ "@scure/base" "~1.1.0"
+
"@segment/analytics-react-native@2.13.0":
version "2.13.0"
resolved "https://registry.yarnpkg.com/@segment/analytics-react-native/-/analytics-react-native-2.13.0.tgz#2a612253219cd91ea3716f46d4fa837231d968d8"
@@ -5851,6 +6309,14 @@
dependencies:
"@types/node" "*"
+"@types/chrome@^0.0.136":
+ version "0.0.136"
+ resolved "https://registry.yarnpkg.com/@types/chrome/-/chrome-0.0.136.tgz#7c011b9f997b0156f25a140188a0c5689d3f368f"
+ integrity sha512-XDEiRhLkMd+SB7Iw3ZUIj/fov3wLd4HyTdLltVszkgl1dBfc3Rb7oPMVZ2Mz2TLqnF7Ow+StbR8E7r9lqpb4DA==
+ dependencies:
+ "@types/filesystem" "*"
+ "@types/har-format" "*"
+
"@types/connect@*":
version "3.4.35"
resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1"
@@ -5944,6 +6410,18 @@
"@types/qs" "*"
"@types/serve-static" "*"
+"@types/filesystem@*":
+ version "0.0.32"
+ resolved "https://registry.yarnpkg.com/@types/filesystem/-/filesystem-0.0.32.tgz#307df7cc084a2293c3c1a31151b178063e0a8edf"
+ integrity sha512-Yuf4jR5YYMR2DVgwuCiP11s0xuVRyPKmz8vo6HBY3CGdeMj8af93CFZX+T82+VD1+UqHOxTq31lO7MI7lepBtQ==
+ dependencies:
+ "@types/filewriter" "*"
+
+"@types/filewriter@*":
+ version "0.0.29"
+ resolved "https://registry.yarnpkg.com/@types/filewriter/-/filewriter-0.0.29.tgz#a48795ecadf957f6c0d10e0c34af86c098fa5bee"
+ integrity sha512-BsPXH/irW0ht0Ji6iw/jJaK8Lj3FJemon2gvEqHKpCdDCeemHa+rI3WBGq5z7cDMZgoLjY40oninGxqk+8NzNQ==
+
"@types/find-root@1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@types/find-root/-/find-root-1.1.2.tgz#32b05d0d9028866bbe29bb9eaaf00170017a9110"
@@ -5982,6 +6460,11 @@
resolved "https://registry.yarnpkg.com/@types/hammerjs/-/hammerjs-2.0.40.tgz#ded0240b6ea1ad7afc1e60374c49087aaea5dbd8"
integrity sha512-VbjwR1fhsn2h2KXAY4oy1fm7dCxaKy0D+deTb8Ilc3Eo3rc5+5eA4rfYmZaHgNJKxVyI0f6WIXzO2zLkVmQPHA==
+"@types/har-format@*":
+ version "1.2.9"
+ resolved "https://registry.yarnpkg.com/@types/har-format/-/har-format-1.2.9.tgz#b9b3a9bfc33a078e7d898a00b09662910577f4a4"
+ integrity sha512-rffW6MhQ9yoa75bdNi+rjZBAvu2HhehWJXlhuWXnWdENeuKe82wUgAwxYOb7KRKKmxYN+D/iRKd2NDQMLqlUmg==
+
"@types/hoist-non-react-statics@^3.3.0":
version "3.3.1"
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
@@ -6376,7 +6859,7 @@
dependencies:
"@types/react" "*"
-"@types/react@*", "@types/react@^17.0.11":
+"@types/react@*":
version "17.0.14"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.14.tgz#f0629761ca02945c4e8fea99b8177f4c5c61fb0f"
integrity sha512-0WwKHUbWuQWOce61UexYuWTGuGY/8JvtUe/dtQ6lR4sZ3UiylHotJeWpf3ArP9+DSGUoLY3wbU59VyMrJps5VQ==
@@ -6385,6 +6868,15 @@
"@types/scheduler" "*"
csstype "^3.0.2"
+"@types/react@^17.0.11":
+ version "17.0.59"
+ resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.59.tgz#5aa4e161a356fcb824d81f166e01bad9e82243bb"
+ integrity sha512-gSON5zWYIGyoBcycCE75E9+r6dCC2dHdsrVkOEiIYNU5+Q28HcBAuqvDuxHcCbMfHBHdeT5Tva/AFn3rnMKE4g==
+ dependencies:
+ "@types/prop-types" "*"
+ "@types/scheduler" "*"
+ csstype "^3.0.2"
+
"@types/reactcss@*":
version "1.2.6"
resolved "https://registry.yarnpkg.com/@types/reactcss/-/reactcss-1.2.6.tgz#133c1e7e896f2726370d1d5a26bf06a30a038bcc"
@@ -6392,6 +6884,14 @@
dependencies:
"@types/react" "*"
+"@types/readable-stream@^2.3.15":
+ version "2.3.15"
+ resolved "https://registry.yarnpkg.com/@types/readable-stream/-/readable-stream-2.3.15.tgz#3d79c9ceb1b6a57d5f6e6976f489b9b5384321ae"
+ integrity sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ==
+ dependencies:
+ "@types/node" "*"
+ safe-buffer "~5.1.1"
+
"@types/readdir-glob@*":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@types/readdir-glob/-/readdir-glob-1.1.1.tgz#27ac2db283e6aa3d110b14ff9da44fcd1a5c38b1"
@@ -7567,6 +8067,11 @@
resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.6.tgz#8a1524eb5bd5e965c1e3735476f0262469f71440"
integrity sha512-uRjjusqpoqfmRkTaNuLJ2VohVr67Q5YwDATW3VU7PfzTj6IRaihGrYI7zckGZjxQPBIp63nfvJbM+Yu5ICh0Bg==
+"@xstate/fsm@^2.0.0":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/@xstate/fsm/-/fsm-2.0.0.tgz#221db16b606df3f4675f5be6058b633ec4b8b44a"
+ integrity sha512-p/zcvBMoU2ap5byMefLkR+AM+Eh99CU/SDEQeccgKlmFNOMDwphaRGqdk+emvel/SaGZ7Rf9sDvzAplLzLdEVQ==
+
"@yarnpkg/lockfile@^1.0.0", "@yarnpkg/lockfile@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
@@ -9636,6 +10141,13 @@ browserify-unibabel@^3.0.0:
resolved "https://registry.yarnpkg.com/browserify-unibabel/-/browserify-unibabel-3.0.0.tgz#5a6b8f0f704ce388d3927df47337e25830f71dda"
integrity sha1-WmuPD3BM44jTkn30czfiWDD3Hdo=
+browserify-zlib@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.1.4.tgz#bb35f8a519f600e0fa6b8485241c979d0141fb2d"
+ integrity sha512-19OEpq7vWgsH6WkvkBJQDFvJS1uPcbFOQ4v9CU839dO+ZZXUZO6XpE6hNCqvlIIj+4fZvRiJ6DsAQ382GwiyTQ==
+ dependencies:
+ pako "~0.2.0"
+
browserslist@^4.16.6:
version "4.16.6"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2"
@@ -9815,6 +10327,13 @@ bufio@^1.0.7:
resolved "https://registry.yarnpkg.com/bufio/-/bufio-1.1.3.tgz#7f8e524fd719ced2caa563a09d50550f283f745f"
integrity sha512-W0ydG8t+ST+drUpEwl1N+dU9Ije06g8+43CLtvEIzfKo9nPFLXbKqDYE2XSg4w6RugsBcCj7pEU7jOpBC6BqrA==
+builtins@^5.0.0:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/builtins/-/builtins-5.0.1.tgz#87f6db9ab0458be728564fa81d876d8d74552fa9"
+ integrity sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==
+ dependencies:
+ semver "^7.0.0"
+
bunyan-debug-stream@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/bunyan-debug-stream/-/bunyan-debug-stream-3.1.0.tgz#78309c67ad85cfb8f011155334152c49209dcda8"
@@ -10664,6 +11183,16 @@ concat-stream@^1.4.4:
readable-stream "^2.2.2"
typedarray "^0.0.6"
+concat-stream@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1"
+ integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==
+ dependencies:
+ buffer-from "^1.0.0"
+ inherits "^2.0.3"
+ readable-stream "^3.0.2"
+ typedarray "^0.0.6"
+
connect@^3.6.5:
version "3.7.0"
resolved "https://registry.yarnpkg.com/connect/-/connect-3.7.0.tgz#5d49348910caa5e07a01800b030d0c35f20484f8"
@@ -10679,6 +11208,11 @@ console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control-
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=
+constants@^0.0.2:
+ version "0.0.2"
+ resolved "https://registry.yarnpkg.com/constants/-/constants-0.0.2.tgz#f973bcf3b69b6493b3b3645723124fbabce25094"
+ integrity sha512-K8aYHRLm9Q/b/Im6CQESZdkH7fblazlncmqPujXt9DP5bxKfi7vVNjRKXx70SnnwCkW6R86o3AcSnkXL7XLq4g==
+
content-disposition@0.5.4:
version "0.5.4"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
@@ -10904,6 +11438,13 @@ create-react-context@0.3.0:
gud "^1.0.0"
warning "^4.0.3"
+cron-parser@^4.5.0:
+ version "4.7.0"
+ resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-4.7.0.tgz#184eac151f810e7bf2b32ad37b4dd4a711797cdc"
+ integrity sha512-BdAELR+MCT2ZWsIBhZKDuUqIUCBjHHulPJnm53OfdRLA4EWBjva3R+KM5NeidJuGsNXdEcZkjC7SCnkW5rAFSA==
+ dependencies:
+ luxon "^3.1.0"
+
cross-fetch@2.2.6, cross-fetch@^2.1.0, cross-fetch@^3.1.5:
version "2.2.6"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-2.2.6.tgz#2ef0bb39a24ac034787965c457368a28730e220a"
@@ -11521,7 +12062,7 @@ detect-browser@5.2.0:
resolved "https://registry.yarnpkg.com/detect-browser/-/detect-browser-5.2.0.tgz#c9cd5afa96a6a19fda0bbe9e9be48a6b6e1e9c97"
integrity sha512-tr7XntDAu50BVENgQfajMLzacmSe34D+qZc4zjnniz0ZVuw/TZcLcyxHQjYpJTM36sGEkZZlYLnIM1hH7alTMA==
-detect-browser@5.3.0:
+detect-browser@5.3.0, detect-browser@^5.2.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/detect-browser/-/detect-browser-5.3.0.tgz#9705ef2bddf46072d0f7265a1fe300e36fe7ceca"
integrity sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==
@@ -11862,6 +12403,16 @@ duplexer@~0.1.1:
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6"
integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==
+duplexify@^3.5.0, duplexify@^3.6.0:
+ version "3.7.1"
+ resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309"
+ integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==
+ dependencies:
+ end-of-stream "^1.0.0"
+ inherits "^2.0.1"
+ readable-stream "^2.0.0"
+ stream-shift "^1.0.0"
+
duplexify@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.2.tgz#18b4f8d28289132fa0b9573c898d9f903f81c7b0"
@@ -12034,7 +12585,7 @@ encodeurl@~1.0.2:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
-end-of-stream@^1.1.0, end-of-stream@^1.4.0, end-of-stream@^1.4.1:
+end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.0, end-of-stream@^1.4.1, end-of-stream@^1.4.4:
version "1.4.4"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
@@ -13137,6 +13688,16 @@ ethereum-cryptography@^1.1.2:
"@scure/bip32" "1.1.0"
"@scure/bip39" "1.1.0"
+ethereum-cryptography@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-2.0.0.tgz#e052b49fa81affae29402e977b8d3a31f88612b6"
+ integrity sha512-g25m4EtfQGjstWgVE1aIz7XYYjf3kH5kG17ULWVB5dH6uLahsoltOhACzSxyDV+fhn4gbR4xRrOXGe6r2uh4Bg==
+ dependencies:
+ "@noble/curves" "1.0.0"
+ "@noble/hashes" "1.3.0"
+ "@scure/bip32" "1.3.0"
+ "@scure/bip39" "1.2.0"
+
ethereum-ens-network-map@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/ethereum-ens-network-map/-/ethereum-ens-network-map-1.0.2.tgz#4e27bad18dae7bd95d84edbcac2c9e739fc959b9"
@@ -13883,6 +14444,13 @@ extend@~3.0.2:
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
+extension-port-stream@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/extension-port-stream/-/extension-port-stream-2.0.1.tgz#d374820c581418c2275d3c4439ade0b82c4cfac6"
+ integrity sha512-ltrv4Dh/979I04+D4Te6TFygfRSOc5EBzzlHRldWMS8v73V80qWluxH88hqF0qyUsBXTb8NmzlmSipcre6a+rg==
+ dependencies:
+ webextension-polyfill-ts "^0.22.0"
+
external-editor@^3.0.3:
version "3.1.0"
resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495"
@@ -14016,6 +14584,13 @@ fast-text-encoding@^1.0.6:
resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz#0aa25f7f638222e3396d72bf936afcf1d42d6867"
integrity sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==
+fast-xml-parser@4.2.4:
+ version "4.2.4"
+ resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.2.4.tgz#6e846ede1e56ad9e5ef07d8720809edf0ed07e9b"
+ integrity sha512-fbfMDvgBNIdDJLdLOwacjFAPYt67tr31H9ZhWSm45CDAxvd0I6WTlSOUo7K2P/K5sA5JgMKG64PI3DMcaFdWpQ==
+ dependencies:
+ strnum "^1.0.5"
+
fastq@^1.6.0:
version "1.12.0"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.12.0.tgz#ed7b6ab5d62393fb2cc591c853652a5c318bf794"
@@ -14900,6 +15475,18 @@ gud@^1.0.0:
resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0"
integrity sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==
+gunzip-maybe@^1.4.2:
+ version "1.4.2"
+ resolved "https://registry.yarnpkg.com/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz#b913564ae3be0eda6f3de36464837a9cd94b98ac"
+ integrity sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==
+ dependencies:
+ browserify-zlib "^0.1.4"
+ is-deflate "^1.0.0"
+ is-gzip "^1.0.0"
+ peek-stream "^1.1.0"
+ pumpify "^1.3.3"
+ through2 "^2.0.3"
+
handlebars@^4.7.7:
version "4.7.7"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1"
@@ -15446,7 +16033,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
-inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3:
+inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3, inherits@~2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -15737,6 +16324,11 @@ is-decimal@^1.0.0:
resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5"
integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==
+is-deflate@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-deflate/-/is-deflate-1.0.0.tgz#c862901c3c161fb09dac7cdc7e784f80e98f2f14"
+ integrity sha512-YDoFpuZWu1VRXlsnlYMzKyVRITXj7Ej/V9gXQ2/pAe7X1J7M/RNOqaIYi6qUn+B7nGyB9pDXrv02dsB58d2ZAQ==
+
is-descriptor@^0.1.0:
version "0.1.6"
resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca"
@@ -15841,6 +16433,11 @@ is-glob@^4.0.3, is-glob@~4.0.1:
dependencies:
is-extglob "^2.1.1"
+is-gzip@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-gzip/-/is-gzip-1.0.0.tgz#6ca8b07b99c77998025900e555ced8ed80879a83"
+ integrity sha512-rcfALRIb1YewtnksfRIHGcIY93QnK8BIQ/2c9yDYcG/Y6+vRoJuTWBmmSEbyLLYtXm7q35pHOHbZFQBaLrhlWQ==
+
is-hex-prefixed@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz#7d8d37e6ad77e5d127148913c573e082d777f554"
@@ -17051,6 +17648,14 @@ json-rpc-middleware-stream@3.0.0:
"@metamask/safe-event-emitter" "^2.0.0"
readable-stream "^2.3.3"
+json-rpc-middleware-stream@^4.2.0, json-rpc-middleware-stream@^4.2.1:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/json-rpc-middleware-stream/-/json-rpc-middleware-stream-4.2.1.tgz#e5cb8795ebfd7503c6ceaa43daaf065687cc2f22"
+ integrity sha512-6QKayke/8lg0nTjOpRCq4JCgRx7bVybldmloIfY21HSDV0GUevcV9i8DJNvuKTJx4KR9EDIf6HTStAnEovGUvA==
+ dependencies:
+ "@metamask/safe-event-emitter" "^2.0.0"
+ readable-stream "^2.3.3"
+
json-rpc-random-id@^1.0.0, json-rpc-random-id@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json-rpc-random-id/-/json-rpc-random-id-1.0.1.tgz#ba49d96aded1444dbb8da3d203748acbbcdec8c8"
@@ -17922,7 +18527,7 @@ ltgt@^2.1.3, ltgt@~2.2.0:
resolved "https://registry.yarnpkg.com/ltgt/-/ltgt-2.2.1.tgz#f35ca91c493f7b73da0e07495304f17b31f87ee5"
integrity sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=
-luxon@^3.3.0:
+luxon@^3.1.0, luxon@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.3.0.tgz#d73ab5b5d2b49a461c47cedbc7e73309b4805b48"
integrity sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==
@@ -18900,6 +19505,11 @@ metro@0.72.3:
ws "^7.5.1"
yargs "^15.3.1"
+micro-ftch@^0.3.1:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/micro-ftch/-/micro-ftch-0.3.1.tgz#6cb83388de4c1f279a034fb0cf96dfc050853c5f"
+ integrity sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg==
+
micromatch@^3.1.10, micromatch@^3.1.4:
version "3.1.10"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
@@ -20277,6 +20887,11 @@ pako@^1.0.5, pako@~1.0.2:
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
+pako@~0.2.0:
+ version "0.2.9"
+ resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75"
+ integrity sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==
+
param-case@^3.0.0:
version "3.0.4"
resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5"
@@ -20541,6 +21156,15 @@ pbkdf2@^3.0.17, pbkdf2@^3.0.3, pbkdf2@^3.0.9:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
+peek-stream@^1.1.0:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/peek-stream/-/peek-stream-1.1.3.tgz#3b35d84b7ccbbd262fff31dc10da56856ead6d67"
+ integrity sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==
+ dependencies:
+ buffer-from "^1.0.0"
+ duplexify "^3.5.0"
+ through2 "^2.0.3"
+
pegjs@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/pegjs/-/pegjs-0.10.0.tgz#cf8bafae6eddff4b5a7efb185269eaaf4610ddbd"
@@ -20961,6 +21585,11 @@ prismjs@^1.27.0, prismjs@^1.8.4, prismjs@~1.17.0:
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.28.0.tgz#0d8f561fa0f7cf6ebca901747828b149147044b6"
integrity sha512-8aaXdYvl1F7iC7Xm1spqSaY/OJBpYW3v+KJ+F17iYxvdc8sfjW194COK5wVhMZX45tGteiBQgdvD/nhxcRwylw==
+process-nextick-args@~1.0.6:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3"
+ integrity sha512-yN0WQmuCX63LP/TMvAg31nvT6m4vDqJEiiv2CAZqWOGNWutc9DfDk1NPYYmKUFmaVM2UwDowH4u5AHWYP/jxKw==
+
process-nextick-args@~2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
@@ -21114,11 +21743,6 @@ pseudomap@^1.0.2:
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
-psl@^1.1.28:
- version "1.8.0"
- resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
- integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==
-
psl@^1.1.33:
version "1.9.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7"
@@ -21144,6 +21768,23 @@ pump@3.0.0, pump@^3.0.0:
end-of-stream "^1.1.0"
once "^1.3.1"
+pump@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909"
+ integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==
+ dependencies:
+ end-of-stream "^1.1.0"
+ once "^1.3.1"
+
+pumpify@^1.3.3:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce"
+ integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==
+ dependencies:
+ duplexify "^3.6.0"
+ inherits "^2.0.3"
+ pump "^2.0.0"
+
punycode@1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
@@ -21649,7 +22290,7 @@ react-native-fs@^2.16.6:
base-64 "^0.1.0"
utf8 "^3.0.0"
-react-native-gesture-handler@^1.10.3:
+react-native-gesture-handler@1.10.3:
version "1.10.3"
resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-1.10.3.tgz#942bbf2963bbf49fa79593600ee9d7b5dab3cfc0"
integrity sha512-cBGMi1IEsIVMgoox4RvMx7V2r6bNKw0uR1Mu1o7NbuHS6BRSVLq0dP34l2ecnPlC+jpWd3le6Yg1nrdCjby2Mw==
@@ -22311,6 +22952,19 @@ readable-stream@1.0.33:
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
+readable-stream@2.3.3:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c"
+ integrity sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.3"
+ isarray "~1.0.0"
+ process-nextick-args "~1.0.6"
+ safe-buffer "~5.1.1"
+ string_decoder "~1.0.3"
+ util-deprecate "~1.0.1"
+
readable-stream@^1.0.26-4, readable-stream@^1.0.27-1, readable-stream@^1.0.31, readable-stream@^1.0.33:
version "1.1.14"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
@@ -22334,6 +22988,15 @@ readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
+readable-stream@^3.0.2, readable-stream@^3.5.0:
+ version "3.6.2"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967"
+ integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
+ dependencies:
+ inherits "^2.0.3"
+ string_decoder "^1.1.1"
+ util-deprecate "^1.0.1"
+
readable-stream@^4.1.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.4.0.tgz#55ce132d60a988c460d75c631e9ccf6a7229b468"
@@ -22354,6 +23017,13 @@ readable-stream@~1.0.15, readable-stream@~1.0.26, readable-stream@~1.0.26-4, rea
isarray "0.0.1"
string_decoder "~0.10.x"
+readable-web-to-node-stream@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz#5d52bb5df7b54861fd48d015e93a2cb87b3ee0bb"
+ integrity sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==
+ dependencies:
+ readable-stream "^3.6.0"
+
readdir-glob@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/readdir-glob/-/readdir-glob-1.1.2.tgz#b185789b8e6a43491635b6953295c5c5e3fd224c"
@@ -22810,6 +23480,11 @@ revalidator@0.1.x:
resolved "https://registry.yarnpkg.com/revalidator/-/revalidator-0.1.8.tgz#fece61bfa0c1b52a206bd6b18198184bdd523a3b"
integrity sha1-/s5hv6DBtSoga9axgZgYS91SOjs=
+rfdc@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b"
+ integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==
+
rgb2hex@0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/rgb2hex/-/rgb2hex-0.2.3.tgz#8aa464c517b8a26c7a79d767dabaec2b49ee78ec"
@@ -23334,6 +24009,11 @@ serve-static@^1.13.1:
parseurl "~1.3.3"
send "0.17.1"
+ses@^0.17.0:
+ version "0.17.0"
+ resolved "https://registry.yarnpkg.com/ses/-/ses-0.17.0.tgz#4e37cd1c4003e4448df2e84983900ccc5e2f095a"
+ integrity sha512-ObQ4DF4OgkmuPVRZLSmB1E+8jWh6lnlSpN9JHnphAUb/5J6k7da+7kj63cXrz53NDPd69rUV3DsfRBNBx8xcPQ==
+
set-blocking@^2.0.0, set-blocking@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
@@ -23976,6 +24656,14 @@ stream-browserify@1.0.0:
inherits "~2.0.1"
readable-stream "^1.0.27-1"
+stream-browserify@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-3.0.0.tgz#22b0a2850cdf6503e73085da1fc7b7d0c2122f2f"
+ integrity sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==
+ dependencies:
+ inherits "~2.0.4"
+ readable-stream "^3.5.0"
+
stream-buffers@2.2.x:
version "2.2.0"
resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-2.2.0.tgz#91d5f5130d1cef96dcfa7f726945188741d09ee4"
@@ -24173,6 +24861,13 @@ string_decoder@~0.10.x:
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
+string_decoder@~1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab"
+ integrity sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==
+ dependencies:
+ safe-buffer "~5.1.0"
+
string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
@@ -24275,6 +24970,11 @@ strip-json-comments@^3.0.1, strip-json-comments@^3.1.0, strip-json-comments@^3.1
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
+strnum@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db"
+ integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==
+
sudo-prompt@^9.0.0:
version "9.2.1"
resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.2.1.tgz#77efb84309c9ca489527a4e749f287e6bdd52afd"
@@ -24285,6 +24985,11 @@ suffix@^0.1.0:
resolved "https://registry.yarnpkg.com/suffix/-/suffix-0.1.1.tgz#cc58231646a0ef1102f79478ef3a9248fd9c842f"
integrity sha512-j5uf6MJtMCfC4vBe5LFktSe4bGyNTBk7I2Kdri0jeLrcv5B9pWfxVa5JQpoxgtR8vaVB7bVxsWgnfQbX5wkhAA==
+superstruct@^0.16.7:
+ version "0.16.7"
+ resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.16.7.tgz#78bb71209d71e6107a260afc166580b137bd243a"
+ integrity sha512-4ZZTrXlP4XzCrgh4vOfPDL6dL7zZm5aPl78eczwFSrwvxtsEnKRrSGID6Sbt0agycUoo4auRdWSNTX+oQ3KFyA==
+
superstruct@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-1.0.3.tgz#de626a5b49c6641ff4d37da3c7598e7a87697046"
@@ -24524,6 +25229,11 @@ test-exclude@^6.0.0:
glob "^7.1.4"
minimatch "^3.0.4"
+text-encoding@^0.7.0:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.7.0.tgz#f895e836e45990624086601798ea98e8f36ee643"
+ integrity sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA==
+
text-hex@1.0.x:
version "1.0.0"
resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5"
@@ -24688,18 +25398,10 @@ toposort@^2.0.2:
resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330"
integrity sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==
-tough-cookie@^2.3.3, tough-cookie@~2.5.0:
- version "2.5.0"
- resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
- integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
- dependencies:
- psl "^1.1.28"
- punycode "^2.1.1"
-
-tough-cookie@^4.0.0:
- version "4.1.2"
- resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874"
- integrity sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==
+tough-cookie@4.1.3, tough-cookie@^2.3.3, tough-cookie@^4.0.0, tough-cookie@~2.5.0:
+ version "4.1.3"
+ resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf"
+ integrity sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==
dependencies:
psl "^1.1.33"
punycode "^2.1.1"
@@ -25338,6 +26040,13 @@ validate-npm-package-license@^3.0.1:
spdx-correct "^3.0.0"
spdx-expression-parse "^3.0.0"
+validate-npm-package-name@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz#f16afd48318e6f90a1ec101377fa0384cfc8c713"
+ integrity sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==
+ dependencies:
+ builtins "^5.0.0"
+
validate.js@^0.13.0, validate.js@^0.x:
version "0.13.1"
resolved "https://registry.yarnpkg.com/validate.js/-/validate.js-0.13.1.tgz#b58bfac04a0f600a340f62e5227e70d95971e92a"
@@ -25665,6 +26374,25 @@ webdriverio@~7.16.13:
serialize-error "^8.0.0"
webdriver "7.16.16"
+webextension-polyfill-ts@^0.22.0:
+ version "0.22.0"
+ resolved "https://registry.yarnpkg.com/webextension-polyfill-ts/-/webextension-polyfill-ts-0.22.0.tgz#86cfd7bab4d9d779d98c8340983f4b691b2343f3"
+ integrity sha512-3P33ClMwZ/qiAT7UH1ROrkRC1KM78umlnPpRhdC/292UyoTTW9NcjJEqDsv83HbibcTB6qCtpVeuB2q2/oniHQ==
+ dependencies:
+ webextension-polyfill "^0.7.0"
+
+webextension-polyfill-ts@^0.25.0:
+ version "0.25.0"
+ resolved "https://registry.yarnpkg.com/webextension-polyfill-ts/-/webextension-polyfill-ts-0.25.0.tgz#fff041626365dbd0e29c40b197e989a55ec221ca"
+ integrity sha512-ikQhwwHYkpBu00pFaUzIKY26I6L87DeRI+Q6jBT1daZUNuu8dSrg5U9l/ZbqdaQ1M/TTSPKeAa3kolP5liuedw==
+ dependencies:
+ webextension-polyfill "^0.7.0"
+
+webextension-polyfill@^0.7.0:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/webextension-polyfill/-/webextension-polyfill-0.7.0.tgz#0df1120ff0266056319ce1a622b09ad8d4a56505"
+ integrity sha512-su48BkMLxqzTTvPSE1eWxKToPS2Tv5DLGxKexLEVpwFd6Po6N8hhSLIvG6acPAg7qERoEaDL+Y5HQJeJeml5Aw==
+
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"