From f8c4eb754760ba7d8189c1890be6a84eba26cc1f Mon Sep 17 00:00:00 2001 From: "runway-github[bot]" <73448015+runway-github[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 20:44:12 +0000 Subject: [PATCH] temp+chore: bump nanoid package to 3.3.8 (#12725) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Resolution created to bump nanoid to version 3.3.8! It can be removed once this controllers are bumped: https://github.com/MetaMask/core/pull/5073 ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [ ] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- .../scripts/bitrise/run-bitrise-e2e-check.ts | 238 ++++++------ CHANGELOG.md | 74 ++++ android/app/build.gradle | 4 +- app/actions/notification/helpers/index.ts | 2 +- .../Asset/__snapshots__/index.test.js.snap | 39 ++ app/components/Views/Asset/index.js | 40 +- app/components/Views/Asset/index.test.js | 56 ++- .../NetworkConnectMultiSelector.tsx | 14 +- app/reducers/swaps/index.js | 20 +- app/store/migrations/index.ts | 2 - .../logs/__snapshots__/index.test.ts.snap | 356 ------------------ app/util/logs/index.test.ts | 73 +--- app/util/logs/index.ts | 4 - app/util/notifications/constants/urls.ts | 11 + .../hooks/usePushNotifications.ts | 2 +- app/util/notifications/methods/common.test.ts | 203 +--------- app/util/notifications/methods/common.ts | 190 +++++----- bitrise.yml | 8 +- e2e/specs/multichain/asset-list.spec.js | 18 +- ...tem-connect-to-non-permitted-chain.spec.js | 4 +- ...on-system-update-chain-permissions.spec.js | 6 +- ios/MetaMask.xcodeproj/project.pbxproj | 24 +- package.json | 4 +- 23 files changed, 491 insertions(+), 901 deletions(-) delete mode 100644 app/util/logs/__snapshots__/index.test.ts.snap create mode 100644 app/util/notifications/constants/urls.ts diff --git a/.github/scripts/bitrise/run-bitrise-e2e-check.ts b/.github/scripts/bitrise/run-bitrise-e2e-check.ts index 123920a036d..62935ca966d 100644 --- a/.github/scripts/bitrise/run-bitrise-e2e-check.ts +++ b/.github/scripts/bitrise/run-bitrise-e2e-check.ts @@ -8,107 +8,11 @@ import { } from '../scripts.types'; import axios from 'axios'; -let octokitInstance: InstanceType | null = null; -let owner: string; -let repo: string; - main().catch((error: Error): void => { console.error(error); process.exit(1); }); - - -function getOctokitInstance(): InstanceType { - if (!octokitInstance) { - const githubToken = process.env.GITHUB_TOKEN; - if (!githubToken) { - throw new Error("GitHub token is not set in the environment variables"); - } - octokitInstance = getOctokit(githubToken); - } - return octokitInstance; -} - -async function upsertStatusCheck( - statusCheckName: string, - commitHash: string, - status: StatusCheckStatusType, - conclusion: CompletedConclusionType | undefined, - summary: string -): Promise { - const octokit = getOctokitInstance(); - - // List existing checks - const listResponse = await octokit.rest.checks.listForRef({ - owner, - repo, - ref: commitHash, - }); - - if (listResponse.status !== 200) { - core.setFailed( - `Failed to list checks for commit ${commitHash}, received status code ${listResponse.status}`, - ); - process.exit(1); - } - - const existingCheck = listResponse.data.check_runs.find(check => check.name === statusCheckName); - - if (existingCheck) { - console.log(`Check already exists: ${existingCheck.name}, updating...`); - // Update the existing check - const updateCheckResponse = await octokit.rest.checks.update({ - owner, - repo, - check_run_id: existingCheck.id, - name: statusCheckName, - status: status, - conclusion: conclusion, - output: { - title: `${statusCheckName} Status Check`, - summary: summary, - }, - }); - - if (updateCheckResponse.status !== 200) { - core.setFailed( - `Failed to update '${statusCheckName}' check with status ${status} for commit ${commitHash}, got status code ${updateCheckResponse.status}`, - ); - process.exit(1); - } - - console.log(`Updated existing check: ${statusCheckName} with id ${existingCheck.id} & status ${status} for commit ${commitHash}`); - - - - } else { - console.log(`Check does not exist: ${statusCheckName}, creating...`); - // Create a new status check - const createCheckResponse = await octokit.rest.checks.create({ - owner, - repo, - name: statusCheckName, - head_sha: commitHash, - status: status, - conclusion: conclusion, - started_at: new Date().toISOString(), - output: { - title: `${statusCheckName} Status Check`, - summary: summary, - }, - }); - - if (createCheckResponse.status !== 201) { - core.setFailed( - `Failed to create '${statusCheckName}' check with status ${status} for commit ${commitHash}, got status code ${createCheckResponse.status}`, - ); - process.exit(1); - } - - console.log(`Created check: ${statusCheckName} with id ${createCheckResponse.data.id} & status ${status} for commit ${commitHash}`); - } -} // Determine whether E2E should run and provide the associated reason function shouldRunBitriseE2E(antiLabel: boolean, hasSmokeTestLabel: boolean, isDocs: boolean, isFork: boolean, isMergeQueue: boolean): [boolean, string] { @@ -139,11 +43,7 @@ async function main(): Promise { const e2ePipeline = process.env.E2E_PIPELINE; const workflowName = process.env.WORKFLOW_NAME; const triggerAction = context.payload.action as PullRequestTriggerType; - // Assuming context.issue comes populated with owner and repo, as typical with GitHub Actions - const { owner: contextOwner, repo: contextRepo, number: pullRequestNumber } = context.issue; - owner = contextOwner; - repo = contextRepo; - + const { owner, repo, number: pullRequestNumber } = context.issue; const removeAndApplyInstructions = `Remove and re-apply the "${e2eLabel}" label to trigger a E2E smoke test on Bitrise.`; const mergeFromMainCommitMessagePrefix = `Merge branch 'main' into`; const pullRequestLink = `https://github.com/MetaMask/metamask-mobile/pull/${pullRequestNumber}`; @@ -180,7 +80,7 @@ async function main(): Promise { const mqCommitHash = context.payload?.merge_group?.head_sha; - const octokit = getOctokitInstance(); + const octokit: InstanceType = getOctokit(githubToken); const { data: prData } = await octokit.rest.pulls.get({ owner, @@ -214,9 +114,34 @@ async function main(): Promise { if (!mergeQueue && !hasSmokeTestLabel && !hasAntiLabel) { // Fail Status due to missing labels - await upsertStatusCheck(statusCheckName, latestCommitHash, StatusCheckStatusType.Completed, - CompletedConclusionType.Failure, `Failed due to missing labels. Please apply either ${e2eLabel} or ${antiLabel}.`); - return + const createStatusCheckResponse = await octokit.rest.checks.create({ + owner, + repo, + name: statusCheckName, + head_sha: latestCommitHash, + status: StatusCheckStatusType.Completed, + conclusion: CompletedConclusionType.Failure, + started_at: new Date().toISOString(), + output: { + title: statusCheckTitle, + summary: `Failed due to missing labels. Please apply either ${e2eLabel} or ${antiLabel}.`, + }, + }); + + if (createStatusCheckResponse.status === 201) { + console.log( + `Created '${statusCheckName}' check with failed status for commit ${latestCommitHash}`, + ); + } else { + core.setFailed( + `Failed to create '${statusCheckName}' check with failed status for commit ${latestCommitHash} with status code ${createStatusCheckResponse.status}`, + ); + process.exit(1); + } + core.setFailed( + `At least 1 E2E Label must be Applied either ${e2eLabel} or ${antiLabel}`, + ); + process.exit(1); } if (!shouldRun) { @@ -224,8 +149,32 @@ async function main(): Promise { `Skipping Bitrise status check. due to the following reason: ${reason}`, ); - await upsertStatusCheck(statusCheckName, latestCommitHash, StatusCheckStatusType.Completed, CompletedConclusionType.Success, - `Skip run since ${reason}`); + + // Post success status (skipped) + const createStatusCheckResponse = await octokit.rest.checks.create({ + owner, + repo, + name: statusCheckName, + head_sha: latestCommitHash, + status: StatusCheckStatusType.Completed, + conclusion: CompletedConclusionType.Success, + started_at: new Date().toISOString(), + output: { + title: statusCheckTitle, + summary: `Skip run since ${reason}`, + }, + }); + + if (createStatusCheckResponse.status === 201) { + console.log( + `Created '${statusCheckName}' check with skipped status for commit ${latestCommitHash}`, + ); + } else { + core.setFailed( + `Failed to create '${statusCheckName}' check with skipped status for commit ${latestCommitHash} with status code ${createStatusCheckResponse.status}`, + ); + process.exit(1); + } return; } @@ -365,9 +314,29 @@ async function main(): Promise { // Post pending status console.log(`Posting pending status for commit ${latestCommitHash}`); - await upsertStatusCheck( statusCheckName, latestCommitHash, StatusCheckStatusType.InProgress, undefined, `Test runs in progress... You can view them at ${buildLink}`); - + const createStatusCheckResponse = await octokit.rest.checks.create({ + owner, + repo, + name: statusCheckName, + head_sha: latestCommitHash, + status: StatusCheckStatusType.InProgress, + started_at: new Date().toISOString(), + output: { + title: statusCheckTitle, + summary: `Test runs in progress... You can view them at ${buildLink}`, + }, + }); + if (createStatusCheckResponse.status === 201) { + console.log( + `Created '${statusCheckName}' check for commit ${latestCommitHash}`, + ); + } else { + core.setFailed( + `Failed to create '${statusCheckName}' check for commit ${latestCommitHash} with status code ${createStatusCheckResponse.status}`, + ); + process.exit(1); + } return; } @@ -414,11 +383,31 @@ async function main(): Promise { if (!bitriseComment) { console.log(`Bitrise comment not detected for commit ${latestCommitHash}`); + // Post fail status + const createStatusCheckResponse = await octokit.rest.checks.create({ + owner, + repo, + name: statusCheckName, + head_sha: latestCommitHash, + status: StatusCheckStatusType.Completed, + conclusion: CompletedConclusionType.Failure, + started_at: new Date().toISOString(), + output: { + title: statusCheckTitle, + summary: `No Bitrise comment found for commit ${latestCommitHash}. Try re-applying the '${e2eLabel}' label.`, + }, + }); - await upsertStatusCheck(statusCheckName, latestCommitHash, StatusCheckStatusType.Completed, - CompletedConclusionType.Failure, - `No Bitrise comment found for commit ${latestCommitHash}. Try re-applying the '${e2eLabel}' label.`); - + if (createStatusCheckResponse.status === 201) { + console.log( + `Created '${statusCheckName}' check for commit ${latestCommitHash}`, + ); + } else { + core.setFailed( + `Failed to create '${statusCheckName}' check for commit ${latestCommitHash} with status code ${createStatusCheckResponse.status}`, + ); + process.exit(1); + } return; } @@ -509,6 +498,27 @@ async function main(): Promise { } // Post status check - await upsertStatusCheck(statusCheckName, latestCommitHash, checkStatus.status, checkStatus.conclusion, statusMessage); + const createStatusCheckResponse = await octokit.rest.checks.create({ + owner, + repo, + name: statusCheckName, + head_sha: latestCommitHash, + started_at: new Date().toISOString(), + output: { + title: statusCheckTitle, + summary: statusMessage, + }, + ...checkStatus, + }); + if (createStatusCheckResponse.status === 201) { + console.log( + `Created '${statusCheckName}' check for commit ${latestCommitHash}`, + ); + } else { + core.setFailed( + `Failed to create '${statusCheckName}' check for commit ${latestCommitHash} with status code ${createStatusCheckResponse.status}`, + ); + process.exit(1); + } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 155b760639f..84dc6d17d93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,80 @@ ## Current Main Branch +## 7.37.1 - Dec 12, 2024 +### Added +- [#12427](https://github.com/MetaMask/metamask-mobile/pull/12427): feat: implement remote feature flag controller (#12427) +- [#12650](https://github.com/MetaMask/metamask-mobile/pull/12650): fix: fix swaps button on asset overview page for multichain feature (#12650) +- [#12507](https://github.com/MetaMask/metamask-mobile/pull/12507): feat: activate portfolio view (#12507) +- [#12540](https://github.com/MetaMask/metamask-mobile/pull/12540): feat: migrate Base network RPC from https://mainnet.base.org to base-… (#12540) +- [#12505](https://github.com/MetaMask/metamask-mobile/pull/12505): feat: add aggregated portfolio balance cross chains (#12505) +- [#12417](https://github.com/MetaMask/metamask-mobile/pull/12417): feat: multichain detect tokens feat (#12417) +- [#12490](https://github.com/MetaMask/metamask-mobile/pull/12490): feat: 7.37.0 (#12490) +- [#12419](https://github.com/MetaMask/metamask-mobile/pull/12419): feat: upgrade transaction controller to get incoming transactions using accounts API (#12419) +- [#12537](https://github.com/MetaMask/metamask-mobile/pull/12537): feat: enable ledger clear signing feature (#12537) +- [#12622](https://github.com/MetaMask/metamask-mobile/pull/12622): feat: Hide the smart transaction status page if we return a txHash asap (#12622) +- [#12623](https://github.com/MetaMask/metamask-mobile/pull/12623): chore: update bug template to include feature branches (#12623) +- [#12244](https://github.com/MetaMask/metamask-mobile/pull/12244): feat(ci): Expo (#12244) +- [#12459](https://github.com/MetaMask/metamask-mobile/pull/12459): feat: upgrade profile-sync-controller to 1.0.0 (#12459) +- [#12294](https://github.com/MetaMask/metamask-mobile/pull/12294): feat: Add Bitcoin accounts (Flask Only) (#12294) +- [#12243](https://github.com/MetaMask/metamask-mobile/pull/12243): feat: cicd e2e label requirements + pr automation (#12243) +- [#12495](https://github.com/MetaMask/metamask-mobile/pull/12495): feat: Support gas fee flows in swaps (#12495) +- [#12431](https://github.com/MetaMask/metamask-mobile/pull/12431): feat: multi chain asset list (#12431) + +### Changed +- [#12538](https://github.com/MetaMask/metamask-mobile/pull/12538): chore: Chore/12435 mvp handle engine does not exist (#12538) +- [#12617](https://github.com/MetaMask/metamask-mobile/pull/12617): docs: Update README.md with new expo instructions (#12617) +- [#12559](https://github.com/MetaMask/metamask-mobile/pull/12559): test: move remaining modal pages and selectors to their respective folders (#12559) +- [#12556](https://github.com/MetaMask/metamask-mobile/pull/12556): test: remove redundent tests in quarantine folder (#12556) +- [#12558](https://github.com/MetaMask/metamask-mobile/pull/12558): test: Create e2e tag for multi chain (#12558) +- [#12531](https://github.com/MetaMask/metamask-mobile/pull/12531): test: Move files to Wallet folder (#12531) +- [#12511](https://github.com/MetaMask/metamask-mobile/pull/12511): test: Move files to Onboarding folder (#12511) +- [#12512](https://github.com/MetaMask/metamask-mobile/pull/12512): test: address regression pipeline slow down (#12512) +- [#12513](https://github.com/MetaMask/metamask-mobile/pull/12513): ci: disable security e2e tests (#12513) +- [#12491](https://github.com/MetaMask/metamask-mobile/pull/12491): chore: chore/7.37.0-Changelog (#12491) +- [#12602](https://github.com/MetaMask/metamask-mobile/pull/12602): chore: Additional e2e test to support `PortfolioView` (#12602) +- [#12321](https://github.com/MetaMask/metamask-mobile/pull/12321): refactor: remove global network from transaction controller (#12321) +- [#12536](https://github.com/MetaMask/metamask-mobile/pull/12536): test: fix mock server (#12536) +- [#12288](https://github.com/MetaMask/metamask-mobile/pull/12288): test: add e2e test for security alert api (#12288) +- [#12597](https://github.com/MetaMask/metamask-mobile/pull/12597): test(3615): additional e2e scenarios editing permissions and non permitted networks (#12597) +- [#12488](https://github.com/MetaMask/metamask-mobile/pull/12488): test(3615): add new e2e test for initial dapp connection and non permitted flow (#12488) +- [#12532](https://github.com/MetaMask/metamask-mobile/pull/12532): refactor: de-anonymize insensitive properties of swaps events (#12532) +- [#12485](https://github.com/MetaMask/metamask-mobile/pull/12485): chore: Stop suppressing pod install failures (#12485) +- [#12574](https://github.com/MetaMask/metamask-mobile/pull/12574): chore: Add option to skip pod install setup step (#12574) +- [#12609](https://github.com/MetaMask/metamask-mobile/pull/12609): chore: update user storage E2E framework (#12609) +- [#12569](https://github.com/MetaMask/metamask-mobile/pull/12569): chore: transfer ownership of auth & profile sync E2E from notifications to identity (#12569) +- [#12534](https://github.com/MetaMask/metamask-mobile/pull/12534): chore: change ownership of profile sync from notifications to identity (#12534) +- [#12543](https://github.com/MetaMask/metamask-mobile/pull/12543): chore: Decrease hot and cold start app to wallet view time (#12543) +- [#12428](https://github.com/MetaMask/metamask-mobile/pull/12428): chore: Add eth hd keyring and key tree to decrease unlock time (#12428) +- [#12555](https://github.com/MetaMask/metamask-mobile/pull/12555): chore: Update accounts packages (#12555) +- [#12563](https://github.com/MetaMask/metamask-mobile/pull/12563): chore: cicd e2e hardening (#12563) +- [#12554](https://github.com/MetaMask/metamask-mobile/pull/12554): chore: fail status when on no labels for retro-label changes (#12554) +- [#12295](https://github.com/MetaMask/metamask-mobile/pull/12295): chore: use getShares contract method from stake-sdk for unstake all flow (#12295) +- [#12551](https://github.com/MetaMask/metamask-mobile/pull/12551): chore: Bump Snaps packages (#12551) + +### Fixed +- [#12659](https://github.com/MetaMask/metamask-mobile/pull/12659): fix: fix token details navigation (#12659) +- [#12624](https://github.com/MetaMask/metamask-mobile/pull/12624): fix: add new translations (#12624) +- [#12373](https://github.com/MetaMask/metamask-mobile/pull/12373): fix: circular dependencies engine-network-handleNetworkSwitch (#12373) +- [#12663](https://github.com/MetaMask/metamask-mobile/pull/12663): fix: disable flaky tests on incoming-transactions.spec (#12663) +- [#12598](https://github.com/MetaMask/metamask-mobile/pull/12598): fix: disable mock poc test (#12598) +- [#12230](https://github.com/MetaMask/metamask-mobile/pull/12230): fix: Jest timer error in unit test (#12230) +- [#12626](https://github.com/MetaMask/metamask-mobile/pull/12626): fix: fix flaky test (#12626) +- [#12372](https://github.com/MetaMask/metamask-mobile/pull/12372): fix: abstract out circular dependencies between engine and networks util (#12372) +- [#12641](https://github.com/MetaMask/metamask-mobile/pull/12641): fix: fix network selector (#12641) +- [#12637](https://github.com/MetaMask/metamask-mobile/pull/12637): fix: fix native tokens filter when all networks is selected (#12637) +- [#12529](https://github.com/MetaMask/metamask-mobile/pull/12529): fix: fix NFTs disappearing after killing app (#12529) +- [#12562](https://github.com/MetaMask/metamask-mobile/pull/12562): fix: Move `AssetPollingProvider` from Root to Nav/Main/index.js (#12562) +- [#12607](https://github.com/MetaMask/metamask-mobile/pull/12607): fix: e2e regression gas api (#12607) +- [#12460](https://github.com/MetaMask/metamask-mobile/pull/12460): fix: add source when local PPOM fails (#12460) +- [#12199](https://github.com/MetaMask/metamask-mobile/pull/12199): fix: 10967 User able to add Ledger account with existing account name (#12199) +- [#12566](https://github.com/MetaMask/metamask-mobile/pull/12566): fix(12527): sdk connection with unknown url causes a bug (#12566) +- [#12405](https://github.com/MetaMask/metamask-mobile/pull/12405): fix(431-2): active network icon has too much margin and adding optional prop (#12405) +- [#12591](https://github.com/MetaMask/metamask-mobile/pull/12591): fix: add resolution for express to fix failing audit on path-to-regexp (#12591) +- [#12567](https://github.com/MetaMask/metamask-mobile/pull/12567): fix: update input handling in useInputHandler to support BACK key functionality (#12567) +- [#12630](https://github.com/MetaMask/metamask-mobile/pull/12630): fix: hide tokens without balance for multichain (#12630) + + ## 7.37.0 - Nov 28, 2024 ### Added - [#12091](https://github.com/MetaMask/metamask-mobile/pull/12091): feat: 2020 Add a performance test for iOS in Bitrise (#12091) diff --git a/android/app/build.gradle b/android/app/build.gradle index c856cc0492f..f38e73ec6cb 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -178,8 +178,8 @@ android { applicationId "io.metamask" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionName "7.37.0" - versionCode 1512 + versionName "7.38.0" + versionCode 1521 testBuildType System.getProperty('testBuildType', 'debug') missingDimensionStrategy 'react-native-camera', 'general' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/actions/notification/helpers/index.ts b/app/actions/notification/helpers/index.ts index 34b6b6ff064..7038cb02841 100644 --- a/app/actions/notification/helpers/index.ts +++ b/app/actions/notification/helpers/index.ts @@ -7,7 +7,7 @@ import { mmStorage, getAllUUIDs, } from '../../../util/notifications'; -import type { UserStorage } from '@metamask/notification-services-controller/notification-services'; +import { UserStorage } from '@metamask/notification-services-controller/dist/NotificationServicesController/types/user-storage/index.cjs'; export type MarkAsReadNotificationsParam = Pick< Notification, diff --git a/app/components/Views/Asset/__snapshots__/index.test.js.snap b/app/components/Views/Asset/__snapshots__/index.test.js.snap index 627aedf5a32..333565b0718 100644 --- a/app/components/Views/Asset/__snapshots__/index.test.js.snap +++ b/app/components/Views/Asset/__snapshots__/index.test.js.snap @@ -1,5 +1,44 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Asset should not display swaps button if the asset is not allowed 1`] = ` + + + + + + + +`; + exports[`Asset should render correctly 1`] = ` StyleSheet.create({ @@ -168,6 +171,10 @@ class Asset extends PureComponent { * Boolean that indicates if native token is supported to buy */ isNetworkBuyNativeTokenSupported: PropTypes.bool, + /** + * Function to set the swaps liveness + */ + setLiveness: PropTypes.func, }; state = { @@ -240,8 +247,27 @@ class Asset extends PureComponent { this.updateNavBar(contentOffset); }; + checkLiveness = async (chainId) => { + try { + const featureFlags = await swapsUtils.fetchSwapsFeatureFlags( + getFeatureFlagChainId(chainId), + AppConstants.SWAPS.CLIENT_ID, + ); + this.props.setLiveness(chainId, featureFlags); + } catch (error) { + Logger.error(error, 'Swaps: error while fetching swaps liveness'); + this.props.setLiveness(chainId, null); + } + }; + componentDidMount() { this.updateNavBar(); + + const tokenChainId = this.props.route?.params?.chainId; + if (tokenChainId) { + this.checkLiveness(tokenChainId); + } + InteractionManager.runAfterInteractions(() => { this.normalizeTransactions(); this.mounted = true; @@ -482,7 +508,9 @@ class Asset extends PureComponent { : isSwapsAllowed(chainId); const isAssetAllowed = - asset.isETH || asset.address?.toLowerCase() in this.props.swapsTokens; + asset.isETH || + asset.isNative || + asset.address?.toLowerCase() in this.props.swapsTokens; const displaySwapsButton = isSwapsFeatureLive && @@ -559,4 +587,12 @@ const mapStateToProps = (state, { route }) => ({ networkClientId: selectNetworkClientId(state), }); -export default connect(mapStateToProps)(withMetricsAwareness(Asset)); +const mapDispatchToProps = (dispatch) => ({ + setLiveness: (chainId, featureFlags) => + dispatch(setSwapsLiveness(chainId, featureFlags)), +}); + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(withMetricsAwareness(Asset)); diff --git a/app/components/Views/Asset/index.test.js b/app/components/Views/Asset/index.test.js index a7461510901..cce38c1ca1e 100644 --- a/app/components/Views/Asset/index.test.js +++ b/app/components/Views/Asset/index.test.js @@ -1,4 +1,5 @@ import React from 'react'; +import { swapsUtils } from '@metamask/swaps-controller/'; import renderWithProvider from '../../../util/test/renderWithProvider'; import { backgroundState } from '../../../util/test/initial-root-state'; import Asset from './'; @@ -48,14 +49,65 @@ describe('Asset', () => { it('should render correctly', () => { const { toJSON } = renderWithProvider( null }} - route={{ params: { symbol: 'ETH', address: 'something', isETH: true } }} + navigation={{ setOptions: jest.fn() }} + route={{ + params: { + symbol: 'ETH', + address: 'something', + isETH: true, + chainId: '0x1', + }, + }} + />, + { + state: mockInitialState, + }, + ); + expect(toJSON()).toMatchSnapshot(); + }); + + it('should call navigation.setOptions on mount', () => { + const mockSetOptions = jest.fn(); + renderWithProvider( + , { state: mockInitialState, }, ); + + expect(mockSetOptions).toHaveBeenCalled(); + }); + + it('should not display swaps button if the asset is not allowed', () => { + jest.spyOn(swapsUtils, 'fetchSwapsFeatureFlags').mockRejectedValue('error'); + const { toJSON } = renderWithProvider( + , + { + state: mockInitialState, + }, + ); + expect(toJSON()).toMatchSnapshot(); }); }); diff --git a/app/components/Views/NetworkConnect/NetworkConnectMultiSelector/NetworkConnectMultiSelector.tsx b/app/components/Views/NetworkConnect/NetworkConnectMultiSelector/NetworkConnectMultiSelector.tsx index e0f0de634c0..6b3a29424ca 100644 --- a/app/components/Views/NetworkConnect/NetworkConnectMultiSelector/NetworkConnectMultiSelector.tsx +++ b/app/components/Views/NetworkConnect/NetworkConnectMultiSelector/NetworkConnectMultiSelector.tsx @@ -95,16 +95,8 @@ const NetworkConnectMultiSelector = ({ if (onNetworksSelected) { onNetworksSelected(selectedChainIds); } else { - // Check if current network was originally permitted and is now being removed - const wasCurrentNetworkOriginallyPermitted = - originalChainIds.includes(currentChainId); - const isCurrentNetworkStillPermitted = - selectedChainIds.includes(currentChainId); - - if ( - wasCurrentNetworkOriginallyPermitted && - !isCurrentNetworkStillPermitted - ) { + // Check if current network is being removed from permissions + if (!selectedChainIds.includes(currentChainId)) { // Find the network configuration for the first permitted chain const networkToSwitch = Object.entries(networkConfigurations).find( ([, { chainId }]) => chainId === selectedChainIds[0], @@ -132,6 +124,7 @@ const NetworkConnectMultiSelector = ({ } catch (e) { Logger.error(e as Error, 'Error checking for permitted chains caveat'); } + if (hasPermittedChains) { Engine.context.PermissionController.updateCaveat( hostname, @@ -160,7 +153,6 @@ const NetworkConnectMultiSelector = ({ } }, [ selectedChainIds, - originalChainIds, hostname, onUserAction, onNetworksSelected, diff --git a/app/reducers/swaps/index.js b/app/reducers/swaps/index.js index 04a57225652..af7fbe11cae 100644 --- a/app/reducers/swaps/index.js +++ b/app/reducers/swaps/index.js @@ -263,22 +263,18 @@ export const swapsTokensObjectSelector = createSelector( ); /** - * Returns a memoized object that only has the addresses cross chains of the tokens as keys + * Returns a memoized object that only has the addesses cross chains of the tokens as keys * and undefined as value. Useful to check if a token is supported by swaps. */ export const swapsTokensMultiChainObjectSelector = createSelector( swapsControllerAndUserTokensMultichain, - (tokens) => { - if (!tokens || tokens.length === 0) { - return {}; - } - - const result = {}; - for (const token of tokens) { - result[token.address] = undefined; - } - return result; - }, + (tokens) => + tokens?.length > 0 + ? tokens.reduce( + (acc, token) => ({ ...acc, [token.address]: undefined }), + {}, + ) + : {}, ); /** diff --git a/app/store/migrations/index.ts b/app/store/migrations/index.ts index 06f50319276..4a8d11ad47c 100644 --- a/app/store/migrations/index.ts +++ b/app/store/migrations/index.ts @@ -63,7 +63,6 @@ import migration59 from './059'; import migration60 from './060'; import migration61 from './061'; import migration62 from './062'; -import migration63 from './063'; type MigrationFunction = (state: unknown) => unknown; type AsyncMigrationFunction = (state: unknown) => Promise; @@ -139,7 +138,6 @@ export const migrationList: MigrationsList = { 60: migration60, 61: migration61, 62: migration62, - 63: migration63, }; // Enable both synchronous and asynchronous migrations diff --git a/app/util/logs/__snapshots__/index.test.ts.snap b/app/util/logs/__snapshots__/index.test.ts.snap deleted file mode 100644 index 197b81ca95f..00000000000 --- a/app/util/logs/__snapshots__/index.test.ts.snap +++ /dev/null @@ -1,356 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`logs :: generateStateLogs generates a valid json export 1`] = ` -{ - "appVersion": "1", - "buildNumber": "123", - "engine": { - "backgroundState": { - "AccountTrackerController": { - "accounts": {}, - "accountsByChainId": { - "0x1": {}, - }, - }, - "AccountsController": { - "internalAccounts": { - "accounts": {}, - "selectedAccount": "", - }, - }, - "AddressBookController": { - "addressBook": {}, - }, - "ApprovalController": { - "approvalFlows": [], - "pendingApprovalCount": 0, - "pendingApprovals": {}, - }, - "AuthenticationController": { - "isSignedIn": false, - }, - "CurrencyRateController": { - "currencyRates": { - "ETH": { - "conversionDate": 0, - "conversionRate": 0, - "usdConversionRate": null, - }, - }, - "currentCurrency": "usd", - }, - "GasFeeController": { - "estimatedGasFeeTimeBounds": {}, - "gasEstimateType": "none", - "gasFeeEstimates": {}, - "gasFeeEstimatesByChainId": {}, - "nonRPCGasFeeApisDisabled": false, - }, - "KeyringController": { - "keyrings": [ - "keyring1", - "keyring2", - ], - }, - "LoggingController": { - "logs": {}, - }, - "NetworkController": { - "networkConfigurationsByChainId": { - "0x1": { - "blockExplorerUrls": [], - "chainId": "0x1", - "defaultRpcEndpointIndex": 0, - "name": "Ethereum Mainnet", - "nativeCurrency": "ETH", - "rpcEndpoints": [ - { - "networkClientId": "mainnet", - "type": "infura", - "url": "https://mainnet.infura.io/v3/{infuraProjectId}", - }, - ], - }, - "0x5": { - "blockExplorerUrls": [], - "chainId": "0x5", - "defaultRpcEndpointIndex": 0, - "name": "Goerli", - "nativeCurrency": "GoerliETH", - "rpcEndpoints": [ - { - "networkClientId": "goerli", - "type": "infura", - "url": "https://goerli.infura.io/v3/{infuraProjectId}", - }, - ], - }, - "0xaa36a7": { - "blockExplorerUrls": [], - "chainId": "0xaa36a7", - "defaultRpcEndpointIndex": 0, - "name": "Sepolia", - "nativeCurrency": "SepoliaETH", - "rpcEndpoints": [ - { - "networkClientId": "sepolia", - "type": "infura", - "url": "https://sepolia.infura.io/v3/{infuraProjectId}", - }, - ], - }, - "0xe704": { - "blockExplorerUrls": [], - "chainId": "0xe704", - "defaultRpcEndpointIndex": 0, - "name": "Linea Goerli", - "nativeCurrency": "LineaETH", - "rpcEndpoints": [ - { - "networkClientId": "linea-goerli", - "type": "infura", - "url": "https://linea-goerli.infura.io/v3/{infuraProjectId}", - }, - ], - }, - "0xe705": { - "blockExplorerUrls": [], - "chainId": "0xe705", - "defaultRpcEndpointIndex": 0, - "name": "Linea Sepolia", - "nativeCurrency": "LineaETH", - "rpcEndpoints": [ - { - "networkClientId": "linea-sepolia", - "type": "infura", - "url": "https://linea-sepolia.infura.io/v3/{infuraProjectId}", - }, - ], - }, - "0xe708": { - "blockExplorerUrls": [], - "chainId": "0xe708", - "defaultRpcEndpointIndex": 0, - "name": "Linea", - "nativeCurrency": "ETH", - "rpcEndpoints": [ - { - "networkClientId": "linea-mainnet", - "type": "infura", - "url": "https://linea-mainnet.infura.io/v3/{infuraProjectId}", - }, - ], - }, - }, - "networksMetadata": { - "mainnet": { - "EIPS": {}, - "status": "unknown", - }, - }, - "selectedNetworkClientId": "mainnet", - }, - "NotificationServicesController": { - "isCheckingAccountsPresence": false, - "isFeatureAnnouncementsEnabled": false, - "isFetchingMetamaskNotifications": false, - "isMetamaskNotificationsFeatureSeen": false, - "isNotificationServicesEnabled": false, - "isUpdatingMetamaskNotifications": false, - "isUpdatingMetamaskNotificationsAccount": [], - "metamaskNotificationsList": [], - "metamaskNotificationsReadList": [], - "subscriptionAccountsSeen": [], - }, - "NotificationServicesPushController": { - "fcmToken": "", - }, - "PPOMController": { - "storageMetadata": [], - "versionInfo": [], - }, - "PermissionController": { - "subjects": {}, - }, - "PreferencesController": { - "displayNftMedia": true, - "featureFlags": {}, - "identities": {}, - "ipfsGateway": "https://dweb.link/ipfs/", - "isIpfsGatewayEnabled": true, - "isMultiAccountBalancesEnabled": true, - "lostIdentities": {}, - "privacyMode": false, - "securityAlertsEnabled": true, - "selectedAddress": "", - "showIncomingTransactions": { - "0x1": true, - "0x13881": true, - "0x38": true, - "0x5": true, - "0x504": true, - "0x505": true, - "0x507": true, - "0x61": true, - "0x64": true, - "0x89": true, - "0xa": true, - "0xa869": true, - "0xa86a": true, - "0xaa36a7": true, - "0xaa37dc": true, - "0xe704": true, - "0xe705": true, - "0xe708": true, - "0xfa": true, - "0xfa2": true, - }, - "showMultiRpcModal": false, - "showTestNetworks": false, - "smartTransactionsOptInStatus": true, - "tokenNetworkFilter": {}, - "tokenSortConfig": { - "key": "tokenFiatAmount", - "order": "dsc", - "sortCallback": "stringNumeric", - }, - "useNftDetection": true, - "useSafeChainsListValidation": true, - "useTokenDetection": true, - "useTransactionSimulations": true, - }, - "RemoteFeatureFlagController": { - "cacheTimestamp": 0, - "remoteFeatureFlags": {}, - }, - "SelectedNetworkController": { - "domains": {}, - }, - "SignatureController": { - "signatureRequests": {}, - "unapprovedPersonalMsgCount": 0, - "unapprovedPersonalMsgs": {}, - "unapprovedTypedMessages": {}, - "unapprovedTypedMessagesCount": 0, - }, - "SmartTransactionsController": { - "smartTransactionsState": { - "fees": { - "approvalTxFees": null, - "tradeTxFees": null, - }, - "feesByChainId": { - "0x1": { - "approvalTxFees": null, - "tradeTxFees": null, - }, - "0xaa36a7": { - "approvalTxFees": null, - "tradeTxFees": null, - }, - }, - "liveness": true, - "livenessByChainId": { - "0x1": true, - "0xaa36a7": true, - }, - "smartTransactions": { - "0x1": [], - }, - "userOptIn": null, - "userOptInV2": null, - }, - }, - "SnapController": { - "snapStates": {}, - "snaps": {}, - "unencryptedSnapStates": {}, - }, - "SnapsRegistry": { - "database": null, - "databaseUnavailable": false, - "lastUpdated": null, - }, - "SubjectMetadataController": { - "subjectMetadata": {}, - }, - "SwapsController": { - "aggregatorMetadata": null, - "aggregatorMetadataLastFetched": 0, - "approvalTransaction": null, - "chainCache": { - "0x1": { - "aggregatorMetadata": null, - "aggregatorMetadataLastFetched": 0, - "tokens": null, - "tokensLastFetched": 0, - "topAssets": null, - "topAssetsLastFetched": 0, - }, - }, - "error": { - "description": null, - "key": null, - }, - "fetchParams": { - "destinationToken": "", - "slippage": 0, - "sourceAmount": 0, - "sourceToken": "", - "walletAddress": "", - }, - "fetchParamsMetaData": { - "destinationTokenInfo": { - "address": "", - "decimals": 0, - "symbol": "", - }, - "sourceTokenInfo": { - "address": "", - "decimals": 0, - "symbol": "", - }, - }, - "isInPolling": false, - "pollingCyclesLeft": 3, - "quoteRefreshSeconds": null, - "quoteValues": {}, - "quotes": {}, - "quotesLastFetched": 0, - "tokens": null, - "tokensLastFetched": 0, - "topAggId": null, - "topAggSavings": null, - "topAssets": null, - "topAssetsLastFetched": 0, - "usedCustomGas": null, - "usedGasEstimate": null, - }, - "TokenBalancesController": { - "tokenBalances": {}, - }, - "TokenListController": { - "preventPollingOnNetworkRestart": false, - "tokenList": {}, - "tokensChainsCache": {}, - }, - "TokenRatesController": { - "marketData": {}, - }, - "TransactionController": { - "lastFetchedBlockNumbers": {}, - "methodData": {}, - "submitHistory": [], - "transactions": [], - }, - "UserStorageController": { - "hasAccountSyncingSyncedAtLeastOnce": false, - "isAccountSyncingReadyToBeDispatched": false, - "isProfileSyncingEnabled": true, - "isProfileSyncingUpdateLoading": false, - }, - }, - }, - "metaMetricsId": "6D796265-7374-4953-6D65-74616D61736B", -} -`; diff --git a/app/util/logs/index.test.ts b/app/util/logs/index.test.ts index ab0cf5b6b79..f6c79401545 100644 --- a/app/util/logs/index.test.ts +++ b/app/util/logs/index.test.ts @@ -12,7 +12,6 @@ import initialRootState, { backgroundState, } from '../../util/test/initial-root-state'; import { merge } from 'lodash'; -import MetaMetrics from '../../core/Analytics/MetaMetrics'; jest.mock('react-native-fs', () => ({ DocumentDirectoryPath: '/mock/path', @@ -48,39 +47,8 @@ jest.mock('../../core/Engine', () => ({ }, })); -jest.mock('../../core/Analytics/MetaMetrics'); - -const mockMetrics = { - isEnabled: jest.fn(() => true), - getMetaMetricsId: jest.fn(() => - Promise.resolve('6D796265-7374-4953-6D65-74616D61736B'), - ), -}; - -(MetaMetrics.getInstance as jest.Mock).mockReturnValue(mockMetrics); - describe('logs :: generateStateLogs', () => { - - it('generates a valid json export', async () => { - const mockStateInput = { - appVersion: '1', - buildNumber: '123', - metaMetricsId: '6D796265-7374-4953-6D65-74616D61736B', - engine: { - backgroundState: { - ...backgroundState, - KeyringController: { - vault: 'vault mock', - }, - }, - }, - }; - const logs = generateStateLogs(mockStateInput); - - expect(JSON.parse(logs)).toMatchSnapshot(); - }); - - it('generates the expected state logs without the explicitly deleted controller states', async () => { + it('should generate the state logs correctly without the explicitly deleted controller states', async () => { const mockStateInput = { engine: { backgroundState: { @@ -106,7 +74,6 @@ describe('logs :: generateStateLogs', () => { const mockStateInput = { appVersion: '1', buildNumber: '123', - metaMetricsId: '6D796265-7374-4953-6D65-74616D61736B', engine: { backgroundState: { ...backgroundState, @@ -127,7 +94,6 @@ describe('logs :: generateStateLogs', () => { expect(logs.includes("vault: 'vault mock'")).toBe(false); expect(logs.includes('appVersion')).toBe(true); expect(logs.includes('buildNumber')).toBe(true); - expect(logs.includes('metaMetricsId')).toBe(true); }); }); @@ -290,41 +256,4 @@ describe('logs :: downloadStateLogs', () => { url: expect.stringContaining('data:text/plain;base64,'), }); }); - - it('does not include metametrics id if not opt-in', async () => { - (getApplicationName as jest.Mock).mockResolvedValue('TestApp'); - (getVersion as jest.Mock).mockResolvedValue('1.0.0'); - (getBuildNumber as jest.Mock).mockResolvedValue('100'); - (Device.isIos as jest.Mock).mockReturnValue(false); - (mockMetrics.isEnabled as jest.Mock).mockReturnValue(false); - - const mockStateInput = merge({}, initialRootState, { - engine: { - backgroundState: { - ...backgroundState, - KeyringController: { - vault: 'vault mock', - }, - }, - }, - }); - - await downloadStateLogs(mockStateInput); - - expect(Share.open).toHaveBeenCalledWith({ - subject: 'TestApp State logs - v1.0.0 (100)', - title: 'TestApp State logs - v1.0.0 (100)', - url: expect.stringContaining('data:text/plain;base64,'), - }); - - // Access the arguments passed to Share.open - const shareOpenCalls = (Share.open as jest.Mock).mock.calls; - expect(shareOpenCalls.length).toBeGreaterThan(0); - const [shareOpenArgs] = shareOpenCalls[0]; - const { url } = shareOpenArgs; - const base64Data = url.replace('data:text/plain;base64,', ''); - const decodedData = Buffer.from(base64Data, 'base64').toString('utf-8'); - const jsonData = JSON.parse(decodedData); - expect(jsonData).not.toHaveProperty('metaMetricsId'); - }); }); diff --git a/app/util/logs/index.ts b/app/util/logs/index.ts index 6363be63977..64bfa2088b5 100644 --- a/app/util/logs/index.ts +++ b/app/util/logs/index.ts @@ -11,7 +11,6 @@ import { Buffer } from 'buffer'; import Logger from '../../util/Logger'; import { RootState } from '../../reducers'; import Device from '../../util/device'; -import {MetaMetrics} from '../../core/Analytics'; // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any, import/prefer-default-export @@ -58,8 +57,6 @@ export const downloadStateLogs = async ( const appName = await getApplicationName(); const appVersion = await getVersion(); const buildNumber = await getBuildNumber(); - const metrics = MetaMetrics.getInstance(); - const metaMetricsId = metrics.isEnabled() ? await metrics.getMetaMetricsId() : undefined; const path = RNFS.DocumentDirectoryPath + `/state-logs-v${appVersion}-(${buildNumber}).json`; @@ -71,7 +68,6 @@ export const downloadStateLogs = async ( ...fullState, appVersion, buildNumber, - metaMetricsId, }, loggedIn, ); diff --git a/app/util/notifications/constants/urls.ts b/app/util/notifications/constants/urls.ts new file mode 100644 index 00000000000..e057041e0f7 --- /dev/null +++ b/app/util/notifications/constants/urls.ts @@ -0,0 +1,11 @@ +enum CHAIN_SCANS_URLS { + ETHEREUM = 'https://etherscan.io', + OPTIMISM = 'https://optimistic.etherscan.io', + BSC = 'https://bscscan.com', + POLYGON = 'https://polygonscan.com', + ARBITRUM = 'https://arbiscan.io', + AVALANCHE = 'https://snowtrace.io', + LINEA = 'https://lineascan.build', +} + +export default CHAIN_SCANS_URLS; diff --git a/app/util/notifications/hooks/usePushNotifications.ts b/app/util/notifications/hooks/usePushNotifications.ts index b7e2e697046..ef356fa968c 100644 --- a/app/util/notifications/hooks/usePushNotifications.ts +++ b/app/util/notifications/hooks/usePushNotifications.ts @@ -5,7 +5,7 @@ import { enablePushNotifications, } from '../../../actions/notification/helpers'; import { mmStorage } from '../settings'; -import { UserStorage } from '@metamask/notification-services-controller/notification-services'; +import { UserStorage } from '@metamask/notification-services-controller/dist/NotificationServicesController/types/user-storage/index.cjs'; import { isNotificationsFeatureEnabled } from '../constants'; export function usePushNotifications() { diff --git a/app/util/notifications/methods/common.test.ts b/app/util/notifications/methods/common.test.ts index a60317e5ec1..68c05880131 100644 --- a/app/util/notifications/methods/common.test.ts +++ b/app/util/notifications/methods/common.test.ts @@ -1,211 +1,28 @@ -import { - formatMenuItemDate, - parseNotification, - getLeadingZeroCount, - formatAmount, - getUsdAmount, -} from './common'; +import dayjs from 'dayjs'; +import { formatMenuItemDate, parseNotification } from './common'; import { strings } from '../../../../locales/i18n'; import { FirebaseMessagingTypes } from '@react-native-firebase/messaging'; describe('formatMenuItemDate', () => { - beforeAll(() => { - jest.useFakeTimers(); - jest.setSystemTime(new Date(Date.UTC(2024, 5, 7, 9, 40, 0))); // 2024-06-07T09:40:00Z - }); - - afterAll(() => { - jest.useRealTimers(); - }); - it('returns "No date" if date is not provided', () => { expect(formatMenuItemDate()).toBe(strings('notifications.no_date')); }); - it('formats date as time if the date is today', () => { - const assertToday = (modifyDate?: (d: Date) => void) => { - const testDate = new Date(); - modifyDate?.(testDate); - expect(formatMenuItemDate(testDate)).toMatch(/^\d{2}:\d{2}$/u); // HH:mm - }; - - // assert current date - assertToday(); - - // assert 1 hour ago - assertToday((testDate) => { - testDate.setUTCHours(testDate.getUTCHours() - 1); - return testDate; - }); - }); - - it('formats date as "yesterday" if the date was yesterday', () => { - const assertYesterday = (modifyDate: (d: Date) => void) => { - const testDate = new Date(); - modifyDate(testDate); - expect(formatMenuItemDate(testDate)).toBe( - strings('notifications.yesterday'), - ); - }; - - // assert exactly 1 day ago - assertYesterday((testDate) => { - testDate.setUTCDate(testDate.getUTCDate() - 1); - }); - - // assert almost a day ago, but was still yesterday - // E.g. if Today way 09:40AM, but date to test was 23 hours ago (yesterday at 10:40AM), we still want to to show yesterday - assertYesterday((testDate) => { - testDate.setUTCDate(testDate.getUTCDate() - 1); - testDate.setUTCHours(testDate.getUTCHours() + 1); - }); - }); - - it('should format date as "Month DD" if the date is this year but not today or yesterday', () => { - const assertMonthsAgo = (modifyDate: (d: Date) => Date | void) => { - let testDate = new Date(); - testDate = modifyDate(testDate) ?? testDate; - expect(formatMenuItemDate(testDate)).toMatch(/^\w{3} \d{1,2}$/u); // E.g. Apr 7 - }; - - // assert exactly 1 month ago - assertMonthsAgo((testDate) => { - testDate.setUTCMonth(testDate.getUTCMonth() - 1); - }); - - // assert 2 months ago - assertMonthsAgo((testDate) => { - testDate.setUTCMonth(testDate.getUTCMonth() - 2); - }); - - // assert almost a month ago (where it is a new month, but not 30 days) - assertMonthsAgo( - () => - // jest mock date is set in july, so we will test with month may - new Date(Date.UTC(2024, 4, 20, 9, 40, 0)), // 2024-05-20T09:40:00Z - ); - }); - - it('should format date as "Mon DD, YYYY" if the date is not this year', () => { - const assertYearsAgo = (modifyDate: (d: Date) => Date | void) => { - let testDate = new Date(); - testDate = modifyDate(testDate) ?? testDate; - expect(formatMenuItemDate(testDate)).toMatch(/^\w{3} \d{1,2}, \d{4}$/u); - }; - - // assert exactly 1 year ago - assertYearsAgo((testDate) => { - testDate.setUTCFullYear(testDate.getUTCFullYear() - 1); - }); - - // assert 2 years ago - assertYearsAgo((testDate) => { - testDate.setUTCFullYear(testDate.getUTCFullYear() - 2); - }); - - // assert almost a year ago (where it is a new year, but not 365 days ago) - assertYearsAgo( - () => - // jest mock date is set in 2024, so we will test with year 2023 - new Date(Date.UTC(2023, 10, 20, 9, 40, 0)), // 2023-11-20T09:40:00Z - ); - }); -}); - -describe('getNotificationData - getLeadingZeroCount() tests', () => { - test('Should handle all test cases', () => { - expect(getLeadingZeroCount(0)).toBe(0); - expect(getLeadingZeroCount(-1)).toBe(0); - expect(getLeadingZeroCount(1e-1)).toBe(0); - - expect(getLeadingZeroCount('1.01')).toBe(1); - expect(getLeadingZeroCount('3e-2')).toBe(1); - expect(getLeadingZeroCount('100.001e1')).toBe(1); - - expect(getLeadingZeroCount('0.00120043')).toBe(2); - }); -}); - -describe('getNotificationData - formatAmount() tests', () => { - test('Should format large numbers', () => { - expect(formatAmount(1000)).toBe('1K'); - expect(formatAmount(1500)).toBe('1.5K'); - expect(formatAmount(1000000)).toBe('1M'); - expect(formatAmount(1000000000)).toBe('1B'); - expect(formatAmount(1000000000000)).toBe('1T'); - expect(formatAmount(1234567)).toBe('1.23M'); - }); - - test('Should format smaller numbers (<1000) with custom decimal place', () => { - const formatOptions = { decimalPlaces: 18 }; - expect(formatAmount(100.0012, formatOptions)).toBe('100.0012'); - expect(formatAmount(100.001200001, formatOptions)).toBe('100.001200001'); - expect(formatAmount(1e-18, formatOptions)).toBe('0.000000000000000001'); - expect(formatAmount(1e-19, formatOptions)).toBe('0'); // number is smaller than decimals given, hence 0 + it('formats date to "HH:mm" if it is a current day', () => { + const date = dayjs().toDate(); + expect(formatMenuItemDate(date)).toBe(dayjs().format('HH:mm')); }); - test('Should format small numbers (<1000) up to 4 decimals otherwise uses ellipses', () => { - const formatOptions = { shouldEllipse: true }; - expect(formatAmount(100.1, formatOptions)).toBe('100.1'); - expect(formatAmount(100.01, formatOptions)).toBe('100.01'); - expect(formatAmount(100.001, formatOptions)).toBe('100.001'); - expect(formatAmount(100.0001, formatOptions)).toBe('100.0001'); - expect(formatAmount(100.00001, formatOptions)).toBe('100.0000...'); // since number is has >4 decimals, it will be truncated - expect(formatAmount(0.00001, formatOptions)).toBe('0.0000...'); // since number is has >4 decimals, it will be truncated + it('formats date to "Yesterday" if it is yesterday', () => { + const date = dayjs().subtract(1, 'day').toDate(); + expect(formatMenuItemDate(date)).toBe(strings('notifications.yesterday')); }); - test('Should format small numbers (<1000) to custom decimal places and ellipse', () => { - const formatOptions = { decimalPlaces: 2, shouldEllipse: true }; - expect(formatAmount(100.1, formatOptions)).toBe('100.1'); - expect(formatAmount(100.01, formatOptions)).toBe('100.01'); - expect(formatAmount(100.001, formatOptions)).toBe('100.00...'); - expect(formatAmount(100.0001, formatOptions)).toBe('100.00...'); - expect(formatAmount(100.00001, formatOptions)).toBe('100.00...'); // since number is has >2 decimals, it will be truncated - expect(formatAmount(0.00001, formatOptions)).toBe('0.00...'); // since number is has >2 decimals, it will be truncated - }); -}); - -describe('getUsdAmount', () => { - it('should return formatted USD amount based on token amount, decimals, and USD rate', () => { - const amount = '1000000000000000000'; // 1 Ether (1e18 wei) - const decimals = '18'; - const usdRate = '2000'; // 1 Ether = $2000 - - const result = getUsdAmount(amount, decimals, usdRate); - expect(result).toBe('2K'); // Since 1 Ether * $2000 = $2000, formatted as '2K' - }); - - it('should return an empty string if any of the parameters are missing', () => { - expect(getUsdAmount('', '18', '2000')).toBe(''); - expect(getUsdAmount('1000000000000000000', '', '2000')).toBe(''); - expect(getUsdAmount('1000000000000000000', '18', '')).toBe(''); - }); - - it('should handle small amounts correctly', () => { - const amount = '1000000000000000'; // 0.001 Ether (1e15 wei) - const decimals = '18'; - const usdRate = '1500'; // 1 Ether = $1500 - - const result = getUsdAmount(amount, decimals, usdRate); - expect(result).toBe('1.5'); // Since 0.001 Ether * $1500 = $1.5 - }); - - it('should handle large amounts correctly', () => { - const amount = '5000000000000000000000'; // 5000 Ether - const decimals = '18'; - const usdRate = '1000'; // 1 Ether = $1000 - - const result = getUsdAmount(amount, decimals, usdRate); - expect(result).toBe('5M'); // Since 5000 Ether * $1000 = $5,000,000, formatted as '5M' - }); -}); - -describe('parseNotification', () => { it('parses notification', () => { const notification = { data: { data: { - type: 'eth_received', + type: 'eth_received', data: { kind: 'eth_received' }, }, }, @@ -218,3 +35,5 @@ describe('parseNotification', () => { }); }); }); + + diff --git a/app/util/notifications/methods/common.ts b/app/util/notifications/methods/common.ts index 5c90b4d5b5d..fcbab88a089 100644 --- a/app/util/notifications/methods/common.ts +++ b/app/util/notifications/methods/common.ts @@ -1,4 +1,5 @@ import dayjs, { Dayjs } from 'dayjs'; +import isYesterday from 'dayjs/plugin/isYesterday'; import relativeTime from 'dayjs/plugin/relativeTime'; import notifee from '@notifee/react-native'; @@ -6,18 +7,8 @@ import localeData from 'dayjs/plugin/localeData'; import { Web3Provider } from '@ethersproject/providers'; import { toHex } from '@metamask/controller-utils'; import BigNumber from 'bignumber.js'; -import { - UserStorage, - USER_STORAGE_VERSION_KEY, - OnChainRawNotification, - OnChainRawNotificationsWithNetworkFields, -} from '@metamask/notification-services-controller/notification-services'; -import { - NOTIFICATION_CHAINS_ID, - NOTIFICATION_NETWORK_CURRENCY_NAME, - NOTIFICATION_NETWORK_CURRENCY_SYMBOL, - SUPPORTED_NOTIFICATION_BLOCK_EXPLORERS, -} from '@metamask/notification-services-controller/notification-services/ui'; +import { NotificationServicesController } from '@metamask/notification-services-controller'; +import { UserStorage } from '@metamask/notification-services-controller/dist/NotificationServicesController/types/user-storage/index.cjs'; import { FirebaseMessagingTypes } from '@react-native-firebase/messaging'; import Engine from '../../../core/Engine'; import { IconName } from '../../../component-library/components/Icons/Icon'; @@ -26,38 +17,17 @@ import { TRIGGER_TYPES } from '../constants'; import { Notification } from '../types'; import { calcTokenAmount } from '../../transactions'; import images from '../../../images/image-icons'; +import CHAIN_SCANS_URLS from '../constants/urls'; import I18n, { strings } from '../../../../locales/i18n'; -/** - * Checks if 2 date objects are on the same day - * - * @param currentDate - * @param dateToCheck - * @returns boolean if dates are same day. - */ -const isSameDay = (currentDate: Date, dateToCheck: Date) => - currentDate.getFullYear() === dateToCheck.getFullYear() && - currentDate.getMonth() === dateToCheck.getMonth() && - currentDate.getDate() === dateToCheck.getDate(); - -/** - * Checks if a date is "yesterday" from the current date - * - * @param currentDate - * @param dateToCheck - * @returns boolean if dates were "yesterday" - */ -const isYesterday = (currentDate: Date, dateToCheck: Date) => { - const yesterday = new Date(currentDate); - yesterday.setDate(currentDate.getDate() - 1); - return isSameDay(yesterday, dateToCheck); -}; - // Extend dayjs with the plugins +dayjs.extend(isYesterday); dayjs.extend(localeData); dayjs.extend(relativeTime); -function formatRelative( +const { UI } = NotificationServicesController; +export const USER_STORAGE_VERSION_KEY: unique symbol = 'v' as never; +export function formatRelative( date: Dayjs, currentDate: Dayjs, locale: string = 'en', @@ -79,32 +49,47 @@ export function formatMenuItemDate(date?: Date, locale: string = 'en'): string { if (!date) { return strings('notifications.no_date'); } - - const currentDate = new Date(); - const currentDayjsDate = dayjs(); + const currentDate = dayjs(); const dayjsDate = dayjs(date); dayjs.locale(locale); // E.g. 12:21 - if (dayjsDate.isSame(currentDayjsDate, 'day')) { + if (dayjsDate.isSame(currentDate, 'day')) { return dayjsDate.format('HH:mm'); } // E.g. Yesterday - if (isYesterday(currentDate, date)) { - return formatRelative(dayjsDate, currentDayjsDate, I18n.locale); + if (dayjs().add(-1, 'day').isYesterday()) { + return formatRelative(dayjsDate, currentDate, I18n.locale); } - // E.g. Oct 21 - if (dayjsDate.isSame(currentDayjsDate, 'year')) { - return dayjsDate.format('MMM D'); + // E.g. 21 Oct + if (dayjsDate.isSame(currentDate, 'year')) { + return dayjsDate.format('D MMM'); } - // E.g. Oct 21, 2022 - return dayjsDate.format('MMM D, YYYY'); + // E.g. 21 Oct 2022 + return dayjsDate.format('D MMM YYYY'); } +/** + * Generates a unique key based on the provided text, index, and a random string. + * + * @param text - The text to be included in the key. + * @param index - The index to be included in the key. + * @returns The generated unique key. + */ +export const getRandomKey = (text: string, index: number) => { + const key = `${text + .replace(/\s+/gu, '_') + .replace(/[^\w-]/gu, '')}-${index}-${Math.random() + .toString(36) + .substring(2, 15)}`; + + return key; +}; + interface FormatOptions { decimalPlaces?: number; shouldEllipse?: boolean; @@ -180,9 +165,9 @@ export const formatAmount = (numericAmount: number, opts?: FormatOptions) => { return numericAmount.toString(); }; -function hasNetworkFeeFields( - notification: OnChainRawNotification, -): notification is OnChainRawNotificationsWithNetworkFields { +export function hasNetworkFeeFields( + notification: NotificationServicesController.Types.OnChainRawNotification, +): notification is NotificationServicesController.Types.OnChainRawNotificationsWithNetworkFields { return 'network_fee' in notification.data; } @@ -199,7 +184,9 @@ export function getProviderByChainId(chainId: HexChainId) { return provider && new Web3Provider(provider); } -export const getNetworkFees = async (notification: OnChainRawNotification) => { +export const getNetworkFees = async ( + notification: NotificationServicesController.Types.OnChainRawNotification, +) => { if (!hasNetworkFeeFields(notification)) { throw new Error('Invalid notification type'); } @@ -349,52 +336,52 @@ export const sortNotifications = ( */ export function getNativeTokenDetailsByChainId(chainId: number) { const chainIdString = chainId.toString(); - if (chainIdString === NOTIFICATION_CHAINS_ID.ETHEREUM) { + if (chainIdString === UI.NOTIFICATION_CHAINS_ID.ETHEREUM) { return { - name: NOTIFICATION_NETWORK_CURRENCY_NAME[chainIdString], - symbol: NOTIFICATION_NETWORK_CURRENCY_SYMBOL[chainIdString], + name: UI.NOTIFICATION_NETWORK_CURRENCY_NAME[chainIdString], + symbol: UI.NOTIFICATION_NETWORK_CURRENCY_SYMBOL[chainIdString], image: images.ETHEREUM, }; } - if (chainIdString === NOTIFICATION_CHAINS_ID.OPTIMISM) { + if (chainIdString === UI.NOTIFICATION_CHAINS_ID.OPTIMISM) { return { - name: NOTIFICATION_NETWORK_CURRENCY_NAME[chainIdString], - symbol: NOTIFICATION_NETWORK_CURRENCY_SYMBOL[chainIdString], + name: UI.NOTIFICATION_NETWORK_CURRENCY_NAME[chainIdString], + symbol: UI.NOTIFICATION_NETWORK_CURRENCY_SYMBOL[chainIdString], image: images.OPTIMISM, }; } - if (chainIdString === NOTIFICATION_CHAINS_ID.BSC) { + if (chainIdString === UI.NOTIFICATION_CHAINS_ID.BSC) { return { - name: NOTIFICATION_NETWORK_CURRENCY_NAME[chainIdString], - symbol: NOTIFICATION_NETWORK_CURRENCY_SYMBOL[chainIdString], + name: UI.NOTIFICATION_NETWORK_CURRENCY_NAME[chainIdString], + symbol: UI.NOTIFICATION_NETWORK_CURRENCY_SYMBOL[chainIdString], image: images.BNB, }; } - if (chainIdString === NOTIFICATION_CHAINS_ID.POLYGON) { + if (chainIdString === UI.NOTIFICATION_CHAINS_ID.POLYGON) { return { - name: NOTIFICATION_NETWORK_CURRENCY_NAME[chainIdString], - symbol: NOTIFICATION_NETWORK_CURRENCY_SYMBOL[chainIdString], + name: UI.NOTIFICATION_NETWORK_CURRENCY_NAME[chainIdString], + symbol: UI.NOTIFICATION_NETWORK_CURRENCY_SYMBOL[chainIdString], image: images.POL, }; } - if (chainIdString === NOTIFICATION_CHAINS_ID.ARBITRUM) { + if (chainIdString === UI.NOTIFICATION_CHAINS_ID.ARBITRUM) { return { - name: NOTIFICATION_NETWORK_CURRENCY_NAME[chainIdString], - symbol: NOTIFICATION_NETWORK_CURRENCY_SYMBOL[chainIdString], + name: UI.NOTIFICATION_NETWORK_CURRENCY_NAME[chainIdString], + symbol: UI.NOTIFICATION_NETWORK_CURRENCY_SYMBOL[chainIdString], image: images.AETH, }; } - if (chainIdString === NOTIFICATION_CHAINS_ID.AVALANCHE) { + if (chainIdString === UI.NOTIFICATION_CHAINS_ID.AVALANCHE) { return { - name: NOTIFICATION_NETWORK_CURRENCY_NAME[chainIdString], - symbol: NOTIFICATION_NETWORK_CURRENCY_SYMBOL[chainIdString], + name: UI.NOTIFICATION_NETWORK_CURRENCY_NAME[chainIdString], + symbol: UI.NOTIFICATION_NETWORK_CURRENCY_SYMBOL[chainIdString], image: images.AVAX, }; } - if (chainIdString === NOTIFICATION_CHAINS_ID.LINEA) { + if (chainIdString === UI.NOTIFICATION_CHAINS_ID.LINEA) { return { - name: NOTIFICATION_NETWORK_CURRENCY_NAME[chainIdString], - symbol: NOTIFICATION_NETWORK_CURRENCY_SYMBOL[chainIdString], + name: UI.NOTIFICATION_NETWORK_CURRENCY_NAME[chainIdString], + symbol: UI.NOTIFICATION_NETWORK_CURRENCY_SYMBOL[chainIdString], image: images['LINEA-MAINNET'], }; } @@ -402,19 +389,34 @@ export function getNativeTokenDetailsByChainId(chainId: number) { return undefined; } -const isSupportedBlockExplorer = ( - chainId: number, -): chainId is keyof typeof SUPPORTED_NOTIFICATION_BLOCK_EXPLORERS => - chainId in SUPPORTED_NOTIFICATION_BLOCK_EXPLORERS; - /** * Gets block explorer information for the notification chains we support * @param chainId Notification Chain Id. This is a subset of chains that support notifications * @returns some default block explorers for the chains we support. */ export function getBlockExplorerByChainId(chainId: number) { - if (isSupportedBlockExplorer(chainId)) { - return SUPPORTED_NOTIFICATION_BLOCK_EXPLORERS[chainId].url; + const chainIdString = chainId.toString(); + + if (chainIdString === UI.NOTIFICATION_CHAINS_ID.ETHEREUM) { + return CHAIN_SCANS_URLS.ETHEREUM; + } + if (chainIdString === UI.NOTIFICATION_CHAINS_ID.OPTIMISM) { + return CHAIN_SCANS_URLS.OPTIMISM; + } + if (chainIdString === UI.NOTIFICATION_CHAINS_ID.BSC) { + return CHAIN_SCANS_URLS.BSC; + } + if (chainIdString === UI.NOTIFICATION_CHAINS_ID.POLYGON) { + return CHAIN_SCANS_URLS.POLYGON; + } + if (chainIdString === UI.NOTIFICATION_CHAINS_ID.ARBITRUM) { + return CHAIN_SCANS_URLS.ARBITRUM; + } + if (chainIdString === UI.NOTIFICATION_CHAINS_ID.AVALANCHE) { + return CHAIN_SCANS_URLS.AVALANCHE; + } + if (chainIdString === UI.NOTIFICATION_CHAINS_ID.LINEA) { + return CHAIN_SCANS_URLS.LINEA; } return undefined; @@ -483,29 +485,28 @@ export function withTimeout(promise: Promise, ms: number): Promise { } export interface NotificationTrigger { - id: string; - chainId: string; - kind: string; - address: string; + id: string + chainId: string + kind: string + address: string } -type MapTriggerFn = (trigger: NotificationTrigger) => Result; +type MapTriggerFn = (trigger: NotificationTrigger) => Result interface TraverseTriggerOpts { - address?: string; - mapTrigger?: MapTriggerFn; + address?: string + mapTrigger?: MapTriggerFn } const triggerToId = (trigger: NotificationTrigger) => trigger.id; const triggerIdentity = (trigger: NotificationTrigger) => trigger; -function traverseUserStorageTriggers( +export function traverseUserStorageTriggers( userStorage: UserStorage, options?: TraverseTriggerOpts, ) { const triggers: ResultTriggers[] = []; - const mapTrigger = - options?.mapTrigger ?? (triggerIdentity as MapTriggerFn); + const mapTrigger = options?.mapTrigger ?? (triggerIdentity as MapTriggerFn); for (const address in userStorage) { if (address === (USER_STORAGE_VERSION_KEY as unknown as string)) continue; @@ -543,12 +544,9 @@ export function getAllUUIDs(userStorage: UserStorage): string[] { return uuids; } -export function parseNotification( - remoteMessage: FirebaseMessagingTypes.RemoteMessage, -) { +export function parseNotification(remoteMessage: FirebaseMessagingTypes.RemoteMessage) { const notification = remoteMessage.data?.data; - const parsedNotification = - typeof notification === 'string' ? JSON.parse(notification) : notification; + const parsedNotification = typeof notification === 'string' ? JSON.parse(notification) : notification; const notificationData = { type: parsedNotification?.type || parsedNotification?.data?.kind, diff --git a/bitrise.yml b/bitrise.yml index 44878376bba..de319a453fe 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -1781,16 +1781,16 @@ app: PROJECT_LOCATION_IOS: ios - opts: is_expand: false - VERSION_NAME: 7.37.0 + VERSION_NAME: 7.38.0 - opts: is_expand: false - VERSION_NUMBER: 1512 + VERSION_NUMBER: 1521 - opts: is_expand: false - FLASK_VERSION_NAME: 7.37.0 + FLASK_VERSION_NAME: 7.38.0 - opts: is_expand: false - FLASK_VERSION_NUMBER: 1512 + FLASK_VERSION_NUMBER: 1521 - opts: is_expand: false ANDROID_APK_LINK: '' diff --git a/e2e/specs/multichain/asset-list.spec.js b/e2e/specs/multichain/asset-list.spec.js index 348942d7e84..ebee82f3168 100644 --- a/e2e/specs/multichain/asset-list.spec.js +++ b/e2e/specs/multichain/asset-list.spec.js @@ -66,14 +66,14 @@ describe(SmokeMultiChain('Import Tokens'), () => { await Assertions.checkIfNotVisible(bnb); }); - it.skip('should switch networks when clicking on swap if an asset on a different network is selected', async () => { + it('should switch networks when clicking on swap if an asset on a different network is selected', async () => { const BNB_NAME = 'BNB Smart Chain'; await WalletView.tapTokenNetworkFilter(); await WalletView.tapTokenNetworkFilterAll(); const bnb = WalletView.tokenInWallet('BNB'); await Assertions.checkIfVisible(bnb); await WalletView.tapOnToken('BNB'); - await TestHelpers.delay(5000); + await Assertions.checkIfVisible(TokenOverview.swapButton); await TokenOverview.tapSwapButton(); await Assertions.checkIfVisible(NetworkEducationModal.container); @@ -82,23 +82,24 @@ describe(SmokeMultiChain('Import Tokens'), () => { BNB_NAME, ); await NetworkEducationModal.tapGotItButton(); - await QuoteView.tapOnCancelButton(); }); it('should switch networks when clicking on send if an asset on a different network is selected', async () => { - const BNB_NAME = 'BNB Smart Chain'; + const ETHEREUM_NAME = 'Ethereum Main Network'; + await QuoteView.tapOnCancelButton(); + await TabBarComponent.tapWallet(); await WalletView.tapTokenNetworkFilter(); await WalletView.tapTokenNetworkFilterAll(); - const bnb = WalletView.tokenInWallet('BNB'); - await Assertions.checkIfVisible(bnb); - await WalletView.tapOnToken('BNB'); + const ethereum = WalletView.tokenInWallet('Ethereum'); + await Assertions.checkIfVisible(ethereum); + await WalletView.tapOnToken(); await Assertions.checkIfVisible(TokenOverview.sendButton); await TokenOverview.tapSendButton(); await Assertions.checkIfVisible(NetworkEducationModal.container); await Assertions.checkIfElementToHaveText( NetworkEducationModal.networkName, - BNB_NAME, + ETHEREUM_NAME, ); await NetworkEducationModal.tapGotItButton(); }); @@ -127,5 +128,6 @@ describe(SmokeMultiChain('Import Tokens'), () => { await TokenOverview.scrollOnScreen(); await Assertions.checkIfVisible(TokenOverview.receiveButton); await Assertions.checkIfVisible(TokenOverview.sendButton); + await Assertions.checkIfVisible(TokenOverview.swapButton); }); }); diff --git a/e2e/specs/multichain/permission-system-connect-to-non-permitted-chain.spec.js b/e2e/specs/multichain/permission-system-connect-to-non-permitted-chain.spec.js index 5541bf13a21..7d5acfe8c50 100644 --- a/e2e/specs/multichain/permission-system-connect-to-non-permitted-chain.spec.js +++ b/e2e/specs/multichain/permission-system-connect-to-non-permitted-chain.spec.js @@ -175,7 +175,6 @@ describe( // Select Sepolia from permitted networks await NetworkNonPemittedBottomSheet.tapSepoliaNetworkName(); await NetworkEducationModal.tapGotItButton(); - await TestHelpers.delay(3000); // another toast to wait for, after switching to Sepolia // Verify network switched to Sepolia await TabBarComponent.tapWallet(); @@ -220,8 +219,7 @@ describe( // Select Linea Sepolia from network selector and update permissions await NetworkNonPemittedBottomSheet.tapLineaSepoliaNetworkName(); await NetworkConnectMultiSelector.tapUpdateButton(); - // await NetworkEducationModal.tapGotItButton(); // commeting this line for now, for some reason the e2e recordings dont currently show a got it modal here - await TestHelpers.delay(3000); // Wait for the toast to disappear + await NetworkEducationModal.tapGotItButton(); // Select Linea Sepolia from permitted networks await NetworkNonPemittedBottomSheet.tapLineaSepoliaNetworkName(); diff --git a/e2e/specs/multichain/permission-system-update-chain-permissions.spec.js b/e2e/specs/multichain/permission-system-update-chain-permissions.spec.js index 8a7f9a62694..a7e42cf11f7 100644 --- a/e2e/specs/multichain/permission-system-update-chain-permissions.spec.js +++ b/e2e/specs/multichain/permission-system-update-chain-permissions.spec.js @@ -67,7 +67,7 @@ describe(SmokeMultiChain('MultiChain Permissions System:'), () => { ); }); - fit('should fallback to Sepolia when removing permission for active Ethereum Mainnet, verifying fallback priority by having both Sepolia and Linea Sepolia as alternative permitted networks', async () => { + it('should fallback to Sepolia when removing permission for active Ethereum Mainnet, verifying fallback priority by having both Sepolia and Linea Sepolia as alternative permitted networks', async () => { await withFixtures( { dapp: true, @@ -87,10 +87,6 @@ describe(SmokeMultiChain('MultiChain Permissions System:'), () => { await TestHelpers.delay(3000); await Browser.navigateToTestDApp(); - // adding delay, - // on artifact recording it shows the toast is stuck, and looks like tapNetworkAvatar is tapped but bottom sheet not coming up yet grey overlay usually behind the bottom sheet was showing, and recording stopped there, yet toast was stuck on screen for 15 seconds anduntil end of recording - await TestHelpers.delay(3000); - // Open network permissions menu await Browser.tapNetworkAvatarButtonOnBrowser(); await ConnectedAccountsModal.tapManagePermissionsButton(); diff --git a/ios/MetaMask.xcodeproj/project.pbxproj b/ios/MetaMask.xcodeproj/project.pbxproj index 604be1574b1..8b691e2f706 100644 --- a/ios/MetaMask.xcodeproj/project.pbxproj +++ b/ios/MetaMask.xcodeproj/project.pbxproj @@ -1380,7 +1380,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1512; + CURRENT_PROJECT_VERSION = 1521; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1418,7 +1418,7 @@ "${inherited}", ); LLVM_LTO = YES; - MARKETING_VERSION = 7.37.0; + MARKETING_VERSION = 7.38.0; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( @@ -1449,7 +1449,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1512; + CURRENT_PROJECT_VERSION = 1521; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1484,7 +1484,7 @@ "${inherited}", ); LLVM_LTO = YES; - MARKETING_VERSION = 7.37.0; + MARKETING_VERSION = 7.38.0; ONLY_ACTIVE_ARCH = NO; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( @@ -1514,7 +1514,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1512; + CURRENT_PROJECT_VERSION = 1521; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1547,7 +1547,7 @@ ); LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)"; LLVM_LTO = YES; - MARKETING_VERSION = 7.37.0; + MARKETING_VERSION = 7.38.0; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( @@ -1576,7 +1576,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1512; + CURRENT_PROJECT_VERSION = 1521; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1607,7 +1607,7 @@ ); LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)"; LLVM_LTO = YES; - MARKETING_VERSION = 7.37.0; + MARKETING_VERSION = 7.38.0; ONLY_ACTIVE_ARCH = NO; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( @@ -1731,7 +1731,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1512; + CURRENT_PROJECT_VERSION = 1521; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1768,7 +1768,7 @@ "\"$(SRCROOT)/MetaMask/System/Library/Frameworks\"", ); LLVM_LTO = YES; - MARKETING_VERSION = 7.37.0; + MARKETING_VERSION = 7.38.0; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = ( "$(inherited)", @@ -1800,7 +1800,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1512; + CURRENT_PROJECT_VERSION = 1521; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1835,7 +1835,7 @@ "\"$(SRCROOT)/MetaMask/System/Library/Frameworks\"", ); LLVM_LTO = YES; - MARKETING_VERSION = 7.37.0; + MARKETING_VERSION = 7.38.0; ONLY_ACTIVE_ARCH = NO; OTHER_CFLAGS = ( "$(inherited)", diff --git a/package.json b/package.json index 162e30ccf6d..e2b84cc8491 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "metamask", - "version": "7.37.0", + "version": "7.38.0", "private": true, "scripts": { "audit:ci": "./scripts/yarn-audit.sh", @@ -605,4 +605,4 @@ } }, "packageManager": "yarn@1.22.22" -} \ No newline at end of file +}