diff --git a/.github/workflows/build-android-e2e.yml b/.github/workflows/build-android-e2e.yml
index 35d674bcee51..7019ff201040 100644
--- a/.github/workflows/build-android-e2e.yml
+++ b/.github/workflows/build-android-e2e.yml
@@ -9,9 +9,6 @@ on:
apk-uploaded:
description: 'Whether the APK was successfully uploaded'
value: ${{ jobs.build-android-apks.outputs.apk-uploaded }}
- aab-uploaded:
- description: 'Whether the AAB was successfully uploaded'
- value: ${{ jobs.build-android-apks.outputs.aab-uploaded }}
inputs:
build_type:
description: 'The type of build to perform'
@@ -32,17 +29,15 @@ on:
jobs:
build-android-apks:
name: Build Android E2E APKs
- runs-on: ghcr.io/cirruslabs/ubuntu-runner-amd64:24.04-xl # Bumped from lg to xl to prevent Daemon disappearance issue (Daemon OOM issue in CI)
+ runs-on: ghcr.io/cirruslabs/ubuntu-runner-amd64:24.04-lg # lg runner: 16 vCPUs, 48GB RAM
timeout-minutes: 40
env:
GRADLE_USER_HOME: /home/admin/_work/.gradle
CACHE_GENERATION: v1 # Increment this to bust the cache (v1, v2, v3, etc.)
outputs:
apk-uploaded: ${{ steps.upload-apk.outcome == 'success' }}
- aab-uploaded: ${{ steps.upload-aab.outcome == 'success' }}
apk-target-path: ${{ steps.determine-target-paths.outputs.apk-target-path }}
test-apk-target-path: ${{ steps.determine-target-paths.outputs.test-apk-target-path }}
- aab-target-path: ${{ steps.determine-target-paths.outputs.aab-target-path }}
artifact_name: ${{ steps.determine-target-paths.outputs.artifact_name }}
steps:
@@ -88,14 +83,12 @@ jobs:
{
echo "apk-target-path=android/app/build/outputs/apk/flask/release"
echo "test-apk-target-path=android/app/build/outputs/apk/androidTest/flask/release"
- echo "aab-target-path=android/app/build/outputs/bundle/flaskRelease"
echo "artifact_name=app-flask-release"
} >> "$GITHUB_OUTPUT"
elif [[ "${{ inputs.build_type }}" == "main" ]]; then
{
echo "apk-target-path=android/app/build/outputs/apk/prod/release"
echo "test-apk-target-path=android/app/build/outputs/apk/androidTest/prod/release"
- echo "aab-target-path=android/app/build/outputs/bundle/prodRelease"
echo "artifact_name=app-prod-release"
} >> "$GITHUB_OUTPUT"
else
@@ -110,7 +103,6 @@ jobs:
path: |
${{ steps.determine-target-paths.outputs.apk-target-path }}/${{ steps.determine-target-paths.outputs.artifact_name }}.apk
${{ steps.determine-target-paths.outputs.test-apk-target-path }}/${{ steps.determine-target-paths.outputs.artifact_name }}-androidTest.apk
- ${{ steps.determine-target-paths.outputs.aab-target-path }}/${{ steps.determine-target-paths.outputs.artifact_name }}.aab
# Include Gradle properties in key to force rebuild when properties change
# Keep the `hashFiles` call for Gradle config in-sync with these steps:
# - "Cache Gradle dependencies"
@@ -241,7 +233,6 @@ jobs:
path: |
${{ steps.determine-target-paths.outputs.apk-target-path }}/${{ steps.determine-target-paths.outputs.artifact_name }}.apk
${{ steps.determine-target-paths.outputs.test-apk-target-path }}/${{ steps.determine-target-paths.outputs.artifact_name }}-androidTest.apk
- ${{ steps.determine-target-paths.outputs.aab-target-path }}/${{ steps.determine-target-paths.outputs.artifact_name }}.aab
# Keep the `hashFiles` call for Gradle config in-sync with these steps:
# - "Check and restore cached APKs if Fingerprint is found"
# - "Cache Gradle dependencies"
@@ -264,13 +255,3 @@ jobs:
path: ${{ steps.determine-target-paths.outputs.test-apk-target-path }}/${{ steps.determine-target-paths.outputs.artifact_name }}-androidTest.apk
retention-days: 7
if-no-files-found: error
-
- - name: Upload Android AAB
- id: upload-aab
- uses: actions/upload-artifact@v4
- with:
- name: ${{ inputs.build_type }}-${{ inputs.metamask_environment }}-release.aab
- path: ${{ steps.determine-target-paths.outputs.aab-target-path }}/${{ steps.determine-target-paths.outputs.artifact_name }}.aab
- retention-days: 7
- if-no-files-found: warn
- continue-on-error: true
diff --git a/.github/workflows/run-e2e-workflow.yml b/.github/workflows/run-e2e-workflow.yml
index fcc4a9e05e62..6ec79db10127 100644
--- a/.github/workflows/run-e2e-workflow.yml
+++ b/.github/workflows/run-e2e-workflow.yml
@@ -56,7 +56,6 @@ jobs:
outputs:
apk-target-path: ${{ steps.determine-target-paths.outputs.apk-target-path }}
test-apk-target-path: ${{ steps.determine-target-paths.outputs.test-apk-target-path }}
- aab-target-path: ${{ steps.determine-target-paths.outputs.aab-target-path }}
env:
PREBUILT_IOS_APP_PATH: artifacts/MetaMask.app
@@ -131,14 +130,12 @@ jobs:
{
echo "apk-target-path=android/app/build/outputs/apk/flask/release"
echo "test-apk-target-path=android/app/build/outputs/apk/androidTest/flask/release"
- echo "aab-target-path=android/app/build/outputs/bundle/flaskRelease"
echo "artifact_name=app-flask-release"
} >> "$GITHUB_OUTPUT"
elif [[ "${{ inputs.build_type }}" == "main" ]]; then
{
echo "apk-target-path=android/app/build/outputs/apk/prod/release"
echo "test-apk-target-path=android/app/build/outputs/apk/androidTest/prod/release"
- echo "aab-target-path=android/app/build/outputs/bundle/prodRelease"
echo "artifact_name=app-prod-release"
} >> "$GITHUB_OUTPUT"
else
@@ -152,7 +149,6 @@ jobs:
echo "🏗 Setting up Android artifacts from build job..."
mkdir -p ${{ steps.determine-target-paths.outputs.apk-target-path }}
mkdir -p ${{ steps.determine-target-paths.outputs.test-apk-target-path }}
- mkdir -p ${{ steps.determine-target-paths.outputs.aab-target-path }}
- name: Download Android build artifacts
if: ${{ inputs.platform == 'android' }}
diff --git a/android/gradle.properties.github b/android/gradle.properties.github
index 768591f08511..f3582e40f7c8 100644
--- a/android/gradle.properties.github
+++ b/android/gradle.properties.github
@@ -1,16 +1,16 @@
# GitHub Actions-specific Gradle settings
# Optimized for E2E builds on GitHub Actions runners
-# JVM configuration - balanced settings to avoid OOM while maintaining performance
-# Using 16GB heap to leave room for parallel workers and native memory
-org.gradle.jvmargs=-Xmx16g -XX:MaxMetaspaceSize=1g -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:+UseStringDeduplication -XX:+OptimizeStringConcat
+# JVM configuration - tuned for 48GB runner to avoid OOM while maintaining performance
+# Heap: 12GB to leave room for Node.js/Metro and native memory
+# ExitOnOutOfMemoryError: fail-fast on OOM for CI
+org.gradle.jvmargs=-Xmx12g -Xms4g -XX:MaxMetaspaceSize=1g -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:+UseStringDeduplication -XX:MaxGCPauseMillis=500 -XX:+ExitOnOutOfMemoryError -Dfile.encoding=UTF-8
# Enable performance optimizations but limit parallelism to prevent OOM
org.gradle.parallel=true
-org.gradle.configureondemand=true
org.gradle.caching=true
-org.gradle.daemon=true
-org.gradle.workers.max=6
+org.gradle.daemon=false
+org.gradle.workers.max=2
org.gradle.vfs.watch=false
# CI-specific optimizations - enabled for GitHub Actions
@@ -54,4 +54,4 @@ hermesEnabled=true
android.disableResourceValidation=true
# Use legacy packaging to compress native libraries in the resulting APK.
-expo.useLegacyPackaging=false
\ No newline at end of file
+expo.useLegacyPackaging=false
diff --git a/app/components/Nav/Main/MainNavigator.js b/app/components/Nav/Main/MainNavigator.js
index 21428861aea7..8eab51c38968 100644
--- a/app/components/Nav/Main/MainNavigator.js
+++ b/app/components/Nav/Main/MainNavigator.js
@@ -1080,10 +1080,43 @@ const MainNavigator = () => {
component={NotificationsModeView}
/>
-
+ ({
+ cardStyle: {
+ transform: [
+ {
+ translateX: current.progress.interpolate({
+ inputRange: [0, 1],
+ outputRange: [layouts.screen.width, 0],
+ }),
+ },
+ ],
+ },
+ }),
+ }}
+ />
({
+ cardStyle: {
+ transform: [
+ {
+ translateX: current.progress.interpolate({
+ inputRange: [0, 1],
+ outputRange: [layouts.screen.width, 0],
+ }),
+ },
+ ],
+ },
+ }),
+ }}
/>
{
component={BridgeModalStack}
options={clearStackNavigatorOptions}
/>
-
+ ({
+ cardStyle: {
+ transform: [
+ {
+ translateX: current.progress.interpolate({
+ inputRange: [0, 1],
+ outputRange: [layouts.screen.width, 0],
+ }),
+ },
+ ],
+ },
+ }),
+ }}
+ />
{
component={DeFiProtocolPositionDetails}
options={{
headerShown: true,
+ animationEnabled: true,
+ cardStyleInterpolator: ({ current, layouts }) => ({
+ cardStyle: {
+ transform: [
+ {
+ translateX: current.progress.interpolate({
+ inputRange: [0, 1],
+ outputRange: [layouts.screen.width, 0],
+ }),
+ },
+ ],
+ },
+ }),
}}
/>
{
diff --git a/app/components/Nav/Main/__snapshots__/MainNavigator.test.tsx.snap b/app/components/Nav/Main/__snapshots__/MainNavigator.test.tsx.snap
index bf4ec4e82eae..a113f1d016d2 100644
--- a/app/components/Nav/Main/__snapshots__/MainNavigator.test.tsx.snap
+++ b/app/components/Nav/Main/__snapshots__/MainNavigator.test.tsx.snap
@@ -131,10 +131,22 @@ exports[`MainNavigator matches rendered snapshot 1`] = `
{
if (name === 'NftController') {
return {
- addNft: jest.fn(),
+ addNfts: jest.fn(),
state: {},
};
}
@@ -52,7 +52,7 @@ describe('NftDetectionControllerInit', () => {
expect(controllerMock).toHaveBeenCalledWith({
messenger: expect.any(Object),
disabled: false,
- addNft: expect.any(Function),
+ addNfts: expect.any(Function),
getNftState: expect.any(Function),
});
});
diff --git a/app/core/Engine/controllers/nft-detection-controller-init.ts b/app/core/Engine/controllers/nft-detection-controller-init.ts
index c60a493bb08f..232341dbc367 100644
--- a/app/core/Engine/controllers/nft-detection-controller-init.ts
+++ b/app/core/Engine/controllers/nft-detection-controller-init.ts
@@ -20,7 +20,7 @@ export const nftDetectionControllerInit: ControllerInitFunction<
const controller = new NftDetectionController({
messenger: controllerMessenger,
disabled: false,
- addNft: nftController.addNft.bind(nftController),
+ addNfts: nftController.addNfts.bind(nftController),
getNftState: () => nftController.state,
});
diff --git a/appwright/tests/performance/login/perps-add-funds.spec.js b/appwright/tests/performance/login/perps-add-funds.spec.js
new file mode 100644
index 000000000000..a604f901e00e
--- /dev/null
+++ b/appwright/tests/performance/login/perps-add-funds.spec.js
@@ -0,0 +1,68 @@
+import { test } from '../../../fixtures/performance-test.js';
+
+import TimerHelper from '../../../utils/TimersHelper.js';
+import LoginScreen from '../../../../wdio/screen-objects/LoginScreen.js';
+import WalletMainScreen from '../../../../wdio/screen-objects/WalletMainScreen.js';
+import TabBarModal from '../../../../wdio/screen-objects/Modals/TabBarModal.js';
+import WalletActionModal from '../../../../wdio/screen-objects/Modals/WalletActionModal.js';
+import PerpsTutorialScreen from '../../../../wdio/screen-objects/PerpsTutorialScreen.js';
+import PerpsMarketListView from '../../../../wdio/screen-objects/PerpsMarketListView.js';
+import PerpsTabView from '../../../../wdio/screen-objects/PerpsTabView.js';
+import PerpsDepositScreen from '../../../../wdio/screen-objects/PerpsDepositScreen.js';
+import { login } from '../../../utils/Flows.js';
+
+async function screensSetup(device) {
+ const screens = [
+ LoginScreen,
+ WalletMainScreen,
+ TabBarModal,
+ WalletActionModal,
+ PerpsTutorialScreen,
+ PerpsMarketListView,
+ PerpsTabView,
+ PerpsDepositScreen,
+ ];
+ screens.forEach((screen) => {
+ screen.device = device;
+ });
+}
+
+/* Scenario 5: Perps add funds */
+test('Perps add funds', async ({ device, performanceTracker }, testInfo) => {
+ test.setTimeout(10 * 60 * 1000); // 10 minutes
+
+ const selectPerpsMainScreenTimer = new TimerHelper(
+ 'Select Perps Main Screen',
+ );
+ const openAddFundsTimer = new TimerHelper('Open Add Funds');
+ const getQuoteTimer = new TimerHelper('Get Quote');
+ await screensSetup(device);
+
+ await login(device);
+ await TabBarModal.tapActionButton();
+
+ // Open Perps Main Screen
+ selectPerpsMainScreenTimer.start();
+ await WalletActionModal.tapPerpsButton();
+ selectPerpsMainScreenTimer.stop();
+ performanceTracker.addTimer(selectPerpsMainScreenTimer);
+
+ // Skip tutorial
+ await PerpsTutorialScreen.tapSkip();
+
+ // Open Add Funds flow
+ openAddFundsTimer.start();
+ await PerpsTutorialScreen.tapAddFunds();
+ await PerpsDepositScreen.isAmountInputVisible();
+ openAddFundsTimer.stop();
+ performanceTracker.addTimer(openAddFundsTimer);
+
+ // Get quote
+ getQuoteTimer.start();
+ await PerpsDepositScreen.fillUsdAmount(5);
+ await PerpsDepositScreen.isAddFundsVisible();
+ await PerpsDepositScreen.isTotalVisible();
+ getQuoteTimer.stop();
+ performanceTracker.addTimer(getQuoteTimer);
+ await performanceTracker.attachToTest(testInfo);
+});
diff --git a/appwright/tests/performance/login/perps-position-management.spec.js b/appwright/tests/performance/login/perps-position-management.spec.js
new file mode 100644
index 000000000000..e98d1cac4e8d
--- /dev/null
+++ b/appwright/tests/performance/login/perps-position-management.spec.js
@@ -0,0 +1,113 @@
+import { test } from '../../../fixtures/performance-test.js';
+
+import TimerHelper from '../../../utils/TimersHelper.js';
+import OnboardingSheet from '../../../../wdio/screen-objects/Onboarding/OnboardingSheet.js';
+import CreatePasswordScreen from '../../../../wdio/screen-objects/Onboarding/CreatePasswordScreen.js';
+import WalletMainScreen from '../../../../wdio/screen-objects/WalletMainScreen.js';
+import TabBarModal from '../../../../wdio/screen-objects/Modals/TabBarModal.js';
+import WalletActionModal from '../../../../wdio/screen-objects/Modals/WalletActionModal.js';
+import PerpsTutorialScreen from '../../../../wdio/screen-objects/PerpsTutorialScreen.js';
+import PerpsMarketListView from '../../../../wdio/screen-objects/PerpsMarketListView.js';
+import PerpsTabView from '../../../../wdio/screen-objects/PerpsTabView.js';
+import PerpsDepositScreen from '../../../../wdio/screen-objects/PerpsDepositScreen.js';
+import PerpsMarketDetailsView from '../../../../wdio/screen-objects/PerpsMarketDetailsView.js';
+import PerpsOrderView from '../../../../wdio/screen-objects/PerpsOrderView.js';
+import PerpsClosePositionView from '../../../../wdio/screen-objects/PerpsClosePositionView.js';
+import PerpsPositionDetailsView from '../../../../wdio/screen-objects/PerpsPositionDetailsView.js';
+import PerpsPositionsView from '../../../../wdio/screen-objects/PerpsPositionsView.js';
+import { login, selectAccountDevice } from '../../../utils/Flows.js';
+
+async function screensSetup(device) {
+ const screens = [
+ OnboardingSheet,
+ CreatePasswordScreen,
+ WalletMainScreen,
+ TabBarModal,
+ WalletActionModal,
+ PerpsTutorialScreen,
+ PerpsMarketListView,
+ PerpsTabView,
+ PerpsDepositScreen,
+ PerpsMarketDetailsView,
+ PerpsOrderView,
+ PerpsClosePositionView,
+ PerpsPositionDetailsView,
+ PerpsPositionsView,
+ ];
+ screens.forEach((screen) => {
+ screen.device = device;
+ });
+}
+
+/* Scenario 5: Perps onboarding + add funds 10 USD ARB.USDC + Open Position + Close Position */
+test('Perps open position and close it', async ({
+ device,
+ performanceTracker,
+}, testInfo) => {
+ test.setTimeout(10 * 60 * 1000); // 10 minutes
+
+ const selectPerpsMainScreenTimer = new TimerHelper(
+ 'Select Perps Main Screen',
+ );
+ const skipTutorialTimer = new TimerHelper('Skip Tutorial');
+ const selectMarketTimer = new TimerHelper('Select Market BTC');
+ const openOrderScreenTimer = new TimerHelper('Open Order Screen');
+ const openPositionTimer = new TimerHelper('Open Long Position');
+ const setLeverageTimer = new TimerHelper('Set Leverage');
+ const closePositionTimer = new TimerHelper('Close Position');
+ await screensSetup(device);
+ await login(device);
+
+ // Perps requires independent account for each device to avoid clashes when running tests in parallel
+ await selectAccountDevice(device, testInfo);
+
+ await TabBarModal.tapActionButton();
+
+ selectPerpsMainScreenTimer.start();
+ await WalletActionModal.tapPerpsButton();
+ selectPerpsMainScreenTimer.stop();
+ performanceTracker.addTimer(selectPerpsMainScreenTimer);
+
+ // Skip tutorial
+ skipTutorialTimer.start();
+ await PerpsTutorialScreen.tapSkip();
+ skipTutorialTimer.stop();
+ performanceTracker.addTimer(skipTutorialTimer);
+
+ selectMarketTimer.start();
+ // Selecting BTC market
+ await PerpsMarketListView.selectMarket('BTC');
+ selectMarketTimer.stop();
+ performanceTracker.addTimer(selectMarketTimer);
+
+ // TODO: Add a check to see if the position is open
+ // If position open, fail the test
+ if (await PerpsPositionDetailsView.isPositionOpen()) {
+ throw new Error('Position is already open');
+ }
+
+ // Open Position
+ openOrderScreenTimer.start();
+ await PerpsMarketDetailsView.tapLongButton();
+ openOrderScreenTimer.stop();
+ performanceTracker.addTimer(openOrderScreenTimer);
+
+ // Set leverage to 40x
+ setLeverageTimer.start();
+ await PerpsOrderView.setLeverage(40);
+ setLeverageTimer.stop();
+ performanceTracker.addTimer(setLeverageTimer);
+
+ openPositionTimer.start();
+ await PerpsOrderView.tapPlaceOrder();
+ openPositionTimer.stop();
+ performanceTracker.addTimer(openPositionTimer);
+
+ // Close Position
+ closePositionTimer.start();
+ await PerpsPositionDetailsView.closePositionWithRetry();
+ closePositionTimer.stop();
+ performanceTracker.addTimer(closePositionTimer);
+
+ await performanceTracker.attachToTest(testInfo);
+});
diff --git a/appwright/tests/performance/onboarding/perps-onboarding.spec.js b/appwright/tests/performance/onboarding/perps-onboarding.spec.js
deleted file mode 100644
index 195a3fc34937..000000000000
--- a/appwright/tests/performance/onboarding/perps-onboarding.spec.js
+++ /dev/null
@@ -1,97 +0,0 @@
-import { test } from '../../../fixtures/performance-test.js';
-
-import TimerHelper from '../../../utils/TimersHelper.js';
-import OnboardingSheet from '../../../../wdio/screen-objects/Onboarding/OnboardingSheet.js';
-import ImportFromSeedScreen from '../../../../wdio/screen-objects/Onboarding/ImportFromSeedScreen.js';
-import CreatePasswordScreen from '../../../../wdio/screen-objects/Onboarding/CreatePasswordScreen.js';
-import WalletMainScreen from '../../../../wdio/screen-objects/WalletMainScreen.js';
-import TabBarModal from '../../../../wdio/screen-objects/Modals/TabBarModal.js';
-import WalletActionModal from '../../../../wdio/screen-objects/Modals/WalletActionModal.js';
-import PerpsTutorialScreen from '../../../../wdio/screen-objects/PerpsTutorialScreen.js';
-import PerpsMarketListView from '../../../../wdio/screen-objects/PerpsMarketListView.js';
-import PerpsTabView from '../../../../wdio/screen-objects/PerpsTabView.js';
-import PerpsDepositScreen from '../../../../wdio/screen-objects/PerpsDepositScreen.js';
-import { onboardingFlowImportSRP } from '../../../utils/Flows.js';
-
-async function screensSetup(device) {
- const screens = [
- OnboardingSheet,
- ImportFromSeedScreen,
- CreatePasswordScreen,
- WalletMainScreen,
- TabBarModal,
- WalletActionModal,
- PerpsTutorialScreen,
- PerpsMarketListView,
- PerpsTabView,
- PerpsDepositScreen,
- ];
- screens.forEach((screen) => {
- screen.device = device;
- });
-}
-
-/* Scenario 5: Perps onboarding + add funds 10 USD ARB.USDC */
-// TODO: Fix this test: https://consensyssoftware.atlassian.net/browse/MMQA-1190
-test.skip('Perps onboarding + add funds 10 USD ARB.USDC', async ({
- device,
- performanceTracker,
-}, testInfo) => {
- test.setTimeout(10 * 60 * 1000); // 10 minutes
- await screensSetup(device);
-
- await onboardingFlowImportSRP(device, process.env.TEST_SRP_3);
- await WalletMainScreen.isTokenVisible('ETH');
- await TabBarModal.tapTradeButton();
-
- // Open Perps tab
- await TimerHelper.withTimer(
- performanceTracker,
- 'Open Perps tab',
- async () => {
- await PerpsTabView.tapPerpsTab();
- await PerpsTutorialScreen.expectFirstScreenVisible();
- },
- );
- // Open Tutorial flow
- await PerpsTutorialScreen.flowTapContinueTutorial(6);
-
- // Open Add Funds flow
- await TimerHelper.withTimer(
- performanceTracker,
- 'Open Add Funds',
- async () => {
- await PerpsTutorialScreen.tapAddFunds();
- await PerpsDepositScreen.isAmountInputVisible();
- },
- );
- // Select pay token
- await TimerHelper.withTimer(
- performanceTracker,
- 'Select pay token - 1 click USDC.arb',
- async () => {
- await PerpsDepositScreen.tapPayWith();
- await PerpsDepositScreen.selectPayTokenByText('USDC');
- },
- );
-
- // Fill amount
- await TimerHelper.withTimer(
- performanceTracker,
- 'Fill amount - 2 USD',
- async () => {
- await PerpsDepositScreen.fillUsdAmount('2');
- },
- );
-
- // Cancel
- await TimerHelper.withTimer(
- performanceTracker,
- 'Cancel - 1 click',
- async () => {
- await PerpsDepositScreen.checkTransactionFeeIsVisible();
- },
- );
-
- await performanceTracker.attachToTest(testInfo);
-});
diff --git a/appwright/utils/Flows.js b/appwright/utils/Flows.js
index 1ecd29d43b5b..e1696f31bc28 100644
--- a/appwright/utils/Flows.js
+++ b/appwright/utils/Flows.js
@@ -19,6 +19,55 @@ import AppwrightGestures from '../../e2e/framework/AppwrightGestures.js';
import AppwrightSelectors from '../../e2e/framework/AppwrightSelectors.js';
import { expect } from 'appwright';
+export async function selectAccountDevice(device, testInfo) {
+ // Access device name from testInfo.project.use.device
+ const deviceName = testInfo.project.use.device.name;
+ console.log(`📱 Device executing the test: ${deviceName}`);
+
+ let accountName;
+
+ // Define account mapping based on device name
+ // The device names must match those in appwright.config.ts or device-matrix.json
+ switch (deviceName) {
+ case 'Samsung Galaxy S23 Ultra':
+ accountName = 'Account 3';
+ break;
+ case 'Google Pixel 8 Pro':
+ console.log(
+ `🔄 Account 1 is selected by default in the app for device: ${deviceName}`,
+ );
+ return;
+ case 'iPhone 16 Pro Max':
+ accountName = 'Account 4';
+ break;
+ case 'iPhone 12':
+ accountName = 'Account 5';
+ break;
+ default:
+ console.log(
+ `🔄 Account 1 is selected by default in the app for device: ${deviceName}`,
+ );
+ return;
+ }
+ // Account 2 is called stable and not used in this function
+
+ console.log(
+ `🔄 Switching to account: ${accountName} for device: ${deviceName}`,
+ );
+
+ // Set device for screen objects
+ WalletMainScreen.device = device;
+ AccountListComponent.device = device;
+
+ // Perform account switch
+ await WalletMainScreen.tapIdenticon();
+ await AccountListComponent.isComponentDisplayed();
+ await AccountListComponent.tapOnAccountByName(accountName);
+
+ // Verify we are back on main screen (tapping account usually closes modal)
+ await WalletMainScreen.isMainWalletViewVisible();
+}
+
export async function onboardingFlowImportSRP(device, srp) {
WelcomeScreen.device = device;
TermOfUseScreen.device = device;
diff --git a/appwright/utils/TimersHelper.js b/appwright/utils/TimersHelper.js
index 44620eea3416..2fb62ff5a82d 100644
--- a/appwright/utils/TimersHelper.js
+++ b/appwright/utils/TimersHelper.js
@@ -56,22 +56,6 @@ class TimerHelper {
get id() {
return this._id;
}
-
- // Runs the provided async function while timing it, and automatically
- // registers the timer with the given performanceTracker.
- // Usage:
- // await TimerHelper.withTimer(performanceTracker, 'Step name', async () => { /* ... */ });
- static async withTimer(performanceTracker, id, fn) {
- const timer = new TimerHelper(id);
- timer.start();
- try {
- const result = await fn();
- return result;
- } finally {
- timer.stop();
- performanceTracker.addTimer(timer);
- }
- }
}
export default TimerHelper;
diff --git a/e2e/framework/services/providers/browserstack/BrowserStackConfigBuilder.ts b/e2e/framework/services/providers/browserstack/BrowserStackConfigBuilder.ts
index e67d6c89acba..c9c00497e75a 100644
--- a/e2e/framework/services/providers/browserstack/BrowserStackConfigBuilder.ts
+++ b/e2e/framework/services/providers/browserstack/BrowserStackConfigBuilder.ts
@@ -64,6 +64,7 @@ export class BrowserStackConfigBuilder {
appProfiling: 'true',
selfHeal: 'true',
networkProfile: '4g-lte-advanced-good',
+ geoLocation: 'FR',
},
'appium:autoGrantPermissions': true,
'appium:app': appBsUrl,
diff --git a/package.json b/package.json
index 45263e0f298a..2288b34da37b 100644
--- a/package.json
+++ b/package.json
@@ -198,7 +198,7 @@
"@metamask/address-book-controller": "^7.0.0",
"@metamask/app-metadata-controller": "^2.0.0",
"@metamask/approval-controller": "^8.0.0",
- "@metamask/assets-controllers": "^93.0.0",
+ "@metamask/assets-controllers": "^94.0.0",
"@metamask/base-controller": "^9.0.0",
"@metamask/bitcoin-wallet-snap": "^1.8.0",
"@metamask/bridge-controller": "patch:@metamask/bridge-controller@npm%3A61.0.0#~/.yarn/patches/@metamask-bridge-controller-npm-61.0.0-8c413c463f.patch",
diff --git a/scripts/build.sh b/scripts/build.sh
index 54827d144511..7c0c54a341fd 100755
--- a/scripts/build.sh
+++ b/scripts/build.sh
@@ -547,6 +547,8 @@ generateAndroidBinary() {
local reactNativeArchitecturesArg=""
# Define Test build type arg
local testBuildTypeArg=""
+ # Define Gradle debug flags
+ local gradleDebugFlags=""
# Check if configuration is valid
if [ "$configuration" != "Debug" ] && [ "$configuration" != "Release" ] ; then
@@ -572,14 +574,19 @@ generateAndroidBinary() {
if [ "$METAMASK_ENVIRONMENT" = "e2e" ] ; then
# Only build for x86_64 for E2E builds
reactNativeArchitecturesArg="-PreactNativeArchitectures=x86_64"
+ # Enable Gradle debugging flags for E2E builds to investigate Daemon disappearance issues
+ gradleDebugFlags="--stacktrace --info"
+ echo "📊 E2E build: Enabling Gradle debugging flags (--stacktrace --info)"
fi
fi
# Generate Android APKs
echo "Generating Android binary for ($flavor) flavor with ($configuration) configuration"
- ./gradlew $assembleApkTask $assembleTestApkTask $testBuildTypeArg $reactNativeArchitecturesArg
+ ./gradlew $assembleApkTask $assembleTestApkTask $testBuildTypeArg $reactNativeArchitecturesArg $gradleDebugFlags
- if [ "$configuration" = "Release" ] ; then
+ # Skip AAB bundle for E2E environments - AAB cannot be installed on emulators
+ # and is only needed for Play Store distribution
+ if [ "$configuration" = "Release" ] && [ "$METAMASK_ENVIRONMENT" != "e2e" ] ; then
# Generate AAB bundle (not needed for E2E)
bundleConfiguration="bundle${flavor}Release"
echo "Generating AAB bundle for ($flavor) flavor with ($configuration) configuration"
diff --git a/wdio/screen-objects/BridgeScreen.js b/wdio/screen-objects/BridgeScreen.js
index c29f50ece017..cc47d4da7b34 100644
--- a/wdio/screen-objects/BridgeScreen.js
+++ b/wdio/screen-objects/BridgeScreen.js
@@ -7,6 +7,7 @@ import { QuoteViewSelectorText } from '../../e2e/selectors/swaps/QuoteView.selec
import Selectors from '../helpers/Selectors.js';
import { LoginViewSelectors } from '../../e2e/selectors/wallet/LoginView.selectors';
import { splitAmountIntoDigits } from 'appwright/utils/Utils.js';
+import AmountScreen from './AmountScreen';
class BridgeScreen {
@@ -64,28 +65,8 @@ class BridgeScreen {
}
async enterSourceTokenAmount(amount) {
- // Split amount into digits
- const digits = splitAmountIntoDigits(amount);
- console.log('Amount digits:', digits);
- for (const digit of digits) {
- if (AppwrightSelectors.isAndroid(this._device)) {
- if (digit != '.') {
- const numberKey = await AppwrightSelectors.getElementByXpath(this._device, `//android.widget.Button[@content-desc='${digit}']`)
- await appwrightExpect(numberKey).toBeVisible({ timeout: 30000 });
- await AppwrightGestures.tap(numberKey);
- }
- else {
- const numberKey = await AppwrightSelectors.getElementByXpath(this._device, `//android.view.View[@text="."]`);
- await appwrightExpect(numberKey).toBeVisible({ timeout: 30000 });
- await AppwrightGestures.tap(numberKey);
- }
- }
- else {
- const numberKey = await AppwrightSelectors.getElementByXpath(this._device, `//XCUIElementTypeButton[@name="${digit}"]`);
- await appwrightExpect(numberKey).toBeVisible({ timeout: 30000 });
- await AppwrightGestures.tap(numberKey);
- }
- }
+ AmountScreen.device = this._device;
+ await AmountScreen.enterAmount(amount);
}
async selectNetworkAndTokenTo(network, token) {
diff --git a/wdio/screen-objects/PerpsClosePositionView.js b/wdio/screen-objects/PerpsClosePositionView.js
new file mode 100644
index 000000000000..bcbe90ba097f
--- /dev/null
+++ b/wdio/screen-objects/PerpsClosePositionView.js
@@ -0,0 +1,24 @@
+import AppwrightSelectors from '../../e2e/framework/AppwrightSelectors';
+import AppwrightGestures from '../../e2e/framework/AppwrightGestures';
+
+class PerpsClosePositionView {
+ get device() {
+ return this._device;
+ }
+
+ set device(device) {
+ this._device = device;
+ }
+
+ get confirmButton() {
+ return AppwrightSelectors.getElementByID(this._device, 'close-position-confirm-button');
+ }
+
+ async tapConfirmButton() {
+ await AppwrightGestures.tap(this.confirmButton);
+ }
+}
+
+export default new PerpsClosePositionView();
+
+
diff --git a/wdio/screen-objects/PerpsDepositScreen.js b/wdio/screen-objects/PerpsDepositScreen.js
index 8edeb2180df0..881ae6a1c177 100644
--- a/wdio/screen-objects/PerpsDepositScreen.js
+++ b/wdio/screen-objects/PerpsDepositScreen.js
@@ -1,7 +1,7 @@
import AppwrightSelectors from '../../e2e/framework/AppwrightSelectors';
import AppwrightGestures from '../../e2e/framework/AppwrightGestures';
import AmountScreen from './AmountScreen';
-import { expect } from 'appwright';
+import { expect as appwrightExpect } from 'appwright';
class PerpsDepositScreen {
@@ -26,6 +26,10 @@ class PerpsDepositScreen {
return AppwrightSelectors.getElementByID(this._device, 'custom-amount-input');
}
+ get backButton() {
+ return AppwrightSelectors.getElementByID(this._device, 'Add funds-navbar-back-button');
+ }
+
get payWithButton() {
return AppwrightSelectors.getElementByCatchAll(
this._device,
@@ -33,9 +37,17 @@ class PerpsDepositScreen {
);
}
+ get addFundsButton() {
+ return AppwrightSelectors.getElementByText(this._device, 'Add funds');
+ }
+
+ get totalText() {
+ return AppwrightSelectors.getElementByText(this._device, 'Total');
+ }
+
async isAmountInputVisible() {
const input = await this.amountInput;
- await input.isVisible({ timeout: 15000 });
+ await appwrightExpect(input).toBeVisible();
}
async selectPayTokenByText(token) {
@@ -61,9 +73,23 @@ class PerpsDepositScreen {
await AppwrightGestures.tap(this.cancelButton); // Use static tap method with retry logic
}
+ async tapBackButton() {
+ await AppwrightGestures.tap(this.backButton); // Use static tap method with retry logic
+ }
+
async checkTransactionFeeIsVisible() {
const transactionFee = await AppwrightSelectors.getElementByID(this._device, 'bridge-fee-row');
- await expect(transactionFee).toBeVisible();
+ await appwrightExpect(transactionFee).toBeVisible();
+ }
+
+ async isAddFundsVisible() {
+ const addFunds = await this.addFundsButton;
+ await appwrightExpect(addFunds).toBeVisible();
+ }
+
+ async isTotalVisible() {
+ const total = await AppwrightSelectors.getElementByText(this._device, 'Total');
+ await appwrightExpect(total).toBeVisible();
}
}
diff --git a/wdio/screen-objects/PerpsMarketDetailsView.js b/wdio/screen-objects/PerpsMarketDetailsView.js
new file mode 100644
index 000000000000..f3f85ef828b1
--- /dev/null
+++ b/wdio/screen-objects/PerpsMarketDetailsView.js
@@ -0,0 +1,31 @@
+import AppwrightSelectors from '../../e2e/framework/AppwrightSelectors';
+import AppwrightGestures from '../../e2e/framework/AppwrightGestures';
+
+class PerpsMarketDetailsView {
+ get device() {
+ return this._device;
+ }
+
+ set device(device) {
+ this._device = device;
+ }
+
+ get longButton() {
+ return AppwrightSelectors.getElementByID(this._device, 'perps-market-details-long-button');
+ }
+
+ get shortButton() {
+ return AppwrightSelectors.getElementByID(this._device, 'perps-market-details-short-button');
+ }
+
+ async tapLongButton() {
+ await AppwrightGestures.tap(this.longButton);
+ }
+
+ async tapShortButton() {
+ await AppwrightGestures.tap(this.shortButton);
+ }
+}
+
+export default new PerpsMarketDetailsView();
+
diff --git a/wdio/screen-objects/PerpsMarketListView.js b/wdio/screen-objects/PerpsMarketListView.js
index 9ce4137e1d14..4b5d92b47236 100644
--- a/wdio/screen-objects/PerpsMarketListView.js
+++ b/wdio/screen-objects/PerpsMarketListView.js
@@ -1,5 +1,6 @@
import AppwrightSelectors from '../../e2e/framework/AppwrightSelectors';
import AppwrightGestures from '../../e2e/framework/AppwrightGestures';
+import { expect as appwrightExpect } from 'appwright';
class PerpsMarketListView {
@@ -22,15 +23,18 @@ class PerpsMarketListView {
async isHeaderVisible() {
const header = await this.listHeader;
- await header.isVisible({ timeout: 10000 });
+ await appwrightExpect(header).toBeVisible({ timeout: 10000 });
}
async tapBackButtonMarketList() {
await AppwrightGestures.tap(this.backButtonMarketList); // Use static tap method with retry logic
}
+
+ async selectMarket(symbol) {
+ // ID format from Perps.selectors.ts: `perps-market-row-item-${symbol}`
+ const marketRow = await AppwrightSelectors.getElementByID(this._device, `perps-market-row-item-${symbol}`);
+ await AppwrightGestures.tap(marketRow);
+ }
}
export default new PerpsMarketListView();
-
-
-
diff --git a/wdio/screen-objects/PerpsOrderView.js b/wdio/screen-objects/PerpsOrderView.js
new file mode 100644
index 000000000000..5df4e2eec74c
--- /dev/null
+++ b/wdio/screen-objects/PerpsOrderView.js
@@ -0,0 +1,64 @@
+import AppwrightSelectors from '../../e2e/framework/AppwrightSelectors';
+import AppwrightGestures from '../../e2e/framework/AppwrightGestures';
+import AmountScreen from './AmountScreen';
+import { expect as appwrightExpect } from 'appwright';
+import { splitAmountIntoDigits } from 'appwright/utils/Utils';
+import PerpsPositionDetailsView from './PerpsPositionDetailsView';
+
+class PerpsOrderView {
+ get device() {
+ return this._device;
+ }
+
+ set device(device) {
+ this._device = device;
+ }
+
+ get placeOrderButton() {
+ return AppwrightSelectors.getElementByID(this._device, 'perps-order-view-place-order-button');
+ }
+
+ get keypad() {
+ return AppwrightSelectors.getElementByID(this._device, 'perps-order-view-keypad');
+ }
+
+ get leverageButton() {
+ return AppwrightSelectors.getElementByText(this._device, 'Leverage');
+ }
+
+ async leverageOption(leverage) {
+ return AppwrightSelectors.getElementByText(this._device, `${leverage}x`);
+ }
+
+ async confirmLeverageButton(leverage) {
+ return AppwrightSelectors.getElementByText(this._device, `Set ${leverage}x`);
+ }
+
+ async tapPlaceOrder() {
+ await AppwrightGestures.tap(this.placeOrderButton);
+ appwrightExpect(await PerpsPositionDetailsView.isPositionOpen()).toBe(true);
+ }
+
+ // Reuse logic from AmountScreen.js for Keypad interaction
+ async tapNumberKey(digit) {
+ AmountScreen.device = this._device;
+ await AmountScreen.tapNumberKey(digit);
+ }
+
+ async enterAmount(text) {
+ // Since PerpsOrderView likely only supports keypad input for amount in the UI flow being tested
+ const digits = splitAmountIntoDigits(text);
+ for (const digit of digits) {
+ console.log('Tapping digit:', digit);
+ await this.tapNumberKey(digit);
+ }
+ }
+
+ async setLeverage(leverage) {
+ await AppwrightGestures.tap(this.leverageButton);
+ await AppwrightGestures.tap(await this.leverageOption(leverage));
+ await AppwrightGestures.tap(await this.confirmLeverageButton(leverage));
+ }
+}
+
+export default new PerpsOrderView();
\ No newline at end of file
diff --git a/wdio/screen-objects/PerpsPositionDetailsView.js b/wdio/screen-objects/PerpsPositionDetailsView.js
new file mode 100644
index 000000000000..07fdc3b2cce4
--- /dev/null
+++ b/wdio/screen-objects/PerpsPositionDetailsView.js
@@ -0,0 +1,56 @@
+import AppwrightSelectors from '../../e2e/framework/AppwrightSelectors';
+import AppwrightGestures from '../../e2e/framework/AppwrightGestures';
+import Utilities from '../../e2e/framework/Utilities';
+
+class PerpsPositionDetailsView {
+ get device() {
+ return this._device;
+ }
+
+ set device(device) {
+ this._device = device;
+ }
+
+ get closePositionButton() {
+ return AppwrightSelectors.getElementByID(this._device, 'perps-market-details-close-button');
+ }
+
+ get positionOpenButton() {
+ return AppwrightSelectors.getElementByID(this._device, 'position-open-button');
+ }
+
+ get confirmClosePositionButton() {
+ return AppwrightSelectors.getElementByID(this._device, 'close-position-confirm-button');
+ }
+
+ async tapClosePositionButton() {
+ await AppwrightGestures.tap(this.closePositionButton);
+ await AppwrightGestures.tap(this.confirmClosePositionButton);
+ }
+
+ async isPositionOpen() {
+ const closePositionButton = await this.closePositionButton;
+ return await closePositionButton.isVisible();
+ }
+
+ async closePositionWithRetry() {
+ await Utilities.executeWithRetry(async () => {
+ if (await this.isPositionOpen()) {
+ await this.tapClosePositionButton();
+ const closePositionButton = await this.closePositionButton;
+ await AppwrightSelectors.waitForElementToDisappear(
+ closePositionButton,
+ 'Close Position Button',
+ 5000,
+ );
+ }
+ }, {
+ description: 'close position',
+ elemDescription: 'Close Position Button',
+ });
+ }
+}
+
+export default new PerpsPositionDetailsView();
+
+
diff --git a/wdio/screen-objects/PerpsPositionsView.js b/wdio/screen-objects/PerpsPositionsView.js
new file mode 100644
index 000000000000..b9454c4dd65b
--- /dev/null
+++ b/wdio/screen-objects/PerpsPositionsView.js
@@ -0,0 +1,23 @@
+import AppwrightSelectors from '../../e2e/framework/AppwrightSelectors';
+import AppwrightGestures from '../../e2e/framework/AppwrightGestures';
+
+class PerpsPositionsView {
+ get device() {
+ return this._device;
+ }
+
+ set device(device) {
+ this._device = device;
+ }
+
+ get positionItem() {
+ return AppwrightSelectors.getElementByID(this._device, 'perps-positions-item');
+ }
+
+ async tapPositionItem() {
+ await AppwrightGestures.tap(this.positionItem);
+ }
+}
+
+export default new PerpsPositionsView();
+
diff --git a/wdio/screen-objects/PerpsTabView.js b/wdio/screen-objects/PerpsTabView.js
index 27e5547167e7..7ecbda41d3dc 100644
--- a/wdio/screen-objects/PerpsTabView.js
+++ b/wdio/screen-objects/PerpsTabView.js
@@ -1,5 +1,6 @@
import AppwrightSelectors from '../../e2e/framework/AppwrightSelectors';
import AppwrightGestures from '../../e2e/framework/AppwrightGestures';
+import { expect as appwrightExpect } from 'appwright';
class PerpsTabView {
@@ -13,7 +14,7 @@ class PerpsTabView {
}
get perpsTabButton() {
- return AppwrightSelectors.getElementByID(this._device, 'wallet-perps-action');
+ return AppwrightSelectors.getElementByID(this._device, 'undefined-tab-1');
}
get addFundsButton() {
@@ -24,17 +25,25 @@ class PerpsTabView {
return AppwrightSelectors.getElementByID(this._device, 'perps-start-trading-button');
}
+ get startTradingButton() {
+ return AppwrightSelectors.getElementByText(this._device, 'Start trading');
+ }
+
async tapPerpsTab() {
await AppwrightGestures.tap(this.perpsTabButton); // Use static tap method with retry logic
}
+ async tapStartTradingButton() {
+ await AppwrightGestures.tap(this.startTradingButton); // Use static tap method with retry logic
+ }
+
async tapAddFunds() {
await AppwrightGestures.tap(this.addFundsButton); // Use static tap method with retry logic
}
async tapOnboardingButton() {
const button = await this.onboardingButton;
- await button.isVisible({ timeout: 5000 });
+ await appwrightExpect(button).toBeVisible({ timeout: 5000 });
await AppwrightGestures.tap(this.onboardingButton); // Use static tap method with retry logic
}
}
diff --git a/yarn.lock b/yarn.lock
index 7ff52b87dc71..fb0f467e13a8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7132,7 +7132,7 @@ __metadata:
languageName: node
linkType: hard
-"@metamask/assets-controllers@npm:^93.0.0, @metamask/assets-controllers@npm:^93.1.0":
+"@metamask/assets-controllers@npm:^93.1.0":
version: 93.1.0
resolution: "@metamask/assets-controllers@npm:93.1.0"
dependencies:
@@ -7186,6 +7186,60 @@ __metadata:
languageName: node
linkType: hard
+"@metamask/assets-controllers@npm:^94.0.0":
+ version: 94.0.0
+ resolution: "@metamask/assets-controllers@npm:94.0.0"
+ dependencies:
+ "@ethereumjs/util": "npm:^9.1.0"
+ "@ethersproject/abi": "npm:^5.7.0"
+ "@ethersproject/address": "npm:^5.7.0"
+ "@ethersproject/bignumber": "npm:^5.7.0"
+ "@ethersproject/contracts": "npm:^5.7.0"
+ "@ethersproject/providers": "npm:^5.7.0"
+ "@metamask/abi-utils": "npm:^2.0.3"
+ "@metamask/account-tree-controller": "npm:^4.0.0"
+ "@metamask/accounts-controller": "npm:^35.0.0"
+ "@metamask/approval-controller": "npm:^8.0.0"
+ "@metamask/base-controller": "npm:^9.0.0"
+ "@metamask/contract-metadata": "npm:^2.4.0"
+ "@metamask/controller-utils": "npm:^11.16.0"
+ "@metamask/core-backend": "npm:^5.0.0"
+ "@metamask/eth-query": "npm:^4.0.0"
+ "@metamask/keyring-api": "npm:^21.0.0"
+ "@metamask/keyring-controller": "npm:^25.0.0"
+ "@metamask/messenger": "npm:^0.3.0"
+ "@metamask/metamask-eth-abis": "npm:^3.1.1"
+ "@metamask/multichain-account-service": "npm:^4.0.1"
+ "@metamask/network-controller": "npm:^27.0.0"
+ "@metamask/permission-controller": "npm:^12.1.1"
+ "@metamask/phishing-controller": "npm:^16.1.0"
+ "@metamask/polling-controller": "npm:^16.0.0"
+ "@metamask/preferences-controller": "npm:^22.0.0"
+ "@metamask/profile-sync-controller": "npm:^27.0.0"
+ "@metamask/rpc-errors": "npm:^7.0.2"
+ "@metamask/snaps-controllers": "npm:^14.0.1"
+ "@metamask/snaps-sdk": "npm:^9.0.0"
+ "@metamask/snaps-utils": "npm:^11.0.0"
+ "@metamask/transaction-controller": "npm:^62.6.0"
+ "@metamask/utils": "npm:^11.8.1"
+ "@types/bn.js": "npm:^5.1.5"
+ "@types/uuid": "npm:^8.3.0"
+ async-mutex: "npm:^0.5.0"
+ bitcoin-address-validation: "npm:^2.2.3"
+ bn.js: "npm:^5.2.1"
+ immer: "npm:^9.0.6"
+ lodash: "npm:^4.17.21"
+ multiformats: "npm:^9.9.0"
+ reselect: "npm:^5.1.1"
+ single-call-balance-checker-abi: "npm:^1.0.0"
+ uuid: "npm:^8.3.2"
+ peerDependencies:
+ "@metamask/providers": ^22.0.0
+ webextension-polyfill: ^0.10.0 || ^0.11.0 || ^0.12.0
+ checksum: 10/86324e75db4adffbfc7c4f93138de25242360578e3aa0fd26f78ef84d4390fb04042cb1582d64139754de60f315a9b8a8458850c65b0b764b95eb6435f3bb054
+ languageName: node
+ linkType: hard
+
"@metamask/auth-network-utils@npm:^0.3.0":
version: 0.3.1
resolution: "@metamask/auth-network-utils@npm:0.3.1"
@@ -8394,19 +8448,23 @@ __metadata:
languageName: node
linkType: hard
-"@metamask/multichain-account-service@npm:^4.0.0":
- version: 4.0.0
- resolution: "@metamask/multichain-account-service@npm:4.0.0"
+"@metamask/multichain-account-service@npm:^4.0.0, @metamask/multichain-account-service@npm:^4.0.1":
+ version: 4.0.1
+ resolution: "@metamask/multichain-account-service@npm:4.0.1"
dependencies:
"@ethereumjs/util": "npm:^9.1.0"
+ "@metamask/accounts-controller": "npm:^35.0.0"
"@metamask/base-controller": "npm:^9.0.0"
+ "@metamask/error-reporting-service": "npm:^3.0.0"
"@metamask/eth-snap-keyring": "npm:^18.0.0"
"@metamask/key-tree": "npm:^10.1.1"
"@metamask/keyring-api": "npm:^21.0.0"
+ "@metamask/keyring-controller": "npm:^25.0.0"
"@metamask/keyring-internal-api": "npm:^9.0.0"
"@metamask/keyring-snap-client": "npm:^8.0.0"
"@metamask/keyring-utils": "npm:^3.1.0"
"@metamask/messenger": "npm:^0.3.0"
+ "@metamask/snaps-controllers": "npm:^14.0.1"
"@metamask/snaps-sdk": "npm:^9.0.0"
"@metamask/snaps-utils": "npm:^11.0.0"
"@metamask/superstruct": "npm:^3.1.0"
@@ -8414,13 +8472,9 @@ __metadata:
async-mutex: "npm:^0.5.0"
peerDependencies:
"@metamask/account-api": ^0.12.0
- "@metamask/accounts-controller": ^35.0.0
- "@metamask/error-reporting-service": ^3.0.0
- "@metamask/keyring-controller": ^25.0.0
"@metamask/providers": ^22.0.0
- "@metamask/snaps-controllers": ^14.0.0
webextension-polyfill: ^0.10.0 || ^0.11.0 || ^0.12.0
- checksum: 10/b5e5cb6f7d4a8e077935a2a47e230f788ada79cc25829c781e3a26f9b80acaa93980f66bb9d931498400ae3873882e2040066cc83bdea36735029dacb39ad7db
+ checksum: 10/a664bed3b1f54c27c26f0eec2e07b666dbc09d80fb6cad6f081fecc40b6029971988cad0a9cc010ce97fea83b962d31809aae21e37792c19e94dce509eeb98e2
languageName: node
linkType: hard
@@ -34239,7 +34293,7 @@ __metadata:
"@metamask/address-book-controller": "npm:^7.0.0"
"@metamask/app-metadata-controller": "npm:^2.0.0"
"@metamask/approval-controller": "npm:^8.0.0"
- "@metamask/assets-controllers": "npm:^93.0.0"
+ "@metamask/assets-controllers": "npm:^94.0.0"
"@metamask/auto-changelog": "npm:^5.3.0"
"@metamask/base-controller": "npm:^9.0.0"
"@metamask/bitcoin-wallet-snap": "npm:^1.8.0"