From 4346af6b5c1dae92329246447649295df785c92b Mon Sep 17 00:00:00 2001 From: metamaskbot Date: Thu, 19 Sep 2024 22:50:56 +0000 Subject: [PATCH 01/27] 7.32.0 --- android/app/build.gradle | 4 ++-- bitrise.yml | 8 ++++---- ios/MetaMask.xcodeproj/project.pbxproj | 24 ++++++++++++------------ package.json | 2 +- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 2363b3c882e..ae26808e691 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -173,8 +173,8 @@ android { applicationId "io.metamask" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1432 - versionName "7.31.0" + versionCode 1435 + versionName "7.32.0" testBuildType System.getProperty('testBuildType', 'debug') missingDimensionStrategy 'react-native-camera', 'general' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/bitrise.yml b/bitrise.yml index cae5c05edf1..f8c598290ae 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -1497,16 +1497,16 @@ app: PROJECT_LOCATION_IOS: ios - opts: is_expand: false - VERSION_NAME: 7.31.0 + VERSION_NAME: 7.32.0 - opts: is_expand: false - VERSION_NUMBER: 1432 + VERSION_NUMBER: 1435 - opts: is_expand: false - FLASK_VERSION_NAME: 7.31.0 + FLASK_VERSION_NAME: 7.32.0 - opts: is_expand: false - FLASK_VERSION_NUMBER: 1432 + FLASK_VERSION_NUMBER: 1435 - opts: is_expand: false ANDROID_APK_LINK: '' diff --git a/ios/MetaMask.xcodeproj/project.pbxproj b/ios/MetaMask.xcodeproj/project.pbxproj index 4670dbe9385..1134d2b444d 100644 --- a/ios/MetaMask.xcodeproj/project.pbxproj +++ b/ios/MetaMask.xcodeproj/project.pbxproj @@ -1273,7 +1273,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1432; + CURRENT_PROJECT_VERSION = 1435; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1310,7 +1310,7 @@ "${inherited}", ); LLVM_LTO = YES; - MARKETING_VERSION = 7.31.0; + MARKETING_VERSION = 7.32.0; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( @@ -1338,7 +1338,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1432; + CURRENT_PROJECT_VERSION = 1435; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1373,7 +1373,7 @@ "${inherited}", ); LLVM_LTO = YES; - MARKETING_VERSION = 7.31.0; + MARKETING_VERSION = 7.32.0; ONLY_ACTIVE_ARCH = NO; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( @@ -1401,7 +1401,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1432; + CURRENT_PROJECT_VERSION = 1435; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1434,7 +1434,7 @@ ); LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)"; LLVM_LTO = YES; - MARKETING_VERSION = 7.31.0; + MARKETING_VERSION = 7.32.0; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( @@ -1462,7 +1462,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1432; + CURRENT_PROJECT_VERSION = 1435; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1493,7 +1493,7 @@ ); LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)"; LLVM_LTO = YES; - MARKETING_VERSION = 7.31.0; + MARKETING_VERSION = 7.32.0; ONLY_ACTIVE_ARCH = NO; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( @@ -1624,7 +1624,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1432; + CURRENT_PROJECT_VERSION = 1435; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1661,7 +1661,7 @@ "\"$(SRCROOT)/MetaMask/System/Library/Frameworks\"", ); LLVM_LTO = YES; - MARKETING_VERSION = 7.31.0; + MARKETING_VERSION = 7.32.0; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = ( "$(inherited)", @@ -1692,7 +1692,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1432; + CURRENT_PROJECT_VERSION = 1435; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1727,7 +1727,7 @@ "\"$(SRCROOT)/MetaMask/System/Library/Frameworks\"", ); LLVM_LTO = YES; - MARKETING_VERSION = 7.31.0; + MARKETING_VERSION = 7.32.0; ONLY_ACTIVE_ARCH = NO; OTHER_CFLAGS = ( "$(inherited)", diff --git a/package.json b/package.json index e0cd11921f7..b82cfbb8c55 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "metamask", - "version": "7.31.0", + "version": "7.32.0", "private": true, "scripts": { "audit:ci": "./scripts/yarn-audit.sh", From acae7b0e6ca4cb9f6ddb988ba96a7f1d333684e2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 19 Sep 2024 18:47:30 -0700 Subject: [PATCH 02/27] chore: chore/7.32.0-Changelog (#11334) This is PR updateds the change log for 7.32.0 and generates the test plan here [commit.csv](https://github.com/MetaMask/metamask-mobile/blob/release/7.32.0/commits.csv) --------- Co-authored-by: metamaskbot Co-authored-by: Cal-L --- CHANGELOG.md | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b50b2835a3..dbe1547d548 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,96 @@ ## Current Main Branch +## 7.32.0 - Sep 19, 2024 + +### Added +- [#11093](https://github.com/MetaMask/metamask-mobile/pull/11093): feat: 7.31.0 (#11093) +- [#10294](https://github.com/MetaMask/metamask-mobile/pull/10294): feat: create redux slice for featureFlags (#10294) +- [#11310](https://github.com/MetaMask/metamask-mobile/pull/11310): fix: quick fix on feature flag & notification state (#11310) +- [#11200](https://github.com/MetaMask/metamask-mobile/pull/11200): fix: add feature flag on profile sync (#11200) +- [#11314](https://github.com/MetaMask/metamask-mobile/pull/11314): feat: reject connection properly (#11314) +- [#11132](https://github.com/MetaMask/metamask-mobile/pull/11132): feat: Add performance tracing infrastructure (#11132) +- [#10061](https://github.com/MetaMask/metamask-mobile/pull/10061): feat: new receive flow (#10061) +- [#11174](https://github.com/MetaMask/metamask-mobile/pull/11174): feat(2796): behind feature flag permission settings multichain 2of2 (#11174) +- [#11019](https://github.com/MetaMask/metamask-mobile/pull/11019): feat(2793): mocked UI screen displaying multichain dapp permission summary 2of2 (#11019) +- [#10988](https://github.com/MetaMask/metamask-mobile/pull/10988): feat(2808): add a mocked UI checkbox list that will later allow adding the ability to edit network permission (#10988) +- [#11168](https://github.com/MetaMask/metamask-mobile/pull/11168): feat: add pooled staking input flow screen (#11168) +- [#10964](https://github.com/MetaMask/metamask-mobile/pull/10964): feat: build your earnings component stub in eth token details (#10964) +- [#11117](https://github.com/MetaMask/metamask-mobile/pull/11117): fix: add feat flag (#11117) +- [#11051](https://github.com/MetaMask/metamask-mobile/pull/11051): feat: add brand evo font files (#11051) +- [#11285](https://github.com/MetaMask/metamask-mobile/pull/11285): Feat/notifications add analytics (#11285) +- [#10755](https://github.com/MetaMask/metamask-mobile/pull/10755): feat: ledger account selection screen add hd options to sync with extension (#10755) +- [#11195](https://github.com/MetaMask/metamask-mobile/pull/11195): feat: add AppState dependency to load notifications (#11195) +- [#11175](https://github.com/MetaMask/metamask-mobile/pull/11175): feat: add product announcements toggle (#11175) + +### Changed +- [#11148](https://github.com/MetaMask/metamask-mobile/pull/11148): chore: remove animation and add new splash screen (#11148) +- [#11306](https://github.com/MetaMask/metamask-mobile/pull/11306): chore: update @sentry/react-native to version 5.33.0 (#11306) +- [#11144](https://github.com/MetaMask/metamask-mobile/pull/11144): test: E2E Mocking Setup For Detox Tests (#11144) +- [#11212](https://github.com/MetaMask/metamask-mobile/pull/11212): chore: Update CI workflow triggers to support release branches (#11212) +- [#11243](https://github.com/MetaMask/metamask-mobile/pull/11243): chore(js-ts): Convert ModalNavbarTitle to TypeScript (#11243) +- [#11213](https://github.com/MetaMask/metamask-mobile/pull/11213): test: Appium separate and optimize app launch time measurements (#11213) +- [#11264](https://github.com/MetaMask/metamask-mobile/pull/11264): chore: remove triggers for actions not needed during the merge-queue CI (#11264) +- [#11222](https://github.com/MetaMask/metamask-mobile/pull/11222): chore: add bitrise document link to the bitrise failed comment (#11222) +- [#11145](https://github.com/MetaMask/metamask-mobile/pull/11145): chore: update performance for new allocation (#11145) +- [#11184](https://github.com/MetaMask/metamask-mobile/pull/11184): test: remove notifications launch arg in E2E (#11184) +- [#11186](https://github.com/MetaMask/metamask-mobile/pull/11186): ci: prevent detox E2E lock failure (#11186) +- [#11141](https://github.com/MetaMask/metamask-mobile/pull/11141): chore: update express for all the packages (#11141) +- [#11124](https://github.com/MetaMask/metamask-mobile/pull/11124): docs: Update Appium documentation (#11124) +- [#10865](https://github.com/MetaMask/metamask-mobile/pull/10865): chore: update eslint v^8.44 (#10865) +- [#11096](https://github.com/MetaMask/metamask-mobile/pull/11096): test: detox black list gas api endpoint (#11096) +- [#11246](https://github.com/MetaMask/metamask-mobile/pull/11246): chore: Remove `eth-sign` (#11246) +- [#11220](https://github.com/MetaMask/metamask-mobile/pull/11220): chore: Update package @blockaid/ppom_release to version 1.5.3 (#11220) +- [#11244](https://github.com/MetaMask/metamask-mobile/pull/11244): chore(js-ts): Convert useInterval.js to TypeScript (#11244) +- [#11089](https://github.com/MetaMask/metamask-mobile/pull/11089): chore: add staking team to codeowners file (#11089) +- [#11049](https://github.com/MetaMask/metamask-mobile/pull/11049): chore: update balance design (#11049) +- [#11011](https://github.com/MetaMask/metamask-mobile/pull/11011): chore: Capture currency change in MetaMetrics (#11011) +- [#10468](https://github.com/MetaMask/metamask-mobile/pull/10468): chore: Capture custom rpc url in `trackEvent` (#10468) +- [#11207](https://github.com/MetaMask/metamask-mobile/pull/11207): chore(deps): Bump `@metamask/base-controller` from `^6.0.0` to `^7.0.0` (#11207) +- [#11235](https://github.com/MetaMask/metamask-mobile/pull/11235): ci: avoid running release pipeline on every commit to the release branch (#11235) +- [#11094](https://github.com/MetaMask/metamask-mobile/pull/11094): chore: chore/7.31.0-Changelog (#11094) +- [#10788](https://github.com/MetaMask/metamask-mobile/pull/10788): chore: Add `@metamask/selected-network-controller` & integrate (#10788) +- [#11084](https://github.com/MetaMask/metamask-mobile/pull/11084): fix: locks api spec version for api spec tests (#11084) +- [#11122](https://github.com/MetaMask/metamask-mobile/pull/11122): test: e2e for auto-lock (#11122) +- [#11143](https://github.com/MetaMask/metamask-mobile/pull/11143): chore: bump react native webview to 14.0.3 version (#11143) +- [#11284](https://github.com/MetaMask/metamask-mobile/pull/11284): chore: add notifications state awareness inapp badge (#11284) +- [#11209](https://github.com/MetaMask/metamask-mobile/pull/11209): chore(runway): cherry-pick fix: freeze during swap with approval (#11209) +- [#11157](https://github.com/MetaMask/metamask-mobile/pull/11157): chore(runway): cherry-pick chore: bump send for all the packages (#11157) +- [#11082](https://github.com/MetaMask/metamask-mobile/pull/11082): chore: bump network controller 20.0.0 (#11082) +- [#11095](https://github.com/MetaMask/metamask-mobile/pull/11095): chore(runway): cherry-pick fix: Intermittent Display Issue of Fiat Currency on Main Wallet View (#11095) +- [#11181](https://github.com/MetaMask/metamask-mobile/pull/11181): chore(runway): cherry-pick fix: fix check token balance is zero (#11181) +- [#11208](https://github.com/MetaMask/metamask-mobile/pull/11208): chore(runway): cherry-pick chore: update performance for new allocation (#11208) +- [#10821](https://github.com/MetaMask/metamask-mobile/pull/10821): chore(deps): bump `accounts-controller` to v18.1.0 and `keyring-api` to v8.1.0 (#10821) + +### Fixed +- [#11302](https://github.com/MetaMask/metamask-mobile/pull/11302): fix: cp & resolve merge conflict (#11302) +- [#11130](https://github.com/MetaMask/metamask-mobile/pull/11130): fix(action): add a workaround for known bots (#11130) +- [#11173](https://github.com/MetaMask/metamask-mobile/pull/11173): fix: dset version (#11173) +- [#10899](https://github.com/MetaMask/metamask-mobile/pull/10899): fix: Android crash when svgs use the " html entity (#10899) +- [#11126](https://github.com/MetaMask/metamask-mobile/pull/11126): fix: Skip sonar cloud gate in step instead (#11126) +- [#11121](https://github.com/MetaMask/metamask-mobile/pull/11121): "fix: Add new job to verify ""All jobs pass"" job for required PR check (#11121)" +- [#11266](https://github.com/MetaMask/metamask-mobile/pull/11266): fix: notification permission flow (#11266) +- [#11252](https://github.com/MetaMask/metamask-mobile/pull/11252): fix: notification permission request message (#11252) +- [#11155](https://github.com/MetaMask/metamask-mobile/pull/11155): fix: android crashing on date formating Intl usage. (#11155) +- [#11137](https://github.com/MetaMask/metamask-mobile/pull/11137): fix: notifications bugs (#11137) +- [#11110](https://github.com/MetaMask/metamask-mobile/pull/11110): fix: accounts notifications switch (#11110) +- [#11146](https://github.com/MetaMask/metamask-mobile/pull/11146): fix: update nativesdk with improved concurrency handling (#11146) +- [#11165](https://github.com/MetaMask/metamask-mobile/pull/11165): fix: freeze during swap with approval (#11165) +- [#11161](https://github.com/MetaMask/metamask-mobile/pull/11161): fix: blockaid loader on confirmation pages (#11161) +- [#10989](https://github.com/MetaMask/metamask-mobile/pull/10989): fix: closing of gas info tooltip (#10989) +- [#10348](https://github.com/MetaMask/metamask-mobile/pull/10348): fix: confirmations UI adjustments (#10348) +- [#10842](https://github.com/MetaMask/metamask-mobile/pull/10842): fix: app crash due to minimal input must be string error (#10842) +- [#11112](https://github.com/MetaMask/metamask-mobile/pull/11112): fix: update token details monetization button (#11112) +- [#11172](https://github.com/MetaMask/metamask-mobile/pull/11172): fix: fix check token balance is zero (#11172) +- [#11087](https://github.com/MetaMask/metamask-mobile/pull/11087): fix: Intermittent Display Issue of Fiat Currency on Main Wallet View (#11087) +- [#11176](https://github.com/MetaMask/metamask-mobile/pull/11176): fix: switch from bundled to url EE (#11176) +- [#11281](https://github.com/MetaMask/metamask-mobile/pull/11281): fix: Fix the styling issue of link in SearchingForDeviceStep component (#11281) +- [#11265](https://github.com/MetaMask/metamask-mobile/pull/11265): fix: notification account syncing (#11265) +- [#11218](https://github.com/MetaMask/metamask-mobile/pull/11218): fix: close icon on notifications list screen (#11218) +- [#11193](https://github.com/MetaMask/metamask-mobile/pull/11193): fix: ItemMenu crash using dayjs (#11193) +- [#11098](https://github.com/MetaMask/metamask-mobile/pull/11098): fix: badge count and ui polishing (#11098) + + ## 7.31.0 - Sep 6, 2024 ### Added - [#10747](https://github.com/MetaMask/metamask-mobile/pull/10747): feat: 2805 grant permission to network with missmatching rpc url (#10747) From 7a7f20233f68275c142f9fc860c0158e3efb7f0e Mon Sep 17 00:00:00 2001 From: tommasini <46944231+tommasini@users.noreply.github.com> Date: Fri, 20 Sep 2024 18:36:08 +0100 Subject: [PATCH 03/27] fix: splash screen image on android (#11346) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Splash screen on android needs to be a smaller image ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** Dark mode https://github.com/user-attachments/assets/74359131-dabb-4c1d-81f5-683ba055f95c Light Mode https://github.com/user-attachments/assets/4efca273-449b-4809-9593-0683d1b0a5b4 ### **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. --- .../app/src/main/res/drawable-hdpi/fox.png | Bin 0 -> 7927 bytes .../app/src/main/res/drawable-ldpi/fox.png | Bin 0 -> 4177 bytes .../app/src/main/res/drawable-mdpi/fox.png | Bin 0 -> 5535 bytes .../src/main/res/drawable-night-hdpi/fox.png | Bin 0 -> 7466 bytes .../src/main/res/drawable-night-ldpi/fox.png | Bin 0 -> 3877 bytes .../src/main/res/drawable-night-mdpi/fox.png | Bin 0 -> 5222 bytes .../src/main/res/drawable-night-xhdpi/fox.png | Bin 0 -> 10070 bytes .../main/res/drawable-night-xxhdpi/fox.png | Bin 0 -> 14779 bytes .../main/res/drawable-night-xxxhdpi/fox.png | Bin 0 -> 19880 bytes .../app/src/main/res/drawable-night/fox.png | Bin 24194 -> 6000 bytes .../app/src/main/res/drawable-xhdpi/fox.png | Bin 0 -> 10510 bytes .../app/src/main/res/drawable-xxhdpi/fox.png | Bin 0 -> 15518 bytes .../app/src/main/res/drawable-xxxhdpi/fox.png | Bin 0 -> 20689 bytes android/app/src/main/res/drawable/fox.png | Bin 60919 -> 6511 bytes android/app/src/qa/res/drawable-hdpi/fox.png | Bin 0 -> 7927 bytes android/app/src/qa/res/drawable-ldpi/fox.png | Bin 0 -> 4177 bytes android/app/src/qa/res/drawable-mdpi/fox.png | Bin 0 -> 5535 bytes .../src/qa/res/drawable-night-hdpi/fox.png | Bin 0 -> 7466 bytes .../src/qa/res/drawable-night-ldpi/fox.png | Bin 0 -> 3877 bytes .../src/qa/res/drawable-night-mdpi/fox.png | Bin 0 -> 5222 bytes .../src/qa/res/drawable-night-xhdpi/fox.png | Bin 0 -> 10070 bytes .../src/qa/res/drawable-night-xxhdpi/fox.png | Bin 0 -> 14779 bytes .../src/qa/res/drawable-night-xxxhdpi/fox.png | Bin 0 -> 19880 bytes android/app/src/qa/res/drawable-night/fox.png | Bin 24194 -> 6000 bytes android/app/src/qa/res/drawable-xhdpi/fox.png | Bin 0 -> 10510 bytes .../app/src/qa/res/drawable-xxhdpi/fox.png | Bin 0 -> 15518 bytes .../app/src/qa/res/drawable-xxxhdpi/fox.png | Bin 0 -> 20689 bytes android/app/src/qa/res/drawable/fox.png | Bin 60919 -> 6511 bytes 28 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 android/app/src/main/res/drawable-hdpi/fox.png create mode 100644 android/app/src/main/res/drawable-ldpi/fox.png create mode 100644 android/app/src/main/res/drawable-mdpi/fox.png create mode 100644 android/app/src/main/res/drawable-night-hdpi/fox.png create mode 100644 android/app/src/main/res/drawable-night-ldpi/fox.png create mode 100644 android/app/src/main/res/drawable-night-mdpi/fox.png create mode 100644 android/app/src/main/res/drawable-night-xhdpi/fox.png create mode 100644 android/app/src/main/res/drawable-night-xxhdpi/fox.png create mode 100644 android/app/src/main/res/drawable-night-xxxhdpi/fox.png create mode 100644 android/app/src/main/res/drawable-xhdpi/fox.png create mode 100644 android/app/src/main/res/drawable-xxhdpi/fox.png create mode 100644 android/app/src/main/res/drawable-xxxhdpi/fox.png create mode 100644 android/app/src/qa/res/drawable-hdpi/fox.png create mode 100644 android/app/src/qa/res/drawable-ldpi/fox.png create mode 100644 android/app/src/qa/res/drawable-mdpi/fox.png create mode 100644 android/app/src/qa/res/drawable-night-hdpi/fox.png create mode 100644 android/app/src/qa/res/drawable-night-ldpi/fox.png create mode 100644 android/app/src/qa/res/drawable-night-mdpi/fox.png create mode 100644 android/app/src/qa/res/drawable-night-xhdpi/fox.png create mode 100644 android/app/src/qa/res/drawable-night-xxhdpi/fox.png create mode 100644 android/app/src/qa/res/drawable-night-xxxhdpi/fox.png create mode 100644 android/app/src/qa/res/drawable-xhdpi/fox.png create mode 100644 android/app/src/qa/res/drawable-xxhdpi/fox.png create mode 100644 android/app/src/qa/res/drawable-xxxhdpi/fox.png diff --git a/android/app/src/main/res/drawable-hdpi/fox.png b/android/app/src/main/res/drawable-hdpi/fox.png new file mode 100644 index 0000000000000000000000000000000000000000..4b47a895d5642699da062c9fe658113c860e8a92 GIT binary patch literal 7927 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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-ldpi/fox.png b/android/app/src/main/res/drawable-ldpi/fox.png new file mode 100644 index 0000000000000000000000000000000000000000..8f67706879161c9e87eebf3d9ac5db0f14f9072b GIT binary patch literal 4177 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 bz>NJD`C+z`jsDs200000NkvXXu0mjfZ4~Ot literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-mdpi/fox.png b/android/app/src/main/res/drawable-mdpi/fox.png new file mode 100644 index 0000000000000000000000000000000000000000..847df51137f146ab30dee84ff91726a1d9c5a49b GIT binary patch literal 5535 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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-night-hdpi/fox.png b/android/app/src/main/res/drawable-night-hdpi/fox.png new file mode 100644 index 0000000000000000000000000000000000000000..9b6a906a050bbeb068483e26554019a4b185db3c GIT binary patch literal 7466 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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-night-ldpi/fox.png b/android/app/src/main/res/drawable-night-ldpi/fox.png new file mode 100644 index 0000000000000000000000000000000000000000..95869e44a8e44e67aceedf03121762e97feb1a31 GIT binary patch literal 3877 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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-night-mdpi/fox.png b/android/app/src/main/res/drawable-night-mdpi/fox.png new file mode 100644 index 0000000000000000000000000000000000000000..33b9efad77ba82c52eb23fa680af28a8417efe18 GIT binary patch literal 5222 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 WcLi;##^U{35KrrgzFG|o9`%1QcjLhT literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-night-xhdpi/fox.png b/android/app/src/main/res/drawable-night-xhdpi/fox.png new file mode 100644 index 0000000000000000000000000000000000000000..99f7eb5f2a8dd035e2b6c2d00437a2374e4d8213 GIT binary patch literal 10070 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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-night-xxxhdpi/fox.png b/android/app/src/main/res/drawable-night-xxxhdpi/fox.png new file mode 100644 index 0000000000000000000000000000000000000000..5ca07c345bc6190e2c5ce6fd94d3e2cd90f39629 GIT binary patch literal 19880 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 Ms;sG0r(hNFe|hzdsQ>@~ literal 24194 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 UWaAU;_eD4bu!>BL6eQ&T0P*nJN&o-= literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-xxhdpi/fox.png b/android/app/src/main/res/drawable-xxhdpi/fox.png new file mode 100644 index 0000000000000000000000000000000000000000..c2a417d3f95ccd2f5868a60af9c3508b6c1c8fee GIT binary patch literal 15518 zcmdsdWmjBV(=8e-!QF$qyOUrIp>cP2X}oa_2ZzR;paFurCjo*4cXua1aEEuF^L)7f z;W9=KM)zJ)tEy(Ls#!Zq6)gV_ofI7o4(=UDK}H=84t@yuh@!p$erdMywE+I1xhm+n z!@<25`u77bo5pPl{0Q%^F8=|pdW`%K_=033tt<@(SC@eCWQGg}C&mksk=FEqKUwvQ zHJlHi`n#Tc%aOhVBa~$+?$;CbrGv0le4XZN9(iwATov?(~7(7`m)X;I++2a?u?;R*l3%4m_^?LcbQ)Yd5da_6);eV9M0?)K} zl8mLjrgumgc(3kvZt^m@i;RHHhp?ObE#-In8Z@ToH4_o}p)bTgPC+t#zJ7ew{V0_b zzIet!Y4`q9QR`Q`s{`+9PnMv766LCDa8@aWuvV8ky{d7%6g1$7oPRv+jvC@wqVqB$ zA2ZNG^fUzM|sHV|3;Nkc0+=tt>V}DoL?)a~Y z1!A#uAz*R|S2c11%($23n?eC%Es0i}D=|e;MufeX+dSXrx9|#|r_0;d_BXsW4fpQX ztE;aLgSsXQXSIW#_O40TdulbSX3UT|UI)8%b)n`lPUK0CT3ROsKDuYMe;ePUgBr71QH;eu(dMfE;H z@%6mi4T`=F>=BagalR+a-Xj6k=&r`A+`T-FnnB_*6xt9%i-#pln5l*ja$}4G_h>uZ zugb$BCe=!iM@&B4F^uwpe@dSl=!k}m{6j>dEO}FMB)X5954~ zGw=J#4omnx_ymj#{@*x!O<){mV*b5t(#_f6REqtU z`lv%@SyH?*W6x!5@{04#vB{%!jJAG&3H)4C^<=V1wQ)e2lrSm`9bIHrBq{;WLq^Z1 z1iL+wDpyGSVuqNELK_RZw7N7SCRHmvrs|g*NWz7|jD^cWE!<_gTc%eY1Q#XrwOdEQ8&tpY__o!jKA|J~Qp+i`W7)_qn%D%ZBsDU1;l21D%2SZznr1xYj1i zjmG%gPO`^YMD$B+n+{Obag6{9g~wKCN~JHLD!2OjAhr2JXtZ9UtoRLA5J{<-h*r=~ z)Yva$7;>69+o;Ss*VGCuJ2Jc9^`@?iQsBHttqQa(^<#eaXYlKPorlM^0hem&~- z94Xk5g#Vvz&4GnC8{6E-#RL?w6qOAtG96#q5KW%ectVPp_DC3`ii8H<6&ucmDyOGT zmaG2Ec;<)3AL2b;hAA$fru19Ytsk=)i$EmzIN2SuYz*gFX-0_;Nv>O}J?v?^^h=TK zRc+M#rH8d}h(Rn{eVflKW@R7H2xQd~x;CGzYG#C6YKu>q-VXzw8S40n5n+6?67T<&hqhxUh8lrk@0{V$mBf zHQaG5!U}9hzCq{DL6r>90ddZ&QD<$tO$qDYniRAFe}ShRN;%eG z#5tyU-ny}P-88pQoxq8d6J1Nv&dA~x7YJk@59nd`3??TnHhjN}O{W2#<}X*TO8zUI zib$UzRycQPEo0A~il0r%qpH_Y^36QUBODT%kpf4k@J2v%RIj(1xrC)R-a+cWmRQvE8vxK(~Y!c6*?72WUoSZUeJ?-D=N;1rXFT1N9Trys2qcb4*06y|wFhg!A}y;$HJ&OXFQ z+RjDx3;QY}(y1};=5v3~u{_EZ@|PL`9VPLX#3SDN??Q(Y&M;hVr10`nR^6S>`R`Xv zb7tnzX;eSoPVWEOS@&tubGe=(BY*e1jMFe6gL;+=_DM93Lvx^pxT=3Mtb=BA|YUfJI2>1UvaI5$JX$V`p9pi z?+t61j1vj?`-wdGLh#73bOaNXun#_T_p@i;BGqS-Kl3M>jmWz}#)6)g_xych+!=nG z(v>5PfLNqPl-3m8&%+5Ql`p>X4DzS))MSzi@M3X5`|M67pUo=0%GX$veUHo~+=!%) z%;?Gy;wp-%*G7a7dlgpQVrncVSAIGE7P z^FMHGBtb^$YT_JGW~4T1)=p>Iglx1*?gzL<4u=CvHN&XOKeI5$FMolIPf%Bw7#Q7W z<)LsB4UOk!_#KMnYn5xK)@!s|wn3xdCV`qRiaXFc;2 zd<;QnV3m8gA&;YN-V#*&Q>HB-Q83s_Hew*&=&o6Lopn(gZ&pvYMt8&i`)PW_zx7R% zk>B;RbLMwV(SXe(gb-Zdf(u7Q)}7`Lo!0wZk z+MlVq{G9WYz>_P!Va6xCnNw@=(flk%O!HzRhFT!ecq0dkzEf;s7z=C?`5|MihUJ|A zQ3A}SjAdZn1w@hUEpo7I82_KAFN8>-l7l-}(Z5qn;)f0lP$$c>W3lKR;JNVY5LuNQ zwgX7R8CtI2J5>4sn@+3Hj!DQhBnI00bzto7@<**@04p;v~n zN^t`{fet|QVQVSRkn(sgx|dMXQzVU4q0oRhx6nXT>AwMYs?;&{dvZ_r6y=E4Qi)0L zQlLF4d8hEINR&eP+&xmT#%~sZ?tHn=6AfDYwhRTtd<1{XGNun~F!2wzADan}ciaP( zgtNbS(4XB*W~nw_-YC|*NSZh?*D<>hgH2kaElSO6kz}}6G^JvrhLQ5#ijFt-ksG^U zv&pANXE-nj>oUM6P%|P8J}9LxU7DZb{Fr1^?)8X>PIT6Kn}z;qfbD40sa1}?9C6%- z_i}kO@LAH>P#!XtQhOPh1&ALeoO*cvw4>A0&|UsDa-NoKZiwH?*|061MNJ6<*&+k*-zXGv zrKCYc8dtdx+f`Xlj_L}I#PC{sN|O5*?eY+C!4ZJNyY3W=ppRDrqw4#8fB2T4*WZDzH$nR?TcJtpb;i1T1XoltH# z9xBoZ{e+eXc;m|LTej)FIR?44HB9stq&20bZ#SrE2Z)o<7 z`!_j?7Ct{jRQZQ{Am}}x@NM{X#p|6#_C6KkBww!Ly-z5e_tW9cHyc%*-lgZ z2TB$PD&c=ZDf3wZ>?r_P19`VkTl0gCWJ~R~EQI(O`Mk$gZx2BbwU%X~4u+%h_tO`7 z$c8vw7m^*AV!es|N%14FE0M&rg-Vn~+mtM-Gbxe}fqdeQm(THuDXA@FJ0yQ0c|fd0PV%w*BL0FF zgX-Z%sY%4Qg$4ak8~NSbr=#44tz~rP5w?j<=pgmE$dG@Zk+%=$kb!rn)|SZTXmQVr ze)5`0OquY9&>24VAS4Ic+3=)da|x5hps!`OP)hZ~BW-8wzcDmYZk{$1(KJ%l4U_TT zAD-Xxi~b1Afu=myb=^y=jbDo@q6|aN-%`Eq{9HLnckq6RmSP5?UPdfR#5ye4 zjz#yRZ*oU$WL18`$5dgOHvxy~-#i>Rsd%Bo6#{cK8Io%VrcbXjfyAQ}y(>8MSf(CcEd`a|?a*dwR-L5p;-U?-l!j61CJ5k*i43$Ls+bp>SE z3ci@HZ1PS14;#Ma&FWh8s#&5wt{BZsJMQO5Ltw$D0h?30`lY$UIp2qUW`1QqeWbzU z|Ej13F4fZwqj` z!q+NF$_@abUwLzT&fKqkX#v!~H1ToiCjy3;N?BxBw)%Sz9O`^}RZ4X4;AXXMhCQQ- z6kM^Xd!TyG^ZtvfDEaw^7H27Ydn;ORjlaKKaifMOlOExABnv#Y9Bi0(PT9>O#H0X! zfwyeZM%MRw?f!0UDKi#a;@_B5Pd`-!K^|Uwa?QKQ{TJEgOb3gMV9$6;;|0iOHUJaA zg$5`h-YrepE#ZQ`(P2*bwZyaybntF{DE5x2L&nhHST4JFniN3mS!MbMI^;v;t(`-z zTKX~lD8P5OOTwsKb|yv4_Ncx4YWMYdZ2CVP%?u5{$QqUHgFamHl8U>wFoYJ$J@yUO zS$=chf3qH^{WOGwN!YQsgkJ*GklHLBcGXuu$gNJ&z)jf?8^8Vk?9W>YIIezIdl2dPk z8{(sVw|D7~L2K|g)2S@YV^a8U1zBaeLM|58vXyUiA=5E%!%wt*PmvQONCV*8U@ zi00tBPNfm{(|hWOqc>%G$@tXUi79YHtqFhZdF4f||Lj}cIuRYZapylZ+*o{w9{b>l+U!a#?;29Go#@yG{mJHE@LnvnzfxA*5{Q!KC$%H zjO9_dkbtqGVZzvA8!O4Gvq((Y=@z6Ck(8_p$c;v*fU$~|$tAPbJ2i&rqWwY@%z4S8 zK45ZXXX}DoAaxz)MyL&@Mr&RZg8ZEm7lqYD5SHmc`{;VJ1{cj$4BPP|=!m%DqIph^ z-f;3lul21r8*J?PfjL$6-=`kZhf|A@6n=$BrIo__vIz8|5=t_%9uI*q+?%d9AE!Gu zg^&=XaoMgPp9h%U3ylsF=^fxO*jb_vcIBnjs@1$O!T%>}R;d;fyw4TWM-gt00HbO> zJTR8GADZyhf#ziTaMVzm)CIM|PnnZ-W-45iL>~I!Pk?EJK`7f{QFK{gWEN6}ARmprRdQ+lz~t4-fY<1!4}o>K`~f*a)8SR=p5ei-#~oY5vCfDj{?ZZ{baCeK zbNn{)R&G7Qre1mvNgB6ViWh#N@Vr0Fg}%gg->XPUvOV=0pQ(3;0o5k12IaO&1E0ev zo5Y`joe-VopNOdZ4y@7JgX&y%#pulVW+j7)Su|55Ian=7UA?Nb%{)>hjJ=sB3N=M+ zm}ej_ARB$o(3v>#=b8pA%(SB_s!bgBMpCfD>&EucvHp}#y_yRR-P<&|ShFdsUN1NL zUgBh7txR1l4?6HwzXqZ&n4P>2$nWkV*`H2ePN;ik7$HjWBc8NR!GWD(d(e?d{W; z>rn&`YZ;BU|7c$`p$qrxy~5AW#U=XkqxkJ)zr}=9$HwSHxprt2LoQ5)7{yjaNU8N` zc@AXsJ?JwoWD?|9mV!tJo4f$?{s|_(5`L8@`^&xVP9b zl^+K<;~Qz*H?S;w3Vx9+%YR2Rx&I^;d)T!1OL!7+;DJlm@A{7bi zsadOjG^&cC zBi8(jx;zs=m?{2iEpw<0qxW9CN}TH~lfMOHW)9v3?N>TLeND4HdnPw=QD|35f8b(Y zx@7c=dOhlPb0TcdSC3uYvvZq{;TC(GXucU3*4T;mV35t_}&?l=9Fpszp`@uC}j1yHR{F< z*T50dN>Yz-%+E}2h@==_F=wmLlj|xxVPL_JEo3(#HFRI$aED6+!_#umX}MU6Xq>V@If^nR;aSQ<}N6wX$inCD$ea7*4F`pu!F z`&oC)*zWfAzd)BIrjES@4jB(?$zu& zTU4;{+Z^NAapgVSzjmbYbdl0iXH2mCB6%4pF*c6dlRy7R9kLIW+29L@|4)0VpEZw} zjAQ1H@Y!lw6bD~&JM_aaE_sK^bF)Y2O@CKR%I%x<7;jA`SY#J~ieyE>m*4C%07Gh3 zvjCY=%7_*~P##2Jje+f?L<8kAi;9LmAz0syZsn3~LGRXbL#=}UO+H#OuUs|r>#3ty zbV`7toJqNSPHpAMzbiL$0dq_xwR~BPA%Q`~zcD#uG{HU7OpAqRIioY4@Hvas(mFVv&vjO3>ngZ}b;!YbWdbYkTZ zjds5`>eUq=JS#)zdUPc?r>Y1P-&1n0b!sf&;tNxBI8-Z}ZKyGz?B(L|beU4mvqM#>qgZiX*xs@aCigbXwV>{OcKS)!7Z zXHZW41s=YOaS;6oI7kq$vX7;SOmx%23N8Misn8UkMZqDzz~vNCnd~U=GO5Y2g@@?8 zRnQ0cD4+%DM9qKD0iDZQSXgT;h`R0IVzU(zk7kV*jgYZCeom=$1JwIPy~0y7rTjY` z{+SGS7Qg&XN$9^%BU38%|9z@oF}q{UT1DNj*V!v8s)o$|BFY=lcx~&6!K{-Ca9$sp z08et~Be~(hCbT&RbNuYd1?m(^>fH)_BoOk~EPH-g;jGvOA4V!r1%H5n**(zs5dju1 zum`r)bWes5p6$@;y?iMbk6raN1*Bmclz z2{H-^E@Mq>hKzpr0n6x~XmDR&XjOwy-sT=Vf)G z@4hhD)qNB+4sL07Il}_;UERIeYwoj~K>nBhuLwpofd=T2e{e`qjpzh7@NQDQgBH)) z+t`&PVKx237{8Uv6(%96bKQhc6|?WGE3Nc}B`Ucz)4V=C&9d>2FtCOKOVKM0{X+Ai z#O?>S>dml@%=YcDmnzYSs7LUd_rL5r6mj7swNhg8y{&X1^;@^i8ZEdb{INFB_;GPQw=D{lm@?7swwx!Met!4#~yRRqg zlR~CF$<#MQQhE_q#2)~c(f6|7pTt5ve4jG0)jLG@9-@p+xfL1TX;no7uB=eAn6mXY15tmXpUgSNE>y8cPOp>K zX)<2J*VAx4FSM~Cz=gwRl<-$!5zNkgXBgR8#*cdqSH_8;K~aa zamUqBD|LFdg8FFtdzD3i+W|bX==cJk0x8q$T7ThZ%!2Tn; zsZN<5V;06n9VCnY{I|H=L4amc1<>^4#@PBKG_>md3$oNCv|As%k}^v4TxwFpw015c zdBE5u0)kT_KkWHD9ix112lBn&FidsFVru}k0-U{&i8zzjo&ZY7W*jGEB)dn z#eQE(0)OGatd-^Y^|NMW7H}dugY_Y4!93=yx0VRse(hYb)|7kT*%mUbK`sS-z^P%h zDE9@QG&b9v>v< zOmRF~+=9$ni92eGUP)d}%YJSbSn9Sy&r65ab_0$!(+@u|&c-Iro<^PZPr|dm-5-&; zUpZh^Pt^E(cy6!zgtxKmftPdt-UM9X-IdrHOQ4ZvvA#RhmK~Z3u*u2qVsw8`G~xP? z$|@)PJS=^_cy?Nt{JLK~fk@ZEd*^p~wM;{OIhfkgxOHWp0S|-JUaEhhSH8ReO-va6 z%h|xnAIF5ii*4-WnbvW|MC42^r!S9JH5_Y_#H+F=2^=m(>;86Fs~NABU&#~`-2DC~ z3XO_Pur^ozujBq{)mtvpPDjw~y$aL8J&FDLEtcI0)ssMz#7m`zlXy4DTT0Mc=NVI# zR-l7x{#E8Gere9{<`Bb7Hmj^uJAF^Hat5s=G9(Ol82a|9sGd>KXFAJLBeG*mQSER| zzTO{o8ZUgFm=Zj9YaZKkPSF}gi-k@dUK)=BL4mGACeMTf#;y5jaj7EiKCHy&gfgDlZb%YgI zSUGipAbziN!+k;fkE2ACdVAGRCc|q_@0A^NM;)G#loqsXc=`BHfJj5h37fm_Zj_#K z3=P~L+c6t$bf6O!YCd~}o?jQmwNlBfMUWPV)hy=s(6hLlj1i|~$x)GD4pCEpu{dg4o^`CF_8d-)D>KSvF$hDCFQpp+ z3CtOUr5xuOi4G-5*FHtbmm39!yP2;|+1;a_vDyi64#o&8l4Pf=6so;Zk~D<_O1M>T zj(i`f;uUCBQB$rQ&{by+J1fK zF+=W+7ox_`T_}EM;jN)6m!1%dqQu+8;sk((?`N}aEj%EPB@o2oZ}Zp)uj5{-16j1X z-1L2MN@@{q4lZyo(>mv1^NMMTbS{?{QCAp9fF{D>PrEN~q8`)xzno&1e7|ve_Vs>@ zw0pMb;iIy4<)gQwBe@CK+;@LId;VoAlli*6WTeD8-LGV?tqP@O;Qp1W`|o!Nzyl}x#6v46LenZgIFO6HLnm$K>7ChIW$+%b2W0yYX^0iXCGCXY>wt|raAaRGD! z@sINTF&?_KU7x0;KDsZkWyS4FkpfN|6a(wPx2#1et)7ku&G>jdrE~eTxGtJE*<^EZ zFP4f4tDlfa8}t$LdHMC~o$rz0Yx!9d zjKL@;j?X|@-=$ikMpAc8?bu;y+x$wmgxT=^NrmFbZZ$gXi+n#-x#|bkio1=&c}j+0 z9DpSmT4tpBcHsLe)^90PR${X8>v&b&UHIVsJ!XntZr*$x{}=1pk$v=(>$cA!VM7j0 zq$g{_@}Sx^$LXi|+(wJB_2L&qqQy-1i~wfBe(E?PttwYp&UrK~nv0)#Jl=X5E?}7r#cyQs^#4z11X+t2q+($+A!8 zsGCH_j-Mh0qI?1%FX5&1&Smo`epcAEQSg zj1t-yYV|R&E1c%7i!0DCld%8Fq)xmaD2NcqFQ3fxQ|Y=9f{&F>3}=r`IcC3L{sZmj ziY+sdZ)3hlVpb4Ak_Xo~>#rqQN{`ym%%cBCuO=|hKh&;UHC{$bH;`%ZbeLu~Qn%UM z<|)5Qeo^;?$;49vFP%s1nvenhg`0!+NWm zwAqn`7dHFtM}A#2_I?{rcYiP$Q+NOAR85R+Vd`q&O#J$R1R(AXZciIHR~%>E_UEs`V?1%X22Ed20OzMABn zJ3GR!!tCYfTiNG_>*H~lOzgmIM(Y`Y(fs{&y`RfW|4CZ^i3xUom;FjjYWA&exu$;g zti5OS-C6v?RoQQ-MBvJ;lvUihh11Ys;y$gp6Ik|L{%R2gqtQqd*$izD6IVRkIE38!SJU*;4s`k5<`GGs`ol4H$2|G>?lXzMknOLBM+!O=}Xcg>@KEs&a*?`B?>yP2=Tt0b%z#jT#q;;!I@L5E9J?5 z&cUS_g1)=ivMl_-ys;l{D5ew19CBCp&&&=K-7feCt!>h0vHUqH<~+%HX6;J2p{F-P zP};cNXrC{5_YC14*+fq{Au->NntbS!I+6U&Nt9(Pv^u>AF2<(M+0e!61raP4;Qu6M z#7HPH)5;~PMD33|Dc;-tw-9I^>R%MfM}R_+sPskkYa-TGS8iJHrj?B07mvT!OPT## zpdZZI=#o;Hf1@n3_LzjFV2|eYW&PyECr)K(u1RC3Sc ztjJ~o3bz3bd!57RFVf772yb77AyD-k;`3;@Y9R^6m!7>FBG*|*(y3}`6ooX}*EcA* zW$NbVh2neln!-^>TQ5{a*ip&L5GM5>IWi|7xG#lZPPyO!aIjh&83{p1@ zseU`Hjt|M8*n+>Iy`#7~-*@DIT1c6I&L}0b%nHJz@axc&cIv?YyZ_`KgE}P_gR3uj zObP-RIdmnfYWk2ZDJ~q}QR`3j4pgqk>8ej5bGp2Fk$w@3*^8OzxRg|^k=b^2JSwG6 zkbV5d&O^d=X@vnokPx^i9^c3|&Z9rM*rrP4Lpi=DP)F!^euXcLuX;{Sci)#_;$KIh z97D~lJWvjRr5%jrZkC)6H&luV)3(~PtH?lX6Y9F{L)i;GpY?iSW{2-Pf)qPK(@vNY zv;8i0aF93_RyoE9JxkPw&YQ3kEHX=}yZ^JR4?vtbo21&RN4-{6At!6CCnwb>`Rl~Y;BBjYz`+fR%+U&6P;dhW#Zv^g&@sra<399qPF z>ssj0qX`jEouB_QT>4?>#huW7ceL~WN8J*~>L)o`?Mw=friwEmLMye*(!UIMk^ z4n{<$LZ5sR@&oj`?e5>P_P!~jW?x3!yZa$|NRVJ`*`U|)q4+$&)G5ErdkxAsCwpxy z#AdKCg-thvYyc*h7kdy)6KArZlWH%=B1Rgd7G-S1gn{wa_7>A)0uCL=x8O=O>pG^` zHi%PqqUAYb3*Y_@>s^qjC8%7ELWJ=ZDYT@H6u}H@cdv~3*vfI4wlNb&JQF>#F1g0L zb6ECr#W7p;u-<%3+yg z{QA35%wZTfK=VL-@gL^l0hLo(7Rq@9WgIqW-mI?b7sWkI@%(Be>ZSN+-N=+8fY&2Mmv4AY{CfG?k2@mhsd-5|;9=%zaU zC?Fz~WO$Fo5H83gR|jIL&nwg(^}Tb?^ro-foRS-TJF%?fnTKc%BQ6AQATRQd!0cX| z#d%ix5dHZb*3z>?k*ran3QepjG@Mnpe;X<%mRBE1l}~h*+F;)IT)5g_R@0pRx-?wRm`G=}$%2 zPp`7bkPQYv2sFB`T#}4yMJv#KW~?9eo!#AI(uW{LIOB(Uz&8)>_4l4KY7#i)gOr_h@93scHLvQ}n>DZUS;zl9VcmCu9^Tj6{#b3$$&K~u6 zT85WXR32I!!{g3}9XtWZ7A{*#=*dwbXtj(ao@ONkBXD%$%EDo5WheZ!w7s)M=SITR zKXjbQg|MqBJ!O}ygGQDWCbLC4IW$ukk66%KPq?@wtIb470K@M=(XX28-KiBP#ydM1 zfZeRci>iV(R_z7Eg24WMu2a{>if8bhTR6Ef!hNcAZCeoXabZ&BYepkR*%A(Sds!le zO!Q`kO^YT}$^jYdHietTbSU@T=6;(Z+RqT!KE+V~9;sZxPI8#k-clJlEH?5I37Z>m z7BkqFt;xPijji7#Fq53)`(oyW`k%Cl7OEBo{PvDK#wFpKQf=O6Yhlh3x8zLy7F|yG zJjzxT9;{M^<}4cv)EY7Ejjn1#Mc30CXuZaE2Ts`isxnQMNs2+Ys+e>Edg4w-JF!8U1Zmlz3U&qKkt124CIr(SdT zg|8Q9u4teVbJn9kLxICMv_B-=z(plfgcM6BtqlMsECWkLFGi#sc`wD zJ$CBL-ui6s6jr5HOAu3lz~}wNt+iVYjmWA%CK2h7SeDnTTNyslOv6~ql~p7%Tay_M z?8Il(dHeKYG>|M(bmYNe_*V|oP7(4Gl1*p&Mlsz%3MFJpMFSNHlo+o8FCScluHDqe zfs=hvYNVQN-|^$w`J@`F(o`u2X(q>+Rvn;sUVrajI+vo_)1g`TiOz?>yv5H+nQ-W& z<$xOj!71W^Z$9;N^guC&y@r$E){@4IRkS-KE19QzeXCAb;9a_t!^a}C&&yJJH~-CI zQNwqQeU>N}*i|l<5}5T;BA0QRzBa>mjA}HS9X;s_@LN+!+`IpD-e5EIr9x}2>Rs>K z6jT^BL0OW)_i}AJ!CmFd=+#td?;E!x5XCQ%4@9)O$@@vz>&=t4t$I8^ift3QLAoKh z$8;y>Iny)|pE`A~sYO11{a#LxEA$;XkdWW^kquEC8-L__=_Q?57^{-J#_j zbpjn#S#Z?AMdNP{b^qX?3Rt<>LjTt>11CaX~Ne|4J-D0EAiQeTTwe@2$` z2)I<_VnjORaK8{Z%91d}K?iB=PNYW?l*ozhkiL(0@}mqlY}c>?{`y0fwSAXM^1xP& zs0-C#Qm=0G6VfF7!0@2lP{^Ms+s%l~%*@E?wc>mgpFYsLakX}|l*m?GA3jwYg;0FM zMnnuJ%^$5>NfU(}>i?SJt0mJJdV^1n{c zEVB~rjA3Csby^Th=m{TP2ZMf8M{o9lE?gha6UmtRt?cR#N8Q;73tgPSpqI8iPj3{B z5iS~g<32)ZNga|LUz>$AzNxp~eN-*VXggdcrZ;A_YSEBNkwvBrx(sbqNy}9&yqh39 zUSN~|w)IRptOqr$iM)%Q%atg4ubs~GyN9CRZ$cOSqIkNRL=ETcoEL~t7DplTQf}?( z^2@t*_SzC(Dr15S2u(Q8hm6WKMbZ=1Xa|ycFE1MAP|P6#1|zH2-W}ug2_il1ped3eK`sILa~$3bOeEz zq+h>b3Ir01fq%j{Dv67LD#l3;fj{8Qz_MTvs5%Pm*#H3qQZSYL0#coSfl~nvY&CR&+XS~H9+`^y zUH8%#G^t|BL$*NVIC<%8X z`}mimwfph5v*bP$_jNzJfqn!;o+VQui=icI9~4n*xO|@Vpqp37ygqQLc8eF!2-Cx{ zvj6#;4T+Omv-rBl-PxW82A!n8B6gtPn;+6K+x##3nVH(7ck)W(!N7eKvd34cJ4IPP z9+7O?ge7rg{2izWL@M9%L^7CMkHnR7{4qr^JfbnsO5nUX7=0OtEcQLQ?+sZKk^*fE zp9)suSw_hUlsn{py@JR9*GNXAzmB^mW-b!| z`9!XBJ_nuk7)u$uBT|TkH=;m>yLgVL9MIIxDpUj@ba4Xb)BkPp24t~E;2&oiQqJT18M}fD-QulWexz?|^Q$WK}+cW#+ z&kX^y-!ee(qS%PJ2s&XT`{R`VggZ#T1GT|0JCgFBircs*JXt(Ie%BZ8r#2eGMpr&2KrSeN_|2cPba$9EQYd=_3|AQO71T~R zRqZrun4a*iqx@7Lf|XzGlia7TtfKwqg&{Z7x-f@)u zFa%kdJIe8!fB**H7-!9M^FJMXoO3?n{>wAfOlc1(At$8<()^$+ep3*)?~LQCfZQWo_8YJe_?0>#Sqz8~c45_H zTmNSEG4!cwc3tHSM?4ky6TAV%l;5zLDrL2NG^ounw$>1a>r@_)4zkpsm#&f6qGu%> zwPZAU>jyAZ+I``BxX#jjlb$3SCsB(f@`9IjmuPF!?iZomBsLw;?x(TGKWVEHP;FU2t{MQ* zB$5S#l9c94S%rmyC2ovYl*0*!{k#1u4x2p7H4KUg#NzE<)ymLtj&h}lu{)09u504; zicCHkP!FIx`z=AdV#c0A$j`q*iGudmZohiL^8i#F3w16eCPRpVlw&Dy0P&kar?o0e z(!x>R-TLDDA(=Ska6b;jTGUUJuCLdHQ!;Z!w)`la*At*Bc+d@DkhJ+CiIWcoY=~da z%{_s*8^1n=zv^0lpC;(hoJ{&$Uw=nL@-zqhe`xkoU9Z^A}-?Q5CJ&O zC>_qL^!Gf-1m4_konq%up=<&4vfZi<7x1)tC7{1^oPPOoKprP8l{HUA?zvQ)#ZBG% zpo;|93&vicAA#8SbM_~k&anA9y9VDod8y&BU!&y5cTrB^r$)<|TZM0vmc4WtSN3Od z)wj0)1DZ^TiS~niy2#RLZJmxz6pt)kS{38%Y%KSf$u2_bH7Qu?U11|?C0e~m>&`Ubpoe0a z(Rw+7aZ7bV8LI6*UbSDd%RADGoCm_ZGLwk^!<7UEArxB05@9it{COJRo@9sSby=dnBXs5UR^mV~%**g^k-=&IYn9!iK z!(I64w36Q0yd!!g_>>}9A#7*wiM(w+zjnT*7_B21&^rzVE@JhOA|IP$ciUFkf(sNP zCB*Swvov7j57UV3qGpxjyvI@f99@9`NGm9ost-$DH8jCQtC&$NvNvwXN5k^g<;q@x zMo|mFfaZqp5BT3z@7UdslhcHJ485Dtv&vA|(03KTvD0kThWTa+?2x*x&$Yi*ISF!S&osgWudtZY0$N>u)r5&dy1bv1gi>#4&C8kDJ4y*Wl-74uucZR3gY!?=nVfb1V z6pF9EKMGqB#cQR0xGo;;NkO++*cW8WtRugpv>Ok+7-bz13I6kbS0$gpU|t2WUM9QX zcLf+8`@@e#kuo5=k&lrzf&Hjt8uoG2X$HwOm<>~^^5Z}&Ry;?ckZY%=_<0heG)o5m z9Xw1Q@~%W^0xGo}j`?=l$Ge|@R_oULe}^p-vEBK%`xGmSUbQ17dZmI4gBj`jN{ZHg z;WYKjK6(1HR}^}Uzhr}bRJ9@c+JzO~3nkD)q%ro?^VQm+5**=CJG>x`#rR4bS0E(t zD>DJhW&ZAGq%LZh2JnpC0TA@{b|ep7(-jW6T2!8(&*nc6vP0*>B`5P?m6vDU`(VA) zI741x_&#KFPzEA>r!*iNvAZX1MU%_2Tc z$GFqq@I5Y~h`t76UxnD>5lL2DV3((BfoPIVIi?F_rO>W(O#NHR%ow_*cvaccw>))d zoZhEH_u7yRMtwp6S>TCq!SLwT7!#FNAOFT_^%BzzV?5rKTPmflXbc5?i+nz$C4Dp~ zR~0{3r#k}l)ffYU7v{nF()D-lUtl{jzqNEb4;;Q4sDJEkz-RI|w5lKc5w?m|1OKbC zF9{GnUe8XdMNa2nGB9_Zn$9Mlvzv>ma5-M_^tmOXSq~YaAx82OM!yd9L+0mJ)FDCX z>6KkeH`vr=V^&0L_K8iKnIA1MDt7sB&!q7hwx_hnjsAUq%WkZWB8&{sFB0G&?fCXw zu&-_d<%$1jgG*3IJp4ESg|3Nei0|z_+K|m(EO$-ht7lA(r%-dnUH3!zZ4jdJ)(w(kh6 z#3w5^WEWef%IbWB_x+%N954|;xm$Aes}yfuPZl7q(bheiw6txt zUD_{LliYOHyerUZ=jB@O^k`P5iH0kx1XG|}A}F%fsgjKc+V=_~83UeV!OX=+VR1L& zGDNVlWH*Thn9BhqmOem+06Ea0oMQZCc#B>&i3vBfw=~k($6nWR_YIcKE=)yXt9vag z2X4RL5!RaR+~gCr8U)CvIB|Sg>BRlHkDNnl{1j|bZ?#aksfVU7%n0}#4(t-y&Lm3G?+)ph(nEjTgD-VXLzvSPJqss!-D=X`Y!6JXC2mu5FW<^ToG z-fCF=<8{!_J0LXaw9KBKXRUZc+mm8vQw;F&E=>{XrW2j?Y!~XcJI&)OxT;;3;R$O> z;VH1$4JgK4O6sZ6tv?4$*9jH#H&1c8m@edtt+u&7VI`?y`x5pQlGPmlm+hc>P|31e zylEQZ=uQ1lM5#JdT~VXwq}x`mn}ysps;IZx4?}_F&l){c^S9T406=LDv0j;ULQY=d z6Pb3cr(Q1ggOksn)`A9bF$-(@a%lWVM*B$M6LHzg;~_pI+=Xb@ z^xYy0u4X(~jkHh1NHBCJ*UBk!G>a`kjo$#ea>ESi-`_X4_iD(}WH&J-Fzad}Kly)A z0=H7MR~1E=&bUW7zqieNK9|U^nj6>o4VS4%mgB;9=5t{Jca)?HTl8X?_9&+>np}b&SX-9SUx>$VAoY~T%+zhta zt_vw&lm@-;dv;QhfAa`7_teMz zIPIvRDWHphr0yBG{J=5oOs>vW!#rH*{ZMJ5U^|=3yw5fqVrBcPLYBSM)F%M%LOe)9 zgJlQ?O>=nGsgC!U9v_qT&QZEFCVYn;wqGKjZI>W_41H!0lY46v!r&iX3laRecZA!u z3=*55hyZ+L*>BZ-&_W#s>}?#zbEiPgHPkRA&s|;`0v8TKq{73{;Gt(ZEGhxh_ zP6&O(&v3Vcy)tvLYD@+zaD456LfTx6!LUEdPVeJ8Q$+pI8L_pz*YKnVWjA}adx#$` zM6V#FAX7r8nDZN+lUhFW)`MV+av*Hstj?1$PUYPlQqMSQrlIx_UoEeq*Ynl}lm@lU zo_jp~`=0boCqnVf_X+ie^7h-jr7<6AkO)xC#oR(4w?>Yp&v9W{V_ZSU>k!MU>y*!r zi`nTdOUc-Q^?dG78bO2>FULsUl3=$@8aX*B{;b zi%7{}mfC2ww*y6v&{vRuHVH~@n{KP7kHfN8E41RSm|T=cEfbs&(T3}~6}#Jz!ypF%*}bS6I~l+4%>|Ob-sA@X zBkWqrbzQW$uwC=8{F&=~tS!VrJ}v>4!{vThCO zsEWQP>^j0nGSJKc6HFjm*7bB!8CF^TmcrNU{FPItj|xy}J^_6+)2m`SrT}j=ed#|A z;YPzxxVf@sK+Z^=058m@O=9GUWoaBFGBI{ODb%Zd0ObHhSd44QO{HMXZ4^_ucL;^u ziZ3(^Qco$M9d?VOFt{8)f;txH#4%$fe&=b2J-R#O$khVTWkX(p`_isMC+~0kS|Xo8 zzb~JXmy*8_fj0n$@p;sU;T#w5)D0PlQ3_xc#3MAp?q^;2`~z5TJ~{gy^y%hztEdjQ zwC4wMc?^3d#h%B6aaQp^Dbe}u)v^Ilf3EyEi zWI6vXyc$Wr%xuJKeI7TuU|Z9=t<-Uqgn10+x2D=Y3cNlAp?i-o6Is*!OvC4aS&tx8 z1HoikKookE!`0Pp!88D`PQ^UpT04|lW(>QKb#N<*`mHb(_kuJfq8*+h6u?} z-lp}um8Yh$G|PteZ+{+b$8C_|0i4F_poViNk6x}CttcrBm|#1_I%+MXyMpZFnAQXI zpHE39BoGt`w1l=G%ir2B@}}oIpJo~`NMu!zO~V{OrNsOnJLx_lZ^y+;8^m8Qz7A+|xx+IbWgvCRBEyplrB-$Njnc{;9KnQ%0kjf+N+u z&XMx5=c_$^O06Ne?x|8~IBsv827ddn#|PEtSqhLSkP7A5Q3Q*vZU6b~JBy*aHYVLh zy(N>}M-gFAb^IgIq}}!RtJ@JEiraN@tH~EAyWV20^uuYm_}F*&w8*g!6@9SYtj0iE z)Pqj(dp&?7Tu5e*Z?IGRV_0d?Bf3i?)K13VIJK^b*=el+GI<<*q>=owYYMiav6|7# zw4Nk3TZVTj)>QMRbr$oNeD&4LN>Aq`6EkiQkhi>d0K3BO`-EtN&!{! zwr^E9N7JPZYv*&MM@ZfG4Ws1rb~8Dt0^(b&Z*-rXZ?=2k^+AAcmcHMX-qg4kpXIz| z-23SMT+8rpYopY-IIFS()Y6gut{v4pUDi?yq8vM=-C-lgIazIxBm2<3Qop3Cg<$Lq zy$U)m#u|()DO(614DI7K}K8z>9oO zZ#mg|n+{P&xAsYLxx_N(KWdunS`dCs&6-(zJTojDUl>3EY?vwyWOde02K1c zGOXsQ(yQ5;(GO|#L#0~dax6#o5DoZ!etDcWF&)m)ay3Oos7WWOk75pD`}$b`Cn&0X zJ^9@-!7m-WVKu+csR+sWl`<8H)Q+Ijo0!IPV{jW`0&lQMx8qUgy|%s(7W{Zx-@>4y z%gQEgO6qaAv$zVcj}332pZ?mErsysWPrrDxE9*j35TNg{K2`PkPh?Z^!!PGVn~G!~ zIr@YBxojpvmeaML88P}q>5=f;pUR)lcv{E&7BF#qYjvWmY4Ge!%%31HwK{RyT>Q%0 z(F-G{M~8!o>GU6W+~YCK;RoT4RTe!-Co2WgPtAtL*Q<_&1Z z*AEazbuM~eKZf)#DW&1)NW&a)%kUbtGb(yyBl~#IJaT-?S&zi_E52DrX?T4)fayA0 zHGCIitK%2)YA1wwIaRRD{8Qs1&n6L=B1jtJ#&eg#(O1I#NC8F1M zQM~qoF95}O4e-@+Wv9Ar0Y(`UjyqXKCj7=bFrONmKN29rweXW?EzlFmS}y zvu)?iUrQ|OY#qg4QOMs)GYXDT2uOIx%;vZxW2uk~tPCph1dS7@owzk?vb?P3$MtND z-Xc(82G@&hJ*z{(UuUldCA7WU5~{v>;!^Q|j7mkdt%praKFVe7j#p`v*8P*m_61*3 z9dl#nugwPR#-esP$K>2o8RI;4eU(g4&HBNm;yk;t-(Gjwx{C@!h3?NP5%0OS0xByW zj((0ojGcooczSS$+i)1CNG{Gt1FArSt4NpRv*6aP6+Y!lHD7 zbuCCImneVQK}>@yRwd+ZGjV;(>79=m%sJ1-&_pG1Vqt$hM1%GcC`li*qfp(_IBgCT znUivLS-=;j%wlFA(m@ePZWHfXz9QVk9_A0(1PSTWg3fTl&Ob*z#D3U z{p4w=!O6J3_E8(!9UkgF*+1>1aAE;rCXaQ&l;?31Cz2a?&s9o`QzYWfAjgc+Kp)63 zySVRDS&*$%98m(a-oHBj=IBQ;%x$aW4~KpPNcvOG;(NF&qEz0QtF-F&Y^S+X?QZ^MzA&`#jNeRJr#I_^18v*B_Q`Ld1sQ=>@5>I12dKGR4ILp(*PW>~9-Q!GI!51|kWwWOJg z;nZVK(+(ezFZD+>PTyE60t{esYq5Jb6Et7QPCeSAy`Slwk&j?FPKSw-!b*!hO8c8+ z2xg%_1{4A8*n6E91_Q*-9N*w*1}Fb;_yt$}8Ep>X}}n zOMW6HsV8p6r-7ksVJlZg-+Tuuj%FU8Zy>e7yT>BiICNk@hzA&ooRV}*FO5lWa=frr z^Zv_aH)ASQbqH*?Rf9ThQX4-KI1S&o`m^KDasL~juU-grU-~5-=%?5|68CSR_HiHF z*N%ws-oGHaU$kTFsF@ywP2)wU6AlG7XY$&aLu=csqjyt;IW`=4v3$B3;qK zO4_MFKy>uoj=ArDHodjDJ7F&Xnf0cgj`pq5WA@#9&6Mx{8*zth98Zxz9(LWnovl4w zdH-;V?5L?OhCn$yQ5Act<~C!0Pp4NBljy1e^QVf@8=vQW2G-rEN~`Zv99n7z9muI0 zZfoBxyj+gJA)B)3l3a9>YQPv9a6mfkusVsq7cJNqV+jMOD;g=!)r;qr@DLN3s#7P&&$tw%M&qxI@%HwteCp9alA}kQDo~=*P;g4{!VpL+<6D6M1!2ApuRkSFhFrM%S&mG@Wl*pi z$_-txRNy~#Csb@Re7JtzU*aH7BEOQ~=)dPql8j0uyYjTtH@S7>W=7-06pA8=u`HCGUHhZ2*ghwDy+_K3MD%o)txsfhyptf&Pg_){%&>DT755Iu4n;pZj@jC zmPh8U@Tl<9VLSwx{Q(Z5X~+A~dq+d<%UgM?+jsv;K}Ckm1w()#jW zpEIXd`Us}!4QL4f1Ac4V(}isDQH=LTt$~%=*N6ZX%vA0Gym-yUDl2Ete(4^ZbH(N| z_e|RBQnw+0*(sm#86>1GAD0n!YrSPct&$(OzQ43!vp8R;>i~efnfew+(!W?`N!s3J z@d^7%%VCDCoGSv3Soz=1^KIa8$vV>lx7_-I8EU)7WB&@geuVsVAvfAwNGw${{H_G6 zNUC%UCM|JcI2@Zawe`#8SHD|m?oR6tcX%8IvG|s2m?RBCL^=W*yNBxpv=g%`dz)w14+%Vu3NcaQWJ=24R4sYV`UqcCAWyG%Y;8eXekh5CP{ z{8$BpeCe6wTwG?i2f=F4?c=OYFxSkvmokDOt*Y?t-Y%tA8_7ybhGe?|VSvM#oMVST zeBc(-f*w5oxuwq9nUAcu4uZ~eH+Z4ym=scpR-^Co3Os&5hx#~)a(Z>oAt|UCv!3_C zL+Uy-2oZ&7>6p*Umo1aA0*hYR{4=cww2#!6^a3u^kY!d4uI@ zJdOMEpCLu#?z2f2$#LlPw}k&a7RY()#xm~y&*6M`??mS3_+f8$t#RHWYusZu34fi_ zJ=GD^!@gS?#L2jf2XEZn|4I=MYQBilJMinCwNDK<4JRHTGe~o$V{jCck5(_GcRsFM zGO=*A>)X`mfHkya_Vwp9|L=nXg)ovRY0s8LR-rc2A#UPZ#SimYs9AVRSe(`_CN>Ay zR9OreG-Xyva`=uneO4{|{<6TZH57;|eZXX$chyQm9)UOoWGdb~77Iy1oq0(s=lXSw zWHeq@E~!~XSED2Fs|Z80>JYf@5HL9nIx)F-s+?A_nwsonhg|{U=MyROm38jP8*=ze zVQb8dQ!V++7{DNycV$z!Y}Z!6K`McA1RmFZ=xF#?bIaqWYJ1P=B{hl^R<3j=O?i`; zmpMbDt!+-GIwQs+dme|S(fWCzlsx4z8RK@HJi&IwyZRPo@K6^(u4A#RE1>#wf82EEty)?CHd+hqL~Fa$OFj{x;U zRSn2rj&9B0`Qekvvz`W%?8jPR;&22bqG@(NF}p?49T3J-!F&PY0Hq0#3&L8*64%Lo zTRYvzR{LIvqRBd6C5_Yf+M};~*}ufR_HX8}Zf+#Kg79X2wha_n0gvZb5<=Uf5gcuS z%dnAvX3#X{lIX@#ep)T*)dMkZEvLQNxjXJ-w`oGA0sku_K&gu<6|*KY68vYV52)Qc zYirv``GKhaN4RBqFK)_bO^ix^(i&dGvPYse| z4?GD7wM?1<1{zbRzFaRq^fEB2~3Ydbyjj?yU)m+I~963CCBFZnU zLuuGDz)&TL#oG8+CTKr}djXkevcHaD!2h;$Ry)n`(59=O%ZJj*x@X@U5z*BX zD3csm=-EG^T4{D86zt^j%=Y9odv;K8y>z-5HDavFDwI_AzM69p@1|G^#0Df#)yu9` zYES0ScZoA5-bmE&NpTh06H4mB@=k*CbxqBLPxO36Je~8nN>{|9A~aS72?f&XS3j=Q zo`X=OA$Xh#Ep;pTU#iPw01S8?IV#-kZpTxQ*jZ_?e7K5JUaa0VjjUK>FgH;YfwUsq zj8FVYFW$!4DgRmleP6(3J=Rt}JQ)A!CIquxnHuz3&m$8`t??;ia?dgKk^kW4nzWhE zJstt-$lAB>hNy?EJmsZ@xGUC&?`rj#l8P4a!Z|;V(w^)4%Fao`V#5N~$LebKtte2l z_V4uB6RNZ`-BTae6EP!C0%17qR`g1`UMRirPIfX})h3h4iV-_E#qPWfj;7i<0Sxjh zI#vyNC*YVvT68-bX_!;ke4Mp+B_~s=LgBs?Jr9B}4!kFtqRlf#%c#^&q^CNkd6Ya<-P^yrb1X&%bz$5ku~`?wLD3*JOtsnz)mWjwgvvM2mK@Y z-|`GXk+=h3zZ=Uqtzct1vHxuSt&@?=%VvQ4U%IXALYVzl;P?KmYD+84?7J~Yq}3yDmyeO&Rt4JwP;J5%$h=U+TT^ht^S#3K{|vU0lr z?za+8F23t9$uF)QEaO1FAdI1W38n=6M-HcGKPr{+->1uewl&@(R^wE6*&}hWQn6sJ zT}Szhq|5E!bg+3p;@{(;jkJJiUUw(~JGRpF7HBv8hq)1SGUI2`$&G~vcg4?&uap4A zjS5GMWLVIN-cnm}eWXGH2&6Bbz6!C|bkGSk8PeqFS#cPS1H&7}vKxRu?H;4+-B6Cg z!J+7k3(WO)-jO|a3sV8tD1A#cM|=1;9NSES5E<~Rlnbq6Bu**yD!_c$WjM+p;&w22 z<{c+j(&+BW_JZ+yG(^MKF;6VNfRBoX$&gH_bapepn`pw?v+*CQPy0_8UFq)|!3pHz zCL;HpyYov+pAV6{2=JW)UYp(d`SKIw_&~c1*P&BW8Q2p~3uG);WN_WT=LCY@q|>_b z+;JzzV#NYiRR4Dcfyoe^O`Z2}98T($BjKH&zDgQ`#$W2?FD(0HNNBBKZh!-|saHYa zC`Q-jTs9xrG|0J4$!f}p(2Smv)YG|G)lleR6%&fH^_^|1Pz?e_QJ+-r^|RvfJOTl? z7%uHf|E{Es!aRmT6FRXOXGE<`i;uU;?tmirHqbsgtB{@Qc-KJ{7Mm&XN*UC>dH9eh zizkI*qo&u|AkJm`*s0Y&d4Cp9JO7sNlU}l5z|pFE$IHa}szS?RBn$h(T|w#fLGbO& zfZgIn;e;r%?#)1i@gpIr-kxf48WMbwjP-J(cV<|J66-raeZPwKmuEculme4Hxzaf6 zm)C3`!4t@i;US66Dt~L7D@tF;?$(|=pSpQ@pU%*S)PzDz8>p zUK;!8i~Oq^qA1FIh}ca`BuwLeDFOL~1662Z;^9NyT;)~c4j=8t>1@;G&cF!Ijs7&(}FH$C#?>Qaf0Q31eK_}8u9)+Ze_ z*|YBOWh4g!d3cF^eliQaM5!X4c(+U@dJS!sEkrfGh<@k4XQT?6*EF!;$8aMC4Cnn{ zT*R%5>*o_~Kj~P9?;3A5YjzUc=bVfh0xy+ardbq^C)ZW7gyf4R^r)19DPv9uwBPm) zdKPpCjlbMq%jVWJj!Vj`DApVozw;$Xcs(+jG{;9>WoB7Ml z6}aCDtN~ZlSiqnGdSCq;L2C?MzZIl-q&t$-3&k}JG9ym7mZ4KV+UIL`ze`1N3U2~0 z)?LOt?e~l_^qKhRluaw2@q*qLb=Qz(W)Fsk!LQB}qd@Qi22sfcj+Q+o$U@^WLVZCSLQdOm_?Dt{ndn&;v)9 z5C~YC`;Vws#*sJ5t6-Y`+GT!ev#C!Km}pMpWQ<61ENVyR@db>V1n7gj&CIl2ZdU<1 zs^2uHP9@+%&LqVfnRVD$kkO9L`9H0RB+x-gcPBbFi+}n|l~vU-?~|b^Uv>+>HVN9W z&PHEY@*Uh?Sc{l=ZtL<#reh2c^e}Nb48YKgry@z8L#`)sT{)F+xR1}AW%BzDXZB3P z`kxR8fV7ripy8mYg;qeCe!xUBGs4^Q;jFdlEP}Psh^CF59T|whL03mF z@JT|QkP^hxgWJ+x(-~Im*LKblfVoRmFXOT2qw%;7ib#5x8Jk5@+_b!_5yik0;yI18 z6Jp$V!DclLs6A={rz!wm_tv;`V)0k(vBi$q2rur9PGl4P$1oWrVSK=43=cdyg0w2nROt&_nzt`Lk z=B2<9DgTE1yjjI%6BNlGPE5m=4qVY_Ir{zH`FJS?llTEFvjQwr$xL+4KRO9~*Frnm zgTy;dvEAb4(?k~8F>!5?vhtBcT)2jhhS>fMcD_gJ9ec$sW~%ybiR4Tlr1O#5A#duW zu@fu7hnoy8@`{-r{?mitpAh}X)Mz+__N}j5l@`a+o%L~@{m=D#l{{c_0BeQ-tO+P^ zcK6O;;w@Yg%{aj)9ynwE^NjUltNsJu6=TXgei$0xWizaakrhRWm(|*k#DK$SX`+>? zqn-_ua$a@K%=@MGt8=ef&ieue=tMEx^2nmDZAeCrj+VJwZobT%SZ1q8}kdUqxSI{0jGx5VL4N>}K&QSQgm@`2RboaTD)}11W zP6}SK=Z9`f4UfDVQQpdri?%P9k-817q7ZWayca!&_%OeIPT)!2B&#K1gtIC1U4;^k zEoUNX%M1pMXi7=e`*>R2rvNX)<)gcCy#25kB%{u!C!a62K6YOr&5ob+n4(eCh(6-M zmnSMh5BQ$w8BU$UiOOj{rJOwP2`Qu}qXWHtvV z1*;=W+F761Yf_j#jT>$%8k&S(v#hKB@g)ZuL0wiaJoe zAzI|BfHGQry^S+5zkAL1g6sSz0qQTtPkc_`W4bbvd>>o<3u_EL*h@L)&RQlNSk;*D zEtvJt^KBynA{d=&_rE2~C4^#e-hW=PTCY7{{l3u%Wi+R`jw@E8I@8nVZwu{qEz0t- z$};&Q)HHi|Tw_(bq+|Xwb4y@}mXjfr#v9(CHh={+2g-;J$(B(NN|Bwr5Z85gc?Uy) z2>@5+`nutroZ>a*=`m_5YM&JF%IRdJF95?kpjuj;IcBI0(IAhOl=e3;r#D-G=z(!3 zX!d8 zZbBiFL7G(BqsccN+|<+a$q%XA(2-A&?1nVsW4clKmn_i$HSNaUPWc|8t`+}(v1h6;FU zE&<>1Z`t`A{=ryDPo3aLjiC$|fwTkm-nGZ)v@s7k-pIK8lO$sBUEE^B%`fo|NJX+J zI>;LsNA>;V7U*1`H@Pco+#(@;|Iul?JLvtdw$ zB=C@PIZF6;BtR2leB^XL{DGyt6Btqf)ht!NzWWRPa?O#&*)eLsav8xkZo;bEz=BM& ziDvxB?h2ybpf`efT^2f8>wAmWah_+{F!8}LYDiL<#xTVb9;L2GB$rAw(A)rZ;jw5| z=ZoH=f!cto6IAh@(V4hL541t(&kFj-G^WXB+zIMpfj0ncNTb#o4tm1~Q(y-IKc=Gb zUN`vz`4R{=ZljC<*n|yK10d`sDw99a5Ln+T;8ljN0u=>g@X0$+An4*U=9;~7eB*4)o`M6_hBS?4eHRFL{8SQ5MW+|h4m zjTZ9QqB2v&-kc8``S>UNU6f+jKSz`0TaKd;zL|Z}quMh|^PfU9chFMFdtEtpX`SEJ zmy%vNE{GRFYCcIHj;6^j!R-onx-CBr>Hh4zJe>=bt;O(*BP!~3S}W>q@Ng?+>E;D0 za3R_gn%wZNNpRr-Rcmxa5t`K0NE5vkK;XK?q= zXG8%s!xxW=VDyeqPGAV^IZc(?lCr^rnw3TDJ#;NoFl>l$N!03eoOo_Iwb6Wy=S%&E z0&P#Tbhbo6m^I7mP{+I)>_%TAmp=WeMi^r@b3Qr#ycn2v@cA{QNA? zZAbx5eiZUa^(69i+}cCO2Hglepg&|YFZFl6M-{=IZ?I|9dVioi5mY&^s+2YIQLBN! z$mXnQ=GdA%sHjX3vqbnrMQI6~yGILBCg&S4v4=zHnL%jXx(hGKy*5Ju`76Em@kFpk zsR>_)#A21KL|HETu4g;{B~PGBhiu8n*8}fP$%t%*+rA!4;-NZI!|YW z2X!FCsP56|cXo@5*on69D^cDRUZkn`XAjrx<{R7JKZSc_iHv*8Ix3iK=+Yz)S#~~_ zfg%v_17o6G##g?`kjagJy4s%;JrO+-$R)S-F-&t5eq&!Fs7lF56|Y!YFiIRXuY?F& zwXnjm;v3VHZQW$lO3kYygJ5$yvdD@+*(_EG?qtvLxQcV6$hn<1*&@N)I8-P+Wo*cO zTf_Z7Iq!K--zWX}Z}aGcB&GIm5G=Oc17oy4GmMm=+>I(S2_CAiyB8AP=pT{Qg}Xit ze-dedA{G{AX38spK##pP&kSHV#PpY9bcBzz;ylj`mVyAB*!gVS7}q~SKvpR#8ArE~ z7#F*)_|AUwDWgRmaSB_gX>ooA>-ep&Tv0&HPS?iY9 zEH-&2y4)M0vW4G@!ibIobpb)X&=ZL)J`6w81f<2&ym$xQHGKzLvI^_crrj@Il?n=o zVEY7gj$~K`8mt-1;*!kXxTEH>NG;h<$rTgbXO2gXDcVFbJiqh3dOGGoGR3VJT^fJE z8bhb=CeQgl^b!iCtb}(T#TdPf2D?K;&$d~#o2q`rmWsd-!IO*A@1~b!r&*lB+zL~J zv*+D#v$SmRD9#~b9%ifRNS)*4A+uhtQSM_M8=M5&>!OH=jo=Gh-{O6nqNHE&eu*Da zLQL0C3zF)XPzjjwmHW-d$u%@J7xSJpWQIDm{(hvTNgTd<;P+ukSbZkL^K54q5`qRU zpTxUAPny;~1iEMH+Fw2SCqGy)biynYNGBx@1^~}kPhI!}Nc|6ZfS2UAvg=}!bN6^( zVWv77HjNKSnV%Z#Y>f-cSsnQ5lHgbq>eu(3)J=61{kjDk{gN3{&T`^^GJ=f!kPLQg zlF8)6VBxnTdIv&^{3vtYiy+@C_^BIr$E^Ur+1+`+{M{1U6^B>`(eM2BGEYAH+Kk8+;tvUi2!zxD&l zlB9Ci&7Z8iv4;IFMBR-M0E? za=wez?6kKxL1^#lh(lqf24$yNusnWvj}{ea!^8g6HTc90F4W4*Ac#In49ZxPkN3nI z8(772ZK6w{t@x>pAe|{EI^Kh^!_c%;e8nat5 zDlwt?v)@%e`(zchXDT|@uE_{+PHOIROJH;*&-Z&ySa>8|DV4y3YE^!aeL9;-3FlJv zRF?fwTak8o(qx&IHahWewLkq2QQ5_2b?Tty9hV*&?%ucJ^NiH+kn06#+t))hQGoIQPX~_-Gn$DM8Va{O zw6xFO<%)PTWvog*1Hf@fiAhO!Z|?hHl858N0P>0`M#YxAS0pJ{(Rd!aL8gvz86}ZV zQIhE+I|(*ueSyTCHhZ)enyg%IBX7m6-e?P6jXN2>K=tEU};nH`Jb$ z{IG$U{2LmK8o_GsO}#7^ zvIM|fh^knex6ZMkzDTilV;gTE+)u7Xt405*d^AB0{Yu38TIHmDic#66wV!*dBST*f!KnnC>Fl=m( z3#3d~CGeJQ0-T&y2T?%D%e``S0#B17qeXwSiQMA@n6$4|AD8Wn`neLhY45njhhdF> zGD`T82mB`Kxd@Edby6fZmn3h1!)| zNg=~!J#mgRi*WFHDgdS&#Nn!=`blzXkNu&bb;Xl4c;94fK#iW1ffs8^AR#Ru?<NC7P8X8)4#n&J04dw)$;fgHqkw;FCO2b4^n_c79SGhyO zS`Ah_;5XYDPRZS-oI(K4-KcqduFcKvkxmuX)Ke!T(xPc%!j8vZ4^6Rt0VuWE6V!Xu z0*wgg`5wm&Fnv^C&-Di{pWSU5t81$x@&UDnpy+fK4Qw`$lxSF;Tq2_ZS`V!#u2fIC zR(_^X)~6;%Lb5y}-9_V#B8@QPmO}ZH0(Q#NM*a81foF~1Rw5x_)`C^}|CDScj!>>& z*gnWI$%HT_ag8O7rN+%Jdx{w*YceBhB4O-Q7_to_Ylz4;ktO$rFqXkw>$Q|6BHLsn z8Ag`q_qo46;Jn{^&ikD6p6B__%g~9ZqsfuG@k8#QQl=6g(&6(XBVBtBX3ecc+m1;T z>lY;+Iq77~rZIwxq_HSYQbKif7Y0J?+&D+5qcradF!LkX*)m=QS(l}zEjbnPbV zr_qa=8;f$wqQCNAuTA(U);-yPk;ZnGYDRusVlB!4+C?E|41Pavj) zo?CEkx%2l=H=nZ_Uv!bSvFYe`zMQIojZN#~qncA@$R|1`xuhaD395?DU$uE9Tdwjk zOzh+VL=Fb{W>yBVS`#3q!67;RdZ;tgD$)7Q$i8dcCj0y)SSbOQmXJ+8))mU&{vu3V zQWn5Y8?lys4j+zO#Ybuq-jZqS=3G^X=DavX`p@IE0P5jepwju<`lYpX-j#BFR#IR3 ziFP1Whex=R_M?6w<$A=UxUlS{>tg{*BJHV(L&O%6aXWv>`%3!1!92=$PN^ zDKa!jmX(?6rdA=I_we*(6;QyGQWv|8m9Z(G-e3rjo>i>w-7r;^hV~>AdRDMxpIYBexcM4c69%w=<#rjZ8 z-V^WHw__Zcer~ZNvk@s@0CSUh0EdFnjb%Ge$na3uQ$A(V47#SPgfWK7Y+XPvpI{dZTc$HSScjhr zm#kMugsJ0%J>!{NMmzAf2OoZ6Y-_kszwqNDgo-lgzRwNFUhAHF?1*lI&r#RtsOQO1 za&cYbz2YPNfHoo1Dg;xOagf}*_F7b5qL7 z2`d`&(rsYN!t%SuIb!bVcy4Hn-}LA1t`T9LBEw(|%4HPT`n5GEVV6fcuFQq3&`5C~c-cJFZ!0~-(bWZQ z#aLb4xVp>hYK#fn&6UwXa zUx9Wg$sIye$}7o+0Vo z@Z|`xUUp!pKZ{b&A2#srWipuw2+q3NzBD#4TlVWjTxM32m9KN`z|-9;=Pu8D55AFV zJrE*XSZbjFKC!&Vd;Sy;+ffscJuMZg`4{;oVO^zfYc8BuCZUIy$;I~LX7ln+vlH^A zuk7@a;tq$JQ#Ck1I7VQt^Ud<>A(N_LCsP4K@ksAx`NGS{c{J~M?CUb05U1p3Vw2Tm zbXvr;hcqNoTBzimxqL!sQq;#a^*880L64ht3mBo~xK`slVd^RmjNvB<$Loz>N_h$cjizx6=uE#wwdx>%-MT4ws<2-S1KN507!1U%bwH0m+}MyRicxuoEda= z`GUNrKY|*E36mv41cwY zzvQ_3SJ=Sq6pfW?7baS7)Pj50&>jq+=YM%94T@82(7-B13bCHcM#cPR?E)zP#olCM ziES^QuXHJMC1n0{yJ5v=Gh?ULx=-li5}x~nT4vuYkNR*dCf#m$jG!uWt3>pNWc>Z9 z`ehF$@MUO^bF$XvT(GNyi;Fgy*t8nN=pankcFs#7nwR>7qqaV8POJm(mA30deo-^~ zYA@`}7ez2IBLyS%V@%Ro-KDqZ>;j=l5Yq(ANT`iKuIQ|R0&^t-sh|~?VF%k;$vW?> zll=#Xh->9?)A_UxFBJu`82kMMsw^KBQFYN(Ua@ztq_3>q8#pEu1z2v+P7@jjG38tI z$Dqb*`pNqmCT|c6`d5Q1zQI}Ht~Zb@`Z!$-Gj*4x(`K&N3k0o| zfKMknyaUN=flB};bWb;xw{ro5o`aX0v$2Xp)vEqN&OdKVPbD5ijCN_ zjdjObEQ%WkH{+n$0?vzHO}it(_0VbRJhIEvP>&C2`Xn^ly&mdQQA#oqb1jxf86SF~ zLbS#K+JpH$Vt*EcLg^{rrrj)x5C#TEFWEicHhQg@0243(15Ci2BK(I?|!QmOXGeo2Evg!<^%1Q2L3}U%%|S0$>ojZCRjV0uk);* z&nz6c%XmP}LHvT$l$X*T>O$8Kxp0hwJ2;9gAaWbZRVzJb-f@pb^_e?iG!wQVrUP^p za;?gF6_pS{1iH%_R@T>JrofE@)biVa765;fE-bly@byJl6bGY;6=*I1fGo#FTQv~yfmi2DewWRg#^zev{ytI>PB2i zRaEX_UqkM07|ip7aKC2KK#Ls5Ks9R2U$ZS0Z=z9whVYBraJ8g-XzYj5g;Dxref`!?C05>vs!D#XUpGuqrh`_5F#F;osHpsE=z9!bnHTnRMFErvp3)R}D*h%`bI=!Y2_C%nNDt)I z9LbH(5{q zOF-)PwH8Z`iXy+7u5r1d~ya2%z;4>Mngu>R z-ACkagyRIT$nVA>QD>bNh&(kemcM2ZfxU+JxUM+fX>PC>e0d@K_+=}oDyZstZ6G}s z_sreS*Pze-d$BmIYy0+$GYl1%TI@qElPn$n;WZT-7g3rUw!{t|xLNScN^OR|P=RGh;I*#b#5FD7 zDM>?9^4)K80D2|;%4*-n0R zRPb^z014j+Ml(rn(v9A=6IwE)HscVq0(V>W${uuESeDyw*v5uXjJ+J=mLT@`n~)-0 z50=GJKYn#uJa-!Y{8CO-n+sC^3ViLB3)k(Hco~S zVl~B3)hb1b)y?JUCAiu{JNH@iwXubuB!P3|7E}A%izlt%Q^)N`CR#| zh^;j>Ogacw(y?2R_o0V=53H>7)RL5fP|3oEUM>A~mkaU{B^Fn2TgVGI$Uh&4|OQ z2UY99`-Ka{^9UVzT)?`;zyPZ+!VXt&p@M7#M!WjT#PC$%|E7fnsDE{$qxB+;-3Etm zZ?PHq7Q%;2G{?y>&>9^M|FXVJt0ZQ=XLtbG4&|MBD4ra2QL>eW7)T>1N`H#Ysn7oX;kIFz>7z<7mxjFRwlP;l?4KCPZT*3|4sOn~#2P0D!MVPsKpS zX%qHpjnilz>KGzLeHAOG8#LQtv%$4bAtCIazwy+eEhq${eE&D0 z(S{gH%-e!!-dJ1hdi!m`WGejHlK@wt%Xb^c1{?+Frr{KCzQE7a!W9ix)K-xggkRHb z15-hxH_%(H>gE7G)9vBohho9SY)Hu*E3ph=)DH8mcCEyF$%fl41s#ki!6VGvhmVLr z2xv53%vXd(^#k@1K8!o=@2n`9(7by&(3{cYn=13wraSL=Le0^v6c@U5fJ2hXEAr{@{rAO zdA2V@Zvz5~*)mU9iWIucj3qOvjU;8TN)@}4nD`Yc>1MM=r|Ad%7pG=t^r6JWx zg`7;eZgHZrl@+*naZ!;y&Q8!K7NkvvO+W^-V@(q?EkV}HGh#|}L4zLr@(d%)WfbAT z0_|s3k4rvH{)D)P*`%f$Mv{D4d;BG*w#S4CW+z%KE&n0M>I~ZT7C-N;&q-4rnLt8r_?2&l<~BDpQZgmnCRN*N7cM0G!j8#yO4ALS0uMuD3VwzDBw|xDV&0v zI$Hm3X3=yqh)z$3K1Lq2NeqSF*&Ge-84J?q*IDNgbqOh37mo&eZeKDWC z0{FJaB##GQW$^i?a-VZ9=xRZQx|Z!4QMbWOw{>m4SLSxrJsLo4K~FT4NI9=A#;-e8 zH7;DYS>MLN4{wZukSVcnq0&3FkHN9~QY@*eWjra7B4QUYBnpTgHsH2lf(9_-$4|o6 zN)=9RNpS$u*%%=C(RUo=-7Fr-Gb;4Ku_Ter`TXeQQXZDNj}nmI)c!6e#4?{KnnXJg zfHyc*>4tmXxgI=&T1%WuC0Z(x%cvPh22?I5U?)4z5|b(X+dDAhq5YmF#?@cs_k4QiQ9EjT+wF zSFA^=#RrClha}fgiALkeik>WHCw6FVIto<`LWbb5N&`P-PP538vz*dbY8hDZ51Ev>jd0lvuWa=9|!t|hwhQcP(HH$ub(|jbgUzF36 z5`UwsK96STIAxKjuIi-rtMejc;FU+!xga*m{R2{<(R@WmRL#$uhwWncPV8k;0w(#1 zpwjs1Qrg;_Oc7v9s~(7y?C@|;``_(~Rjf_pfhs?$qNcTeNiP22d&n~W!YF$A-bE!9 z_SdlfM?yuahw)EpA#*xn;boyLAz z^6hDZuwzc9HD257lrr&hS7zf-E<`#a{Elw_;d@&B4f+X2k6P33&p5O#A7Aqh^3};P z3X`i|-biCSV_Ndfkk^t=q=y7izpHOvmH+F(sQ08R3uRl^Zf2eY#Ch`MznNn! zp%Y1^qat*Swy#N}Tr(>o)Ws4zVNuTj@8R3|MYbpvB2<3si@Bxn)|Gn^L@XPro`?M$ z0(^WJlO0p&Uq7YTg;2a3vARXNX}??5cA=PQq%TQrL{4m}$q7AO{%^SS`2;$JebSl7 z_qFf`$WPv^X=lY`mME0IZH{C%D_ba2N&Z7zs3Zo#Zq1UADOhic%*k|LJJo8jIixp&Zlol8#QIUvdf94qaVWjVft-%$@r)$A z{DuhLs|b)N#(U)3Pfq~?4S4r2k+%6cZD-S;t1k~ipbtN#EgYYNvI4Bqo-kJ`5zd!9 zc1s?*$i6XD(r3GOzY1IL%91BhD(GZjma+JqF-xAWVflmo?gh9_xYz@UM`h`->wLzw zilUH+%9``dy!nnBnh@W=$Nlp&m~2^f7-M`fz&UAf33i|_7I=rHi1f=Yl{5Iof?!O; z)lCqwf^#yay^8HAqu>it-Q0VWHrKhthlEi=C9GtTXNwmP_T*xlmMvgAo(i&JX zZaT+EfC~K&0uVnwdFmL&w6z}b1PrOae+FUm8d05ukJ%~}+4+WuZTKu02=a3VGUf|! zMU}PPPwu%fj_Si(@f#}l@E!-n%X9CuhWJZZBrjH6a{U~*+=GHiZM-w}V@UZmbJvo9 zPm+lqG^(7u6*P}GqH?{T?N=KCmiX$pGExef0_kq;(o(05;dkuL zD&p;}v}wv=ji;BT5s#<_+c%H&h8k&#q5<1C$l&GAa*L*8xdD8wp?rqhYU}|#P3)n6 z)(!z=2f!8abK2de35&Y5SVs~xT8YNG z^=Jz~q$i^!V+uBD0jal_(7FaIy8}kWmK|86K>k;j020aDC;mUw>t6e?@4|Ka2f;LE zVMcRm;8==On*tBr`C~%Yo~;M2&_CTo+Yrvk%uzboOOxB24+%}6F!4*0yaCNH zN0GpgerNRBJ@Z+mx2*E|5Fi=BXx??Bl|#{F6@`d0*DJMCS6_foZ_w#VEFZlqd9wTp zKd9zTkF;R`4Bu&F`80BldryTr1>k5oLKN4yIFYY_S!PmF3>8_+aFqnd^Zl+d@juHv zGCR9PPcdA&8or=m=Z$>zJKI2KWy1BU6@Vh8>Qwh%9#c#kW4nBFF$@I$T z!6`*h&MxC7u~kFT!gSGzWQUNYrh!Ja(^EePky7DD;ZtC|s2A`pE@+$}e*Q>?8Z z<&iBJy^L=p0{AzA^vW^Z=1@Q8m%)q(KjIFp*z>4qBEQPz?S*Q zkO1^^%UYg;+bZ?hhZ#y#e ztQm(XU0hoQ8p4DydYFgfeDiz=0a ze`?z4Je%(2Z@SOA(YLKnR(Bf6sxNN1A~!dgu2 zV>xfoZUXZUIo16R+=;RY-&D13PPiyjCIrJwxKHiQIt&*4*ix^QlUNzI0XeI;lG~;w5=_qEF@5STm&s!<@$#`Sa3gRr|CI&fj zTj41PS=vapd|;e^T+-UidL7KTPiNkZt(sVb^+8ITY`BlV;nU@BY+~}4Vi%$D!J=3O zf2+FF!&Za?8=x|~kqF^#6Q@6u(;4?3oVO*tt3mkn@Osw<&8?er26?)E&zMqHpznUU z%S*qDbOUWK7okms- zorN67%}JHuh3HK!8(0}hE_99>xQ3{S>A>zfTK|th+&4QKepOeKhVMj`z?%$mW;Z4^ z&h7VX-gjzg`-EdRMr~_tZN{|>ryInuVQ7KrC+fO?b#+yowmU?ucvGZq0sFf&MPn?hMRw}Bgm88^RpYO!AjODL&{wQlH)~G375&OObS56&BP>E9l_fQJy zLWw`?asZLb1fgw~&)*iTjDMRFPzTl}?iUo%ogFh@40()gS^NkC>dECym7z{`Km+1Y z2NXre9mfVWZ=@OaL!(Dqpd?`5 z-nb&+ZS4?Ls!tIDi5aUE$&O5gItvySe$1%J{}S9dZJ*N9|D65u302TCX4s;^(FqpS zp2yL>e+IvS!yo2Ce!|uF{luA<>I|grdmtm-5f&NAjZLm2DsaTxK#+hMT%Dqk&lj9yF(88Q*5fmHh{<<_B3|bnxAj!t@IG3*Tt;7AG=8HR%V~0a@pV1 zyr(K`Ks~UO?y8%~!h5hN$^db!=T@RK_S%i3!%O3CU(9+Il>ZIrM*86zbuc`?Ot>_~ z-!wX$vDEFSXKYcdl{s!%UYXC@%I2(bxtv%IQqL38<1{kmAJ9>hq6|Zw#F}Sf{-+j~N!xFKpBiiK>-t#O-G-EFNcxF{<&mgZy$V^zz96NsBwrXrPrIaAQ|i z+MEHXAU8nyLb zq?8 z8p;R=ehdf*pFE&~6BiseUnD`jN_7Wg>` z0f{e`tJuY4nM%({qNDi+s_dJK>|Tu zM)HFX;$a&~2Az)YaX@M5W@zRDxmA3=7NNQnll>raa21pYI>$%s?)bRyjFA%cs(bE+ zY>D`lgSo!SeE&T1gWC^GK9i78I&D+>oQN9jmPLVDUvDc$&*Do_)vIX-Rn;-2T}Wx* zn6q^YYo^d{P-$uQ#jO8I9<#iZ1gI{$bAY2=pxtV3!aH-JC}@L%DF2xyNfR5=>NwDq zL?EF;2MqlTE)MD33^{0?-F*n$Sv5+zUFeD5S42^T+&>$OpM~nmCAk%Y!2m6AgjRLs zAqy~6A`|$tT68aC=ITeX>Y}sI-PZK%!KmUk)~B6}N@(mwW{mN&r%vX=&^r7cK*5Dz zfy)K=6_cjL0Y;St=Z?+9NP@2ehA_8jrYzef7b%=$sDd>bW8C}Jy9O?&4OZi4d%I|W3W#}HrxJSIym)Ot9lno#2Z90OX zskRFHouION(4r@-(Jt+qZfu#T=Wh*FieXkfLS*rE-vTzBr@46ZvJTo~ZZU#8e831j zb$46SCwgz1H_|4jnfgf<+zUbDiTAbxqU_5}NP*-tf1C{49+vC&zm26pY-VdrCYqOA zkAmhs4j|5>77WI4OMwBHuU&4V%34O^hdABHcN=-PTQTGEh_lpuE}0zPZWarB@64Pr z>nDmdB*apX>pbZT313}=h>Ho%{@QrY2fueV35n1LfQv;u*oH1R9b!~FIbr^lW+(?X zR0L%E=)C-0t@%c6*K)Ck9@*4a+m@4~KTl$9uF|!oCh?Xsp`wXqH$aHCdX_6A5{yfDB zSyI13J1aZf6}dA9pADomgX{^J&ws*N`-lUDI1MOtDvgPBV6Kiyn@mn0DMvaCn@@V>ptM*M}f!f>eB zyZCioCkx}dX^Vg4N8^Q<3*eZ9zzl-3OkopY7>UM^u?LWO~ zZ`ZLsPCpxraC>~pEE0IO8O%+pxz)$X9-gJ`DENI)$ea8vyx_44xoD^>3)IP-Ovs@t3zd*TCBM%m zGBVlQxr#jw#2x!=S=Oa4dndS3L{gqBl=cFLP^457@sH+FpC}E!P}mD|p9S(oh4t zWle4i-79<7CSGFnAM_HI8_UNq-WnG3xx|nJSadAhOw(wxX6cyam=i8o^%))kT$6P} ze>xBXJaNJ6MvM}I1|T+>PVe~0E@i7~8*cP%&DI$0nr&j}eb$9WDY~g$Y2D8e=p^;n z!`V$-x>LYi*T&;^3)~U9=q0M17DoMXEHXoslQ-g!lv!~P$_Z9il(!8%=SDO9ZI)+A zO;k-9HaIrbzrTb^T>J}yV&;OvGRKG(4|{~~D%s)#tWrpUYW1&_nP)a9guFi6NKTfIsr zQNYM*z?BS1?cxc~w`fu2<;!k2G3D(*RLk&}?-qGr8wAAXXGL6q)Gv9U)0cFma!Dud z?Qg$RPKe6Cl@0Ot6O`X?d+C0nkP#u@U?*3$e|(DYzo?Jg`rXJ$F2YxR+g0-wKIsu^ ziRzVjebQbDQ>CAUF>L zG5j5;5_>9eXA6~Z2OYkQphL9vp4%XWL7+x2@llOLRwdh#zoa}rU8ggJ-S@W=dYDtv zuUj}LEEV>KYQQ!k!G*BS7|zlyrhhJSN^y{ES+mb!=*|hDL9@_G+>XhJu-(T*a_#*T zr-SQhHSzeP=+yxweAT;%U)oX>o(G%tvsXL*2G@kZr>%uU{9F;mCvf-3DbRe+OFsYA zA8zk5_*HZ;!?wktb&IL{LtGG+W#5_!xWk#SY&#C9i6*Q}(euDC;?gwR+|2lrJ{ zWfK>*Jd8WE%AfJtg5t^p>U_Eo7KPi_2)RI_jv0%B#75`9dMk<0N7Rdye?B zE8W_blR2(nX}>$2iafB%qKyp=k!BnL@OqgHLA?eHC;(dJ4iZN&OchsQ7BHq-r$Tt5 z@7k}1L-}1bX)B(7r`6}sQ$1a5ff}4MmifRir`-|ud>S@i zhjD}dG)olIWb|{SHLCRmn}+Y9K~&Ha0O9%zHc>wKdBWk|KfizSc#fx%&23MBP>AbFf1V2FJP4^bk!CsUeuzO53x@2y%; z4M7~8INxd-ns-T-k7!XES|36c{vshwMM@#5#KH_Dq*-ekT*l@nnbG4ynhG^6R1&ssl4WU2>34mZ<}cp^1U?J7do{NMYbr}Dh}3wi?b!tosaKX zsRcp7zi@1d(v<`WAJvWFJ0J=LcRz!GxAzlg^4_NdwAW%^M$n5wrL(-rObcC5Xl7?{`e@6n>meES z_=_;)%4iyebD4CvQH+WT9qD71fK912Rw~O_3p+LHnAp2y-{Hw+@2B;KkO3qOHs)|} z1Q4E-e%QP#kx>Hy2bBxS@sJ=q`7e_l%pn9X0jAO!48=iawZW%ka# zw$7TPbZ`ty2%f)Lh*9Jfeq#114MN~d;af|nE7h#x7oGSc_QQQ9==N05#68XDs(TmTINb+>4545KVIWw5q#^9SdVCMs~4T$)XFf#ql=pZ=LJ{ip&=SZd_WX< z-g{HfdBWq0CgRfDWG?X1Ym?Y+@omSITi^Oef+;uHSFDr=V<9U?JI|B_H%mPm!!E;j zU55QQ-l<^{gM2h<^uc_OdOEsN3x=T3Pz0ET+f79fQop4r?j@BL6?296Q&qchkkOE# z_2X-wLWR~ney#{?WFbd!N`*Kk_Gcn8XW!@Q)Xc3Yunkvm2x$E;(}AN4#S6U$i$54! z6!C2NJL=EU3N%?^vzoYP7ahopMZe^^l)QGjvf@n1(ErV?XCwM85Z%@B^a}CB{=R(Rj z51OAsf>(pKCH5fjQ~?jLt0VBtN^3~P`=%H-KE{aa2`Sa?(wAijt@1M6jez)*-k_c3lDCW@Xn&jk8OY6_nyop`a7Yc1dL{M ztZ@Dd)gpx)($geZ24P{>iP$g{C)%+*@6 zLJxg^XZ`GT@5AX@%H~QNE5A^dcT)HSSR$g;8xG@-lH>8I&450}^8avy3ZSEz0Iw2y zrb>_bp3tsVq`A9SS)PW>myIDmd`>1rcXFP5$3z*Tx{5z;?T<64^aJ(#8EbT} z_;C#x>PDDeVS|I!#KC}G^>35wK(Qw;=-}1{_TMyrm5&wX15#+rzU+{M_{;5*Q?m*sA#7H#&Rhk;JYUkwnus<_ z*AX-gCKRy0c*2~C^%e%)A^u~>kMlWPWn2hR z1Yy9cvcRiVS1volp`iIBj`3DvrC{VGlFF`p4TZ9lcAFupN_YRA$mCz7l=aO*tP+VcP0x&98m21!{6BiqO%&`!Gb0 zOlEd*DhC^MArP&Y2<=p{A!O&#uvQOYAThlGWO}+g(k@<-+?Gbl6 z6!OO9{M-oqs=+LplqbzyS^KjFNY+m59XJ&1VPqKMAl9WK{M`DQFeXV%N{2`oo5H6}YB$S()JT}&y zy7@&g2a6JW$qNyFygFxwTjUBJ&~(O>V!;3&Ao4l`PeD1S)X=jTKFq?3Mej0%Lp+2;86+ zp7_r!!SG;0`fHE|p6yQsp@Z`{-tlJ#dXMsFqp0ckn$3Po3-a|fiaMrlCpT=L!^VI5 zeh3TEi)(JaNOonsV|OiJcG|lR5jNVWxG*WriCD@F)tQP#>)&sl{R5sV!h$7g~gIQ2Lwvh4_-qo~#nH1Lw8!^}#9j|s>l^;xs;3XN7WMBH{s{rYa z9LP$>rCIw|<<^I*+}~~#bNy@rRoyL&>-+&7rUJ$%@_$t?3+tB$quIxlsI-y9lM66! zsvL_BnQycjN`erUE2Xa$sTW&lR${7*RHv#JW}f`r_B&IKWMlTW&?Any3oFF3vRvA^ znU1+JTSMr|R9=HgS36bg@Js9kKsI;83`cb-Cc?c& zZ*b6+LfPcB8y!#gY1*=iy5bF2ws#RD8ZKJ7d3ZqFDx6f!N~%E1M!ML2L;VVQq*5?UN`)}OdoZjjzQoPGZ< z{h;dnpf7xuCTGTMhY4olB+;k_2nv~60p3{bwNix;9Z3#Wv0hVdy{NGE48ogXSQg~ zf)Ht{h!OAT^`gH!5CQ0c?Vry2T;&=$rP(eBa+eg>lsGl2PMzsCiyyAHW_nQ0vr@1t zzDvHaIjgsRZ9v{8V?nSNY^!)RK{@Lm8GGQ{YZc_88u*wc+T_XTIb9(&o zWK=xR-aq~~!$5$K31aU&J#twyT2ep#0rWQfGxX2f@Vu=_8aUC7q_`AA{EpFyo3~D| zA|X1<*X)lvac7bC4QVMOPHZuc(E0P=)Q|wVB5y~ za!H=1p6ot}0YJ#g&0|KN zE=}o3Sq8B;f^1-2=t5K`)Ath;j1zl|BB}amNU9NwWJxVZ@kfq)l2O8s`wM$3tf{%e zr}^)K9&@ARLPC4QOo7B43sz<&tj?VKv1PC~mW4^vi9g8MfI`s{0!kDVvrHPWHQaHn z=!Z*U9Vdn2_7A7Bu2>KXR}77q92CY9FF^D-o(DgPQIXKF zejdqOle2T6*=<67e_ao+Sl%_E-SrQb#Hl(lBCh7N0ER(hkW3^z<}lS4P8aL}ag#C| zc^#>G9~i2eXXDZthD1JDdzsf1p0(x~)iOXvb#X2}VSizY+T-XM55uYt$9lzBVg-kBSY$fpA zSM1hcNhfclMJo#Pw-?;*1Aa(k%l`U^a~w0kD)=r%;xsD*C!AdiT|5}lNRb_Ce-Yr} zkv#1K=uHbLxmZidd%oB%_ap3%oV}rLTQkvR>!;jx$c~X4#}U+k`d2R@d61ngn)|oA z43oZ$CA$Zp3>+CXyT006jQZ2pWi_T3TR`qOu*RpAGEc*j81DxPX21iluUK^c45$NP zV0Dj^YW9%C;}p~Lh9ER)$V`ZK#ofh17lQHB7$aPj_kT0iaCmG-D zG*Xc8tX%wXl?&MIiq&8&+O5K*xSZN05pSE14u`1k)Ni?GKB_std{SGS@&(Mpe;@jdPS7~C_g~G zkq?debhH?%_a)^7f9yyJcdAY3UNRDfwf&v0n*UyTnmU=Y0aL7KqOhP`lYs1XX4W~<=-OLOWpI#J3fa zaKEYdjiBF+!28}+g5zvbV&o3WP#bbHWO>K{ecm~(c&oF|{mr_0G-ENnNK4DGSuAJc zjl9HEBO6ztKcM@=oW9Rw(hTGokIPF~TB3pFta+TP$#F%as%p0dENF#fz1{c3(tcQB z7wS9O(@52@Cw5x!^(!6zmPj-_pMQ*yILOLJPn2pv4gQV;eR^ndOA)+~Z+}jn4g@&7 zl0jC_$bDYL;X4;#aL%o-C#dL&4I!GMn3G~1oeNx?N&Kute6rG;9;w}mxqerS8K*3; zxf;M_FI{I3KC<1j(3x`@nNL5;u3k1}hp4=z4I0!0c>w+yZv2nNKq1&=W0YCxm!AYs zQu9DX5;k{jMm49WYG_|Fk?>sm= zz6#%cyw34eMFqaI93#cm>2vF1J7*EY#R!p;2&Z}TR1KRgA0nrhF!CuM&ksM0A_;v} zSc`DS#l*US1Kq(JK7ZEe$B}Zq{BtW5w8^I?yi^A<4TAVTpkP8=o8zgvFoGYNsKMHJ z?@7IoYu0G0ck0s~H+Id_*AiQaE}q1VM@#WMGP|ZAx;1J0V2CD&pDAv7W0ofQOOXEs zzK0|Ri?_{is?2oCG`idfVzY8zZ+07$8F1DH4pR)$X4hvRypgDjz<(VL+|pr)u3)gl z0tK7vD!tq!(D1c=t{XTsQybTXZ!VW%_0_LFa*s~V3*JR3ZQf6cxKjZf(yDcN(mlk- zBk${K@?tLHsOrLlpgbq!AbtK~0-n5H*W?@(D5$=G(q>!Qfu{sNsS-u8i#6=kB#k_;S6*7oWfe1a7GleR=ou zHFB~KM&tn~ za?|EWb<3LM-9tiYtN18}CZgBBZk7Y5yv;Tk^bH%ha@mdiUxkNy(&)s}3ySRtU{n1TMp4lf)kW9#%6hNC1RCFPvOCuN(>G83 z4=YbANld>R`MV`S^XtQ^aJXB;H&Mrk5zY_EA-h5$#XOZ6bu$ex!7E$?8&ZM`Sl8!y z6?l~YJ%8lDI;Y-56qhC!c&otCevm(dUe*$Etp;;F{7u7FR^sL6p!WM4M;`$o_%)mi zR~w-TLnRCDlJt{JktUfUDbiBY@Bvk|-3}D}c^$$dRf=`0DHBQZi=p`Kxo)j&mB zf7k28DEBTV^>_2N^utBd!_7`ICFNJMoBo3E;4cXijGYe{6q__lGmMX(|4}23a40_4 zuN;dDr$*|^sA5 zwTQ!jKxelix&)Z@9b~HGd2Zf3@Vy%1IV;;0+>{aht$N8G=eVvW-RI)$QC;zA>sKoA zpa3%l&4g^7P5=q=4nhOPao&S!Oo@^S8vQFA01aGC5Y508Yq$r8TtW6dyp z`}An;aE$|HZ)#CpWMw#a!f@0Gu$sv-xOQei#A5Tpe2Tp6d9c_hDEyf;EFAn<1T37B zSP-jBzcvE+ORTC<^&U+eQE!#UCDZpfqU1P`e&{`e(eK}OpSj644fR$mv!@GK})xBMqJQvJ95=JSf zI_U9%(Df<~*220ZD&jN{EY1w!nt*n_J<0joo=+AF9hAwFFCcVepoVZeIw1%Kyan}Q zOw~Ca3EF(W^2#gRh(=BupZ$^~rMDXA1K}wY3MSEkHwwzO3Hm=38JQ~G(3v-rMeI?! zP6_*J;k9ITlLuj0)nv}Mix-`cc_!^B>6A6N<1eC@OQVf?z01DIVQx`ewh+o6DM*9s zhX`+W&J&k~Lpzy;vo#_8h^0SYzxy0`BG~&WXb)QOM!#s$Gxt2@gnWL#&*KCECD_ia zF`ik7JJpc!um@fi*b2uj!8vpWnL4?{9Z63#Wbdnl@WK6D9pRE6U>i}yYG`9tQwG-; zVELK3pyOa?JaV^j3dQaWc^5K!39hik+mtEKellp9>x}(uzZRP6co;HWE!BWzDe5q_ zg^WnE5`Fd3AkxNi_)GKMG!4&PH;(<@$64im43f`!ZiBK`T;b^hUgoOaZRSBLqU5P| z^0J{4OQ48=D#yRuz3Q~53l#^AP)G|BKLJBaGQJi7e;;<*ZM0NY#_#mLSW7evu^7Fn zn{BV=1Gkl2gW_(}tJG%F^j!Ah$FV<`OH8}-`R*G7C`#5o!$z51=@`wl^4}N`jfkL} z{W;5=%~xF^4|%$ZC5d1g_J1=?YRq8o29}2kT5WA`+NSw;NxkOi1_|Zbd0hOKayb?; zEz8K%B%-m{(j5X{P@JiP(E?RA>c0fYp}M;?-|^X@Dvul*5WFO2HViWfrw)$XHhI6V$a@alisuGFs@H9?H^IS7<~;g zJ={(3zDK5DqU`VuhFN=MWLXyQoqiI|iF)jwN{FDegj>V>q>PG3qCnU}ShkQ&?2&*K zd-_|LKv--3U%WMa!qy01K0?LRiOPy;HBepnXAv>|9^KJNMBsV+UF)%ssz&+yeGC9T z!=|8)Xu!M%yomS3v9XcoXh*)yMEa0NI+gi^562<<9}S%Krn(sYZHC3%ZbmaME@|wU zWrb7a;eXsp`NsRy{S%+pyO@F`sb6>Sh{>N_oslCAx@)(^KT0E!llApr zI15J~2IrB%#qQ?q(y({Dg%3)H*SY+d(IZ*)hA7(CB`sb%8~$=fZvkd-U-TXg=t?_x zjGD|q>9{;qYpAv3s3!o7{6kc}4)nWNv~L7{H+c=`u$dz$>@xul%O;*SPoa5~E7>~Y zV&CPKz30dZ2c5_pc?yNur_}f7s-27uy_yG2f`u?CNUSaX2sQ;6KX1{ZoCnAiWr&r> zucpN|)1u9ZSSg8`{f+D?_ET?!^hmt-43h{!{oPa7B|+aZ%JBUSjM*l6U~PA0fm*2U z`qJMzKfxM1-Hyzx9XrN!Kh;)p=$V{hPa^_@Bz3E4&}5DMK$eP_&H5(xqZ~LL=5t(C zPv^``3t?6JF>MR))#u@E zA^5^Ea*VUd?Jx|ICg*8w&BDAQ1mZ;g{7&%{Z zti>`=h|7$4zn9FUdGyo6hQM(T9z>~ix+^*OLmMh^t7{Qh{L^w5H|5)buFe#WZlOEL z`w?^}0Nmw-_DToBm9IKiMB10Ym#4H{#`qj@QWV6pa=X6_cC$nUk0wlOPd2T;6trOA+b7VXJY-8{f-AC7yszi_Lc;hPB3Z8K66v>9_)O_tVwZ z@)MrvGVsOI7hVN+`JOO_EvF6-6tXc=%`qyCP2$jIARY_-ged`Mb!^+DwN3_-rt(tz zTEhc&n}B~58yu{uVI#UGmVC`3X9fNNzqv?_e<#9+VaP+hzC47mU#0cwLCW|PsmOu% zo6pInue4#ad`W>V6xT%>gm>^39;WghhI{dPR8D*-Aza)DyrJw18L!7XCfUC1ItTobW`+>_Vxdoi~*cz{2kf+$6=_%5%Bpwl5sfHN#vl} z%&juh>}_m@j7Usv^1yWZm_jK}QeUfl8hJ`tTQ#=nEOaWF?=huKsM@J~bAi>&PNUcV zEys$GU2tTw!QA`Sw@~FSN41~u6^Nmu4$&l&*F47sOaUFH$lasoIa;|_HX`5BOsNCs zcQPXOYA{hfam=8c%3Wod$4FCVnN(B8(t}}Frzo@M!Wbvt)ZLrcvFb!B%H*(8s``lx z7^HM1nLS2;KAF-D9X5KiA4gJu2g4EQu%ZOp@kQeKfwuIMUr);r?Bqc%H$y=dSu z41<_Wm3d)1&E>F$<6wvHCIl=c3;P>od^Z`(?aq(eCIrd~v~c;T1W@*NNzD#S4KQxM z-ME$AEmmbxep#*JYya_X0D(aD+axuBJajR6a#~xd)C0!~+wgaG+cGmIOe+$M?ya|^ zjNT2?HGn=`?K)JK`FU`j4_C{}Des_jtvwx`lTZL$rI#?gOZbg^`*EL2*;$R3+x=x8 zVR=)n8u5I1VD*U|(LDyPTWm0^Wzq!wuV* z5m%3(3a=x4>9wzO+16e}9bK_Yq=S*VvMM#8wa@q`LoFEfzzM5ZYjWPSS19*W@4w*( zcN3#HGE)X8GYtgJ@RI2``@6ghmb}-}V4(#r{u5oLcMjR+$*%R=^BG{DeC6zNTobw# z@OFJCFF!%sDOtR02HxK$!?T;3?53X9ySEks`oG>`p#X?EL~<{=dV;)>vOWAtM5-Ig z{lyvb^$_P$MOF;R(_BO(jeR+4tcgLV?Q6AQqTgY}NA81KWd80;H*w-b;WdgRVG`d_ zKU@rBx{2mHNd005I<>mVR(@j?hcxZanFt!#e=~Dd6n^`Vk5BWgRJ=m{Awq?8$o>ax zm=$YcCBEjvFvxff^Vw#+Bj7cjCZVCF-RCj$j~6%}VcFtlgGyz*Z!o54K9zm3-bBTS zxD^#PeLkXE_O#$u+D@)P3#P^a?L{q8(I2#j@1L*=BsRk*e+f_wtWYnJJXElrxV% zq^|JYS*XZzyy~XYe^Q7FV6Rr8wwqf97$I2BJF~9wgv|f!PfWnr5~Ph#!(qrwoeyZ> z0~#g`{Ue_UrYKnHCHIMOaj|2|hj5c8DCm%QN$k^e^Vkrm`^I(w#3N0kxwx2T{8L|T z_?!sINxZafuAo+yrWa0@v^cf+H)4FilsR}$)2#qE>qmW+KNL&=@W}&Ye)@v$!*FB@ z#8?p>5837}yiX!g3r&**5lhPO)xo`%Cx%q!i&C0Ge-G>O-s^bYm}c3Qu_B-GeN>XV zeRI@{qA0|B(yZX5&F02^uFa^%72!)JObSy-LV_U(_^*>sUH2t{1{c_Hk}eRqeZPeV z;l8E`e@L@XqH-ZNlGOMM@(EGnTH4(v+wjDcGA!Q6Av}&)&9eXHvTL}3t!FqZ8)QRL z*F0X2cj@SJZEMp*grWQ*y7cGyuHuG*6w5EQ;FAij?%A1mTI zQrMpCFkp1>?LdiXafCBp9F!Q(VkiREvG|J&_4Yn0Uw%g-F+VKy%#E|M%FrBXE1X7RqgkmpbVZrvp0n%|RXHrMz zP^6Et%s;`Pe=5Xa8)Rk~eAHVAPps3S4y1vUW&*j_ogsz&2wSQxi6@882D~rDTVSAS z=3*xjO6WZsMd+>4Ic*c=&&3lozF*^ETEmcPTc4s*)y}h=w=FbA+dO+xRR4IC-C=BC z{$)l=BaI2i<}_-QDd$HsD7YU7x~F`w3JSXE2vy(re7!<8P>&<%3f|wRG&Br$iL>U2 z@`aF$IMME`rZ;>L!Wpgh(-f3ewlL2kQ>R@tX&3n`)Wc8HBFA<$-rg$oWiYLICtG>PR4W3=C?1NIhlpmK5*Wa=XF3-YItw2-#PD{PaNL@R;+fV!73%tl1 zwY*#ZL|fbx;-s`kCkrYZ5KXMS9sXGjPN9J#JRYWhxFT-d+9OUZXOSeB9tFUs@{uTv$< zs@jKs&4fW9vrgu6#2W!5VwGyKZKemh0deA0)()-p1>b8?gGZ_1g%Z!AuHAwefWO@q z)|6EC403<-1TZ7HS$wpNpZ5Bfx3VXvdEq0G^5is37z>3LarXI_^*Oi2e8w&NlpZQP zWF%sf7|*M`Wm!1mw^WBSaDnrcpVrlYvQpL~6}@tCoTokD-+7P~K83*eFl-|$mx zTj^jMQ1F;R+d4(sq?7dHo;QuO;ah*{s@Y8WGo|8PNCa(Z)aCN_WH4V%G?uJUK3Yk%B$KpqjP%T5y0-nFUtB168v*7)N}jY^!y2J; zk7wUYG{7fEet6?4X|l9Lv3v6UmotJ(h6Rii9*Oku%4Zw|aO2_qlDkC%l%V#71})Fj zi4SeTb~Q5IscGn=j-#c2Rc#>>L=I8Y@gk!1MjX$eBtpA!`R3hB{A%<3dg-U5KiA@| zTdVq@Dz=!@LEqduqxAslG56uyAX)cl)vMBwJc?MEQcfW0J8ELp)o?92Zd||-<3A2& z1ucEmx$kWNs}&wbiGaQMfS-ay*QjK1GDh#rg3AwiE-#bMxUYm!3a|6oIO!}te`_#N zfB%E0FcxmzF)gdEHvJB+D4ca+)sOpUrkXKU0^z@|EdO5eXw+Q@bqNo2@Q`l?1*``L zd=<)!oHho%{rOpBmh``hf%^1TNORI7avJN5%B>SW=+N)3}N@f|u9$~VB$*!VS*2qUgbo1T^@sarfR zWRCDsrun@`MO^)O5@5R2)x*vinxO`g$$L!?yl~*{Vy#BpBF7g=mLoKr zlM36ff+1=-Y)H&e>KsCv^cyMfnJl_9ldj2g{{XqFEB3&Bl4XiqHZvKNb$`awB@Zls zKa52!H?E?C@BU~XQ|-)bodli9F{(_*XT$5)Pzhf^*Qfg=&a!d>K+fya&aA;feg{6v zm=b~9FngVGQiAt5BrK)d=!$cALE!y9mpI)ed!KHB;#TI@qT(hWmt~>J&QpG~8ZvH$ zGXy>t-BsBS7VzEb!r@mQ8%1K?>1guuH?% zKUer8dznwx*@3sFWyc2eNN`;ZAsuyhfTT!`I$ZhEd5xrb=jlxB?{l~H6A9$whE@K- zzMBNCic5s_haxhLjP{iYc)veX2wrM2k9X4sqvue^yDEJM`O9r8KfTWoCj5FRR1*4N zfWBdfTyZZ#dWJdzG0iPVo=Jw6XHF3p5jNl;gwyD!ISek{q*;$RHm*`^PgU63s>tw z=4BFGZl&(b@`b%$WLhtmN@a2kTvMeVR~GVl>EDcWEA9|Eukbw{rEz}``KN4+{4CO7 z*Uf-}8oUVlv{P}w*BtAPDHHm!$WG!t5pDl|;O0G)=-8FVY6K(CCc8w4fc{15=WWvO zzKl+q)1Rr*2`Ro6r)j3eYDUt!9teGoj##A%I#4Jj2gS0VscHer@4$bJ9#wdmeZ`YN z9x5RV2k3q<)dR?N$1lG?)Y(5C5tn+hSN5|i_}Ul;a;sZ1N+tZ|s!Z=<_pm3Zr`;Up zpHZ9t5S#Az(Dc48KdNi~I=Er6g9w7jDn%M~qLiP}loC5Kq7SThaFAtX z9v+q7mvQ9vrVwLvj%^z%RP4`8FAVV}a%0p(h~imZIWSD$+4H;*dcF7Ka<>zT{1#4E z?O5o>^+;2y1Pl~)^YM2#8P*^f(&#b272Ah*%P@>&VH68uWMky#rc0Ex;L8sHh>^$)6aml^!kONm~0aa4NU zXNa^1$#C(3%7)alK8EhoA}cG_(^jnT8>0CC9P%UX+}DjmrbX>bJIx#Xi45WF+E{B0=*A8XH&C2-@L2)$E?sN zMpcv>%a&CIAc2E|jI%*%B{}O0{)7L2YFCRz2)xWeA@)U_EmqsBb*nG&r!eMIE9bcu zD*QEub!j(7rBq(rI;>g4AwN6?+kg^;kvW z+f^04ohQx%=x{ZZ*+MXL{?BimL z&D<_E*@F%kzOo0dvWOW90$CrYuUrhsGI%|4#g6ft)i0i`0{kWm6=-CvU3Wtd7VdVa zD%I~hA7*`;C&|Nkv&^1>v$ggMIkP@Q)#vrCdB=4L7w$pOqcztT$|O~IG$d6-hBbYd7DkXWL(rl6{Ls& z3?tMi?RDmi@%wTjO%q@|#9Uv7IJO(%5d)*tTukc7q$+y0LBBwvEP4 z8>h*;`Nnu}jQ0o5z5DF7*IIk7Ip;Y_RUGmTJ?G{mquSidMnmW$lX@M<95U*RpDKtdC(P5R^1`IuvD|{bbZs6?2zdDnOK1WDmh(E z81bY4fkfENr()Nh^0oELr+|fl%B{$%v%lB6+_tLbIyM?hOG;1BHG>8V3?iYoqFkb~ zY_7#b5#^Cl{+^!wyhmPGk3R;T9)r1uaiZiq=tb{Gf>!hxbBrFCxfhAPFAL)B#O|!&M}-?>WyQ-m!9Sje2ct`pvqc7u@Mg@xwh6e}o%r1w!XBJbgDNq=WrG*&yxLR~EZR?3;o|TEVYZn2B_mgw=Bm<-(;kpWIkaAoLr1_Ob`;Yp(VI^?JI)jQ{EKYY4xpM#2Y3w`2kJIq(at&k}}}0bR&` za=naIojFUka7V+X^PwlfrBjiBHZ|*J6!hOJ7w!RdOQ#iAnoD~G2ijQYb>*oYR8+7h zBm+HSuHfT9N~-hDY3)&(adsMDoKtbYY@wVi1Iyvl(<5Tb*UH;(D1pdK3*pql_@Ner zGbDCAe3sFKW`!A&_Rcqzx2I!YQd>G^wH{UMbo5nJXvb&dU-%c}U}wW`foV#_vfV8VX{zGO(Q!{-gDVkZV-1vHRJ1b*xH<%u3~l@Mnw zG>U|NhZI$O2F9o_!q{m=mb&-GDf7v-ak#bD|;dL%;+f@=amiq3lMH;@`<}KgX&Fw~N4A@6R-w?!Qq2c2Dk0O$Y!XYige28ZYWV&~^WaoZ z_`K6T`j{!&j)eb$l*$|+}0ygOddN700G2jF~;)?KgrW-d~1 zjU&HZ-}mLZW@WQunfVB|p+O>_9uoaMdnsG=trdD};M5ZrHD?Tl;3+DsyPkQvkBqEY zbc|E-?MC?$1IxQ7_RC+R!v8@sLL8o_XaN>N)jhQ@27%4Hs$e z50wIJu;Rh;?GuOSp+l=dcmMn%xj)v7X5#F54uw!&iF>62x<$WvHO>cRT!F3qLI04u zcKFRhyZNEeVkc;=u_%7s3Cj@>h!{!n9%KOrI-QolRK&%i0n1$=sK{ z(*@lYexkunFp7cNe@lRXHQc7hVT2xUb|*f63x7_iCXnGrW5hiPIHK>;N?-mk?2l~K zE*LZebkXj!Rd$>Ca%EkUw~C1VQf||V>foiPx!`w6+`p%}L4tc3eLI%YHbKlNia!Ni z(lrqYHzr!D|A^-_w7x7q9n=o9SWN{eCJ&S#pylL;$?qbF>AeLpJNNi)JRBTEf~ISq z83`A3d;8=MkU-K<++=FA*Er&z(|%KUsb;&8)&GePR-B~@>}Rww0unv#-kR-Q^inYo zVS{hf(8BG-@P~vTI@P0HnxpS=IeT^$0JudRxCcD!ELb@eNWekU-)}?RM+0Is9wa`?rHrA79gn z^i<&WUP;_oo(i#0rf4FisnF_dV5L5ON0(o;5!84A=kC(TL6Mh8e`S-HE0S(Gc)zE8 z0MY^w&q%P1P$c0_wetHv=Uji_Hp6s?uff75grdTtf!#WyC>l#DatHn5_An&LNo%D- zTQEp=LkQe**NKJ&5BbVkg-j5qwI1`=8Kre-Ysed2(E$+BV6)|ncP1E0dl3#4M|OK8 zWXk##ZU5*L(=M{refUAsgrIlxDRhH{2YWt5l#-U<4DLk?ki_@;7u0Q$qlq8D=rbhV zw><}1#(I1p2PWJ;0}q5{rhJm3*7XG)D)6b z1e$0d)9}`Cn;ROeRy;%CHvL9&2yW_11eJtl%WuQfwbp$T@uo=9`~^wQ1*v0!L9VAl zm`#K2eXeFi2O8LI9u%TgeoBPIsAk|!!Xa(!Qm z9qhBe`Qh^8Xx{b;cgM82&D>J{3YtKuy5eI~uL-BFf(!&zp<;qUCTJdA2^oaU39Di8 zr%rRza{Wrs@;dKGkHzLo#=vZvx%mgXL;zmc z8&uCHXe=25O4}!Ab%*DHEnT?`Nw6-?nYzoc0nH%t*f!RMlM?ZdRDs7kY6vv2og8tI z&{8tTc+F07-n|$Kl*ZH!bq+X~HU(?3In&|7iR@xu3nI?#E`_cCSO8U@+!fc#Lm43h zJ#bX}_4>kYxrN3Ru`$`AloOk72fx7|B1R5XQ!7M^ngU-fnu-#QIf<$PD&TB@j2U z8dLvOf9B1OyM3RHuxJogJ!Y>{73A=cl%aZtZUnyHg0(-Km@gjIJ4P)`Zsd>{E(fI3 zRyIzAri)KvU8p7k)HG03QM>i#N>DxE+)}pO@^?iYJ;&}RgoX|UUaANA_=vr?=lWt* zUhYLwvG}%mt^_}B=`*41?z6+cKhDcV@*Ae~Y|V`8PiHc1jyFf(8Frxm%b0wh9MBzB zTn|-sdb_SA=l8DoWOdA1edZWhooPx)DcD^YeY{s6E28R)711%-kflaCSy{Q$Y=`}G zUfUP^ohX5l)n#Am!X&-F$g>Tz@(A?!uTNzfgNzSb=a@t&QTR71|BPN=kEtF{5gME} zNP4bV-$T?~V`8!!j2=!SHhMp%<=^XU&6i~#-``9bv9H*Hy0*@uoshWRL7q{v%2uuW* z;r15-x7P@cS7LM5hl92YCDd1#4*LH7AFro}rx=ce9~vJBGh?m`6Yop_)e{#3UH~xX zc(QtE1l#xcAwBqjDyp-=necjIa`}tzo;DaJj455Uza>~C6vO$x`iM5aFHg^_Zj3(WP=Gde zs*u(bAwU#XAkkt?X9Zr}x%xqLnj|#XiUrDs#;`(WlTzqbO!?t5A&3G7*5{+$7E>noPSPwYiRx_}o zYxB^1lNtycfK0mEqj*(oB@N=M3BV%ieRF3$wmlW8)BQp8H)&sRD>;h!nt|GUNK zl7Npa&OyHU@|%cR%ooi^I2%yY#b<{hG$ykqYrWQ|oDUZoQS1V&^?9F1>ptN}aZpnCefw=etiS?>OYfbyMT_cu zNTbD6kU=G}0H?84;@c(KIb&xD!5r4lxEd(`2O47JhfcuqLySC2e%J2ypf&eb_s)P} zMbiOu6|QPsCcZV^UUPMobLx9^a6w8C6Z(Xn>@bocfKmVZ0=}!J%9cq4UW^^aSStkM zX&~R%TfYqC9SMstbsQBz8NiWvw;{KiPX_rqLXfMnB;J(}aIPSHs4_>%pXQ1g-=7fn zS2J4Nh{MM0L^yYLZ=SHDwtEdNAriTfORlAo=>6 z{F0A*a+urEf#wFX$TzEDLXR1JoatX$=aNC)VhlU5GUFSN4h2OoQzD#1t`}d0X@yA-C}&l{Ez{yn zuMLY5PW9dNt({x4Gsi92P$d1duM9OJK)Ty%gmNS5iWff~3-uCcWy@&i?wCqlOuPdS zIRBKj;9GMhqTaxMS2{<}IM*}J1R6DGZQQ_#?4>aZNtT4UNAM%d&_A`ptb|>jyEPm2 z?GwDQLps;Ao-Nnfjz2fPwz-ryZPoB^na-}f{Tlzq99fPc%J&IMf}V|L!5NKu16K*i zjH+%#aS>w0>6tFd$?{L^zB;VD+GIc52O6ysrj5UN>9!gHCX>M=_3Cox)2J-g%-*7` zjJA}hu~&#F)iab86MmrKTVADSFqwoeeXPbPD2=tQ0yBNsA-wC5irc||9sgdh;vT=nkZyNP`R=4_k0mTSIeTaYc8J`Ra2SYp)vus*WbF^}4dO+qUm zF*|kW)4_X0JZK;S9@NzWFhV(sD?HUdWJZc24l>(lxx*0JMs7AUs{d9OZY@kY1(RQ& zo-kkYdn%C+D49{0poW2i@%jCSmy>{}r*&icK@W+LO_Wn?;L5JDoe^K{93g#+ z+*Vq@wwAKP$^Fw4N?L+F{`=sI$F@QT1%?&8sJE`Z-2Di!9I7)$FdH|2{3(173tz&c z3rqCEd_HMr%^sXck9X%S|4K~MyhwqN-SxF7Jb2BxbTy)|%*!}N3eSZ7_4`-T)m|W$ zV{AGnMgj4V!uCSd&xW#tZW-~^HYsP5c<@?l}^r_CKQ7mOq^P6{U^Z%BwA# z`RG@M>;z2qBnhvBRZUkGftIG`i+KE=@us=`ZQZSJ?+?#>r?@|}!bki)<=wOHee`q6 zw(A#F%w03fVKAIsTO@ncY1Um`>c5RAktz5{k_mhZc{RH)(S9h8y)M6^Kn?uN>z|<{ zyZW1mSOFs73Omt_@19|#a&Hxua9?xFxVAmBWXQ1Xta<%Tj$i0(39b|A+t|p`L?V;x zhlS1-8=A6po`xRM`MI5q_l1g3m0YHk{Bu%)H+jV??DGf(izPJ9JaU}5!&{^Fq(3lM~QE|QM z2n~6mpA}WX{&z@KAE4$A@A?v!dZq{C%Bax{LA4_tF<9>np(4FJv*NaCONOnygDY9u zK=o3$i4ao*Hbcs(`)~N&?p{m8%JX=>)9Vslx+)lN3O&rE$5?sK@`&4a?LIlejX=hl zyMQR%A}4x&KRUnh@$b6yE`zfV_~m+3afYvwCOSC2+%GZm|7Hd0zh3=16{b0kir!%yN6yE?|^E0A`_ zXKc0T?%w(TT&}VYzk`1`YQ#|gZPSs=Ke-nz>+whZic&EU6Jq}F;XLFpaLM;!1H6Zz zD2FG?PK=xKz=~ajixay9GesYQDJN9)UB36Fz~KFIV9Sbq7&R)lA!+0pF(j9cuXOr) zDaiWcoLO6het;vqnM%tvl}#w=o@}!9qjYP{O*iK^b*;|$cXPSyd$mtc zP5Hu57{B0lgKln=dXP%TDIh}mw$i3ky&U-uCQ!C*b?tg2T`5DwDr5KU#&G0LofrsO zr%!HjMyVF>RQ|3^OgbJuzTVCi7w#GO5BbR;CqQ|@f6F~I-TP?GnunIV@=|_pmF??O z`$C{@n$Kxtn5iv}YVqoh?}O%g%kOvr%3%UB6O{Rrj$*>hB$UM2^= zmQy$GE!z#(P06pGKOjpqD1ZA92mi;rtaexuKGW4Ok%Q&mF!~^0k!N4)4%o*#dNFq# z9sKL`>?p}i({ba3CZSwINYpr)&xzj@*)9mbC}H$eb&q||^n5XrpppJ%f*vCEn_jqL zF_i<3c+~flut#H`Alsg(TgKqSxUJP|*F@^ynnUW##sPIQV8|CDJIrNEgQsQ9Tss27 z4;(wQ!kfM+YI$uf^O&w^;i7O-PgXH~@XJb?`B-*Wm9F$}1Mc?mJY%IS4Yd%6$wT6` z)&-vMjSyxy*Vn#AEx7XJc626gvkkyP_3)0QOYOTSYu-cDNXbU1oumP*noE~vR7)!1 zgSLee`IT~ph3&N@aRMR&sihvcOX`^RwDiCVo$rHubF|ij0Dx6Qt z1yJEv@(uB-pw0I+R9G!Pjlu33aEpqlnu*lhE^DLebVAKgM$N@gli`I|PvhQ=afFuh$*R&G{0}g9Lj9fTVwX zth$_$RW-AH{KNQe!e^Ov&Sp_!eB;^UTB=3O@$3x;xX*xHb_ z6ycVwUU@l0&-CHmExE}0c$t|x)DzQH|L2-o`*R(spr*|WZv0lr01e#w6+OI+`nYSK zq5+o5rr1H2RLu<;OAT*_R`zg^JN5zUm8|mAo2Z;H#NdNltnAM9sMX(xn|iG=YUVX&(cG>DwbO}mZOE_3jA{M?Gur+zygD1vo+epkr=s;Jk=tybOgtg zalxUcgvRE|*aw0~k0{#DuY03k(ayvcE$UxfD-v!jRQY)@m+1jYwz0x4zCBo*?C*}7 zH-vD7!DTpcJo>D$K(}K{)x}fVJL>&D7flh=PL{6Ze&X(+*#B8|8UAdL#N8U`YSXc_ z>p`%7w?nkcUt6HQ74y^2i8Z`bz%f~HB#PgzDZ&kFrKMS2AuDDFpf1P-w;$pf{(2ZG zoz=@l5c>tBxcQU-hU_Oze%hYyXr_uyUVY3PY$-Va7nQ2O@;Yt8oCw>GqleN|MiWMmHd>4uDJ^8=n8UNm*Q_PeKsajh*^jj|?BNPD(p ztHO$9u?JPvJw5f0dTG=ZvuZ>-s(X(cZu~!xDMs6Wm{!DA zj+IZSo2dGaD|T(^ttOT`VOe{twsrz-5vnfQKXmcATv60US)G?ilSn@aBp?^EU`aQ0 za5i*_;e_gfZDTKt(>=V**{Hg1F9Ymwo!M}VSLFGU2myE#|(>jYocdc@2n}KYQ^uSM0qh$Z= z=9O%l<03s`Z%=rk$~;216-Xzew_DQ80Mc$);7e%VTB20DBp(x^5M`?1 zH=~xir1agh2ZQb3GFRlw-LoB~@Ws}Ukmuc&i%??;^dr#eZwsE&+F^|0QgZ2k zSkzfq`|6h=;nb5sv@~FHHD+;`D=?@RL&<|D_Gi#RRs;Zgzy;$Us`kehRAs@EX6&z0oA0YPGKf*?UU~wwp z-hT!6r!vcJ4CqHo%=gw0G&?K2i8Va^h!eqZSxMpiF#T27+s)WDqh;*X;~q>KIC2gn zV~t#_*@4_hW)i@~CBLvIjN;;42t1?coS8GZJmi+L{=-=g`i%=SjUOL@9_FK?3;U~q z8vOB*W!FKB2YV}t9;{HrRcDwN6HKG`7mc9VHn}h9c4g)DL}|lo!&%mQ7Q0!W;E5En z)pYKqtqz0_KDsclF84!ei52l=tn>x^N!llKd>=@Zl=A|ft#hW{cjHw^>$FBb{BuISb znte$$En0a+e6epV&Fe};>vCjwDX4ZFMP7UVv9E{%h4@VKz)^;+e})mf>gt2(^1Qol zbOm@t{w7@4ACFbaBg(Y%{YPgJ@1>X=cHzLcT<^!R;B|$jL@6-1*_qZ}kgg+xEZ8fx z;O8TUm9D*>ZLsRU{6n0k>Ww?2$p&BgjhkR!CKcwRLbWBb;MPTuSVqK~Z8qZJe#=xz zE9Q!sWHWMgRGoO13WN;ci7MTpf$X^&MZk@ZE~IUkwvyw@eB#kGl+tD`(2Z)9Rshc; zOe@;8{YG5)tdTKKJ5pVy|1jUSD0!^k^fV6i1KHk*g5Pcm^W35as-Q{yCg(8+OrE-L zlasR59tnwh&Ueqd+ecTnoaxKRg1%e|+hrf(u1X~yAA_dl2zaN?e&2|RdXU-ErHl_) z{I^M1mz^kFWzc}?2Vp>9X4Jnc7a?}$tY>j0 zSx9&_cWu)>24&0dJ`t0Fqxu?kopqJ%ZZe7dezmK9nh0k zn{ot=%{#)@-fh50n%;k$!@vBqM#I(aS%~M^l`04LeJARvUAO16w1CYj-=7->MEkPK z9xlJis~8$_8rVK^QGX>M=}0CP0NIX-yWoJp0by}9xPX^W@m5aWZz2$&BVl|9BZHHq z6(g&9ej=_Lz0?I?^-~#{K-nf*AKLm&-Zo2NxY%*BvAnFyp~&-rBbW!el|V@6t_$@J z#vqz7r+Ih*sf3j||N8uW2UXpN9B)dhl&91$sd(Buf)GYooa^< z8ePh6t8SwR{jVjyryO)@r8e`g4;2s z5k>CdvNHy#`aTzObugCv?C&S27%AstoaoQ|B~|^cu6YKb?hMK1k4q|F9MECLwWln` zitr}iRSg*Xo#;P$F3IMEb3CX^H3QGilo@VAW`T>yl$pXbCzQBN4*6gzX+Jg?9Kv|a zbE)KKN(GUKx@$rK9?FcBw=b<+a4T`@a^#E18vBpYSe3V!x37lLIYN{L2-RfzK|PP! zsfUJ|3>sT_DwQv^%fG&_D((dCY$S_t6GQJqb z`0EPG?m0GIX&y}&$p7X{sbmFO)$7Q<76045ZLp}%zKRZTEl2%dI}Z8Q)m(I*Gqcgq z5{jX%Kfce>Oi0d)m&P)Ce|a}rHRCbklt&qT)<}7PNec=G;URimWtWa)8 zh``G{>klL5>wX}CcD1yZ*ahef1XWbVF!7)80cUW(ZMyVX7Rg;I;ua;qwffGGZ;y8w zVZ#~N^jgO(<2ica0=(dtpGa>2{%|Qzz_d5Z5epqnhd^_i7bz%H)H8KO9HPzBUH=Cq z@vbg=9owq{jgej@ttF7tpRH+s`}Ub19k+d?4mm^RCGxO*tvqq}D(X`kl6`j8?!lQbKts#t)N-87*oR$Gh7UIHr5fnu8v zH0o>oJ+%Dv9;~EZJyvSHB3sS`ifY5Sa?^b_fx#E&pTBQ8X-gKrW{RVMLHk`=WU6T{ zNk6ADVBGuc;}hJ-Z90_9hix$UUMM-;GlIUCvWXI_<8!2Z7PG}H(d1W{5Cp;vF~#)Z z5I^xZAp;+h1&GYE8BN4G;<8)F-C`kqcdcq{xj$n~fLN>FYFY=JLDs{}D(Ld-)oDR+ zkrs}zYEr$aU3e@R%#h|-iKdK9VVa5h2Dsq@)WpI!_?&*a`Z4<0}hzUig zCo-T~!rib{v@A>V%l%r~aNk?m5W0^7SQyk$L?APJ*8R(&M5keo0*#E8MwFf#FRJ(M zqv4Ul!3p#DpZh)NI}@%5TSNli#nPlVQ_1YU2Qhv7o>Lc%L0NY&SO+|~Ua2_@?ulw8l()z*a%pKhJ5NJf;0SKkdB9kN;C`A?%velJk!~ue;viz!QHUIyT!7=@~0wJ zi)BMuRrkK??5rf)@2t!%VRMZ@XA`IXu6Z|x9|CRlfu}tDiifZ-m=s;C4MK7Mfi~Ed zVF6zYRVp-=UMF@69aBUA(ubI+ZwotWTm+Jcs#m7C1CG8G)Pn}h_G~I$3r+kAsYJA~i>e|-Xg z=Dj5ER$iG-QmKMJ_}_B^z%r}G{ssBTS1zCCPjoHW5*CobhIs=Cvt;)WW_ zg}bJUwLPBhhpuv9YhDi%Pv2DyA;;CGvD~n>F^L_EIATUHWjH`9Oo$YS{{`vp3Watf zkz4CU?&iywHM@a-mY5zTp2m=M(NJp_Y5bETmFyaMlJEf{J2nu3aZS2=#4<4B%uO?Qd`sLm+FMz>`)uU28oC77!n$X4QS1J-z<3y1mC^Xn3iyAZJ=-!8TdtcE*pnIO%^D>l z5E4757qbO1Y(hhlwYCV7mV?%(z?^t`hD@K7F5|qO`&M3+?|Mwrg(|jPmkE))LVmKt z`MjGMl*=GPe`}1>QOldrLM6Xfw+M2Txjb-9cUp_TIiJZ3QUyM4|6rr*WO*nI;I>kJ9_($~(?z!2M9;cCTPW59b^qZz}0KxeSzE zVFt?LzJ_BD+bw92^~1RFW>4IYuSg2dYmaRE<~jCvVBm!I^Rne(2nmYK!9re-^%Cn9 z3h{JM8&EKri!DD;2qlK+LOJcuMfCklteYT|*>G(3XU1Uecodfb^L4f1$GznU!t==A za*Y>H34Ab-4c>9)ggwMKRJHkCx0NpRTAAL~{B<+j&ITAx64Z<%g`Mr&G*TF8@s}8! zWH%6W>ac4uRUyaWM2ds;C&(s4G*)#Ih6g&7O8@SbgIqJ)ybjL`zb~4}UruB~1QEmX z%PB?07?+eKRR67&FT!8072-J}>>G76NDEX)?)P3^c8FyV{qvDu>{_X?_NEj~EeLR< zNgq?eniwv$zsXs&qFgX!iG$5Fp|P1qMe6$Dmay!%@!Lrh>d$3n!iaW&fgSs>ENE;RV7n^>Y+pBDCMt@~EeVS7jG4a)UD zT3(>Ffu@wwcnpdMqc~ZFlF&l71YpttWTMY0!z_gjH)w86r%+@H(==A(Ml9Scoo3Gj z4w)osh({HB(9R$pIRUQS*9THjOP8+BX;(8Y6&1kmLg@)}=`g$JJ=l8usjQUJ ze@p;FG1uQHgyFOd1I{?smKfZ}zMA~N&Ge18-QfQ&sI5&}Oo_P(@#2CoKu)rwQMU=i z$~SC`9ox|jLqT!X+EUd=yNMJK2>sbpUg9H?+YQ-XwYe=)d{bH_S$Ts-Ka8b71Yp}>thU6<# z5Zr3RpDBSl#ye8jKMi%RX3t*Utw9#%A>Xl)f+rb|{LQ@cS>Q^SNp^t(B{78CW7IHc zUjj}rY2gy1U@R)u!}Abo-VPjjq9VbSQNJlrRhx8)ARQ|(ufsS;Nc=RPgAZ2B#L{vM zpn;l+cZzk4orjFD0GUWc6f+%wkK@F|bmHJXYGcR`8uTx1LD_m^Ow3kFE4Rf@ekG90 z=$Aeb2ZrTpp15xPB%RIM7t3tQFJE8WRWpbI z#a3%XeAei)MRY@~pF{zorzRNb@n&BjY2S9UFy!KP3F7?9j2bBdzVKRUW8_NFg9G+N zz^w^-@CW)U?DG>b~wuG`dTSzFqs#>}0=phhpo|!E_l5Is$ z1RKrG(;A0RiBUo24LKvxwLBbgBSI>APsUH358G?KeQ&wAhRHA;QzTdQvlK0*D7Qff z8+dqKJ5XUhJGLsTURi|arcFAa3tm#ZbHdv&XJ{+@DtQ(ql{ME;+kk0Z=;ta|^d()h z!PC)ehMUTS#<)RD%GQ|B)AP19Lw$v(6fAM`Viz00NNbx0I!NIR zeb#jsiYT`Fs`?-rPBt$?nyfg?lQYLJLf#bKqt;Oc*->}%)t%`H+>~ZrTQ`)~rM;JU z)cq~-DbL&adDRe?+R{rhkhsF2w!oOYqi&pE(Zjq*rizGsSlR`q4%roPB&@W zJTqlQ2pNF`sb6^FqSpA}73!HjT$$eF@nigiu<}^4BI1296f8{NF$3{%sbJM__pXY+ z7ddToR2iAeUCnC73aDjSKu#Td6tyQvhD#UibY;`-KrNwQ1LO3cuLDqA+AMvn`#{sraMa1c53UKNSrMC@%?(OZB(!Gi(G2grPX>c@xNJ!w zJNCS-ER zUhXdt*D+&ywgGO6;|*)H)V4$+Y69}-qV zOZw_IJ)3%~cg9kYY^qMZvI|2TbcxB;ruqrI)$^`8S z#}I48ukKMZ(inNC9Oll#CM5XlpuGcV+MJ9G8UOw!(a*b?@#i%yo;2aAyj0zKL5thf<8K~sgMbU}&%?~#u{L2~ znSVlJOAl~4S`O{VWRN0`3ov7W$@P77Zc(*r4C48=f$N?$#|XCiGa77wxRS8cnn-=M z?N#jt4xSbxea_*j7`~(EY8sFrU!~Q>!2P2Bj~tvtfi{4MpVq1>aXwsN0XA6aH31OV zM4ZokZh(g>S|DRaMYqVkTFkgB}QVKeJ|v#6R+TK-c0<)HshlK=yn@?>F$sp4P9{b@Tk?v3WA&<-fg?$kh>c`rn!kB z4Sryf)diiUCtWqlH*nH=BYg`M?z%z0PHL)}iPh`oa6~tWdoEABOncgosRw}drVU+^ zr$R?jvy*iBg&-MGA8%MyK64W*n3uw@FK*^4?klBkg{W*zEirg3ilc+mP0{G^(#Z$+ zZ>zS+8HVsTU>IAMaDb|&>N*1-mOeHxxC@Dmljc30Am4mFA-1-^dGhR?dPURNv+^|b z0;axP%LQ9X5bI(zYUk*27IW>kB?&M|y_?kX6t#1|V}+9XF~!ir#1M)rD*W(R0K6H_ zQ8bm2{8X()#JN|5)s8Ky+6cTx3v%<4&Q zksG`AWU%T&cGixzc%d-%_SFH3kQZ!xd7HrE9(_9^@%TAHP$L35@7IXQL7*0_H(JbS zRn+x|>MH%3FD9<`A4w!r-k)aqDDD;EOHlmB5!f#Z;%Ld%XZ~r8NPw8H{1^B>!9E**Dwam&NOLTsq(xCLRwKMnCt7X zrAotz<`%(;lxU2(kob$UV!jB_h%3tEiTnbmmNk1o3Eu7La9Q4 zRP*kr4?!>kLZCnOh>IWOQ9qC>uUgvU6XC zpJqS}d7fJ2d$cFcI^15bE47t3nPjZGI*DATZ))05Dng+EW%xcD?BwzxxXCPjdZxGi zS>4)zf4|~)`KsBZL4>wy=o{gDAp&CfQCa3>9Bo1P?M*aRn2oD!kLf|uHaNg71nTm~ zBf#=#VCKQ7gi?Y}uIP6i_QR{aOyzV&1{)#g`>!^axwv$!Obrd$ylKpJ=d?!1LtBil zz2H3jnZkc_ZwzvwvGImX@_c(Cy~^Y%ONoRI4&% zdS7OTC#64*M{}^5(ZYVD2m`GX5w?0Kly^_h$1-hx+RXELdLj$w!bfyUCt==LFdKO* zod!xzo_Op54}J6at-7DjagOv`2B#zy3lY+${-SOZyzLn;OpQE@Om)OuvWLo#MJ?zl zv&eh9WsD@9+4;kb`ulbFg&CsH(t|dW9*^Wg;UoZbkEd^pGiKfs zNXc=yOXD+V-e$XEjZf#>qI|7xZ`N{VHrJOthW9qg+br4fSg85k)5STJThYu3hByLG zLIi$KJexBbTFFhCL7+Z_2XIR_gV6)nPDgegDK^FNz4g{BdhmbYKU(uyL^L5i>hWXQ z+I*TgHQR7lokUDa=~7C@7Fc-O4+v{JdDlGB&&e`H7EqWus{QiyNTAUiWxp`!-k|LS zXT;h7uf9f!L$y?f2$4erv6CTvbR5aVRD_EbJR7u+k5LLZVFIS$o1$!)mmlVanK@r(N^AvbGZ)ialwBA;=yC{~88^fZ&Q0T;U0 zGd4x%Is32_ih$5Gex)yJw}Vt4RJk6wtVyx#lqc4ER}%4w<(MZ&;)Ltr9~6t0#oU&Z z@s;sUki_Y{>>_{?H54GyKhBSe3?ORt#vlOK*F=FT9FswrM1>BuYH;WOJ-{mvMgQ~z zrEdTp^5L%{t*qaf&uXjq69fBU$-{CQ3H=JFL#y>Q#h%^C7-k-LRJz&_JTiVdJS4LHS=V0Dwl0WcS__ zh0AX!QLuMnW77~tQIs0RH}GKQ^(#E<(bz<(^idh)nD>naz$Q@5O5f>jH3Eb`DA0m( zSW|*B7n*~>FFRs(-~AJ$V$|W{>td)BBCb*uV7`Nvapq)itmibK2@3FT@Z=DRw10V7?lafud?;k@wM;;0JtfP;X+tMK0E8DPPR&a6A^m#R^t;_k%KA zmZ^;WEk%;hvpd{21p0jmCKC7Q2<}EFYn-oNwhlSj?JmZ~zLE87_c_~fHYV1}orvs7 zfagN z^spD9>6416tH;6yu`a?2p>goUZZ^65yrG(sBtWJ*ERX{!ozX-#7zkWrZ;^f{xLjZsZj#SS%ISPU*j-cz3ie8n(bjoFe2QWUeKTWHSeb47kwSjTt!>8KtcEmm+aW%U!g?c)pn%Q0zgXKnnURY=O$m zXbv*F!gvj*@&xxLd8o?_<;Zz$OzC;=&4xnUjKK&;#rSs2;#r}963Psr#!|x^5rdN2 z%)nw_j(BG3wAt1;QhX(Ao5u9I+SAarnJ+O{9+ST*>nVvd9X3s{a!i>KQZr|}rzqav zVr4z9DOd?)L(VDy<_!R0H+e@$IO{Ok9Zb)7M@WQto79$+rajJu;|0D^xtdB3l37pC zX?54jtQbb35Ky$2ZuXWZPVptdq{%uOIy4N5+-{s`V@?m?QS&Ffs!a%CP0joVZ4sm1 zn>cQygE>G6SdxT%Ko@~$uQ3*+j7_gwCiE&ZJrp5J9^0pfd!v*kYfUAJ`wDsgR#_ms zU%6vn`o(?*Md~nZcj0GzKpPf)dX|c1U?tZ+r4z2)@uQgY(XFa~N6fIqXPS2i@&uS2o4*H_;+XaCdiim&GNJ-~@uZ2X|dG zK=2UUEm#Qd?rd;^`{M5I+|6_Aeuw+wUo~&Gwsz*XXQX@1Io+L}nU0CTAjrT=yENmQ zL<9$q0X%&3pfV&WB+*XDjg+rqjr{fNkr=6rZY9^TJ z$Gy|ao9(I@+aJ2n^FfZ-QV!De{HIUvFw*AlZZazsjvk#kp_gsUpe+k;mO`(lEu3w0 zQr?vHhoD?!^yhR+X#L<@!qB+*peMU7b8D(wqcE6`*x74l%@O$|(S+oNw~eQ9{qZcd zNj3B;00A2-*cbQvV8`0mP;`WzV>xsN^T=~Hc z46Qq+xIQP$_aT&*MJ#Mvvb#Kd2?ybmj+uY%T?cL_zD_sD7JrYAaJ|cYosCWWt?3v@ z@hcyR4@$1yy;qy#mxTdr>au1>>8!*YXtMd?;vxb{zi0ok2=98@>LZ8qqx({;xITrg zck4famQ?vu?zW@?Sbl~&thS8TsE`JzDdBb-R)k8nLhC0&Nbv^}wHz9bQ7Rr=& z`V5Uep^^4Rh{oq@Y}^YOD|Dq(Khp=!!VUej?fbKeHRVAnN3=@jtgfV-30%?}@R6aK}n@!$c&G2s~bY0-HZTyh^?8xiCmYZ>0FulQv z71wse(qh`Ao|Y^cVfZO~K_9WB%yzyL;dP(!X6Lkl1`*+&b#!w0ZQdfqD7=YwMz|%h z)@MTNa>vM#W~-qzJ7w(@j8JD9xp;iyp->Y12_)AI4{N(3*8NEX+gX{)Q6uc9E^fo` zCW~#uLGI2k47_-&gI`n`N0Z>%DonflW&1E7n~g9!+QFdyiJR`p?;UAwR`oZ8#VJJb z_pX#Y@LZ_}gK=`WkFK2bzix0H_zHr%dgr?3jX{&LjS^PUh>eT8{E1emL!e`6>sxYY zp!`^A22csXS$sAJ8H#a2(~PKZ?izqYX(%ILSk3YYyHwgF{zF)61^-5A@VXIwoQgI* zR|BWh?FnYj_D>>&A8oXhr{|ifnJ)E1Y`UhkZmCistSHAE4UJl;0z;TH&{<(h3n6}9 zq~1x)T1}X*`v<83f(FtQ;+T~@m;y@7@5#lZ;rt)>6{)+8zajiq&0L6<5XkSDr|#Ac z{HeYbmyCA%R-`c`TP)lA!q|b*Q+JRZ`qI{*|I5$+BQ+d9ZmIb2Y*rCYR1lITZT0m? z=9IpK58m;LY06)!{2D!7y$jKohC4Itjdd=6c|u0C&FAel_md+IfCE8QEYG(AE0pB zHD-QXEdbpA_G=OBvU6@e7b5V?{*lKgnjuMR!JW5J<85H`7T+r zdpm%_%u33HlF-gQL*z~Ca^qDm^WK?rBPt*F(^ejAr+)Peh!#6jdcM>mW5uxUr@@+FkxA_$c3(s&@d6yMzAJhhJ*L};C6j&M z#Pl#AZKS@hvaHD0McT}6#ck1QBKCb0pp6;je24{3bUCuahyitb3^#BmKq>V7OnH?QU@kq ze*#oqGnVLVOFEwJvBW+rCQ*b1TLst#ud+%N5eAbWZ9iQ^EwZc(X^*2M;!+hAU^+fV zcTzr1WfruMmvjbfFQ?pciaw1HvtpE^6Nw-S=rDxFhBjg_gchaW{)yJR1w|$a!+3pX zZhu8en(O%ONboGl&}MbE+_oJR&;GJmY^$T?gZ_kt^6ZXAJ|d9M99>m4i#$1l>XyR~ zZ>qR-dzf||jWiQ0`BWNk7_LAZ9rOmkS*)DlWA#Hc67FKeRkJL5Yw*l5|L}biqp+;q z2M5DqPO4Q8lFhlo+qTz|KS5JITVBRxs9bbF3Gq9cr#L$}eehRpw^O0RK~I7q4te^q zs*}`;O3FEAOY7iQ#&2#@=*|#|(5E|%AG(|Z69#8wUBr9PtM=tS%$?;bki&O57GiyY zk{fu6ah(Gc2(WoD9@C$cn5Uz)HpN`EJD>K!+KwMJjYsH2PJAEWZ{901Y~f}l(~ijT zmv9t9V}fSP?eunEbA)5cRF!vrDH&Y3oz*@4x3+K3YN6s4+_i>Hgp zA=OIEkFJoqE(lwWAfgp7er9QY6NsWyIqf|`9t&Uzfa3Mq zkkGsL8R=ko)Ork4Fq6K;hS>3k)8T8BJI9u935TeY%Co;%-_X;YnHx$V-3vflr3E!vWQjCGTlW6 z3=M4C+~h?vTP~(Z#b+e2bu>d`gU#J=-o$J_XO@ie?nVPs*yF7e%XkxJ2e7WI;v&XA z=qf2KFf;%0(#vaEQY$_OWQm;g&tuR{+Ok(ZzJoeS?x-50hG(z=ar#M$PKyLB5@Z~b z&Q5PE(^-^nXkG^WzJp*lhAwfnn>r8{y@?CzC&tH_%1mlS7HAFYhun_Syb#v3uyC zrzOgaUHFtY=qD8%%_JV2e;f<6n3yjZ20Gc$Q{8ZB8Q(7e>z4Y2fND7w>5$g*(2r_y z#Zot{Azk{k)57b{j#2bVXsn)9P;6jL$){ixraqA1vX~RUOnMk9 zoJf}dfs?Iv(n}U@Pj8xyibQ!A*_6z18oO0Cgkx?1E&c(7fIA6&kJVT~?T=eG2BrA00m0uoO)^)PiLY;y)-ogL5yTzfsU+sB@b@n^xSlgeB@bt>X6hDi}ew)r_C%rLvb6l*R=K6&Ja+z6w&gOh%!1;xukRqnOuSntEPsf3WH35q2o>boG} zgJ$~TCg9xXrA0o1BJrDNRtA#DbXHE$^3#x~KOxh2fLlDmoRo}Nf$8AWr)S?GLrqU?8_R#5l$IOK+Z4uX&`n5=z3cGahS&}*mZ$zNaWc_+u9CcQ zblo35SLM>;ff9W2M{T|Z5?i}$Bb>r4m`|yq1$-5*cPp+V_1^C1fhNPw#L)&A!UVBB z7Db(Kq8-D>OKpUwIJj7jM-u8xRmO##*6UV3S?4URCbD#!1d;I;K2;l*d{AnEchZFdK0YW`z9 z4^6%Ov`W;~qBEa@NP&&vCX+kbs^sTVr56*u0!5*Nx!9EO~ zBYOS3fiAd$#kBW}uY9o?YndY75 zKE86kw8q?IyH)f*Zf*P@x8`>A%aL8mWCWZtHVWq9lxE5~tsC8Hd7ZJ+(BaJJ`dM@D z4F5G2UB}p0ztrzM>|}Y*I^}FRO>igrqnL3+0P%w7S;~#if;8nj{e2fgl)l>z#x?Uz z@zx*5Xg#Yu=Y>wiCRchA051;L0XQ|k+Qi|XN(7C^<}!bC$SY+kW?8HJ-crG@6!l#$ zPWrp=aD6=&Yn?s0$g<5XRY$(MmTXwg&U%|V2k_YsT=G>&^@Ivw;OP>a-`b29OuSgB zPA>csrJ4Njk9N?oE3XWuJMU*yIn1W``0U3==Br(|D(xy+jo|StKkpQowTKjWcvJ)9 z1mdG;sR~qOF}ItQsN;~gw5AGH(J1w30&P_=ScB+E&?iQE`wKOYB6^{kv!!1lavwuL zzmq~yT4HYDx;USxMH}<=kBM#VFGgr{2_b$?O(J_nKu;&bT_!=*c zr(xdEhFP65Xc&KnI^$L`qlRSwqFDDkXYBFgnZn-6Z1zx#F)1hNC&?13e$`_Qlz6fS zpiJ1LLw?Rm6%Rw3-_WgYi+;XV*T&~hy2CeWcUJVP&U=9qe~JOpm7zQtqjgnQ-pa3Xj4d@SGE zky^(Que;9X3A(z8Zmlh*+(Qp`bvK@_rxd601N6-J-k^=b4QKB!4|#@Mgw2qfvWNd=i6PZ zuI$0j2%f)qr)|)h$%soU{#T()>04QWCA@X>8lq8~R&u{@2Y{aJ#B`&DCO0!)?5<0F z_QatD3bSEDk(4*1NAamZ8HUUWi3I*IiRSGkQc9MWIp*9((6hC6xM1d3g3&bT2Rhes zl+OVOx6IAetoGx1GAv)V9;Cr%7oq0w>R(lhT3z=j>wKprOSrVfjXG$lgp?dZC`n^_<;#~*q!m*Z7}RF@NzqAMS|RRb=AaHaR3W{K|Sg8Gv! z>NPa8%O!)PK`hyPee0>^)$8u|I4Vf%K+#yaObFSU$UUR>>tFfh&m-cEr#pf1N~QBI zVU-;po18mZNTUq`c^bMTL?>}`$;aeAn_xT={0e|EMZ0F{&;=gWaQSm`WQOmN$kP;0Hve*@XwT6PNGQU{o;{NO&DL-+aaXK0ww) zRoY}C_73ZlneL-S;BE$-_p1+v^i#?Il6TIR;)%ZiAy1;TSv68h!8+;1>vMITSU4&1 zK>XJ0k7=|0*obU)yyXXxy@7s>&by)~DIz58uQCT86M(ClUyw}vd!9@N0zC|`Wnj* z=Zqjc;901Pv!d%XH$PVm#9Pq4<1!+Y>O(zo+>k)5+kG?ed3o}p<-9SCAUEf4pKWj1 z2a1b3=m};2sS2`P8A?g+LXm!6934E6)E~E(X0N!r?5B1s$YxtH7Ij%-bBXPj|s1ZmUu#R(XRMp zr(g|sE)l>n(+#9n#&u%Srg)%ALoLv`}{^C-iOp^-|N3MvXTZ z&QpCgW#!^?YnW0GI~`$|x=w_s`MDQdZj)(UI(q);N=DCyb6`7TvaV|{F|k`;g^w(B ziu9a8Yn+lzh2d4UZi4-z6IFc|CqR`#_!kTLyJcMTuU@Sml+1DSrS4t5So%fUWvL4O z3WE5^8oZQ?lpnbajEgryv!m&=!)5e5uPBSl&&yCNlTuchfr1y4G$}1RkCVIBl(C+e zD(ILQqX{+6uW)=Dv(7a>AGP~O>ku9aK%!oX$`)hGm}fa}@OBLj=4ZREQLIqe2oajJ z9l>bnMoCrx8eexV>UxW-NlEVL_`Y}bhAVlQ)okNlc<;Yz1_nC*y>P3Wix8MnSZLAI zv@=aQr3$`j3Ov#)rGI-Da&X}o=Cl;}DX(Z*hQrYT+%0l>A}x^VLSG$kv*;ku6|D47 zhCko=hIsOavDBkLcg%X16I0?rnn57&#($+M);VSZYRolG5q>%MeiCA zv?^8TsH%9f7Y={9nIN;RTxY&y#tZMxv=5z}jHxUp_7_Zz)e&reCZF+iK(}99ZZWZ= z^xJ8?QWbFp(WO2S9IsUsEXI6-6;GY$DQ@nlNc~b~{$V6%WYt67~sZUIyG7o;;$%miAz5wJ$?w2-J{71(yh20|-a*Q5OJx1TfI5G5yAeW2OlHJT zilJ8oG}A}AmxPjsm{`eS|2`z)uJ_J1NIdU;Fdcam6<};#iG|!<`s*D48;+~>OC}z3 zw`u-LK(fC{fcnq{>zm-8K7XM~hL(osu|rh?r)VopdMF?v?}K`uCLCW5XKd&mfkZYE z2+G%{Cx<@ooCwyr89Gok9+g|{cO^JlUhm%3-}BjsY3x8Tu=#hNF6ZNGAwr`a6>jzE z%f?u0IO>CHU)%5lTj{i+cBlb!%Gg}(@(uH|kac?3O7a2dquy;^Q^qhO^rd?kKvQAh zoox8;l;{OXn)cyl272}&%Zr=Sw2;cAJlxlJGJkUOIpu@221`G6ubT|_a@6KKzoHTv zyaY?xF6uXlX}LlVJx4_q>t|P=2-R+BCf@-9T6Q6Rw|g=mLk2v@(8Dw(mLyfiK17bd zGrx$XeDPnQ|8V`2QJ!nFz^d@kJJGXxjb%HfXgL zn|>o(xX`NM0GHqG@nmUXQ zIOfSQBA!sfdikEk5OP5&mg+%G=OCEeUNvI z1-7Mb+L9yMxL$O%n-|_+mKz;j?kNBj(l&=PWz*(03-)M94|=S#zYd%!+vEIFFu$ds zUhR$nVKMo{@4tMOUf&>=TBx@iArQv{7QM)r2(o65jh~lIT;)*Y0KHDfV@UMlM#O{mNLEs6`!?>g5F-mXa=isJ4}a zjq-rJz3-i`o5D;y+2Xd;_Hh5+sr2um%~3)gOwTi4hTcBz-IMPn-g~3u#?$5^UObS- zKwJJco?8#v)?tYqSNB<&8DO@Q5e`BJ$nLEw;36@Mq;kf0UJkRmiKc)>F8Xo3D)pNj zld>#K^IkAulwII$3YzzwFKqaHX}{JwP@syQ^4N@yIA4PHn?V8Fv9-7B_au%X{LRSW zV8iMm#`9KtamN|2{20bOrcMd8OdTZxZ$zd_xzggc2YE7>leE?*JED-Jw$59% z%K-!%V@??(1Ypuu8RW69-uKFJF&hEIS?9T4?9_N~bXs>=|Hn=I(w}ks!rOj4OBm9{|&p=dmy>x$ zia&Uo99MO}|8Pb;GCs4Imv6(TIK9^`8@T>iVXmLo!5L1x2dV$KdZepj6Y1Bph35PK zPbRqD1~N`agyf5=8tiG3y~Vi2=b;(*_8*sUc`j_-@sp-g9?G;t)SA!ictqq0IseM{ zN^hv!-;oA>N@_~3BYSTaEJb45g{u67=wh{EF*-d4l#==^8NHYJfL;|Ub*tfjjvoB4 z$pn(Knn9%bFG?mZHI=>^fX}R*(sf~4@B}UVpUSyt13T4j{OLMDFZN z-wk4|Z&&=Zy>peTFJiCnL#rs59B)6m{N5WzC{*E)Ek4w&MARjmcD)>CiHJLbA^7`Jr@^Y#4-AT9jbaBh5;#Pr~>ghpX}?0x3Z5;%C8D0E1M*;<;P~2IH?-KFAGSV@!&SMv2lu2FEk_ zH)wO6z@MsH_Z(kuNfRc7l>L~`T9HnVWhTEHqq-48+Gq2+t=G@%aXK|^K~Ohay#TED zU^5oK(CCDo$+}@=-@NDxn{o8^fQPWb_Z`Kc_o+EKSXV5!6AX9iemtMq`snYUDH!n1*dHTu+j*kYd11T4JiQ0= zC&lxLT?_$UIFjHEX_{8Y(wZ&XDM3Gph6x;`fQK8Mjj&{vDBu=_%qD zSnVY6;gnT>h1T6uFK0`j+~6KgkU(mt9$9GL{6#dTDOtHuz=LImAJdEi3|t=^omkP} zHe%w<-5q7PwZf3nprG?t+hUqW zc9~# z?ncV1t5uy@;XG8!rb;ftm@UttKmD8X3phN&-9sq&eMfVWQP_$+HuT6A?+cw|9Flxr z$z?LXl_{ePkgZ1w@`$pAY!JqF=#iTnE7$MfG>p=AYsX}fuli^^oh#XPJXnJcW)%|H z@~kmZF-Xw$l2DY2gCo_%61I%B!DCeWDzS7s5P^i zbic(~=lB%QvHKhlYVi&XNyfirtEz&lj0PF2j)+^4$0T7!W^Pz8c;zYS%K?t>mr`A* zDzrIlt0s)8wXwVB(6&=@{80X%K1?9hlG9_-C5FVr?Tt~$CLxoKk!OFC zp)~ot8TtpJm8{N?vPbD6Ye?@-M7*UmoMv)lUFnxl-^azI;fG!zft+((dR&6OJe>CY0 zwHs*L8dQJ)hqP$u3wCb9XA77EW68(mzg>a#=HNJEm6X2;@)W{^yMw7zYxqLreNxj< zUj6#V5SlVtRS$I!IhMgYRM0wEJ!Iacy+?*}-I7xN5mVA8aMY3*{75;LsCZcL#_M?T#_!uU&l599dy%)0;x-QffhLDc zoByOGPopIcE=09WdA{5Z;Q}U-=?r}2}6v4ynnE^3}zPp-)wWP?u?W^9X= zeW4iJ|7`i=ATm&;5Hw+^B6}nfCz&S-AJy2n;z5-0xfLV|Yc4IYWVg7B>Oqmmrao&aZe<&T!47l84y z-e(l%nevMolfiw(j+>~BfUd`eet6+F+GqhQ;GH{ z@NA3Y%i3S@V6per;QX~>eoZST7Y1E~V*ZN2dSHl=!jmOK>^BY$_N?3jMCl~J2vLlC zAma!A4sfMdip3l~k{qBFzD{fcV;&{{NljCM=QR+wU+XjW$A3pL}aBBIuU`c#_~%tM9153?1^mHw5TG=FvsI}JlI#)u&A4fLuv9;Fz%Rl~v9$hWX1*rn zI~?OJcQXRq9=XEaJv;s~$p%lo{_0%rIb1A;_mQV)nlmo#JnX^iI48Q@;(!CJ>vXX) zypVjw(~CW?Agn_JU4lZ^I)ie_z>U65gwN<|0FOJHf?o%d)wvTmoGi|IYd@xBDsq=fH=_#pYV`A&ldLfkPLY@fNNpe6qNP8wEe1udiVW(_AfZ^S|u6!xii8 zioJl~d1jO;Omnn{ur1kBnx(MdvFAjETL~cp?6_Yz;bRB0k+E8h zXMH&1LE*=NdBcjs#gKSDC&s7~g1e}q3Pf-MyhB8skjA)%JrxRiX+1r4^j+NKeG(_r zEs4ZVZ7a=*Mh-o?u~(`3yYF3!%YVvi*L(`{p1$@+KQN$&Ju_Z6@<}_+e%GHHdC%V= z=j3{^FzieN-&fdjx;T7WACJzjBZDPEJ)a%o*QPdFUU~lA^{?Lf1YmON zimd?*fCnQI4`!iHt}k^i)YNVDDNM|dIQFJ<5r1!qAvH7KEI?nth~5}78%WD#c_HrY zZ8QJt={T0pvNLm{1m7+otg)>gFDvyx<02HtdDQTWz|N})#4S5g7f2dChhoKN0h zUIAP(W`!J|%%g0NkK5A%isdOf<|b{oE+`iy#?VLiZPLnyLe(OkMLb!CQzp=De| zZR|L)vh|bao8r^ZlgshI#%}&8o!H|#sq9O&$uKu3A4Hr(1Urv$L{j1p3saua*{6JI}%|9T`%P9{_{?z;W`Wkt> zQkI;)f2TeVd;iA1y$8M4<*P}uAEH~XaGj<3J1dODI|QC@icCsJN>_7g+l zH;un7dJo89drx-aceRmxOm$yxrEQ z=X4{>XKZRnZ+o!3^F_%)q3Y%)X%lqp(O#Mn!BA)dfA!+;mj(#QU^_ZqtJsI+bAD|HO7+{sK@xzB!zf< z*o%<&_h5#TJwG*?8Sjz6ciyNWVFe!`5skdT$hAhEY*VY*bP-EG<$C`q-2GVu+dPN% zJ0~YcUmdoU&Xo-26r_Wvc2K-4vy|XKhUi0fWZI;6%HQikjHeEDC*A|NnsXvFL~=%< zb$Y5p=-UeU^$t>8M0w+sW*Vu z6^P&8|MQKaBbx;MTtEGiPdJz&Z!)2xtlMyo7EoH6P?ZED{p4;F1qp(X`OK9j$pdrX z_bN)~w^WWk;|0X^NY4!H*mmDJ$L#D0gVJbnI~3fEeJCEqrA>yuIXSAtt%%B_suinC zR@|e3d2bQ8Aizh? zPP=fdA4<)RNO7xi-^T5{r_s2Yt`nfr>;x=2kBNot8Yw@I6zF_KlY`0)g#tABHNXBnTw?7(p zU!%|hsAi6PL%&y2lT$Mf>IbhF(SkQNOe+zE?MDxg z-(*R}LmF4Y6>$0K#(o&2kK`$Ha;VTTvTTSD8)?^?b-+(IZc%C9oiFuYAJyy$T z7kyzUGGUnp?NRCTH$lQ<+xI3B`T6-i_fKeKMW0l}jlq41PRe+LSTGP?mtlOR{w^;i zf#la`Y88=yK|)?3N{a2_+>7K(^k?BeEY}IIKSo0I_NTAU#)e+!opYaWo*%B_ySKV5hY#Ds)wATxDVW1B4A+EoQE zv?~ZtzUf0SSs_UEw6H5%+IdEmE0UX152$3g=|e>AyZ$6DOwJ-GWK~#iLP*b3&GFRX zW1dC;(j`eCy5YC3_WEd+9Qf-Ze^_E5T0JMb4~|>f&%%NZga!|RXpyfw82)4Ak8aO` zzvivzO&f!r_8g(jd(!6OiMAI(ooJMf2J)t2pT0P_ zC|X(N|10dW0&o?p`3Zetj|pWFM}bTyewpUpJvUuBqmlrk46??#fZ2#^s68`&pKJx+ zcp^cLvOnXY4Y>@}O^DlZ=Jnr&nJ5m#76vsNFx^)d{vjubgroMp%yA*>cBN#fB~8dH z0QoT&Zt68^Ge&DiO{i{n5DBcwZ$IewXqV{>S$u#FV@kU*&H;p!|{7I*+>A$07_R~8_WZ@2g{%7njq8@!ZAyRnbA~SX@fXmxye!w4|5WB zw~o?jrjP&-ZKgK2#{c1xNu1L?Z7?qU$UegQ+tbR=gQRAYK0(b&hE|0u)aGD!7Q~gf zXY$MuDyL9E9;aWMQk6v+2p$jID9`n&(us(1M32wL1Up5Tp<^1`2Uh6usWkNH1WX$pdu#DB~Nqi5xL9C33s!g-Gm5DX`c(h$`3%Oybr5#!W%MlStje z5=`DatSZ6|7t9#s=TDEaxKp3Ef9B6LWnr%20KEEo-b4`kq*C%g>-1iX5G-0^D?(4v z9&s#bnCq%;%&`Ov0LPI-r3jpogULZ=GF3dUZjX%L=9|IvMC z5*U__G~0E(+Epx%`Qa~{!kTb6^VuK+7p?O=!L$w#Od0W9FwwQ4&01AhNzvl{at+#c z1KLRjMX@d~PGmo@XTE$gM|K%tM*XFz@v@;XuRubo#GjF4+x^{;wtTL# z|4vtjM)W4xyK}DQC234S!7`i*(~`E<)=MtCP0}C!eH_0~p^e+FA*|}K;2a{X+?Ur9 zHNbISM&^g3xC?W^`o}7oO-06r}n^8D)>>Qt8 zb_#RQ#W6-ylRG3drq5+aS)xjleEGKH9zHn*0$=FIeH0t!e5}>e7w$E z#Q)@Wg$CRiaiTd>79~uGNu5)Cs-WmZIW1)~E?N!hD_h5SKc^op=-t3WAsXNOtg<|l z>MTThl3oxBryDY@r1`+J@OP}Ye7N`vP%Q`RT@i>0)iBQCG_P1qs{?x-eynCN`-K8? z7Mw5U{>(!8AQ5hkA9o;>74BATdFv}`a3`cZd3v$F2ZXJl_9`!`-neEz#fkZ(xZudq ztJ2QgGB5WW7m1=>bfv!dX;qWxZsf|_?}BWq*%r`h5`c>#UkksK=10laSA!qH>L9j> z$YQeT3e!0K25^+mH(`8IZ`OE2EK&3^fop#^G28`yFqR&X*aq{9FkyD0zkEx5EI_}1 zUVj&G-9@>uzQ60H=bKjb43H)<)&kC5rifHd+flv7b4z`11i_h@@R5?A$FJ08Q;lQ< z_EO1XO0Q7dbp;G*YOnhcU>$!iQo(V9@f|9QpF4-j~@0i&oH1VSOmkH~-FAB#ECk7a4L{VX_W2n%u!5Ywn`8Ehvjh%$4{7%u zE&K2$>sKL9r_DfYv)7iuA#A%lfYfe{#nI=sT*EVAyEYbuDD|kgA8iba2pJ2EYHzlA zns7nS;C|sghQxy{@|$#TPV$akQ5^gXq2x|uL{$ioS2jXFG~U<8e=$<~!vs~J_L`jz z$3L=Rjw*f{;e?+LDO*7T zzIcfhCkpHDVVbVMFAqplZN7tB;%(j^GEaho1U&=MsRA(x92wqJmj84{-Vc)G({(#_ z!!1e#p9f?Cyi14}W5KU)fVjJSu?OAM9=j=x=9qQpg5~rl3ehrMnlpxgYVX{a#nCqpp{->q}6UmY2l|G_)c)WUO4y)0q8^=ENePRK| zNbPqUI!9ie4nJ}cOizE19J(5#+kX5omzTYoA)bRtde_LE${gHnarp)x4H>MV0L|1O zt21{@PZYpRG}z5_O9TnL5e*I(A-Rieu_f<%*cA)q z+cT|QjnGos@D|_PNihSVrg8CBANha(yqz(1xz0+Aihket=y# z=+Q^M^lAT;u?$qb$Iq~A%@H+Ps=P>}`sqSAODJshMC7@dE&#?~y~R`i?wl z@B2S3LVXEPRTeX+?|Txam!Jk@jHZ-{f*=Gv`_&p19b$uTDvr7f+u;aGGr?vcBZ_RG}1W3jShQ$rWm zYsaXw(J%iEee&W~t6W_KcS1l%;*8$)VzFufk>5R>)mHmm=S4sSTAKhgM=MbS$2OaI zVA=NVNaI*rUWpLKmi0KOXmAuLgdm)Yhw}nQ2|6eegceSs?)9t6V7&_hA5EbY#uKoL z_ssZTCO$)`pd zoMhS#almpj4o{P*G7ekdNsKOr6~+??;htGB{`ACWz>_xWo&P_bwxq8&U$ao$;zd1ZS{IA%+X+Eyw=Iv!Y ziq#5vE`Xcy+geF@ubbeG&UzbK%+mvvEsoC4kN?2~+VLR41PZ|*!1lvH!bUtem<6L` zDn$SZQ0w7JBZt2A+@?*N{CE{jKfZ3qtFtlK&kP{PrWOfGmRC@K2a032m$Goo*aR~f z$7W1kF4k&k;gWP77@z8<+=&0umMvSJMH)ZG<6j~l$691uJA&g4NE2~W=5~k+dFba5 z;;%ye--G(le?G^Wx(V>O&if!UeHUgREu~3rVq4Fr@D5X>mf%&_8wj1}UOZUej>d4~ zletpcJq$A$*|%3BQ~4YRQ$Fmd^=j&j8|}}*`bC%`a03+u5vWLj111E%(n6t%amr7k zPjN!{D?FSWo4AyxANw77two;i!ujcAf1BX%Q=xUAi{B+!mO+9>_Z4VXAH=E4%TCww zbqCtzzFHD?KpG2u|5EUATU83%RM>gaxbvd8U)Nz=r;WX1IRa_kJY&X;pTjkz+etDn z+42FxJI$2tfIxjU+V11{ehLo0W%Rn{09hOg>=zi%R^ZIdhaGm9|0F27wD=J_z@0 zYXb&=g;P5QtWknc91NJ}qa3cZ?daq8;FU06J$nuiHa9}!;@BLo_+UH*pA_7G?YRJL zlmXWwK`{9h8F>tQAQ)26R2WYH-QT);{OL*0{17pG(e%Pg%$>MrK85@oxp^$!Gjt2d z1*#n>xR+X|On|=%Tt0#G%ZnU+Q?wypdY(BR`F;)?HVxvwq^7@}1N$|Vm3=sBU2qPt}^ZozuY;X%s%Z&vRQxw8xW}RP{hxUvs0Bu8DhQ zEAAP-ZKAoDQ$gt@^oMDP@5`IAQdwH{6aAa(H`d^dMM&8v6q+Vw)M`P4IAT?Kbk z`Ze6x)JC0yqb5NT5brwr=%a@=ZVdFBi+Z+^>%1WZmF&u0r&R(GXaa#K1U-%suHMXH zY*>-WWM(0qV>{bwb%QXIM1EXsnsHhV#OLeiQ!&8L<5*WM?@m0AF=#vZaNWSUwbUP6 z_aWVeTGDnv8tcG;1Dr6jzq$&z&wdAN_d}5LD!5z4duEEO0w6N_#wx50c@=Vh!fo1i zjgPnE|1ovy)HU7R-G7EUz619T-{korz}s#k(dH{LR(=NSBK~0S-o3uOr3_$OBvH@r zgWXT@c?Uj^sI`npdpz=3gR%XpAPVnw72F981I2B)QJ3SZD{Wd4IPIbzgwW21se%ZI zKpPUkFxLa@}nq80}T_0~y_Tu=9#fumJ6RZqAiLzaXV=nH6 znAdV54=NenijB-Tnf`*;`~sSulavtb`JaTH`gM>!Da6+-yrYiCfmtAhclUR`%N8rT z@^WqJ0${w-I&?G%?Jk)sx(Kuwfh$m6x8^PfHz46d$&bS=v*I<+#8`E>+q4}L|3xs` zR2<9QR(Yq)WU3+q(wgkseYsD{@3pEWtnvfXqU`74)9w9UCCBzV4X;1j(f2x4qM2=r$Pk+&>X)A0scea^?Uf4W@E_;(Hx(` zIK@fR8A$NkjuW+TL<;Y)RlgQ><~ZafP*7lNLJ6?f7u5b%#r_dwlZI z?4{9^Rs=+#P6V(hYYwu0BaXV8?bV|S#fRYqc*siYJ_az9HdNR=H0JcAf8rsz8xtaV zlc`LK!tTX5{IGwne1$rnW$=UKJ1KdeOB^EabMgR6 zCd8c#ZzZ$ACNKK9aL-~Ub0H5PxektiBajXPG~g?UxsapnLPR74w}?7RcJA(mBCv;B zuSa$I(5J)P0n@|W-ZIQ1zs1^45wD^HK^~U#O*nBkyhsTAKSDy+f}2iYHCr$!si?l1 zhyC|*L(+v76?q4F@7|I4H7r%#{mW&OyP z!SBg9dO$oCY%ZqAOX7qtXqZdPSZs!eCMEAO2f2+pYGWhGduGI)shRa;_z3|2;0y#ziJIlp1BHD+2B)ElC=58)C=uOgCYS#1x-t$?mI;^K! z-LI+amuXx#a&eqsyhO;5TL9tM5eIz?WO#QNYQvMBtJ5jd8KsUufIfqy8quf>GN8q_^*VNSGCPO zfByW1i0nIjLlJ1@J9}X0?c&{u%FNRC_IBC1Yp~9p4S|*uCru*7yff2!4;?8DN@V6TBksykZ*T8ud}f*Rgd71! zAioIEc&wxHi(PT%DQD#6T8O*03)_?@z4D?TS=gyrQNs9{Aad$iS zc;Ce34lwo3)6_ewxIV1TkkL(^G-=W_h^#Cjn#m1dYVJ_8% zI(hQsjih;BNMjhu+|CHr41!vAQBhLv9D!O0#Iz zDl?R?ySQFS7;RdS7uUipzYcFaNZvK_ULxf1V)ja`OJE1o6}i>cqV!L@wY6 zI0E%VVEXjwzedBTg=v=h=OB&a>x4;lB%?f^knk?Z_zCW56uqXS;g-y@g9i^j#q~hV z050GNgdw1{Xshw2^A0F4&-L{5oDNH(K2nr{r%Cq)Vhif*s?4x6$8y$&>^Xk?_}lf- zB3Uf#YBv#`K1|tP5|!zi5t3(_TgYbr2MA|M-07V4QJ?lphfo*kJHWN+iMuePob)rx z^3@9$E==IxKMKWh7&(#mN9e#FWH&8!0#dCd@PJ`K&{HGILCMi?nMBKgg-^#?8bx z%ig@eFpak&l-GdbRo zh+A>= z7y%j#Eusn@aaUGpm|i$?}iLi4CV=#2;!OeSkna$*#ygPBv`&Z=pIRYaH0j3XUlbu<3OWm`F9UhJ~0tOnztq=>7 zxeR(P#8y(LsJe3mk|NN`Ow($SLZjP^yrqm8qpvK^2(V0ag12{!2=9G#cJp!=lh)D1 zwYR^EQEb(5nK|%Nr)PGIZ{+9HZ9SPOTA+mTPTm_*unYA5TvbQF5vU^q5aGLtE_jh0 z4U0yxb}<6dh|%^@W59X*1g& z98aA8u@l}vxM1QQr7@pfJ>C;1y_CWnp$wbt+|lLM$fu;%|Bsg4hO|4^+R*x`JTzh6 zVpc|r@7)c9K%L3^7wc*B?!J{Y4Q?ku_(yAry@o7ga3ARhFC>r3)=CKf z)it8d>@oW9C$Zi*36dY}=Y4yeu+}i7^;?8lW2v8%2J-5Z{8Yc)RYstWR|hJHApm~I}aQyhARI-V=k4WuX*2ts!O4~?& zf&;0pwT8FIQ@@C`KMKy4V5(0g--C{?m;yGba7yGo5t>fi6T#~E?LvO4mhdh7Sd|ED`UJ5w?Lp;NF5z5bMRI<4P_ z@e&+v~Y#6Ta^=XrmRaVnZzHKh1Ek-t7DfD1VS!yqtk-n=%xp$CR}RP`1nC+n*U zWbhDEF`4R^%w@>yU6h+#5cg;+*aIrdaa>dfe5t>`|4($_T%0&6g~1GG4V{C(0T|2hJW zKxzmqBWBY9B~+5nbE0BH7N(Y=pw|1RQG-`@yYf$niyq3vA$W$Na%}#j84>91?bWxl z+?e-EoTRypF!30tF|kVGM~@zr8HV;l57vXSJ{v#1Qk+@er@U_@&7K75>fo#|a^T!k zkhI&Pcw!MXg}vsQYbHS2KS!KT@NX>A4!(-&XZXGP;m(Etot<-aBJV9p$h$Wxw)a*D zX$m&Hq4hFBN^7dFUO+GfrTu;{)+;`bC!YvtZ)3Jd)FL}eopT0xH8R?st)Y%eb}MCS zElSJ?QG_xmu{$}+{RebB4uphp8AqTDfmR6LEqrrhqi?C~QRSZyvrViAQMqZjLl@0d zsq-3Sx2}9*=YA7)#cmyh)94JK&vx=~2p`KFMfykYZW;B*E^N}j%dF#0 znd%5IllnI1OAm4S?6Y8dV|D4OPg@E9B%}Ci=n!aOc}=IO7y1UG+oCuk8v@YdrdGt9$pamkQ1&vFLdYyulZ8wHV zOHom%0;amB2FkjaBOnA?*{Uz70WXIO(z~Vn z|D62(A$kONzC~6cFiSf+I&w2x%*g5v^6(e<*K&fXNez*9aQzX1z9OP&3hcr)5fG-V z&G=xJWe8}yxQ|5NPucCNKL_|OWgDJ1C(>&z$9FO6o{McEOJK4N>j~b6 z$os+4y&_yySE2WQu3sV#&lf@GBI?M3Iv=FmBwqhVI`!IB)E9ahQ9rquby|75`@xbPA28cGhs`2m~R(E+SgU zxI9SsT_F+oZSVe@k6$8-l$YRzrYc*MnN@U>ug*sf-qnJU_wVrEAK(kKOB8sX=DYX? zW=op+aF-$ikoTit{SZF*l3k%aobrbYOyaqZtm9H128Sop?Wk-zit zi<|Ff^jGBFUG@l+$h#J-rb6DGxM%-TJw->LX%L{%UPfkv7p4jrrgvY)w8S*Eo*|$* z?eG8%wq=Vu)w6SywG8$2qzZ@t9bJ7Z3yi7{upVTqF-&IS8i6D+5g5xf!VQ$MDVZr5 zBOy-x?q~G{9!M$ZeO2_vgf_l0@1b03@sNb+vT@<$kOSc=|H;j<+0 z7fJ6$uzGmHgbAxHr*96w6=1BA)bMv^^eNt%4!OC6HC-DN7} zAtLY(@(!1y``;t?W+ZESp_LN&<^57z+Ll%}_V?0hSi;EvcXPU!zj|&){?k;*dn+%# z7wq&g1bYhMIrdmCh06u>=yV!I-iee6&5O34IzwJc;c}6eG9`5KT*)>tG*5+*iyEkt zk=#t`TAhZO3kS~X9F=gcnFnMgBPSE4Qn<@E8u@4Z`0?-XE=r@#q504#wd(v* znQUYhew8-%?QMK3rEMTX^Kyy2tp8OcIsL(*N*hqEv~Hzl-c)Ar?`IU-x<01i2(Tcu z3o`w?)cH41m#s#j$9PBkxR!WEDgV`UEy?IAr`d}A#P?0&x}U}F?~u Uws@-EJ^%m!07*qoM6N<$f`*eKo&W#< diff --git a/android/app/src/qa/res/drawable-hdpi/fox.png b/android/app/src/qa/res/drawable-hdpi/fox.png new file mode 100644 index 0000000000000000000000000000000000000000..4b47a895d5642699da062c9fe658113c860e8a92 GIT binary patch literal 7927 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 literal 0 HcmV?d00001 diff --git a/android/app/src/qa/res/drawable-ldpi/fox.png b/android/app/src/qa/res/drawable-ldpi/fox.png new file mode 100644 index 0000000000000000000000000000000000000000..8f67706879161c9e87eebf3d9ac5db0f14f9072b GIT binary patch literal 4177 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 bz>NJD`C+z`jsDs200000NkvXXu0mjfZ4~Ot literal 0 HcmV?d00001 diff --git a/android/app/src/qa/res/drawable-mdpi/fox.png b/android/app/src/qa/res/drawable-mdpi/fox.png new file mode 100644 index 0000000000000000000000000000000000000000..847df51137f146ab30dee84ff91726a1d9c5a49b GIT binary patch literal 5535 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 literal 0 HcmV?d00001 diff --git a/android/app/src/qa/res/drawable-night-hdpi/fox.png b/android/app/src/qa/res/drawable-night-hdpi/fox.png new file mode 100644 index 0000000000000000000000000000000000000000..9b6a906a050bbeb068483e26554019a4b185db3c GIT binary patch literal 7466 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 literal 0 HcmV?d00001 diff --git a/android/app/src/qa/res/drawable-night-ldpi/fox.png b/android/app/src/qa/res/drawable-night-ldpi/fox.png new file mode 100644 index 0000000000000000000000000000000000000000..95869e44a8e44e67aceedf03121762e97feb1a31 GIT binary patch literal 3877 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 literal 0 HcmV?d00001 diff --git a/android/app/src/qa/res/drawable-night-mdpi/fox.png b/android/app/src/qa/res/drawable-night-mdpi/fox.png new file mode 100644 index 0000000000000000000000000000000000000000..33b9efad77ba82c52eb23fa680af28a8417efe18 GIT binary patch literal 5222 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 WcLi;##^U{35KrrgzFG|o9`%1QcjLhT literal 0 HcmV?d00001 diff --git a/android/app/src/qa/res/drawable-night-xhdpi/fox.png b/android/app/src/qa/res/drawable-night-xhdpi/fox.png new file mode 100644 index 0000000000000000000000000000000000000000..99f7eb5f2a8dd035e2b6c2d00437a2374e4d8213 GIT binary patch literal 10070 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 literal 0 HcmV?d00001 diff --git a/android/app/src/qa/res/drawable-night-xxxhdpi/fox.png b/android/app/src/qa/res/drawable-night-xxxhdpi/fox.png new file mode 100644 index 0000000000000000000000000000000000000000..5ca07c345bc6190e2c5ce6fd94d3e2cd90f39629 GIT binary patch literal 19880 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 Ms;sG0r(hNFe|hzdsQ>@~ literal 24194 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 UWaAU;_eD4bu!>BL6eQ&T0P*nJN&o-= literal 0 HcmV?d00001 diff --git a/android/app/src/qa/res/drawable-xxhdpi/fox.png b/android/app/src/qa/res/drawable-xxhdpi/fox.png new file mode 100644 index 0000000000000000000000000000000000000000..c2a417d3f95ccd2f5868a60af9c3508b6c1c8fee GIT binary patch literal 15518 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 Uws@-EJ^%m!07*qoM6N<$f`*eKo&W#< From 9bb8d905a829aa187acb7a760f306de3d12eca54 Mon Sep 17 00:00:00 2001 From: "runway-github[bot]" <73448015+runway-github[bot]@users.noreply.github.com> Date: Fri, 20 Sep 2024 15:03:03 -0600 Subject: [PATCH 04/27] chore(runway): cherry-pick New Crowdin translations by Github Action (#11358) - New Crowdin translations by Github Action (#11092) Co-authored-by: metamaskbot [ab606b4](https://github.com/MetaMask/metamask-mobile/commit/ab606b4136436a03b1640e91776f081fa127ed99) Co-authored-by: MetaMask Bot <37885440+metamaskbot@users.noreply.github.com> Co-authored-by: metamaskbot --- locales/languages/de.json | 98 ++++++++++++----- locales/languages/el.json | 110 +++++++++++++------ locales/languages/es.json | 220 +++++++++++++++++++++++--------------- locales/languages/fr.json | 90 ++++++++++++---- locales/languages/hi.json | 86 +++++++++++---- locales/languages/id.json | 86 +++++++++++---- locales/languages/ja.json | 220 +++++++++++++++++++++++--------------- locales/languages/ko.json | 86 +++++++++++---- locales/languages/pt.json | 86 +++++++++++---- locales/languages/ru.json | 86 +++++++++++---- locales/languages/tl.json | 206 +++++++++++++++++++++-------------- locales/languages/tr.json | 98 ++++++++++++----- locales/languages/vi.json | 86 +++++++++++---- locales/languages/zh.json | 86 +++++++++++---- 14 files changed, 1172 insertions(+), 472 deletions(-) diff --git a/locales/languages/de.json b/locales/languages/de.json index 567042cd2cb..f2cb2870843 100644 --- a/locales/languages/de.json +++ b/locales/languages/de.json @@ -501,7 +501,7 @@ "lists": "Token-Listen", "hide_cta": "Token verbergen", "options": { - "view_on_portfolio": "Auf Portfolio ansehenAuf Portfolio ansehen", + "view_on_portfolio": "Auf Portfolio ansehen", "view_on_block": "Im Block-Explorer anzeigen", "token_details": "Token-Details", "remove_token": "Token entfernen" @@ -604,19 +604,19 @@ "select_token": "Token auswählen", "address_must_be_smart_contract": "Privatadresse erkannt. Geben Sie die Token-Contract-Adresse ein.", "billion_abbreviation": "Mrd.", - "trillion_abbreviation": "T.", + "trillion_abbreviation": "Trill.", "million_abbreviation": "Mio.", "token_details": "Token-Details", "contract_address": "Contract-Adresse", "token_list": "Token-Liste", "market_details": "Marktdetails", "market_cap": "Marktkapitalisierung", - "total_volume": "Gesamtvolumen (24h)", + "total_volume": "Gesamtvolumen (24 Std.)", "volume_to_marketcap": "Volumen / Marktkapitalisierung", "circulating_supply": "Zirkulierende Versorgung", "all_time_high": "Allzeithoch", "all_time_low": "Allzeittief", - "fully_diluted": "Vollständig verdünnt" + "fully_diluted": "Vollständig verwässert" }, "collectible": { "collectible_address": "Adresse", @@ -686,9 +686,8 @@ "suggest_transactions": "Transaktionen zur Bestätigung vorschlagen", "disconnect": "Trennen", "disconnect_all": "Alle trennen", - "reconnect_notice": "Wenn Sie die Verbindung Ihrer Konten von {{dappUrl}} trennen, müssen Sie die Verbindung wiederherstellen, um sie erneut zu verwenden.", + "reconnect_notice": "If you disconnect from {{dappUrl}}, you’ll need to reconnect your accounts and networks to use this site again.", "disconnect_all_accounts": "Alle Konten trennen", - "disconnect_you_from": "Dadurch wird die Verbindung von {{dappUrl}} getrennt", "deceptive_site_ahead": "Täuschende Website voraus", "deceptive_site_desc": "Die Website, die Sie besuchen wollen, ist nicht sicher. Angreifer könnten Sie dazu verleiten, etwas Gefährliches zu tun.", "learn_more": "Mehr erfahren", @@ -846,7 +845,7 @@ "contacts_title": "Kontakte", "contacts_desc": "Konten hinzufügen, bearbeiten, entfernen und verwalten", "permissions_title": "Genehmigungen", - "permissions_desc": "Verwalten Sie die Genehmigungen, die Sie Websites und Apps erteilt haben.", + "permissions_desc": "Verwalten Sie die für Websites und Apps erteilten Genehmigungen", "no_permissions": "Keine Genehmigungen", "no_permissions_desc": "Wenn Sie ein Konto mit einer Website oder einer App verbinden, wird es hier angezeigt.", "security_title": "Sicherheit und Datenschutz", @@ -1066,7 +1065,12 @@ "simulation_details_description": "Schalten Sie dies ein, um die Kontostandänderungen von Transaktionen zu schätzen, bevor Sie sie bestätigen. Dies ist keine Garantie für das endgültige Ergebnis Ihrer Transaktionen. ", "simulation_details_learn_more": "Erfahren Sie mehr.", "aes_crypto_test_form_title": "AES Crypto – Testformular", - "aes_crypto_test_form_description": "Abschnitt, der ausschließlich für E2E-Tests entwickelt wurde. Wenn dies in Ihrer App sichtbar ist, melden Sie es bitte dem MetaMask-Support." + "aes_crypto_test_form_description": "Abschnitt, der ausschließlich für E2E-Tests entwickelt wurde. Wenn dies in Ihrer App sichtbar ist, melden Sie es bitte dem MetaMask-Support.", + "developer_options": { + "title": "Developer Options", + "generate_trace_test": "Generate Trace Test", + "generate_trace_test_desc": "Generate a Developer Test Sentry trace." + } }, "aes_crypto_test_form": { "generate_random_salt": "Zufällige Salt generieren", @@ -1444,7 +1448,8 @@ "hex_data_copied": "Hex-Daten in die Zwischenablage kopiert", "invalid_recipient": "Ungültiger Empfänger", "invalid_recipient_description": "Überprüfen Sie die Adresse und vergewissern Sie sich, dass sie stimmt.", - "swap_tokens": "Tokens tauschen" + "swap_tokens": "Tokens tauschen", + "fromWithColon": "From:" }, "custom_gas": { "total": "Gesamt", @@ -1731,6 +1736,7 @@ "continue": "Fortfahren", "cancel": "Stornieren", "approve": "Genehmigen", + "update": "Update", "edit_network_details": "Netzwerkdetails bearbeiten", "malicious_network_warning": "Ein betrügerischer Netzwerkanbieter kann bezüglich des Status der Blockchain täuschen und Ihre Netzwerkaktivitäten aufzeichnen. Fügen Sie nur vertrauenswürdige benutzerdefinierte Netzwerke hinzu.", "security_link": "https://support.metamask.io/networks-and-sidechains/managing-networks/user-guide-custom-networks-and-sidechains/", @@ -1738,10 +1744,12 @@ "additional_network_information_title": "Zusätzliche Netzwerkinformationen", "network_warning_desc": "Diese Netzwerkverbindung ist von Dritten abhängig. Diese Verbindung kann unzuverlässig sein oder es Dritten ermöglichen, Aktivitäten zu verfolgen.", "additonial_network_information_desc": "Einige dieser Netzwerke werden von Dritten betrieben. Die Verbindungen können weniger zuverlässig sein oder Dritten die Verfolgung von Aktivitäten ermöglichen.", + "connect_more_networks": "Connect more networks", "learn_more": "Mehr erfahren", "learn_more_url": "https://support.metamask.io/networks-and-sidechains/managing-networks/the-risks-of-connecting-to-an-unknown-network/", "switch_network": "Zu Netzwerk wechseln", "switch": "Wechseln", + "select_all": "Select all", "new_network": "Neues Netzwerk hinzugefügt", "network_name": "Netzwerk {{networkName}}", "network_added": " ist jetzt in der Netzwerkauswahl verfügbar.", @@ -1758,7 +1766,19 @@ "show_test_networks": "Testnetzwerke anzeigen", "deprecated_goerli": "Aufgrund der Protokolländerungen von Ethereum: Das Goerli-Testnetzwerk funktioniert möglicherweise nicht mehr so zuverlässig und wird demnächst abgeschaltet.", "network_deprecated_title": "Dieses Netzwerk ist veraltet", - "network_deprecated_description": "Das Netzwerk, zu dem Sie eine Verbindung herstellen möchten, wird von Metamask nicht mehr unterstützt." + "network_deprecated_description": "Das Netzwerk, zu dem Sie eine Verbindung herstellen möchten, wird von Metamask nicht mehr unterstützt.", + "edit_networks_title": "Edit networks" + }, + "permissions": { + "title_this_site_wants_to": "This site wants to:", + "title_dapp_url_wants_to": "{{dappUrl}} wants to:", + "title_dapp_url_has_approval_to": "{{dappUrl}} has approval to:", + "use_enabled_networks": "Use your enabled networks", + "wants_to_see_your_accounts": "See your accounts and suggest transactions", + "requesting_for": "Requesting for ", + "manage_permissions": "Manage Permissions", + "edit": "Edit", + "cancel": "Cancel" }, "select": { "cancel": "Stornieren", @@ -2022,6 +2042,8 @@ "back_to_safety": "Zurück zu Sicherheit" }, "notifications": { + "no_date": "Unknown", + "yesterday": "Yesterday", "staked": "Staked", "received": "Empfangen", "unstaked": "Unstaked", @@ -2097,6 +2119,8 @@ "success_withdrawal_title": "Abhebung abgeschlossen!", "error_title": "Hoppla, etwas ist schiefgelaufen :/", "received_title": "Sie haben {{amount}}{{assetType}} empfangen.", + "default_message_title": "New transaction notification", + "default_message_description": "Tap to view this transaction", "received_payment_title": "Sofortzahlung empfangen", "pending_message": "Warten auf Bestätigung", "pending_deposit_message": "Warten auf Abschluss der Einzahlung", @@ -2112,12 +2136,12 @@ "cancelled_message": "Zum Anzeigen dieser Transaktion tippen", "received_message": "Zum Anzeigen dieser Transaktion tippen", "received_payment_message": "Sie haben {{amount}} DAI empfangen.", - "prompt_title": "Push-Benachrichtigungen aktivieren", + "prompt_title": "Receive Push Notifications", "notifications_enabled_error_title": "Etwas ist schiefgelaufen", "notifications_enabled_error_desc": "Wir konnten die Benachrichtigungen nicht aktivieren. Bitte versuchen Sie es später erneut.", - "prompt_desc": "Aktivieren Sie Benachrichtigungen, damit MetaMask Ihnen Bescheid geben kann, wenn Sie ETH erhalten oder wenn Ihre Transaktionen bestätigt werden.", - "prompt_ok": "Ja", - "prompt_cancel": "Nein, danke!", + "prompt_desc": "Turn on notifications from Settings to get important alerts on wallet activity and more.", + "prompt_ok": "Turn on", + "prompt_cancel": "Maybe later", "wc_connected_title": "Verbunden mit {{title}}", "wc_signed_title": "Unterzeichnet", "wc_sent_tx_title": "Transaktion gesendet", @@ -2798,13 +2822,8 @@ }, "switch_custom_network": { "title_existing_network": "Diese Seite möchte das Netzwerk wechseln.", - "title_enabled_network": "Diese Website möchte:", "title_new_network": "Neues Netzwerk hinzugefügt", "switch_warning": "Dadurch wird das ausgewählte Netzwerk in MetaMask auf ein zuvor hinzugefügtes Netzwerk umgestellt:", - "use_enabled_networks": "Verwenden Sie Ihre aktivierten Netzwerke", - "wants_to_see_your_accounts": "Ihre Konten einsehen und Transaktionen vorschlagen", - "requesting_for": "Anfordern für ", - "edit": "Bearbeiten", "add_network_and_give_dapp_permission_warning": "Sie fügen dieses Netzwerk zu MetaMask hinzu und geben {{dapp_origin}} die Genehmigung, es zu nutzen.", "available": "ist jetzt in der Netzwerkauswahl verfügbar.", "cancel": "Stornieren", @@ -2896,7 +2915,7 @@ }, "transaction_review_eip1559": { "estimated_gas_fee": "Geschätzte Gas-Gebühr", - "suggested_gas_fee": "% {origin} empfohlene Gas-Gebühr", + "network_fee": "Network fee", "max_fee": "Maximale Gebühr", "total": "Gesamt", "max_amount": "Maximalbetrag", @@ -3136,6 +3155,7 @@ "ledger_reminder_message_step_four": "4. Standort ist aktiviert mit Verwendung des genauen Standorts auf", "ledger_reminder_message_step_four_Androidv12plus": "4. Geräte in der Nähe ist aktiviert", "ledger_reminder_message_step_five": "5. Nicht stören muss ausgeschaltet sein", + "blind_signing_message": "6. Enable \"blind signing\" on your Ledger device.", "available_devices": "Verfügbare Geräte", "retry": "Erneut versuchen", "continue": "Fortfahren", @@ -3162,11 +3182,19 @@ "not_supported": "Operation nicht unterstützt", "not_supported_error": "Nur Version 4 der getippten Daten-Unterzeichnung wird unterstützt.", "error_during_connection": "Ein unerwarteter Fehler ist aufgetreten", - "error_during_connection_message": "Es gab ein kleines Problem mit der Verbindung zu Ihrem Ledger-Gerät. Tippen Sie weiter unten auf „Erneut versuchen“, um es noch einmal zu probieren. Dies passiert manchmal, wenn die EHP-App auf Ihrem Ledger-Gerät zu Beginn der Kopplung bereits mit MetaMask Mobile geöffnet ist.", + "error_during_connection_message": "Beim Verbindungsaufbau zu Ihrem Ledger-Gerät ist ein kleines Problem aufgetreten. Tippen Sie unten auf „Erneut versuchen“, um den Vorgang zu wiederholen. Dieses Problem kann gelegentlich auftreten, wenn die ETH-App auf Ihrem Ledger-Gerät zu Beginn des Kopplungsprozesses mit MetaMask Mobile geöffnet ist.", "how_to_install_eth_webview_title": "So installieren Sie die Etherum-App", "nonce_too_low": "Nonce zu niedrig", "nonce_too_low_error": "Die eingestellte Nonce ist zu niedrig", - "select_accounts": "Ein Konto auswählen" + "select_accounts": "Select an account", + "select_hd_path": "Select HD Path", + "select_hd_path_description": "If you don't see the accounts you expect, try switching the HD path or current selected network.", + "ledger_live_path": "Ledger Live", + "ledger_legacy_path": "Legacy(MEW/MyCrypto)", + "ledger_bip44_path": "BIP44(e.g. MetaMask, Trezor)", + "ledger_legacy_label": " (legacy)", + "blind_sign_error": "Blind signing error", + "blind_sign_error_message": "Blind signing is not enabled on your Ledger device. Please enable it in the settings." }, "account_actions": { "edit_name": "Kontonamen bearbeiten", @@ -3220,7 +3248,21 @@ "error_description": "Installation von {{snap}} fehlgeschlagen." }, "stake": { - "stake": "Anteil" + "stake": "Anteil", + "stake_eth": "Stake ETH", + "your_earnings": "Your earnings", + "annual_rate": "Annual rate", + "lifetime_rewards": "Lifetime rewards", + "estimated_annual_earnings": "Estimated annual earnings", + "accessibility_labels": { + "stake_annual_rate_tooltip": "Annual rate tooltip" + }, + "estimated_annual_rewards": "Estimated annual rewards", + "metamask_pool": "MetaMask Pool", + "enter_amount": "Enter amount", + "review": "Review", + "not_enough_eth": "Not enough ETH", + "balance": "Balance" }, "default_settings": { "title": "Ihre Wallet ist bereit", @@ -3269,6 +3311,14 @@ "title": "Wir haben mehrere Anfragen bemerkt" }, "common": { - "please_wait": "Bitte warten" + "please_wait": "Bitte warten", + "disconnect_you_from": "This will disconnect you from {{dappUrl}}", + "disconnect": "Disconnect" + }, + "tooltip_modal": { + "reward_rate": { + "title": "Reward rate", + "tooltip": "Expected yearly increase in the value of your stake, based on the reward rate over the past week." + } } } diff --git a/locales/languages/el.json b/locales/languages/el.json index 8f37ad29c59..6ecee836700 100644 --- a/locales/languages/el.json +++ b/locales/languages/el.json @@ -611,8 +611,8 @@ "token_list": "Λίστα με tokens", "market_details": "Λεπτομέρειες της αγοράς", "market_cap": "Επιχειρηματικά κεφάλαια", - "total_volume": "Συνολική ποσότητα (24 ώρες)", - "volume_to_marketcap": "Ποσότητα / Επιχειριηματικά κεφάλαια", + "total_volume": "Συνολικός όγκος (24 ώρες)", + "volume_to_marketcap": "Όγκος / Επιχειρηματικά κεφάλαια", "circulating_supply": "Διαθέσιμη προσφορά", "all_time_high": "Υψηλό όλων των εποχών", "all_time_low": "Χαμηλό όλων των εποχών", @@ -686,9 +686,8 @@ "suggest_transactions": "Προτείνει συναλλαγές προς έγκριση", "disconnect": "Αποσύνδεση", "disconnect_all": "Αποσύνδεση όλων", - "reconnect_notice": "Αν αποσυνδέσετε τους λογαριασμούς σας από το {{dappUrl}}, θα πρέπει να επανασυνδεθείτε για να τους χρησιμοποιήσετε ξανά.", + "reconnect_notice": "If you disconnect from {{dappUrl}}, you’ll need to reconnect your accounts and networks to use this site again.", "disconnect_all_accounts": "Αποσύνδεση όλων των λογαριασμών", - "disconnect_you_from": "Αυτό θα σας αποσυνδέσει από το {{dappUrl}}", "deceptive_site_ahead": "Παραπλανητικός ιστότοπος εν όψει", "deceptive_site_desc": "Ο ιστότοπος που προσπαθείτε να επισκεφθείτε δεν είναι ασφαλής. Οι εισβολείς μπορεί να σας εξαπατήσουν για να κάνουν κάτι επικίνδυνο.", "learn_more": "Μάθετε περισσότερα", @@ -723,7 +722,7 @@ "description5": "1. Ξεκλειδώστε το Keystone σας", "description6": "2. Πατήστε στο --- Μενού και, στη συνέχεια, μεταβείτε στην επιλογή Συγχρονισμός", "button_continue": "Συνέχεια", - "hint_text": "Σαρώστε το πορτοφόλι σας για ", + "hint_text": "Σαρώστε το πορτοφόλι υλικολογισμικού για ", "purpose_connect": "συνδεθείτε", "purpose_sign": "επιβεβαιώσετε τη συναλλαγή", "select_accounts": "Επιλέξτε Λογαριασμό" @@ -826,7 +825,7 @@ "notifications_title": "Ειδοποιήσεις", "notifications_desc": "Διαχειριστείτε τις ειδοποιήσεις σας", "allow_notifications": "Επιτρέψτε τις ειδοποιήσεις", - "allow_notifications_desc": "Μείνετε ενήμεροι για ό,τι συμβαίνει στο πορτοφόλι σας με τις ειδοποιήσεις. Για να χρησιμοποιήσετε τις ειδοποιήσεις, χρησιμοποιούμε ένα προφίλ για να συγχρονίσουμε ορισμένες ρυθμίσεις στις συσκευές σας.", + "allow_notifications_desc": "Μείνετε ενήμεροι για ό, τι συμβαίνει στο πορτοφόλι σας με τις ειδοποιήσεις. Για να χρησιμοποιήσετε τις ειδοποιήσεις, κάνουμε χρήση ενός προφίλ για να συγχρονίσουμε ορισμένες ρυθμίσεις στις συσκευές σας.", "notifications_opts": { "customize_session_title": "Προσαρμόστε τις ειδοποιήσεις σας", "customize_session_desc": "Ενεργοποιήστε τους τύπους ειδοποιήσεων που θέλετε να λαμβάνετε:", @@ -943,7 +942,7 @@ "network": "δίκτυο", "networks": "δίκτυα", "network_exists": "Αυτό το δίκτυο έχει ήδη προστεθεί.", - "unMatched_chain": "Σύμφωνα με τα αρχεία μας, αυτή η διεύθυνση URL δεν ταιριάζει με γνωστό πάροχο για αυτό το αναγνωριστικό αλυσίδας.", + "unMatched_chain": "Σύμφωνα με τα αρχεία μας, αυτή η διεύθυνση URL δεν αντιστοιχεί με γνωστό πάροχο για αυτό το αναγνωριστικό αλυσίδας.", "unMatched_chain_name": "Αυτό το αναγνωριστικό αλυσίδας δεν ταιριάζει με το όνομα του δικτύου.", "url_associated_to_another_chain_id": "Αυτή η διεύθυνση URL συνδέεται με ένα άλλο αναγνωριστικό αλυσίδας.", "chain_id_associated_with_another_network": "Οι πληροφορίες που έχετε καταχωρίσει συνδέονται με ένα υπάρχον αναγνωριστικό αλυσίδας. Ενημερώστε τις πληροφορίες σας ή", @@ -1066,7 +1065,12 @@ "simulation_details_description": "Ενεργοποιήστε το για να εκτιμήσετε τις αλλαγές στο υπόλοιπο των συναλλαγών πριν τις επιβεβαιώσετε. Αυτό δεν εγγυάται την τελική αποτίμηση των συναλλαγών σας. ", "simulation_details_learn_more": "Μάθετε περισσότερα.", "aes_crypto_test_form_title": "AES Crypto - Φόρμα δοκιμών", - "aes_crypto_test_form_description": "Το τμήμα αναπτύχθηκε αποκλειστικά για δοκιμές E2E. Εάν αυτό είναι ορατό στην εφαρμογή σας, αναφέρετέ το στην υποστήριξη του MetaMask." + "aes_crypto_test_form_description": "Το τμήμα αναπτύχθηκε αποκλειστικά για δοκιμές E2E. Εάν αυτό είναι ορατό στην εφαρμογή σας, αναφέρετέ το στην υποστήριξη του MetaMask.", + "developer_options": { + "title": "Developer Options", + "generate_trace_test": "Generate Trace Test", + "generate_trace_test_desc": "Generate a Developer Test Sentry trace." + } }, "aes_crypto_test_form": { "generate_random_salt": "Δημιουργία τυχαίων δεδομένων", @@ -1332,7 +1336,7 @@ "cancel": "Άκυρο", "save": "Αποθήκευση", "speedup": "Επιτάχυνση", - "sign_with_keystone": "Σύνδεση με το πορτοφόλι υλικού", + "sign_with_keystone": "Σύνδεση με το πορτοφόλι υλικολογισμικού", "sign_with_ledger": "Είσοδος με το Ledger", "from": "Από", "gas_fee": "Τέλος Συναλλαγής", @@ -1406,7 +1410,7 @@ "token_id": "Αναγνωριστικό token", "not_enough_for_gas": "Έχετε 0 {{ticker}} στον λογαριασμό σας για να πληρώσετε τα τέλη συναλλαγής. Αγοράστε μερικά {{ticker}} ή καταθέστε από άλλον λογαριασμό.", "send": "Αποστολή", - "confirm_with_qr_hardware": "Επιβεβαίωση με το πορτοφόλι υλικού", + "confirm_with_qr_hardware": "Επιβεβαίωση με το πορτοφόλι υλικολογισμικού", "confirm_with_ledger_hardware": "Επιβεβαίωση με το Ledger", "confirmed": "Επιβεβαιωμένο", "pending": "Εκκρεμεί", @@ -1439,12 +1443,13 @@ "no_camera_permission": "Η κάμερα δεν είναι εξουσιοδοτημένη. Δώστε άδεια και προσπαθήστε ξανά", "invalid_qr_code_sign": "Μη έγκυρος κωδικός QR. Ελέγξτε τον εξοπλισμό σας και προσπαθήστε ξανά.", "no_camera_permission_android": "Για να συνεχίσετε, πρέπει να παραχωρήσετε στο MetaMask πρόσβαση στην κάμερά σας. Μπορεί επίσης να χρειαστεί να αλλάξετε τις ρυθμίσεις του συστήματός σας.", - "mismatched_qr_request_id": "Αναντίστοιχα δεδομένα συναλλαγών. Χρησιμοποιήστε το πορτοφόλι σας για να συνδεθείτε με τον παρακάτω κωδικό QR και πατήστε «Λήψη υπογραφής».", + "mismatched_qr_request_id": "Αναντιστοιχία δεδομένων συναλλαγών. Χρησιμοποιήστε το πορτοφόλι σας για να συνδεθείτε με τον παρακάτω κωδικό QR και πατήστε «Λήψη υπογραφής».", "fiat_conversion_not_available": "Οι μετατροπές σε παραστατικά χρήματα δεν είναι διαθέσιμες αυτή τη στιγμή", "hex_data_copied": "Τα δεκαεξαδικά δεδομένα αντιγράφηκαν στο πρόχειρο", "invalid_recipient": "Μη έγκυρος παραλήπτης", "invalid_recipient_description": "Ελέγξτε τη διεύθυνση και βεβαιωθείτε ότι είναι έγκυρη", - "swap_tokens": "Ανταλλαγή tokens" + "swap_tokens": "Ανταλλαγή tokens", + "fromWithColon": "From:" }, "custom_gas": { "total": "Σύνολο", @@ -1640,8 +1645,8 @@ "import_wallet_label": "Λογαριασμός που προστέθηκε", "import_wallet_tip": "Όλες οι μελλοντικές συναλλαγές που θα πραγματοποιούνται από αυτή τη συσκευή θα περιλαμβάνουν την ένδειξη «από αυτή τη συσκευή» δίπλα στη χρονοσήμανση. Για συναλλαγές που χρονολογούνται πριν από την προσθήκη του λογαριασμού, το ιστορικό αυτό δεν θα υποδεικνύει ποιες εξερχόμενες συναλλαγές προέρχονται από αυτή τη συσκευή.", "sign_title_scan": "Σάρωση ", - "sign_title_device": "με το πορτοφόλι υλικού σας", - "sign_description_1": "Αφού υπογράψετε με το πορτοφόλι υλικού σας,", + "sign_title_device": "με το πορτοφόλι υλικολογισμικού", + "sign_description_1": "Αφού υπογράψετε με το πορτοφόλι υλικολογισμικού σας,", "sign_description_2": "πατήστε στην επιλογή «Λήψη υπογραφής»", "sign_get_signature": "Λήψη Υπογραφής", "network_fee": "Τέλη Δικτύου" @@ -1731,6 +1736,7 @@ "continue": "Συνέχεια", "cancel": "Άκυρο", "approve": "Έγκριση", + "update": "Update", "edit_network_details": "Επεξεργασία λεπτομερειών δικτύου", "malicious_network_warning": "Ένας κακόβουλος πάροχος δικτύου μπορεί να πει ψέματα για την κατάσταση του blockchain και να καταγράψει τη δραστηριότητα του δικτύου σας. Να προσθέτετε μόνο προσαρμοσμένα δίκτυα που εμπιστεύεστε.", "security_link": "https://support.metamask.io/networks-and-sidechains/managing-networks/user-guide-custom-networks-and-sidechains/", @@ -1738,10 +1744,12 @@ "additional_network_information_title": "Πρόσθετες πληροφορίες δικτύων", "network_warning_desc": "Αυτή η σύνδεση δικτύου βασίζεται σε τρίτους. H σύνδεση ενδέχεται να είναι λιγότερο αξιόπιστη ή να επιτρέπει σε τρίτους να παρακολουθούν τη δραστηριότητα.", "additonial_network_information_desc": "Ορισμένα από αυτά τα δίκτυα βασίζονται σε τρίτους. Οι συνδέσεις μπορεί να είναι λιγότερο αξιόπιστες ή να επιτρέπουν σε τρίτους να παρακολουθούν τη δραστηριότητα.", + "connect_more_networks": "Connect more networks", "learn_more": "Μάθετε περισσότερα", "learn_more_url": "https://support.metamask.io/networks-and-sidechains/managing-networks/the-risks-of-connecting-to-an-unknown-network/", "switch_network": "Εναλλαγή σε δίκτυο", "switch": "Αλλαγή", + "select_all": "Select all", "new_network": "Προστέθηκε νέο δίκτυο", "network_name": "Το Δίκτυο {{networkName}}", "network_added": " είναι τώρα διαθέσιμο στην επιλογή δικτύου.", @@ -1758,7 +1766,19 @@ "show_test_networks": "Εμφάνιση δοκιμαστικών δικτύων", "deprecated_goerli": "Λόγω των αλλαγών στο πρωτόκολλο του δοκιμαστικού δίκτυου Ethereum: Goerli μπορεί να μην λειτουργεί τόσο αξιόπιστα και θα καταργηθεί σύντομα.", "network_deprecated_title": "Αυτό το δίκτυο έχει καταργηθεί", - "network_deprecated_description": "Το δίκτυο στο οποίο προσπαθείτε να συνδεθείτε δεν υποστηρίζεται πλέον από το Metamask." + "network_deprecated_description": "Το δίκτυο στο οποίο προσπαθείτε να συνδεθείτε δεν υποστηρίζεται πλέον από το Metamask.", + "edit_networks_title": "Edit networks" + }, + "permissions": { + "title_this_site_wants_to": "This site wants to:", + "title_dapp_url_wants_to": "{{dappUrl}} wants to:", + "title_dapp_url_has_approval_to": "{{dappUrl}} has approval to:", + "use_enabled_networks": "Use your enabled networks", + "wants_to_see_your_accounts": "See your accounts and suggest transactions", + "requesting_for": "Requesting for ", + "manage_permissions": "Manage Permissions", + "edit": "Edit", + "cancel": "Cancel" }, "select": { "cancel": "Άκυρο", @@ -2022,6 +2042,8 @@ "back_to_safety": "Πίσω στην ασφάλεια" }, "notifications": { + "no_date": "Unknown", + "yesterday": "Yesterday", "staked": "Ποντάρισμα", "received": "Ελήφθη", "unstaked": "Ακύρωση πονταρίσματος", @@ -2038,7 +2060,7 @@ "swap": "Ανταλλαγή", "sent": "Στάλθηκε σε {{address}}", "menu_item_title": { - "metamask_swap_completed": "Ανταλλαγή του {{symbolIn}} για {{symbolOut}}", + "metamask_swap_completed": "Ανταλλαγή του {{symbolIn}} με {{symbolOut}}", "erc20_sent": "Εστάλη σε {{address}}", "erc20_received": "Ελήφθη από {{address}}", "eth_sent": "Εστάλη σε {{address}}", @@ -2097,6 +2119,8 @@ "success_withdrawal_title": "Η ανάληψη ολοκληρώθηκε!", "error_title": "Ωχ, κάτι πήγε στραβά :/", "received_title": "Λάβατε {{amount}} {{assetType}}", + "default_message_title": "New transaction notification", + "default_message_description": "Tap to view this transaction", "received_payment_title": "Ελήφθη άμεση πληρωμή", "pending_message": "Αναμονή για επιβεβαίωση", "pending_deposit_message": "Αναμονή για την ολοκλήρωση της κατάθεσης", @@ -2112,12 +2136,12 @@ "cancelled_message": "Πατήστε για να δείτε τη συναλλαγή", "received_message": "Πατήστε για να δείτε τη συναλλαγή", "received_payment_message": "Λάβατε {{amount}} DAI", - "prompt_title": "Ενεργοποίηση των Ειδοποιήσεων Ώθησης", + "prompt_title": "Receive Push Notifications", "notifications_enabled_error_title": "Κάτι πήγε στραβά", "notifications_enabled_error_desc": "Δεν μπορέσαμε να ενεργοποιήσουμε τις ειδοποιήσεις. Προσπαθήστε ξανά αργότερα.", - "prompt_desc": "Ενεργοποιήστε τις ειδοποιήσεις, ώστε το MetaMask να σας ενημερώνει όταν έχετε λάβει ETH ή όταν οι συναλλαγές σας έχουν επιβεβαιωθεί.", - "prompt_ok": "Ναι", - "prompt_cancel": "Όχι, ευχαριστώ", + "prompt_desc": "Turn on notifications from Settings to get important alerts on wallet activity and more.", + "prompt_ok": "Turn on", + "prompt_cancel": "Maybe later", "wc_connected_title": "Συνδέθηκε σε {{title}}", "wc_signed_title": "Υπογεγραμμένο", "wc_sent_tx_title": "Απεσταλμένη συναλλαγή", @@ -2798,13 +2822,8 @@ }, "switch_custom_network": { "title_existing_network": "Αυτός ο ιστότοπος θα ήθελε να αλλάξει το δίκτυο", - "title_enabled_network": "Αυτός ο ιστότοπος θέλει να:", "title_new_network": "Προστέθηκε νέο δίκτυο", "switch_warning": "Αυτό θα αλλάξει το επιλεγμένο δίκτυο στο MetaMask σε ένα δίκτυο που έχει προστεθεί προηγουμένως:", - "use_enabled_networks": "Χρησιμοποιήσει τα ενεργά σας δίκτυα", - "wants_to_see_your_accounts": "Προβάλει τους λογαριασμούς σας και να προτείνει συναλλαγές", - "requesting_for": "Ζητάει ", - "edit": "Επεξεργασία", "add_network_and_give_dapp_permission_warning": "Προσθέτετε αυτό το δίκτυο στο MetaMask και δίνετε στο {{dapp_origin}} την άδεια να το χρησιμοποιεί.", "available": "είναι τώρα διαθέσιμο στην επιλογή δικτύου.", "cancel": "Άκυρο", @@ -2896,7 +2915,7 @@ }, "transaction_review_eip1559": { "estimated_gas_fee": "Εκτιμώμενο τέλος συναλλαγής", - "suggested_gas_fee": "%{origin} προτεινόμενο τέλος συναλλαγής", + "network_fee": "Network fee", "max_fee": "Μέγιστο τέλος συναλλαγής", "total": "Σύνολο", "max_amount": "Μέγιστο ποσό", @@ -3136,6 +3155,7 @@ "ledger_reminder_message_step_four": "4. Η τοποθεσία είναι ενεργοποιημένη με τη χρήση ακριβούς τοποθεσίας στο", "ledger_reminder_message_step_four_Androidv12plus": "4. Οι κοντινές συσκευές είναι ενεργοποιημένες", "ledger_reminder_message_step_five": "5. Η λειτουργία \"Μην ενοχλείτε\" πρέπει να είναι απενεργοποιημένη", + "blind_signing_message": "6. Enable \"blind signing\" on your Ledger device.", "available_devices": "Διαθέσιμες συσκευές", "retry": "Επανάληψη", "continue": "Συνέχεια", @@ -3162,11 +3182,19 @@ "not_supported": "Η λειτουργία δεν υποστηρίζεται", "not_supported_error": "Υποστηρίζεται μόνο η έκδοση 4 της υπογραφής τυπωμένων δεδομένων.", "error_during_connection": "Παρουσιάστηκε ένα άγνωστο σφάλμα", - "error_during_connection_message": "Προέκυψε ένα μικρό πρόβλημα στη σύνδεση της συσκευής σας Ledger, πατήστε 'Επανάληψη' παρακάτω για να προσπαθήσετε ξανά. Μερικές φορές αυτό συμβαίνει επειδή η εφαρμογή ETH στη συσκευή σας Ledger είναι ανοιχτή κατά την έναρξη της διαδικασίας σύζευξης με το MetaMask Mobile.", + "error_during_connection_message": "Προέκυψε ένα μικρό πρόβλημα στη σύνδεση της συσκευής σας Ledger, πατήστε «Επανάληψη» παρακάτω για να προσπαθήσετε ξανά. Μερικές φορές αυτό συμβαίνει επειδή η εφαρμογή ETH στη συσκευή σας Ledger είναι ανοιχτή κατά την έναρξη της διαδικασίας σύζευξης με το MetaMask Mobile.", "how_to_install_eth_webview_title": "Πώς να εγκαταστήσετε την εφαρμογή Ethereum", "nonce_too_low": "Πολύ χαμηλό nonce", "nonce_too_low_error": "Το καθορισμένο nonce είναι πολύ χαμηλό", - "select_accounts": "Επιλέξτε λογαριασμό" + "select_accounts": "Select an account", + "select_hd_path": "Select HD Path", + "select_hd_path_description": "If you don't see the accounts you expect, try switching the HD path or current selected network.", + "ledger_live_path": "Ledger Live", + "ledger_legacy_path": "Legacy(MEW/MyCrypto)", + "ledger_bip44_path": "BIP44(e.g. MetaMask, Trezor)", + "ledger_legacy_label": " (legacy)", + "blind_sign_error": "Blind signing error", + "blind_sign_error_message": "Blind signing is not enabled on your Ledger device. Please enable it in the settings." }, "account_actions": { "edit_name": "Επεξεργασία ονόματος λογαριασμού", @@ -3220,7 +3248,21 @@ "error_description": "Η εγκατάσταση του {{snap}} απέτυχε." }, "stake": { - "stake": "Stake" + "stake": "Stake", + "stake_eth": "Stake ETH", + "your_earnings": "Your earnings", + "annual_rate": "Annual rate", + "lifetime_rewards": "Lifetime rewards", + "estimated_annual_earnings": "Estimated annual earnings", + "accessibility_labels": { + "stake_annual_rate_tooltip": "Annual rate tooltip" + }, + "estimated_annual_rewards": "Estimated annual rewards", + "metamask_pool": "MetaMask Pool", + "enter_amount": "Enter amount", + "review": "Review", + "not_enough_eth": "Not enough ETH", + "balance": "Balance" }, "default_settings": { "title": "Το πορτοφόλι σας είναι έτοιμο", @@ -3269,6 +3311,14 @@ "title": "Παρατηρήσαμε πολλαπλά αιτήματα" }, "common": { - "please_wait": "Παρακαλώ περιμένετε" + "please_wait": "Παρακαλώ περιμένετε", + "disconnect_you_from": "This will disconnect you from {{dappUrl}}", + "disconnect": "Disconnect" + }, + "tooltip_modal": { + "reward_rate": { + "title": "Reward rate", + "tooltip": "Expected yearly increase in the value of your stake, based on the reward rate over the past week." + } } } diff --git a/locales/languages/es.json b/locales/languages/es.json index bcf77c939ae..f3cfd1029f3 100644 --- a/locales/languages/es.json +++ b/locales/languages/es.json @@ -449,10 +449,10 @@ "use_the_currency_symbol": "utiliza el símbolo de moneda", "use_correct_symbol": "Asegúrese de estar utilizando el símbolo correcto antes de continuar", "chain_id_currently_used": "Esta ID de cadena es utilizada actualmente por el", - "incorrect_network_name_warning": "According to our records, the network name may not correctly match this chain ID.", - "suggested_name": "Suggested name:", + "incorrect_network_name_warning": "Según nuestros registros, es posible que el nombre de la red no coincida correctamente con este ID de cadena.", + "suggested_name": "Nombre sugerido:", "network_check_validation_desc": "reduce las posibilidades de conectarse a una red maliciosa o incorrecta.", - "cant_verify_custom_network_warning": "We can’t verify custom networks. To avoid malicious providers from recording your network activity, only add networks you trust.", + "cant_verify_custom_network_warning": "No podemos verificar redes personalizadas. Para evitar que proveedores maliciosos registren su actividad de red, agregue únicamente redes en las que confíe.", "nfts_autodetection_cta": "Active la detección de NFT en Configuraciones", "learn_more": "Más información", "add_collectibles": "AGREGAR NFT", @@ -501,10 +501,10 @@ "lists": "Listas de tokens", "hide_cta": "Ocultar token", "options": { - "view_on_portfolio": "View on Portfolio", + "view_on_portfolio": "Ver en Portfolio", "view_on_block": "Ver en el explorador de bloques", "token_details": "Detalles del token", - "remove_token": "Remove token" + "remove_token": "Eliminar token" } }, "activity_view": { @@ -551,7 +551,7 @@ "description_title": "Ayúdenos a mejorar MetaMask", "description_content_1": "Nos gustaría recopilar datos básicos de uso para mejorar MetaMask. Tenga en cuenta que nunca venderemos los datos que nos proporcione aquí.", "description_content_2": "Al recopilar métricas, siempre serán...", - "description_content_3": "Learn how we protect your privacy while collecting usage data for your profile.", + "description_content_3": "Conozca cómo protegemos su privacidad mientras recopilamos datos de uso para su perfil.", "checkbox": "Utilizaremos estos datos para saber cómo interactúa con nuestras comunicaciones de marketing. Podemos compartir noticias relevantes (como características de productos).", "action_description_1_prefix": "Privadas:", "action_description_2_prefix": "Generales:", @@ -606,17 +606,17 @@ "billion_abbreviation": "B", "trillion_abbreviation": "T", "million_abbreviation": "M", - "token_details": "Token details", - "contract_address": "Contract address", - "token_list": "Token list", - "market_details": "Market details", - "market_cap": "Market Cap", - "total_volume": "Total Volume (24h)", - "volume_to_marketcap": "Volume / Market Cap", - "circulating_supply": "Circulating supply", - "all_time_high": "All time high", - "all_time_low": "All time low", - "fully_diluted": "Fully diluted" + "token_details": "Detalles del token", + "contract_address": "Dirección del contrato", + "token_list": "Lista de tokens", + "market_details": "Detalles del mercado", + "market_cap": "Capitalización bursátil", + "total_volume": "Volumen total (24 h)", + "volume_to_marketcap": "Volumen / capitalización bursátil", + "circulating_supply": "Suministro circulante", + "all_time_high": "Punto más alto", + "all_time_low": "Punto más bajo", + "fully_diluted": "Totalmente diluido" }, "collectible": { "collectible_address": "Dirección", @@ -666,10 +666,10 @@ "accounts_title": "Cuentas", "connect_account_title": "Conectar cuenta", "connect_accounts_title": "Conectar cuentas", - "edit_accounts_title": "Edit accounts", + "edit_accounts_title": "Editar cuentas", "connected_accounts_title": "Cuentas conectadas", "connect_description": "Comparta la dirección de su cuenta, saldo, actividad y permita que el sitio inicie transacciones.", - "select_accounts_description": "Select the account(s) to use on this site:", + "select_accounts_description": "Seleccione la(s) cuenta(s) a usar en este sitio:", "connect_multiple_accounts": "Conectar múltiples cuentas", "connect_more_accounts": "Conectar más cuentas", "cancel": "Cancelar", @@ -685,10 +685,9 @@ "address_balance_activity_permission": "Ver dirección, saldo de cuenta y actividad", "suggest_transactions": "Sugerir transacciones para aprobar", "disconnect": "Desconectar", - "disconnect_all": "Disconnect all", - "reconnect_notice": "If you disconnect your accounts from {{dappUrl}}, you’ll need to reconnect to use them again.", + "disconnect_all": "Desconectar todo", + "reconnect_notice": "If you disconnect from {{dappUrl}}, you’ll need to reconnect your accounts and networks to use this site again.", "disconnect_all_accounts": "Desconectar todas las cuentas", - "disconnect_you_from": "This will disconnect you from {{dappUrl}}", "deceptive_site_ahead": "Sitio engañoso más adelante", "deceptive_site_desc": "El sitio que intenta visitar no es seguro. Los atacantes pueden engañarlo para que haga algo peligroso.", "learn_more": "Más información", @@ -706,7 +705,7 @@ "accounts_connected": "cuentas conectadas.", "disconnected": "desconectadas.", "disconnected_all": "Todas las cuentas desconectadas.", - "disconnected_from": "Disconnected from {{dappHostName}}", + "disconnected_from": "Desconectado de {{dappHostName}}", "nft_detection_enabled": "Autodetección de NFT habilitada" }, "connect_qr_hardware": { @@ -717,13 +716,13 @@ "keystone": "Keystone", "ngravezero": "Ngrave Zero", "learnMore": "Más información", - "buyNow": "Buy now", + "buyNow": "Comprar ahora", "tutorial": "Tutorial", "description4": "Monedero Keystone (tutorial)", "description5": "1. Desbloquee su monedero Keystone", "description6": "2. Haga clic en el menú ··· y luego vaya a Sincronizar", "button_continue": "Continuar", - "hint_text": "Scan your hardware wallet to ", + "hint_text": "Escanee su monedero físico para ", "purpose_connect": "conectar", "purpose_sign": "confirmar la transacción", "select_accounts": "Seleccionar una cuenta" @@ -744,7 +743,7 @@ "enabling_profile_sync": "Activando la sincronización de perfiles...", "disabling_profile_sync": "Desactivando la sincronización de perfiles...", "notifications_dismiss_modal": "Ignorar", - "select_rpc_url": "Select RPC URL", + "select_rpc_url": "Seleccionar URL de RPC", "title": "Configuración", "current_conversion": "Moneda base", "current_language": "Idioma actual", @@ -826,7 +825,7 @@ "notifications_title": "Notificaciones", "notifications_desc": "Administre sus notificaciones", "allow_notifications": "Permitir notificaciones", - "allow_notifications_desc": "Stay in the loop on what’s happening in your wallet with notifications. To use notifications, we use a profile to sync some settings across your devices.", + "allow_notifications_desc": "Manténgase informado sobre lo que sucede en su billetera con notificaciones. Para usar notificaciones, usamos un perfil para sincronizar algunas configuraciones en sus dispositivos.", "notifications_opts": { "customize_session_title": "Personalice sus notificaciones", "customize_session_desc": "Active los tipos de notificaciones que desea recibir:", @@ -845,10 +844,10 @@ }, "contacts_title": "Contactos", "contacts_desc": "Agregar, editar, quitar y administrar sus cuentas", - "permissions_title": "Permissions", - "permissions_desc": "Manage the permissions given to sites and apps", - "no_permissions": "No permissions", - "no_permissions_desc": "If you connect an account to a site or an app, you’ll see it here.", + "permissions_title": "Permisos", + "permissions_desc": "Administrar los permisos otorgados a sitios y aplicaciones", + "no_permissions": "Sin permisos", + "no_permissions_desc": "Si conecta una cuenta a un sitio o una aplicación, lo verá aquí.", "security_title": "Seguridad y privacidad", "back": "Volver", "security_desc": "Configuración de privacidad, MetaMetrics, clave privada y frase secreta de recuperación del monedero", @@ -938,18 +937,18 @@ "custom_network_name": "Redes personalizadas", "popular": "Popular", "delete": "Eliminar", - "account": "account", - "accounts": "accounts", - "network": "network", - "networks": "networks", + "account": "cuenta", + "accounts": "cuentas", + "network": "red", + "networks": "redes", "network_exists": "La red ya ha sido instalada", - "unMatched_chain": "According to our records, this URL does not match a known provider for this chain ID.", - "unMatched_chain_name": "This chain ID doesn’t match the network name.", - "url_associated_to_another_chain_id": "This URL is associated with another chain ID.", - "chain_id_associated_with_another_network": "The information you have entered is associated with an existing chain ID. Update your information or", - "network_already_exist": "You already have a network with the same chain ID or RPC URL. Enter a new chain ID or RPC URL", - "edit_original_network": "edit the original network", - "find_the_right_one": "Find the right one on:", + "unMatched_chain": "Según nuestros registros, esta URL no coincide con un proveedor conocido para este ID de cadena.", + "unMatched_chain_name": "Este ID de cadena no coincide con el nombre de la red.", + "url_associated_to_another_chain_id": "Esta URL está asociada a otro ID de cadena.", + "chain_id_associated_with_another_network": "La información que ha ingresado está asociada con un ID de cadena existente. Actualice su información o", + "network_already_exist": "Ya tiene una red con el mismo ID de cadena o URL de RPC. Introduzca un nuevo ID de cadena o URL de RPC", + "edit_original_network": "editar la red original", + "find_the_right_one": "Encuentre el correcto en:", "delete_metrics_title": "Eliminar datos de MetaMetrics", "delete_metrics_description_part_one": "Esto eliminará los datos históricos de", "delete_metrics_description_part_two": "MetaMetrics", @@ -1066,7 +1065,12 @@ "simulation_details_description": "Active esta opción para estimar los cambios de saldo de las transacciones antes de confirmarlas. Esto no garantiza el resultado final de sus transacciones. ", "simulation_details_learn_more": "Obtenga más información.", "aes_crypto_test_form_title": "AES Crypto: formulario de prueba", - "aes_crypto_test_form_description": "Sección desarrollada exclusivamente para pruebas E2E. Si esto es visible en su aplicación, infórmelo al soporte de MetaMask." + "aes_crypto_test_form_description": "Sección desarrollada exclusivamente para pruebas E2E. Si esto es visible en su aplicación, infórmelo al soporte de MetaMask.", + "developer_options": { + "title": "Developer Options", + "generate_trace_test": "Generate Trace Test", + "generate_trace_test_desc": "Generate a Developer Test Sentry trace." + } }, "aes_crypto_test_form": { "generate_random_salt": "Generar sal aleatoria", @@ -1179,7 +1183,7 @@ "text": "TEXTO", "qr_code": "CÓDIGO QR", "hold_to_reveal_credential": "No revele su {{credentialName}}", - "reveal_credential": "Reveal {{credentialName}}", + "reveal_credential": "Revelar su {{credentialName}}", "keep_credential_safe": "Proteja su {{credentialName}}", "srp_abbreviation_text": "SRP", "srp_text": "Frase secreta de recuperación", @@ -1306,14 +1310,14 @@ "error": "Error", "attempting_to_scan_with_wallet_locked": "Si desea escanear un código QR, desbloqueé su monedero para poder usarlo.", "attempting_sync_from_wallet_error": "Parece que intenta sincronizar con la extensión. debe borrar el monedero actual. \n\nUna vez que haya borrado o vuelto a instalar una versión nueva de la aplicación, seleccione la opción \"Sincronizar con la extensión MetaMask\". Importante: Antes de borrar el monedero, asegúrese de haber hecho una copia de seguridad de la frase secreta de recuperación.", - "not_allowed_error_title": "Turn on camera access", - "not_allowed_error_desc": "To scan a QR code, you'll need to give MetaMask camera access from your device's settings menu.", + "not_allowed_error_title": "Activar el acceso a la cámara", + "not_allowed_error_desc": "Para escanear un código QR, deberá otorgar acceso a la cámara a MetaMask desde el menú de configuración de su dispositivo.", "unrecognized_address_qr_code_title": "Código QR no reconocido", "unrecognized_address_qr_code_desc": "Lo sentimos, este código QR no está asociado con una dirección de cuenta o una dirección de contrato.", "url_redirection_alert_title": "Está a punto de visitar un enlace externo", "url_redirection_alert_desc": "Los enlaces se pueden usar para tratar de estafar o hacer phishing a las personas, así que asegúrese de visitar solo sitios web en los que confíe.", "label": "Escanear un código QR", - "open_settings": "Settings" + "open_settings": "Configuración" }, "action_view": { "cancel": "Cancelar", @@ -1332,7 +1336,7 @@ "cancel": "Cancelar", "save": "Guardar", "speedup": "Acelerar", - "sign_with_keystone": "Sign with hardware wallet", + "sign_with_keystone": "Firmar con monedero físico", "sign_with_ledger": "Firmar con Ledger", "from": "De", "gas_fee": "Tarifa de gas", @@ -1406,7 +1410,7 @@ "token_id": "ID de token", "not_enough_for_gas": "Tiene 0 {{ticker}} en su cuenta para pagar las tarifas de transacción. Compre algunos {{ticker}} o deposítelos desde otra cuenta.", "send": "Enviar", - "confirm_with_qr_hardware": "Confirm with hardware wallet", + "confirm_with_qr_hardware": "Confirmar con su monedero físico", "confirm_with_ledger_hardware": "Confirmar con Ledger", "confirmed": "Confirmado", "pending": "Pendiente", @@ -1439,12 +1443,13 @@ "no_camera_permission": "La cámara no está autorizada. Por favor, otorgue acceso y vuelva a intentar", "invalid_qr_code_sign": "Código QR no válido. Por favor, revise su hardware y vuelva a intentar.", "no_camera_permission_android": "Debe otorgar a MetaMask acceso a su cámara para continuar. Es posible que también deba cambiar sus configuraciones del sistema.", - "mismatched_qr_request_id": "Incongruent transaction data. Please use your hardware wallet to sign the QR code below and tap 'Get Signature'.", + "mismatched_qr_request_id": "Datos de transacción incongruentes. Utilice su monedero físico para firmar el código QR que aparece a continuación y pulse \"Obtener firma\".", "fiat_conversion_not_available": "Las conversiones a fiat no están disponibles en este momento", "hex_data_copied": "Datos hexadecimales copiados al portapapeles", "invalid_recipient": "Destinatario inválido", "invalid_recipient_description": "Verifique la dirección y asegúrese de que sea válida", - "swap_tokens": "Intercambiar tokens" + "swap_tokens": "Intercambiar tokens", + "fromWithColon": "From:" }, "custom_gas": { "total": "Total", @@ -1640,8 +1645,8 @@ "import_wallet_label": "Cuenta agregada", "import_wallet_tip": "Todas las transacciones futuras que se realicen desde este dispositivos tendrán la etiqueta \"de este dispositivo\" junto a la marca de tiempo. Para las transacciones con fecha anterior a la que se agregó la cuenta, el historial no indicará qué transacciones salientes se originaron de este dispositivo.", "sign_title_scan": "Escanear ", - "sign_title_device": "with your hardware wallet", - "sign_description_1": "After you have signed with your hardware wallet,", + "sign_title_device": "con su monedero físico", + "sign_description_1": "Después de haber firmado con su monedero físico,", "sign_description_2": "pulse en Obtener firma", "sign_get_signature": "Obtener firma", "network_fee": "Tarifa de red" @@ -1714,11 +1719,11 @@ "network_display_name": "Mostrar nombre", "network_chain_id": "Identificador de cadena", "network_rpc_url": "URL de la red", - "network_rpc_url_label": "Network RPC URL", - "new_default_network_url": "New default network URL", - "current_label": "Current", - "new_label": "New", - "review": "Review", + "network_rpc_url_label": "URL de RPC de la red", + "new_default_network_url": "Nueva URL de red por defecto", + "current_label": "Actual", + "new_label": "Nuevo", + "review": "Revisar", "view_details": "Ver detalles", "network_details": "Detalles de la red", "network_select_confirm_use_safe_check": "Al seleccionar Confirmar, se activa la verificación de detalles de la red. Puedes desactivar el registro de detalles de la red ", @@ -1731,6 +1736,7 @@ "continue": "Continuar", "cancel": "Cancelar", "approve": "Aprobar", + "update": "Update", "edit_network_details": "Editar detalles de la red", "malicious_network_warning": "Un proveedor de red malintencionado puede mentir sobre el estado de la cadena de bloques y registrar su actividad de red. Agregue solo redes personalizadas de confianza.", "security_link": "https://support.metamask.io/networks-and-sidechains/managing-networks/user-guide-custom-networks-and-sidechains/", @@ -1738,10 +1744,12 @@ "additional_network_information_title": "Información adicional sobre las redes", "network_warning_desc": "Esta conexión de red depende de terceros. Esta conexión puede ser menos confiable o permite que terceros rastreen la actividad.", "additonial_network_information_desc": "Algunas de estas redes dependen de terceros. Las conexiones pueden ser menos confiables o permitir que terceros realicen un seguimiento de la actividad.", + "connect_more_networks": "Connect more networks", "learn_more": "Más información", "learn_more_url": "https://support.metamask.io/networks-and-sidechains/managing-networks/the-risks-of-connecting-to-an-unknown-network/", "switch_network": "Cambiar de red", "switch": "Cambiar", + "select_all": "Select all", "new_network": "Nueva red agregada", "network_name": "Red {{networkName}}", "network_added": " ya está disponible en el selector de redes.", @@ -1751,14 +1759,26 @@ "add_other_network_here": "aquí.", "you_can": "O puede", "add_network": "agregar más redes manualmente.", - "add_specific_network": "Add {{network_name}}", + "add_specific_network": "Agregar {{network_name}}", "select_network": "Seleccionar una red", "enabled_networks": "Redes habilitadas", "additional_networks": "Redes adicionales", "show_test_networks": "Mostrar redes de prueba", "deprecated_goerli": "Debido a los cambios de protocolo de Ethereum: es posible que la red de prueba de Goerli no funcione de manera tan confiable y pronto quedará obsoleta.", "network_deprecated_title": "Esta red está en desuso", - "network_deprecated_description": "La red a la que está intentando conectarse ya no es compatible con Metamask." + "network_deprecated_description": "La red a la que está intentando conectarse ya no es compatible con Metamask.", + "edit_networks_title": "Edit networks" + }, + "permissions": { + "title_this_site_wants_to": "This site wants to:", + "title_dapp_url_wants_to": "{{dappUrl}} wants to:", + "title_dapp_url_has_approval_to": "{{dappUrl}} has approval to:", + "use_enabled_networks": "Use your enabled networks", + "wants_to_see_your_accounts": "See your accounts and suggest transactions", + "requesting_for": "Requesting for ", + "manage_permissions": "Manage Permissions", + "edit": "Edit", + "cancel": "Cancel" }, "select": { "cancel": "Cancelar", @@ -2022,10 +2042,12 @@ "back_to_safety": "Volver a Seguridad" }, "notifications": { + "no_date": "Unknown", + "yesterday": "Yesterday", "staked": "Con staking", "received": "Recibido", "unstaked": "Sin staking", - "mark_all_as_read": "Mark all as read", + "mark_all_as_read": "Marcar todo como leído", "to": "Para", "rate": "Tasa (tarifa incluida)", "unstaking_requested": "Unstaking solicitado", @@ -2038,7 +2060,7 @@ "swap": "Canjeado", "sent": "Enviado a {{address}}", "menu_item_title": { - "metamask_swap_completed": "Swapped {{symbolIn}} for {{symbolOut}}", + "metamask_swap_completed": "Se canjeó {{symbolIn}} por {{symbolOut}}", "erc20_sent": "Enviado a {{address}}", "erc20_received": "Recibido desde {{address}}", "eth_sent": "Enviado a {{address}}", @@ -2065,7 +2087,7 @@ "title_untake_ready": "Retiro listo", "title_stake": "{{symbol}} en staking", "title_unstake_completed": "Unstaking finalizado", - "title_swapped": "Swapped {{symbolIn}} to {{symbolOut}}", + "title_swapped": "Se canjeó {{symbolIn}} a {{symbolOut}}", "label_address_to": "Para", "label_address_from": "De", "label_address_to_you": "Para (usted)", @@ -2097,6 +2119,8 @@ "success_withdrawal_title": "Retiro completo", "error_title": "Lo lamentamos, se produjo un error :/", "received_title": "Recibió {{amount}} {{assetType}}", + "default_message_title": "New transaction notification", + "default_message_description": "Tap to view this transaction", "received_payment_title": "Pago inmediato recibido", "pending_message": "En espera de confirmación", "pending_deposit_message": "En espera de que el depósito se complete", @@ -2112,12 +2136,12 @@ "cancelled_message": "Presionar para ver esta transacción", "received_message": "Presionar para ver esta transacción", "received_payment_message": "Recibió {{amount}} DAI", - "prompt_title": "Habilitar notificaciones push", + "prompt_title": "Receive Push Notifications", "notifications_enabled_error_title": "Algo salió mal", "notifications_enabled_error_desc": "No pudimos activar las notificaciones. Por favor, inténtelo de nuevo más tarde.", - "prompt_desc": "Habilite las notificaciones para que MetaMask pueda informarle cuando ha recibido ETH o cuando se han confirmado sus transacciones.", - "prompt_ok": "Sí", - "prompt_cancel": "No, gracias", + "prompt_desc": "Turn on notifications from Settings to get important alerts on wallet activity and more.", + "prompt_ok": "Turn on", + "prompt_cancel": "Maybe later", "wc_connected_title": "Conectado a {{title}}", "wc_signed_title": "Firmado", "wc_sent_tx_title": "Transacción enviada", @@ -2144,9 +2168,9 @@ "transaction_id_copied_to_clipboard": "ID de transacción copiado al portapapeles", "activation_card": { "title": "Activar las notificaciones", - "description_1": "Stay in the loop on what's happening in your wallet with notifications.", + "description_1": "Manténgase al tanto de lo que sucede en su monedero mediante notificaciones.", "description_2": "Para utilizar esta función, generaremos un ID anónimo para su cuenta. Se utiliza únicamente para sincronizar sus datos en MetaMask y no se vincula a sus actividades ni a otros identificadores, lo que garantiza su privacidad.", - "learn_more": "Learn how we protect your privacy while using this feature.", + "learn_more": "Descubra cómo protegemos su privacidad mientras utiliza esta función.", "manage_preferences_1": "Puede desactivar las notificaciones en cualquier momento ", "manage_preferences_2": "Configuración > Notificaciones.", "cancel": "Cancelar", @@ -2539,7 +2563,7 @@ "sale_completed_description": "¡Su orden fue exitosa!", "sale_pending_title": "Procesamiento de venta de {{currency}}", "sale_pending_description": "Su orden esta siendo procesada.", - "no_date": "Unknown" + "no_date": "Desconocido" } }, "swaps": { @@ -2798,14 +2822,9 @@ }, "switch_custom_network": { "title_existing_network": "Este sitio desea cambiar la red", - "title_enabled_network": "This site wants to:", "title_new_network": "Se agregó una red nueva", "switch_warning": "Esto cambiará la red seleccionada dentro de MetaMask a una red agregada previamente:", - "use_enabled_networks": "Use your enabled networks", - "wants_to_see_your_accounts": "See your accounts and suggest transactions", - "requesting_for": "Requesting for ", - "edit": "Edit", - "add_network_and_give_dapp_permission_warning": "You're adding this network to MetaMask and giving {{dapp_origin}} permission to use it.", + "add_network_and_give_dapp_permission_warning": "Está agregando esta red a MetaMask y otorgando permiso a {{dapp_origin}} para usarla.", "available": "ya está disponible en el selector de redes.", "cancel": "Cancelar", "switch": "Cambiar red" @@ -2896,7 +2915,7 @@ }, "transaction_review_eip1559": { "estimated_gas_fee": "Tarifa estimada de gas", - "suggested_gas_fee": "%{origin} de gas sugerida", + "network_fee": "Network fee", "max_fee": "Tarifa máxima", "total": "Total", "max_amount": "Cantidad máxima", @@ -3129,17 +3148,18 @@ "cannot_get_account": "No se puede obtener la cuenta", "connect_ledger": "Conectar Ledger", "looking_for_device": "Buscando dispositivo", - "ledger_reminder_message": "Please make sure your Ledger device is:", - "ledger_reminder_message_step_one": "1. Unlock your Ledger device", + "ledger_reminder_message": "Asegúrese de que su dispositivo Ledger esté:", + "ledger_reminder_message_step_one": "1. Desbloquee su dispositivo Ledger", "ledger_reminder_message_step_two": "2. Instale y abra la aplicación Ethereum", "ledger_reminder_message_step_three": "3. Active Bluetooth", "ledger_reminder_message_step_four": "4. La ubicación está habilitada con el uso de ubicación precisa en", "ledger_reminder_message_step_four_Androidv12plus": "4. Los dispositivos cercanos están habilitados", "ledger_reminder_message_step_five": "5. No molestar debe estar desactivado", + "blind_signing_message": "6. Enable \"blind signing\" on your Ledger device.", "available_devices": "Dispositivos disponibles", "retry": "Reintentar", "continue": "Continuar", - "confirm_transaction_on_ledger": "Confirm transaction on your Ledger", + "confirm_transaction_on_ledger": "Confirme la transacción en su Ledger", "bluetooth_enabled_message": "Asegúrese de que Bluetooth esté habilitado", "device_unlocked_message": "El dispositivo está desbloqueado", "ledger_disconnected": "Su dispositivo se desconectó", @@ -3162,11 +3182,19 @@ "not_supported": "Operación no admitida", "not_supported_error": "Solo se admite la versión 4 de la firma de datos escritos.", "error_during_connection": "Ocurrió un error desconocido", - "error_during_connection_message": "There's been a slight problem connecting your Ledger device, tap 'Retry' below to give this another go. Sometimes this occurs due to the ETH app on your Ledger device being open at the start of the pairing process with MetaMask Mobile.", + "error_during_connection_message": "Hubo un pequeño problema al conectar su dispositivo Ledger, pulse \"Reintentar\" a continuación para intentarlo nuevamente. A veces, esto ocurre debido a que la aplicación ETH de su dispositivo Ledger está abierta al comienzo del proceso de emparejamiento con MetaMask Mobile.", "how_to_install_eth_webview_title": "Cómo instalar la aplicación Ethereum", "nonce_too_low": "Nonce demasiado bajo", "nonce_too_low_error": "El nonce establecido es demasiado bajo", - "select_accounts": "Select an Account" + "select_accounts": "Select an account", + "select_hd_path": "Select HD Path", + "select_hd_path_description": "If you don't see the accounts you expect, try switching the HD path or current selected network.", + "ledger_live_path": "Ledger Live", + "ledger_legacy_path": "Legacy(MEW/MyCrypto)", + "ledger_bip44_path": "BIP44(e.g. MetaMask, Trezor)", + "ledger_legacy_label": " (legacy)", + "blind_sign_error": "Blind signing error", + "blind_sign_error_message": "Blind signing is not enabled on your Ledger device. Please enable it in the settings." }, "account_actions": { "edit_name": "Editar nombre de la cuenta", @@ -3220,7 +3248,21 @@ "error_description": "Instalación fallida de {{snap}}." }, "stake": { - "stake": "Staking" + "stake": "Staking", + "stake_eth": "Stake ETH", + "your_earnings": "Your earnings", + "annual_rate": "Annual rate", + "lifetime_rewards": "Lifetime rewards", + "estimated_annual_earnings": "Estimated annual earnings", + "accessibility_labels": { + "stake_annual_rate_tooltip": "Annual rate tooltip" + }, + "estimated_annual_rewards": "Estimated annual rewards", + "metamask_pool": "MetaMask Pool", + "enter_amount": "Enter amount", + "review": "Review", + "not_enough_eth": "Not enough ETH", + "balance": "Balance" }, "default_settings": { "title": "Su monedero está listo", @@ -3239,7 +3281,7 @@ "title_off": "Desactivar la funcionalidad básica", "description_off": "Esto significa que no optimizará completamente su tiempo en MetaMask. Las funciones básicas (como los detalles de los tokenes, la configuración óptima de gas y otras) no estarán disponibles para usted.", "title_on": "Activar la funcionalidad básica", - "description_on": "To optimize your time on MetaMask, you’ll need to turn on this feature. Basic functions (like token details, optimal gas settings, notifications, and others) are important to the web3 experience.", + "description_on": "Para optimizar su tiempo en MetaMask, deberá activar esta función. Las funciones básicas (como los detalles de los tokens, la configuración óptima de gas, las notificaciones y otras) son importantes para la experiencia Web3.", "checkbox_label": "Entiendo y deseo continuar", "buttons": { "cancel": "Cancelar", @@ -3269,6 +3311,14 @@ "title": "Detectamos varias solicitudes" }, "common": { - "please_wait": "Espere, por favor" + "please_wait": "Espere, por favor", + "disconnect_you_from": "This will disconnect you from {{dappUrl}}", + "disconnect": "Disconnect" + }, + "tooltip_modal": { + "reward_rate": { + "title": "Reward rate", + "tooltip": "Expected yearly increase in the value of your stake, based on the reward rate over the past week." + } } } diff --git a/locales/languages/fr.json b/locales/languages/fr.json index 37788b7cc9d..b6b2054adb5 100644 --- a/locales/languages/fr.json +++ b/locales/languages/fr.json @@ -685,10 +685,9 @@ "address_balance_activity_permission": "Consulter l’adresse, le solde et l’activité du compte", "suggest_transactions": "Proposer des transactions à approuver", "disconnect": "Déconnexion", - "disconnect_all": "Déconnecter tous les comptes", - "reconnect_notice": "Si vous déconnectez vos comptes de {{dappUrl}}, vous devrez vous reconnecter pour les utiliser à nouveau.", + "disconnect_all": "Tout déconnecter", + "reconnect_notice": "If you disconnect from {{dappUrl}}, you’ll need to reconnect your accounts and networks to use this site again.", "disconnect_all_accounts": "Déconnecter tous les comptes", - "disconnect_you_from": "Cela vous déconnectera de {{dappUrl}}", "deceptive_site_ahead": "Site trompeur à venir", "deceptive_site_desc": "Le site que vous essayez de visiter n’est pas sécurisé. Les pirates peuvent vous inciter à agir de façon risquée.", "learn_more": "En savoir plus", @@ -1066,7 +1065,12 @@ "simulation_details_description": "Activez cette option pour estimer les changements de solde des transactions avant de les confirmer. Cela ne garantit pas le résultat final de vos transactions. ", "simulation_details_learn_more": "En savoir plus.", "aes_crypto_test_form_title": "AES Crypto - Formulaire de test", - "aes_crypto_test_form_description": "Section exclusivement développée pour les tests de bout en bout. Si vous la voyez dans votre application, veuillez le signaler au service d’assistance de MetaMask." + "aes_crypto_test_form_description": "Section exclusivement développée pour les tests de bout en bout. Si vous la voyez dans votre application, veuillez le signaler au service d’assistance de MetaMask.", + "developer_options": { + "title": "Developer Options", + "generate_trace_test": "Generate Trace Test", + "generate_trace_test_desc": "Generate a Developer Test Sentry trace." + } }, "aes_crypto_test_form": { "generate_random_salt": "Générer du sel aléatoire", @@ -1306,7 +1310,7 @@ "error": "Erreur", "attempting_to_scan_with_wallet_locked": "Il semble que vous essayez de scanner un code QR — vous devez déverrouiller votre portefeuille pour pouvoir l’utiliser.", "attempting_sync_from_wallet_error": "Il semble que vous essayez de vous synchroniser avec l’extension. Pour ce faire, vous devrez effacer votre portefeuille actuel. \n\nAprès avoir effacé ou réinstallé une nouvelle version de l’appli, sélectionnez l’option « Synchroniser avec l’extension MetaMask ». Attention ! Avant d’effacer votre portefeuille, effectuez une copie de votre phrase secrète de récupération.", - "not_allowed_error_title": "Accorder l’autorisation d’accéder à l’appareil photo", + "not_allowed_error_title": "Activer l’accès à l’appareil photo", "not_allowed_error_desc": "Pour scanner un code QR, vous devez autoriser MetaMask à accéder à l’appareil photo depuis le menu des paramètres de votre appareil.", "unrecognized_address_qr_code_title": "Code QR non reconnu", "unrecognized_address_qr_code_desc": "Désolé, ce code QR n’est pas associé à une adresse de compte ou à une adresse de contrat.", @@ -1444,7 +1448,8 @@ "hex_data_copied": "Données hexadécimales copiées dans le presse-papiers", "invalid_recipient": "Destinataire non valide", "invalid_recipient_description": "Vérifiez l’adresse et assurez-vous qu’elle est valide", - "swap_tokens": "Échanger des jetons" + "swap_tokens": "Échanger des jetons", + "fromWithColon": "From:" }, "custom_gas": { "total": "Total", @@ -1731,6 +1736,7 @@ "continue": "Continuer", "cancel": "Annuler", "approve": "Approuver", + "update": "Update", "edit_network_details": "Modifier les détails du réseau", "malicious_network_warning": "Un fournisseur de réseau malveillant peut mentir quant à l’état de la blockchain et enregistrer votre activité sur le réseau. N’ajoutez que des réseaux personnalisés auxquels vous faites confiance.", "security_link": "https://support.metamask.io/networks-and-sidechains/managing-networks/user-guide-custom-networks-and-sidechains/", @@ -1738,10 +1744,12 @@ "additional_network_information_title": "Informations supplémentaires sur les réseaux", "network_warning_desc": "Cette connexion réseau est assurée par des tiers. Elle peut être moins fiable ou permettre à des tiers de suivre l’activité des utilisateurs.", "additonial_network_information_desc": "Certains de ces réseaux dépendent de services tiers. Ils peuvent être moins fiables ou permettre à des tiers de suivre l’activité des utilisateurs.", + "connect_more_networks": "Connect more networks", "learn_more": "En savoir plus", "learn_more_url": "https://support.metamask.io/networks-and-sidechains/managing-networks/the-risks-of-connecting-to-an-unknown-network/", "switch_network": "Changer de réseau", "switch": "Changer", + "select_all": "Select all", "new_network": "Nouveau réseau ajouté", "network_name": "Réseau {{networkName}}", "network_added": " est maintenant disponible dans le sélecteur de réseau.", @@ -1758,7 +1766,19 @@ "show_test_networks": "Afficher les réseaux de test", "deprecated_goerli": "En raison des changements apportés au protocole d’Ethereum, le réseau de test Goerli pourra ne pas fonctionner d’une manière aussi fiable qu’auparavant et deviendra bientôt obsolète.", "network_deprecated_title": "Ce réseau est obsolète", - "network_deprecated_description": "Le réseau auquel vous essayez de vous connecter n’est plus pris en charge par Metamask." + "network_deprecated_description": "Le réseau auquel vous essayez de vous connecter n’est plus pris en charge par Metamask.", + "edit_networks_title": "Edit networks" + }, + "permissions": { + "title_this_site_wants_to": "This site wants to:", + "title_dapp_url_wants_to": "{{dappUrl}} wants to:", + "title_dapp_url_has_approval_to": "{{dappUrl}} has approval to:", + "use_enabled_networks": "Use your enabled networks", + "wants_to_see_your_accounts": "See your accounts and suggest transactions", + "requesting_for": "Requesting for ", + "manage_permissions": "Manage Permissions", + "edit": "Edit", + "cancel": "Cancel" }, "select": { "cancel": "Annuler", @@ -2022,6 +2042,8 @@ "back_to_safety": "Retour à la sécurité" }, "notifications": { + "no_date": "Unknown", + "yesterday": "Yesterday", "staked": "Staké", "received": "Reçu", "unstaked": "Déstaké", @@ -2097,6 +2119,8 @@ "success_withdrawal_title": "Retrait effectué !", "error_title": "Oups ! Nous avons rencontré un problème :/", "received_title": "Vous avez reçu {{amount}} {{assetType}}", + "default_message_title": "New transaction notification", + "default_message_description": "Tap to view this transaction", "received_payment_title": "Paiement instantané reçu", "pending_message": "En attente de confirmation", "pending_deposit_message": "En attente de réalisation du dépôt", @@ -2112,12 +2136,12 @@ "cancelled_message": "Appuyez pour voir cette transaction", "received_message": "Appuyez pour voir cette transaction", "received_payment_message": "Vous avez reçu {{amount}} DAI", - "prompt_title": "Activer les notifications Push", + "prompt_title": "Receive Push Notifications", "notifications_enabled_error_title": "Quelque chose a mal tourné", "notifications_enabled_error_desc": "Nous n’avons pas pu activer les notifications. Veuillez réessayer plus tard.", - "prompt_desc": "Activez les notifications afin que MetaMask puisse vous informer lorsque vous recevez des ETH ou lorsque vos transactions sont confirmées.", - "prompt_ok": "Oui", - "prompt_cancel": "Non, merci.", + "prompt_desc": "Turn on notifications from Settings to get important alerts on wallet activity and more.", + "prompt_ok": "Turn on", + "prompt_cancel": "Maybe later", "wc_connected_title": "Connecté à {{title}}", "wc_signed_title": "Signé", "wc_sent_tx_title": "Transaction envoyée", @@ -2798,13 +2822,8 @@ }, "switch_custom_network": { "title_existing_network": "Ce site souhaite changer de réseau", - "title_enabled_network": "Ce site veut :", "title_new_network": "Nouveau réseau ajouté", "switch_warning": "Ceci permet de remplacer le réseau sélectionné dans MetaMask par un réseau précédemment ajouté :", - "use_enabled_networks": "Utilisez les réseaux que vous avez activés", - "wants_to_see_your_accounts": "Consultez vos comptes et suggérez des transactions", - "requesting_for": "Demande pour ", - "edit": "Modifier", "add_network_and_give_dapp_permission_warning": "Vous allez ajouter ce réseau à MetaMask et donner à {{dapp_origin}} l’autorisation de l’utiliser.", "available": "est désormais disponible dans le sélecteur de réseau.", "cancel": "Annuler", @@ -2896,7 +2915,7 @@ }, "transaction_review_eip1559": { "estimated_gas_fee": "Frais de gaz estimés", - "suggested_gas_fee": "Suggestion de frais de gaz %{origin}", + "network_fee": "Network fee", "max_fee": "Frais maximums", "total": "Total", "max_amount": "Montant maximal", @@ -3136,6 +3155,7 @@ "ledger_reminder_message_step_four": "4. La localisation est activée avec l’autorisation d’utiliser la localisation précise", "ledger_reminder_message_step_four_Androidv12plus": "4. L’option « Appareils à proximité » est activée", "ledger_reminder_message_step_five": "5. L’option « Ne pas déranger » doit être désactivée", + "blind_signing_message": "6. Enable \"blind signing\" on your Ledger device.", "available_devices": "Appareils disponibles", "retry": "Réessayer", "continue": "Continuer", @@ -3166,7 +3186,15 @@ "how_to_install_eth_webview_title": "Comment installer l’application Ethereum", "nonce_too_low": "Nonce trop court", "nonce_too_low_error": "Le nonce défini est trop court", - "select_accounts": "Sélectionner un compte" + "select_accounts": "Select an account", + "select_hd_path": "Select HD Path", + "select_hd_path_description": "If you don't see the accounts you expect, try switching the HD path or current selected network.", + "ledger_live_path": "Ledger Live", + "ledger_legacy_path": "Legacy(MEW/MyCrypto)", + "ledger_bip44_path": "BIP44(e.g. MetaMask, Trezor)", + "ledger_legacy_label": " (legacy)", + "blind_sign_error": "Blind signing error", + "blind_sign_error_message": "Blind signing is not enabled on your Ledger device. Please enable it in the settings." }, "account_actions": { "edit_name": "Modifier le nom du compte", @@ -3220,7 +3248,21 @@ "error_description": "L’installation de {{snap}} a échoué." }, "stake": { - "stake": "Staker" + "stake": "Staker", + "stake_eth": "Stake ETH", + "your_earnings": "Your earnings", + "annual_rate": "Annual rate", + "lifetime_rewards": "Lifetime rewards", + "estimated_annual_earnings": "Estimated annual earnings", + "accessibility_labels": { + "stake_annual_rate_tooltip": "Annual rate tooltip" + }, + "estimated_annual_rewards": "Estimated annual rewards", + "metamask_pool": "MetaMask Pool", + "enter_amount": "Enter amount", + "review": "Review", + "not_enough_eth": "Not enough ETH", + "balance": "Balance" }, "default_settings": { "title": "Votre portefeuille est prêt", @@ -3269,6 +3311,14 @@ "title": "Nous avons constaté un grand nombre de demandes" }, "common": { - "please_wait": "Veuillez patienter" + "please_wait": "Veuillez patienter", + "disconnect_you_from": "This will disconnect you from {{dappUrl}}", + "disconnect": "Disconnect" + }, + "tooltip_modal": { + "reward_rate": { + "title": "Reward rate", + "tooltip": "Expected yearly increase in the value of your stake, based on the reward rate over the past week." + } } } diff --git a/locales/languages/hi.json b/locales/languages/hi.json index 8621e612095..3b3e32ca443 100644 --- a/locales/languages/hi.json +++ b/locales/languages/hi.json @@ -686,9 +686,8 @@ "suggest_transactions": "स्वीकृत करने के लिए लेनदेन का सुझाव देना", "disconnect": "डिसकनेक्ट करें", "disconnect_all": "सभी को डिस्कनेक्ट करें", - "reconnect_notice": "यदि आप अपने एकाउंट्स को {{dappUrl}} से डिस्कनेक्ट कर देते हैं, तो आपको उन्हें दोबारा उपयोग करने के लिए फिर से कनेक्ट करना होगा।", + "reconnect_notice": "If you disconnect from {{dappUrl}}, you’ll need to reconnect your accounts and networks to use this site again.", "disconnect_all_accounts": "सभी खाते डिसकनेक्ट करें", - "disconnect_you_from": "यह आपको {{dappUrl}} से डिस्कनेक्ट कर देगा", "deceptive_site_ahead": "आगे धोखाधड़ी करने वाली साइट मौजूद है", "deceptive_site_desc": "आप जिस साइट पर जाना चाह रहे हैं वह सुरक्षित नहीं है। हमला करने वाले आपको कुछ खतरनाक काम करने के लिए उकसा सकते हैं।", "learn_more": "ज़्यादा जानें", @@ -1066,7 +1065,12 @@ "simulation_details_description": "ट्रांसेक्शन को कन्फर्म करने से पहले बैलेंस अमाउंट में बदलाव का अनुमान लगाने के लिए इसे चालू करें। यह आपके ट्रांसेक्शन के फाइनल आउटकम की गारंटी नहीं देता है। ", "simulation_details_learn_more": "अधिक जानें।", "aes_crypto_test_form_title": "एईएस क्रिप्टो - टेस्ट फॉर्म", - "aes_crypto_test_form_description": "अनुभाग विशेष रूप से E2E परीक्षण के लिए विकसित किया गया है। यदि यह आपके ऐप में दिखाई दे रहा है, तो कृपया MetaMask सपोर्ट को इसकी रिपोर्ट करें।" + "aes_crypto_test_form_description": "अनुभाग विशेष रूप से E2E परीक्षण के लिए विकसित किया गया है। यदि यह आपके ऐप में दिखाई दे रहा है, तो कृपया MetaMask सपोर्ट को इसकी रिपोर्ट करें।", + "developer_options": { + "title": "Developer Options", + "generate_trace_test": "Generate Trace Test", + "generate_trace_test_desc": "Generate a Developer Test Sentry trace." + } }, "aes_crypto_test_form": { "generate_random_salt": "रैंडम सॉल्ट उत्पन्न करें", @@ -1444,7 +1448,8 @@ "hex_data_copied": "Hex डेटा क्लिपबोर्ड पर कॉपी किया गया", "invalid_recipient": "अवैध प्राप्तकर्ता", "invalid_recipient_description": "पते की जांच करें और सुनिश्चित करें कि वह मान्य है", - "swap_tokens": "टोकन स्वैप करें" + "swap_tokens": "टोकन स्वैप करें", + "fromWithColon": "From:" }, "custom_gas": { "total": "कुल", @@ -1731,6 +1736,7 @@ "continue": "जारी रखें", "cancel": "रद्द करें", "approve": "स्वीकृति दें", + "update": "Update", "edit_network_details": "नेटवर्क का ब्यौरा बदलें", "malicious_network_warning": "एक दुर्भावनापूर्ण नेटवर्क प्रदाता ब्लॉकचेन की स्थिति के बारे में झूठ बोल सकता है और आपकी नेटवर्क गतिविधि को रिकॉर्ड कर सकता है। केवल ऐसे कस्टम नेटवर्क जोड़ें जिन पर आपको भरोसा हो।", "security_link": "https://support.metamask.io/networks-and-sidechains/managing-networks/user-guide-custom-networks-and-sidechains/", @@ -1738,10 +1744,12 @@ "additional_network_information_title": "अतिरिक्त नेटवर्क सूचना", "network_warning_desc": "यह नेटवर्क कनेक्शन थर्ड पार्टी पर निर्भर करता है। यह कनेक्शन कम विश्वसनीय हो सकता है या गतिविधि को ट्रैक करने के लिए तृतीय-पक्ष को सक्षम कर सकता है।", "additonial_network_information_desc": "इनमें से कुछ नेटवर्क थर्ड पार्टीज़ पर निर्भर हैं। कनेक्शन कम विश्वसनीय हो सकते हैं या गतिविधि को ट्रैक करने के लिए थर्ड पार्टीज़ को चालू कर सकते हैं।", + "connect_more_networks": "Connect more networks", "learn_more": "अधिक जानें", "learn_more_url": "https://support.metamask.io/networks-and-sidechains/managing-networks/the-risks-of-connecting-to-an-unknown-network/", "switch_network": "नेटवर्क पर स्विच करें", "switch": "बदलें", + "select_all": "Select all", "new_network": "नया नेटवर्क जोड़ा गया", "network_name": "{{networkName}} नेटवर्क", "network_added": "नेटवर्क चयनकर्ता में अब उपलब्ध है।", @@ -1758,7 +1766,19 @@ "show_test_networks": "परीक्षण नेटवर्क दिखाएं", "deprecated_goerli": "Ethereum के प्रोटोकॉल परिवर्तनों के कारण: Goerli परीक्षण नेटवर्क विश्वसनीय रूप से काम नहीं कर सकता है और जल्द ही बंद हो जाएगा।", "network_deprecated_title": "इस नेटवर्क को हटा दिया गया है", - "network_deprecated_description": "जिस नेटवर्क से आप कनेक्ट करने का प्रयास कर रहे हैं उसे अब MetaMask सपोर्ट नहीं करता।" + "network_deprecated_description": "जिस नेटवर्क से आप कनेक्ट करने का प्रयास कर रहे हैं उसे अब MetaMask सपोर्ट नहीं करता।", + "edit_networks_title": "Edit networks" + }, + "permissions": { + "title_this_site_wants_to": "This site wants to:", + "title_dapp_url_wants_to": "{{dappUrl}} wants to:", + "title_dapp_url_has_approval_to": "{{dappUrl}} has approval to:", + "use_enabled_networks": "Use your enabled networks", + "wants_to_see_your_accounts": "See your accounts and suggest transactions", + "requesting_for": "Requesting for ", + "manage_permissions": "Manage Permissions", + "edit": "Edit", + "cancel": "Cancel" }, "select": { "cancel": "रद्द करें", @@ -2022,6 +2042,8 @@ "back_to_safety": "सुरक्षा पर वापस जाएं" }, "notifications": { + "no_date": "Unknown", + "yesterday": "Yesterday", "staked": "स्टेक किया गया", "received": "प्राप्त किया गया", "unstaked": "अनस्टेक किया गया", @@ -2097,6 +2119,8 @@ "success_withdrawal_title": "निकासी पूरा!", "error_title": "ओह, कुछ गलत हुआ :/", "received_title": "आपने {{amount}} {{assetType}}प्राप्त किया", + "default_message_title": "New transaction notification", + "default_message_description": "Tap to view this transaction", "received_payment_title": "तात्कालिक भुगतान प्राप्त किया", "pending_message": "पुष्टिकरण का इंतजार करना", "pending_deposit_message": "जमा पूरा होने का इंतजार करना", @@ -2112,12 +2136,12 @@ "cancelled_message": "इस लेन-देन को देखने के लिए टैप करें", "received_message": "इस लेन-देन को देखने के लिए टैप करें", "received_payment_message": "आपने {{amount}} DAI प्राप्त किया", - "prompt_title": "पुश नोटिफिकेशन को सक्षम करें", + "prompt_title": "Receive Push Notifications", "notifications_enabled_error_title": "कुछ गलत हो गया", "notifications_enabled_error_desc": "हम नोटिफिकेशंस चालू नहीं कर सके। कृपया बाद में फिर से प्रयास करें।", - "prompt_desc": "नोटिफिकेशन को सक्षम करें जिससे MetaMask आपको बता सके कि आपने कब ETH प्राप्त किया है या आपके लेन-देन की पुष्टि कब हुई है।", - "prompt_ok": "हां", - "prompt_cancel": "नहीं, धन्यवाद", + "prompt_desc": "Turn on notifications from Settings to get important alerts on wallet activity and more.", + "prompt_ok": "Turn on", + "prompt_cancel": "Maybe later", "wc_connected_title": "{{title}} से जुड़ा हुआ", "wc_signed_title": "हस्ताक्षर किया हुआ", "wc_sent_tx_title": "लेन-देन भेजा गया", @@ -2798,13 +2822,8 @@ }, "switch_custom_network": { "title_existing_network": "यह साइट नेटवर्क बदलना चाहेगा", - "title_enabled_network": "यह साइट निम्नलिखित करना चाहती है:", "title_new_network": "नया नेटवर्क जोड़ा गया", "switch_warning": "इससे चुना गया नेटवर्क को MetaMask के भीतर पहले से जोड़े गए नेटवर्क में बदल दिया जाएगा:", - "use_enabled_networks": "आपके चालू नेटवर्क का उपयोग करना", - "wants_to_see_your_accounts": "आपके अकाउंट को देखकर ट्रांसेक्शन का सुझाव देना", - "requesting_for": "के लिए अनुरोध कर रहे हैं ", - "edit": "बदलें", "add_network_and_give_dapp_permission_warning": "आप इस नेटवर्क को MetaMask में जोड़ रहे हैं और {{dapp_origin}} को इसका उपयोग करने की अनुमति दे रहे हैं।", "available": "नेटवर्क चयनकर्ता में अब उपलब्ध है।", "cancel": "रद्द करें", @@ -2896,7 +2915,7 @@ }, "transaction_review_eip1559": { "estimated_gas_fee": "अनुमानित गैस शुल्क", - "suggested_gas_fee": "%{origin} अनुमानित गैस शुल्क", + "network_fee": "Network fee", "max_fee": "अधिकतम शुल्क", "total": "कुल", "max_amount": "अधिकतम रकम", @@ -3136,6 +3155,7 @@ "ledger_reminder_message_step_four": "4. सटीक लोकेशन के उपयोग के साथ लोकेशन चालू कर दी गई है", "ledger_reminder_message_step_four_Androidv12plus": "4. नज़दीकी डिवाइसें चालू कर दी गई हैं", "ledger_reminder_message_step_five": "5. 'परेशान न करें' फ़ीचर को बंद कर देना चाहिए", + "blind_signing_message": "6. Enable \"blind signing\" on your Ledger device.", "available_devices": "उपलब्ध डिवाइस", "retry": "फिर से कोशिश करें", "continue": "जारी रखें", @@ -3166,7 +3186,15 @@ "how_to_install_eth_webview_title": "Ethereum ऐप कैसे इंस्टॉल करें", "nonce_too_low": "Nonce बहुत कम है", "nonce_too_low_error": "सेट किया हुआ nonce बहुत कम है", - "select_accounts": "किसी अकाउंट को चुनें" + "select_accounts": "Select an account", + "select_hd_path": "Select HD Path", + "select_hd_path_description": "If you don't see the accounts you expect, try switching the HD path or current selected network.", + "ledger_live_path": "Ledger Live", + "ledger_legacy_path": "Legacy(MEW/MyCrypto)", + "ledger_bip44_path": "BIP44(e.g. MetaMask, Trezor)", + "ledger_legacy_label": " (legacy)", + "blind_sign_error": "Blind signing error", + "blind_sign_error_message": "Blind signing is not enabled on your Ledger device. Please enable it in the settings." }, "account_actions": { "edit_name": "खाते का नाम संपादित करें", @@ -3220,7 +3248,21 @@ "error_description": "{{snap}} का इंस्टॉलेशन नहीं हो पाया।" }, "stake": { - "stake": "हिस्सेदारी" + "stake": "हिस्सेदारी", + "stake_eth": "Stake ETH", + "your_earnings": "Your earnings", + "annual_rate": "Annual rate", + "lifetime_rewards": "Lifetime rewards", + "estimated_annual_earnings": "Estimated annual earnings", + "accessibility_labels": { + "stake_annual_rate_tooltip": "Annual rate tooltip" + }, + "estimated_annual_rewards": "Estimated annual rewards", + "metamask_pool": "MetaMask Pool", + "enter_amount": "Enter amount", + "review": "Review", + "not_enough_eth": "Not enough ETH", + "balance": "Balance" }, "default_settings": { "title": "आपका वॉलेट तैयार है", @@ -3269,6 +3311,14 @@ "title": "हमने कई अनुरोध देखे हैं" }, "common": { - "please_wait": "कृपया प्रतीक्षा करें" + "please_wait": "कृपया प्रतीक्षा करें", + "disconnect_you_from": "This will disconnect you from {{dappUrl}}", + "disconnect": "Disconnect" + }, + "tooltip_modal": { + "reward_rate": { + "title": "Reward rate", + "tooltip": "Expected yearly increase in the value of your stake, based on the reward rate over the past week." + } } } diff --git a/locales/languages/id.json b/locales/languages/id.json index e60ca3d94c5..e703d2fb65a 100644 --- a/locales/languages/id.json +++ b/locales/languages/id.json @@ -686,9 +686,8 @@ "suggest_transactions": "Merekomendasikan transaksi untuk disetujui", "disconnect": "Memutus koneksi", "disconnect_all": "Putuskan semua koneksi", - "reconnect_notice": "Jika Anda memutus koneksi akun dari {{dappUrl}}, Anda harus menghubungkannya kembali agar dapat menggunakannya lagi.", + "reconnect_notice": "If you disconnect from {{dappUrl}}, you’ll need to reconnect your accounts and networks to use this site again.", "disconnect_all_accounts": "Memutus koneksi semua akun", - "disconnect_you_from": "Ini akan memutus koneksi dari {{dappUrl}}", "deceptive_site_ahead": "Situs yang menipu terdeteksi", "deceptive_site_desc": "Situs yang akan Anda kunjungi tidak aman. Penyerang dapat mengelabui Anda untuk melakukan sesuatu yang berbahaya.", "learn_more": "Selengkapnya", @@ -1066,7 +1065,12 @@ "simulation_details_description": "Aktifkan ini untuk mengestimasikan perubahan saldo transaksi sebelum Anda mengonfirmasikannya. Ini tidak menjamin hasil akhir transaksi Anda. ", "simulation_details_learn_more": "Selengkapnya.", "aes_crypto_test_form_title": "Kripto AES - Formulir Uji", - "aes_crypto_test_form_description": "Bagian khusus dikembangkan untuk pengujian E2E. Apabila terlihat di aplikasi, laporkan ke dukungan MetaMask." + "aes_crypto_test_form_description": "Bagian khusus dikembangkan untuk pengujian E2E. Apabila terlihat di aplikasi, laporkan ke dukungan MetaMask.", + "developer_options": { + "title": "Developer Options", + "generate_trace_test": "Generate Trace Test", + "generate_trace_test_desc": "Generate a Developer Test Sentry trace." + } }, "aes_crypto_test_form": { "generate_random_salt": "Buat Salt Acak", @@ -1444,7 +1448,8 @@ "hex_data_copied": "Data hex disalin ke papan klip", "invalid_recipient": "Penerima tidak valid", "invalid_recipient_description": "Periksa alamatnya dan pastikan sudah valid", - "swap_tokens": "Tukar token" + "swap_tokens": "Tukar token", + "fromWithColon": "From:" }, "custom_gas": { "total": "Total", @@ -1731,6 +1736,7 @@ "continue": "Lanjutkan", "cancel": "Batal", "approve": "Setujui", + "update": "Update", "edit_network_details": "Edit detail jaringan", "malicious_network_warning": "Penyedia jaringan jahat dapat berbohong tentang status blockchain dan merekam aktivitas jaringan Anda. Hanya tambahkan jaringan kustom yang Anda percayai.", "security_link": "https://support.metamask.io/networks-and-sidechains/managing-networks/user-guide-custom-networks-and-sidechains/", @@ -1738,10 +1744,12 @@ "additional_network_information_title": "Informasi Jaringan Tambahan", "network_warning_desc": "Koneksi jaringan ini mengandalkan pihak ketiga. Koneksi ini mungkin kurang bisa diandalkan atau memungkinkan pihak ketiga melacak aktivitas.", "additonial_network_information_desc": "Beberapa jaringan ini mengandalkan pihak ketiga. Koneksi ini kurang dapat diandalkan atau memungkinkan pihak ketiga melacak aktivitas.", + "connect_more_networks": "Connect more networks", "learn_more": "Pelajari selengkapnya", "learn_more_url": "https://support.metamask.io/networks-and-sidechains/managing-networks/the-risks-of-connecting-to-an-unknown-network/", "switch_network": "Beralih ke jaringan", "switch": "Alihkan", + "select_all": "Select all", "new_network": "Jaringan baru ditambahkan", "network_name": "Jaringan {{networkName}}", "network_added": " kini tersedia di pemilih jaringan.", @@ -1758,7 +1766,19 @@ "show_test_networks": "Tampilkan jaringan pengujian", "deprecated_goerli": "Sehubungan dengan perubahan protokol Ethereum: Jaringan uji Goerli mungkin tidak dapat beroperasi dengan baik dan akan segera dihentikan.", "network_deprecated_title": "Jaringan ini tidak digunakan lagi", - "network_deprecated_description": "Jaringan yang Anda coba hubungkan tidak lagi didukung di Metamask." + "network_deprecated_description": "Jaringan yang Anda coba hubungkan tidak lagi didukung di Metamask.", + "edit_networks_title": "Edit networks" + }, + "permissions": { + "title_this_site_wants_to": "This site wants to:", + "title_dapp_url_wants_to": "{{dappUrl}} wants to:", + "title_dapp_url_has_approval_to": "{{dappUrl}} has approval to:", + "use_enabled_networks": "Use your enabled networks", + "wants_to_see_your_accounts": "See your accounts and suggest transactions", + "requesting_for": "Requesting for ", + "manage_permissions": "Manage Permissions", + "edit": "Edit", + "cancel": "Cancel" }, "select": { "cancel": "Batal", @@ -2022,6 +2042,8 @@ "back_to_safety": "Kembali ke keamanan" }, "notifications": { + "no_date": "Unknown", + "yesterday": "Yesterday", "staked": "Di-stake", "received": "Diterima", "unstaked": "Stake dibatalkan", @@ -2097,6 +2119,8 @@ "success_withdrawal_title": "Penarikan Selesai!", "error_title": "Ups! Terjadi kesalahan :/", "received_title": "Anda menerima {{amount}} {{assetType}}", + "default_message_title": "New transaction notification", + "default_message_description": "Tap to view this transaction", "received_payment_title": "Pembayaran instan diterima", "pending_message": "Menunggu konfirmasi", "pending_deposit_message": "Menunggu deposit selesai", @@ -2112,12 +2136,12 @@ "cancelled_message": "Ketuk untuk melihat transaksi ini", "received_message": "Ketuk untuk melihat transaksi ini", "received_payment_message": "Anda menerima {{amount}} DAI", - "prompt_title": "Aktifkan Pemberitahuan Push", + "prompt_title": "Receive Push Notifications", "notifications_enabled_error_title": "Terjadi kesalahan", "notifications_enabled_error_desc": "Kami tidak dapat mengaktifkan notifikasi. Coba lagi nanti.", - "prompt_desc": "Aktifkan pemberitahuan agar MetaMask dapat memberitahukan saat Anda menerima ETH atau saat transaksi Anda telah dikonfirmasi.", - "prompt_ok": "Ya", - "prompt_cancel": "Tidak, terima kasih", + "prompt_desc": "Turn on notifications from Settings to get important alerts on wallet activity and more.", + "prompt_ok": "Turn on", + "prompt_cancel": "Maybe later", "wc_connected_title": "Terhubung dengan {{title}}", "wc_signed_title": "Ditandatangani", "wc_sent_tx_title": "Mengirim transaksi", @@ -2798,13 +2822,8 @@ }, "switch_custom_network": { "title_existing_network": "Situs ini ingin beralih jaringan", - "title_enabled_network": "Situs ini ingin:", "title_new_network": "Jaringan baru ditambahkan", "switch_warning": "Ini akan mengalihkan jaringan yang dipilih dalam MetaMask ke jaringan yang ditambahkan sebelumnya:", - "use_enabled_networks": "Menggunakan jaringan aktif", - "wants_to_see_your_accounts": "Melihat akun Anda dan menyarangkan transaksi", - "requesting_for": "Meminta untuk ", - "edit": "Edit", "add_network_and_give_dapp_permission_warning": "Anda menambahkan jaringan ini ke MetaMask dan memberikan {{dapp_origin}} izin untuk menggunakannya.", "available": "kini tersedia di pemilih jaringan.", "cancel": "Batal", @@ -2896,7 +2915,7 @@ }, "transaction_review_eip1559": { "estimated_gas_fee": "Estimasi biaya gas", - "suggested_gas_fee": "%{origin} biaya gas yang disarankan", + "network_fee": "Network fee", "max_fee": "Biaya maks", "total": "Total", "max_amount": "Jumlah maks", @@ -3136,6 +3155,7 @@ "ledger_reminder_message_step_four": "4. Lokasi diaktifkan dengan menggunakan lokasi yang tepat", "ledger_reminder_message_step_four_Androidv12plus": "4. Perangkat terdekat diaktifkan", "ledger_reminder_message_step_five": "5. Jangan ganggu harus dinonaktifkan", + "blind_signing_message": "6. Enable \"blind signing\" on your Ledger device.", "available_devices": "Perangkat yang tersedia", "retry": "Coba lagi", "continue": "Lanjutkan", @@ -3166,7 +3186,15 @@ "how_to_install_eth_webview_title": "Cara menginstal Aplikasi Ethereum", "nonce_too_low": "Nonce terlalu rendah", "nonce_too_low_error": "Nonce yang ditetapkan terlalu rendah", - "select_accounts": "Pilih Akun" + "select_accounts": "Select an account", + "select_hd_path": "Select HD Path", + "select_hd_path_description": "If you don't see the accounts you expect, try switching the HD path or current selected network.", + "ledger_live_path": "Ledger Live", + "ledger_legacy_path": "Legacy(MEW/MyCrypto)", + "ledger_bip44_path": "BIP44(e.g. MetaMask, Trezor)", + "ledger_legacy_label": " (legacy)", + "blind_sign_error": "Blind signing error", + "blind_sign_error_message": "Blind signing is not enabled on your Ledger device. Please enable it in the settings." }, "account_actions": { "edit_name": "Edit nama akun", @@ -3220,7 +3248,21 @@ "error_description": "Instalasi {{snap}} gagal." }, "stake": { - "stake": "Stake" + "stake": "Stake", + "stake_eth": "Stake ETH", + "your_earnings": "Your earnings", + "annual_rate": "Annual rate", + "lifetime_rewards": "Lifetime rewards", + "estimated_annual_earnings": "Estimated annual earnings", + "accessibility_labels": { + "stake_annual_rate_tooltip": "Annual rate tooltip" + }, + "estimated_annual_rewards": "Estimated annual rewards", + "metamask_pool": "MetaMask Pool", + "enter_amount": "Enter amount", + "review": "Review", + "not_enough_eth": "Not enough ETH", + "balance": "Balance" }, "default_settings": { "title": "Dompet Anda sudah siap", @@ -3269,6 +3311,14 @@ "title": "Kami telah melihat banyak permintaan" }, "common": { - "please_wait": "Mohon tunggu" + "please_wait": "Mohon tunggu", + "disconnect_you_from": "This will disconnect you from {{dappUrl}}", + "disconnect": "Disconnect" + }, + "tooltip_modal": { + "reward_rate": { + "title": "Reward rate", + "tooltip": "Expected yearly increase in the value of your stake, based on the reward rate over the past week." + } } } diff --git a/locales/languages/ja.json b/locales/languages/ja.json index 059abc97242..f883b412779 100644 --- a/locales/languages/ja.json +++ b/locales/languages/ja.json @@ -449,10 +449,10 @@ "use_the_currency_symbol": "通貨シンボルを使用", "use_correct_symbol": "続ける前に、正しいシンボルを使用していることを確認してください", "chain_id_currently_used": "このチェーンIDは現在次のネットワークで使用されています:", - "incorrect_network_name_warning": "According to our records, the network name may not correctly match this chain ID.", - "suggested_name": "Suggested name:", + "incorrect_network_name_warning": "当社の記録によると、ネットワーク名がこのチェーンIDと正しく一致していない可能性があります。", + "suggested_name": "提案された名前:", "network_check_validation_desc": "悪質なネットワークや正しくないネットワークに接続してしまう可能性が減ります。", - "cant_verify_custom_network_warning": "We can’t verify custom networks. To avoid malicious providers from recording your network activity, only add networks you trust.", + "cant_verify_custom_network_warning": "カスタムネットワークを検証できません。悪質なプロバイダーによりネットワークアクティビティが記録されるのを避けるため、信頼できるネットワークのみを追加してください。", "nfts_autodetection_cta": "設定でNFTの検出をオンにしてください", "learn_more": "詳細", "add_collectibles": "NFTをインポート", @@ -501,10 +501,10 @@ "lists": "トークンリスト", "hide_cta": "トークンを非表示", "options": { - "view_on_portfolio": "View on Portfolio", + "view_on_portfolio": "Portfolioで表示", "view_on_block": "ブロックエクスプローラーで表示", "token_details": "トークンの詳細", - "remove_token": "Remove token" + "remove_token": "トークンを削除" } }, "activity_view": { @@ -551,7 +551,7 @@ "description_title": "MetaMaskの改善にご協力ください", "description_content_1": "MetaMaskの改善を目的に、基本的な使用状況データを収集したいと思います。ここで提供されるデータが販売されることはありません。", "description_content_2": "指標を収集する際、常に次の条件が適用されます...", - "description_content_3": "Learn how we protect your privacy while collecting usage data for your profile.", + "description_content_3": "当社がプロファイル用に使用状況データを収集しつつ、どのようにユーザーのプライバシーを守るのかご覧ください。", "checkbox": "このデータは、ユーザーによる当社のマーケティングコミュニケーションとのインタラクションを把握するために使用されます。また、関連ニュースをお伝えする場合もあります (製品の機能など)。", "action_description_1_prefix": "非公開:", "action_description_2_prefix": "一般:", @@ -606,17 +606,17 @@ "billion_abbreviation": "B", "trillion_abbreviation": "T", "million_abbreviation": "M", - "token_details": "Token details", - "contract_address": "Contract address", - "token_list": "Token list", - "market_details": "Market details", - "market_cap": "Market Cap", - "total_volume": "Total Volume (24h)", - "volume_to_marketcap": "Volume / Market Cap", - "circulating_supply": "Circulating supply", - "all_time_high": "All time high", - "all_time_low": "All time low", - "fully_diluted": "Fully diluted" + "token_details": "トークンの詳細", + "contract_address": "コントラクトアドレス", + "token_list": "トークンリスト", + "market_details": "マーケットの詳細", + "market_cap": "時価総額", + "total_volume": "合計ボリューム (24時間)", + "volume_to_marketcap": "ボリューム / 時価総額", + "circulating_supply": "循環供給量", + "all_time_high": "最高記録", + "all_time_low": "最低記録", + "fully_diluted": "完全希釈化後" }, "collectible": { "collectible_address": "アドレス", @@ -666,10 +666,10 @@ "accounts_title": "アカウント", "connect_account_title": "アカウントの接続", "connect_accounts_title": "アカウントの接続", - "edit_accounts_title": "Edit accounts", + "edit_accounts_title": "アカウントの編集", "connected_accounts_title": "接続されたアカウント", "connect_description": "アカウントのアドレス、残高、アクティビティを共有すると、サイトがトランザクションを実行できるようになります。", - "select_accounts_description": "Select the account(s) to use on this site:", + "select_accounts_description": "このサイトで使用するアカウントを選択してください:", "connect_multiple_accounts": "複数アカウントを接続", "connect_more_accounts": "他のアカウントを接続", "cancel": "キャンセル", @@ -685,10 +685,9 @@ "address_balance_activity_permission": "アドレス、アカウント残高、アクティビティの表示", "suggest_transactions": "承認するトランザクションの提案", "disconnect": "接続解除", - "disconnect_all": "Disconnect all", - "reconnect_notice": "If you disconnect your accounts from {{dappUrl}}, you’ll need to reconnect to use them again.", + "disconnect_all": "すべて接続解除", + "reconnect_notice": "If you disconnect from {{dappUrl}}, you’ll need to reconnect your accounts and networks to use this site again.", "disconnect_all_accounts": "すべてのアカウントの接続を解除", - "disconnect_you_from": "This will disconnect you from {{dappUrl}}", "deceptive_site_ahead": "偽装サイトにアクセスしようとしています", "deceptive_site_desc": "アクセスしようとしているサイトは安全ではありません。攻撃者が危険な行為に誘導しようとする可能性があります。", "learn_more": "詳細", @@ -706,7 +705,7 @@ "accounts_connected": "アカウントが接続されました。", "disconnected": "接続が解除されました。", "disconnected_all": "すべてのアカウントの接続が解除されました。", - "disconnected_from": "Disconnected from {{dappHostName}}", + "disconnected_from": "{{dappHostName}}への接続を解除", "nft_detection_enabled": "NFTの自動検出が有効になりました" }, "connect_qr_hardware": { @@ -717,13 +716,13 @@ "keystone": "Keystone", "ngravezero": "Ngrave Zero", "learnMore": "詳細", - "buyNow": "Buy now", + "buyNow": "今すぐ購入", "tutorial": "チュートリアル", "description4": "Keystone (チュートリアル)", "description5": "1. Keystoneのロックを解除します", "description6": "2. 「···」メニューをタップして、「同期」に移動します", "button_continue": "続行", - "hint_text": "Scan your hardware wallet to ", + "hint_text": "ハードウェアウォレットをスキャンして", "purpose_connect": "接続", "purpose_sign": "トランザクションを確定", "select_accounts": "アカウントを選択してください" @@ -744,7 +743,7 @@ "enabling_profile_sync": "プロファイルの同期を有効にしています...", "disabling_profile_sync": "プロファイルの同期を無効にしています...", "notifications_dismiss_modal": "閉じる", - "select_rpc_url": "Select RPC URL", + "select_rpc_url": "RPC URLを選択", "title": "設定", "current_conversion": "ベース通貨", "current_language": "現在の言語", @@ -826,7 +825,7 @@ "notifications_title": "通知", "notifications_desc": "通知の管理", "allow_notifications": "通知を許可する", - "allow_notifications_desc": "Stay in the loop on what’s happening in your wallet with notifications. To use notifications, we use a profile to sync some settings across your devices.", + "allow_notifications_desc": "通知を使えば、ウォレットで何が起きているか常に把握できます。通知を使用するために、プロファイルを使用してデバイス間で一部の設定が同期されます。", "notifications_opts": { "customize_session_title": "通知のカスタマイズ", "customize_session_desc": "受け取る通知の種類をオンにします", @@ -845,10 +844,10 @@ }, "contacts_title": "連絡先", "contacts_desc": "アカウントを追加、編集、削除、管理します", - "permissions_title": "Permissions", - "permissions_desc": "Manage the permissions given to sites and apps", - "no_permissions": "No permissions", - "no_permissions_desc": "If you connect an account to a site or an app, you’ll see it here.", + "permissions_title": "アクセス許可", + "permissions_desc": "サイトやアプリに付与されたアクセス許可を管理します", + "no_permissions": "アクセス許可がありません", + "no_permissions_desc": "サイトまたはアプリにアカウントを接続すると、ここに表示されます。", "security_title": "セキュリティとプライバシー", "back": "戻る", "security_desc": "プライバシー設定、MetaMetrics、秘密鍵、ウォレットのシークレットリカバリーフレーズ", @@ -938,18 +937,18 @@ "custom_network_name": "カスタムネットワーク", "popular": "人気", "delete": "削除", - "account": "account", - "accounts": "accounts", - "network": "network", - "networks": "networks", + "account": "アカウント", + "accounts": "アカウント", + "network": "ネットワーク", + "networks": "ネットワーク", "network_exists": "このネットワークはすでに追加されています。", - "unMatched_chain": "According to our records, this URL does not match a known provider for this chain ID.", - "unMatched_chain_name": "This chain ID doesn’t match the network name.", - "url_associated_to_another_chain_id": "This URL is associated with another chain ID.", - "chain_id_associated_with_another_network": "The information you have entered is associated with an existing chain ID. Update your information or", - "network_already_exist": "You already have a network with the same chain ID or RPC URL. Enter a new chain ID or RPC URL", - "edit_original_network": "edit the original network", - "find_the_right_one": "Find the right one on:", + "unMatched_chain": "当社の記録によると、このURLは、このチェーンIDの既知のプロバイダーと一致しません。", + "unMatched_chain_name": "このチェーンIDはネットワーク名と一致しません。", + "url_associated_to_another_chain_id": "このURLは別のチェーンIDに関連付けられています。", + "chain_id_associated_with_another_network": "入力された情報は、既存のチェーンIDと関連付けられています。情報を更新するか、", + "network_already_exist": "同じチェーンIDまたはRPC URLのネットワークがすでに存在します。新しいチェーンIDまたはRPC URLを入力してください。", + "edit_original_network": "元のネットワークを編集", + "find_the_right_one": "次の場所で正しいものを見つけてください:", "delete_metrics_title": "MetaMetricsデータを削除", "delete_metrics_description_part_one": "これにより、ウォレットに関連付けられている過去の", "delete_metrics_description_part_two": "MetaMetrics", @@ -1066,7 +1065,12 @@ "simulation_details_description": "トランザクションを確定する前に残高の増減を予測するには、この機能をオンにします。これはトランザクションの最終的な結果を保証するものではありません。", "simulation_details_learn_more": "詳細。", "aes_crypto_test_form_title": "AES暗号化 - テストフォーム", - "aes_crypto_test_form_description": "E2Eテスト専用に開発されたセクションです。このセクションがアプリに表示される場合は、MetaMaskサポートに報告してください。" + "aes_crypto_test_form_description": "E2Eテスト専用に開発されたセクションです。このセクションがアプリに表示される場合は、MetaMaskサポートに報告してください。", + "developer_options": { + "title": "Developer Options", + "generate_trace_test": "Generate Trace Test", + "generate_trace_test_desc": "Generate a Developer Test Sentry trace." + } }, "aes_crypto_test_form": { "generate_random_salt": "ランダムなソルトを生成", @@ -1179,7 +1183,7 @@ "text": "テキスト", "qr_code": "QRコード", "hold_to_reveal_credential": "長押しして{{credentialName}}を表示", - "reveal_credential": "Reveal {{credentialName}}", + "reveal_credential": "{{credentialName}}を確認", "keep_credential_safe": "{{credentialName}}は安全に保管してください", "srp_abbreviation_text": "SRP", "srp_text": "シークレットリカバリーフレーズ", @@ -1306,14 +1310,14 @@ "error": "エラー", "attempting_to_scan_with_wallet_locked": "QRコードをスキャンしようとしているようですが、使用するにはウォレットのロックを解除する必要があります。", "attempting_sync_from_wallet_error": "拡張機能と同期しようとしているようですが、これを行うには現在のウォレットを消去する必要があります。\n\n消去してアプリの新しいバージョンを再インストールしたら、「MetaMask拡張機能と同期」オプションを選択してください。ウォレットを消去する前に、必ずシークレットリカバリーフレーズがバックアップされていることを確認してください。", - "not_allowed_error_title": "Turn on camera access", - "not_allowed_error_desc": "To scan a QR code, you'll need to give MetaMask camera access from your device's settings menu.", + "not_allowed_error_title": "カメラへのアクセスをオンにしてください", + "not_allowed_error_desc": "QRコードをスキャンするには、デバイスの設定メニューでMetaMaskにカメラへのアクセスを許可する必要があります。", "unrecognized_address_qr_code_title": "認識されないQRコード", "unrecognized_address_qr_code_desc": "申し訳ございませんが、このQRコードはアカウントアドレスまたはコントラクトアドレスに関連付けられていません。", "url_redirection_alert_title": "外部リンクにアクセスしようとしています。", "url_redirection_alert_desc": "リンクは詐欺やフィッシングに利用される場合があるため、信頼できるWebサイトにのみアクセスするようにしてください。", "label": "QRコードをスキャン", - "open_settings": "Settings" + "open_settings": "設定" }, "action_view": { "cancel": "キャンセル", @@ -1332,7 +1336,7 @@ "cancel": "キャンセル", "save": "保存", "speedup": "高速化", - "sign_with_keystone": "Sign with hardware wallet", + "sign_with_keystone": "ハードウェアウォレットで署名", "sign_with_ledger": "Ledgerで署名", "from": "送信元", "gas_fee": "ガス代", @@ -1406,7 +1410,7 @@ "token_id": "トークンID", "not_enough_for_gas": "トランザクション手数料を支払うための{{ticker}}がアカウントにありません。{{ticker}}を購入するか、別のアカウントから入金してください。", "send": "送信", - "confirm_with_qr_hardware": "Confirm with hardware wallet", + "confirm_with_qr_hardware": "ハードウェアウォレットで確定", "confirm_with_ledger_hardware": "Ledgerで確定", "confirmed": "確定済み", "pending": "保留中", @@ -1439,12 +1443,13 @@ "no_camera_permission": "カメラが承認されていません。アクセス許可を付与して再度お試しください", "invalid_qr_code_sign": "無効なQRコードです。ハードウェアを確認して再度お試しください。", "no_camera_permission_android": "続けるには、MetaMaskによるカメラへのアクセスを許可する必要があります。また、システム設定の変更が必要な場合もあります。", - "mismatched_qr_request_id": "Incongruent transaction data. Please use your hardware wallet to sign the QR code below and tap 'Get Signature'.", + "mismatched_qr_request_id": "トランザクションデータが一致していません。ハードウェアウォレットで下のQRコードに署名して、「署名を取得」をタップしてください。", "fiat_conversion_not_available": "法定通貨への換算は現在利用できません", "hex_data_copied": "16進データがクリップボードにコピーされました", "invalid_recipient": "無効な受取人", "invalid_recipient_description": "アドレスが有効であることを確認してください", - "swap_tokens": "トークンをスワップ" + "swap_tokens": "トークンをスワップ", + "fromWithColon": "From:" }, "custom_gas": { "total": "合計", @@ -1640,8 +1645,8 @@ "import_wallet_label": "追加されたアカウント", "import_wallet_tip": "今後このデバイスで行われるトランザクションにはすべて、タイムスタンプの隣に「このデバイスから」というラベルが付きます。このアカウントを追加する前のトランザクションに関しては、履歴 (このデバイスからどの送信トランザクションが処理されたか) が表示されません。", "sign_title_scan": "スキャン", - "sign_title_device": "with your hardware wallet", - "sign_description_1": "After you have signed with your hardware wallet,", + "sign_title_device": "ハードウェアウォレットで", + "sign_description_1": "ハードウェアウォレットで署名した後、", "sign_description_2": "「署名を取得」をタップします", "sign_get_signature": "署名を取得", "network_fee": "ネットワーク手数料" @@ -1714,11 +1719,11 @@ "network_display_name": "表示名", "network_chain_id": "チェーンID", "network_rpc_url": "ネットワークURL", - "network_rpc_url_label": "Network RPC URL", - "new_default_network_url": "New default network URL", - "current_label": "Current", - "new_label": "New", - "review": "Review", + "network_rpc_url_label": "ネットワークRPC URL", + "new_default_network_url": "新しいデフォルトのネットワークURL", + "current_label": "現在", + "new_label": "新", + "review": "レビュー", "view_details": "詳細を表示", "network_details": "ネットワークの詳細", "network_select_confirm_use_safe_check": "「確定」を選択すると、ネットワーク情報の確認がオンになります。ネットワーク情報の確認は、", @@ -1731,6 +1736,7 @@ "continue": "続行", "cancel": "キャンセル", "approve": "承認", + "update": "Update", "edit_network_details": "ネットワークの詳細を編集", "malicious_network_warning": "悪意のあるネットワーク プロバイダーは、ブロックチェーンのステータスを偽り、ユーザーのネットワークアクティビティを記録することがあります。信頼するカスタムネットワークのみを追加してください。", "security_link": "https://support.metamask.io/networks-and-sidechains/managing-networks/user-guide-custom-networks-and-sidechains/", @@ -1738,10 +1744,12 @@ "additional_network_information_title": "その他のネットワーク情報", "network_warning_desc": "このネットワーク接続は第三者に依存しているため、信頼性が低い可能性、または第三者がアクティビティをトラッキングできる可能性があります。", "additonial_network_information_desc": "これらのネットワークの一部はサードパーティに依存しているため、接続の信頼性が低かったり、サードパーティによるアクティビティの追跡が可能になったりする可能性があります。", + "connect_more_networks": "Connect more networks", "learn_more": "詳細", "learn_more_url": "https://support.metamask.io/networks-and-sidechains/managing-networks/the-risks-of-connecting-to-an-unknown-network/", "switch_network": "ネットワークに切り替える", "switch": "切り替える", + "select_all": "Select all", "new_network": "新しいネットワークが追加されました", "network_name": "{{networkName}}ネットワーク", "network_added": "がネットワークセレクターで利用可能になりました。", @@ -1751,14 +1759,26 @@ "add_other_network_here": "こちらから検出できます。", "you_can": "または", "add_network": "他のネットワークを手動で追加できます。", - "add_specific_network": "Add {{network_name}}", + "add_specific_network": "{{network_name}}を追加", "select_network": "ネットワークを選択", "enabled_networks": "ネットワークが有効になりました", "additional_networks": "他のネットワーク", "show_test_networks": "テストネットワークを表示", "deprecated_goerli": "イーサリアムのプロトコルの変更により、Goerliテストネットワークは安定して動作しない可能性があり、近日中に非推奨になります。", "network_deprecated_title": "このネットワークはサポートされなくなりました", - "network_deprecated_description": "接続しようとしているネットワークは現在MetaMaskによりサポートされていません。" + "network_deprecated_description": "接続しようとしているネットワークは現在MetaMaskによりサポートされていません。", + "edit_networks_title": "Edit networks" + }, + "permissions": { + "title_this_site_wants_to": "This site wants to:", + "title_dapp_url_wants_to": "{{dappUrl}} wants to:", + "title_dapp_url_has_approval_to": "{{dappUrl}} has approval to:", + "use_enabled_networks": "Use your enabled networks", + "wants_to_see_your_accounts": "See your accounts and suggest transactions", + "requesting_for": "Requesting for ", + "manage_permissions": "Manage Permissions", + "edit": "Edit", + "cancel": "Cancel" }, "select": { "cancel": "キャンセル", @@ -2022,10 +2042,12 @@ "back_to_safety": "安全な場所に戻る" }, "notifications": { + "no_date": "Unknown", + "yesterday": "Yesterday", "staked": "ステーキングしました", "received": "受け取りました", "unstaked": "ステーキングが解除されました", - "mark_all_as_read": "Mark all as read", + "mark_all_as_read": "すべて既読にする", "to": "送り先", "rate": "レート (手数料込み)", "unstaking_requested": "ステーキングの解除がリクエストされました", @@ -2038,7 +2060,7 @@ "swap": "スワップしました", "sent": "{{address}}に送りました", "menu_item_title": { - "metamask_swap_completed": "Swapped {{symbolIn}} for {{symbolOut}}", + "metamask_swap_completed": "{{symbolIn}}を{{symbolOut}}にスワップしました", "erc20_sent": "{{address}}に送りました", "erc20_received": "{{address}}から受け取りました", "eth_sent": "{{address}}に送りました", @@ -2065,7 +2087,7 @@ "title_untake_ready": "出金準備ができました", "title_stake": "{{symbol}}をステーキングしました", "title_unstake_completed": "ステーキングの解除が完了しました", - "title_swapped": "Swapped {{symbolIn}} to {{symbolOut}}", + "title_swapped": "{{symbolIn}}を{{symbolOut}}にスワップしました", "label_address_to": "送り先", "label_address_from": "送り元", "label_address_to_you": "(あなた) へ", @@ -2097,6 +2119,8 @@ "success_withdrawal_title": "出金完了!", "error_title": "問題が発生しました :/", "received_title": "{{amount}}{{assetType}}受け取りました", + "default_message_title": "New transaction notification", + "default_message_description": "Tap to view this transaction", "received_payment_title": "インスタントペイメントを受け取りました", "pending_message": "承認待ち", "pending_deposit_message": "入金の完了待ち", @@ -2112,12 +2136,12 @@ "cancelled_message": "このトランザクションを表示するにはタップしてください", "received_message": "このトランザクションを表示するにはタップしてください", "received_payment_message": "{{amount}}DAI受け取りました", - "prompt_title": "プッシュ通知を有効にする", + "prompt_title": "Receive Push Notifications", "notifications_enabled_error_title": "問題が発生しました", "notifications_enabled_error_desc": "通知を有効にできませんでした。後ほどもう一度お試しください。", - "prompt_desc": "ETHを受け取ったり、トランザクションが確認された際にMetaMaskが通知できるよう、通知を有効にしてください。", - "prompt_ok": "はい", - "prompt_cancel": "いいえ、結構です", + "prompt_desc": "Turn on notifications from Settings to get important alerts on wallet activity and more.", + "prompt_ok": "Turn on", + "prompt_cancel": "Maybe later", "wc_connected_title": "{{title}}に接続されました", "wc_signed_title": "署名済み", "wc_sent_tx_title": "トランザクションが送信されました", @@ -2144,9 +2168,9 @@ "transaction_id_copied_to_clipboard": "トランザクションIDがクリップボードにコピーされました", "activation_card": { "title": "通知をオンにする", - "description_1": "Stay in the loop on what's happening in your wallet with notifications.", + "description_1": "通知を使えば、ウォレットで何が起きているか常に把握できます。", "description_2": "この機能を使うために、アカウントの匿名のIDが生成されます。このIDはMetaMask内のユーザーのデータの同期にのみ使用され、アクティビティや他の識別要素にはリンクされず、プライバシーは確保されます。", - "learn_more": "Learn how we protect your privacy while using this feature.", + "learn_more": "この機能を使用する際に当社がどのようにユーザーのプライバシーを保護するのか、ご覧ください。", "manage_preferences_1": "通知は", "manage_preferences_2": "「設定」>「通知」でいつでもオフにできます。", "cancel": "キャンセル", @@ -2539,7 +2563,7 @@ "sale_completed_description": "注文に成功しました。", "sale_pending_title": "{{currency}}の売却を処理しています", "sale_pending_description": "ただ今注文を処理しています。", - "no_date": "Unknown" + "no_date": "不明" } }, "swaps": { @@ -2798,14 +2822,9 @@ }, "switch_custom_network": { "title_existing_network": "このサイトがネットワークの変更を求めています", - "title_enabled_network": "This site wants to:", "title_new_network": "新しいネットワークが追加されました", "switch_warning": "これによりMetaMask内で選択されたネットワークが、以前に追加されたものに切り替わります:", - "use_enabled_networks": "Use your enabled networks", - "wants_to_see_your_accounts": "See your accounts and suggest transactions", - "requesting_for": "Requesting for ", - "edit": "Edit", - "add_network_and_give_dapp_permission_warning": "You're adding this network to MetaMask and giving {{dapp_origin}} permission to use it.", + "add_network_and_give_dapp_permission_warning": "このネットワークをMetaMaskに追加し、{{dapp_origin}}がそれを使用することを許可しようとしています。", "available": "がネットワークセレクターで利用可能になりました。", "cancel": "キャンセル", "switch": "ネットワークを切り替える" @@ -2896,7 +2915,7 @@ }, "transaction_review_eip1559": { "estimated_gas_fee": "ガス代の見積もり", - "suggested_gas_fee": "%{origin}の提案するガス代", + "network_fee": "Network fee", "max_fee": "最大手数料", "total": "合計", "max_amount": "最高額", @@ -3129,17 +3148,18 @@ "cannot_get_account": "アカウントを取得できません", "connect_ledger": "Ledgerを接続する", "looking_for_device": "デバイスを検索中", - "ledger_reminder_message": "Please make sure your Ledger device is:", - "ledger_reminder_message_step_one": "1. Unlock your Ledger device", + "ledger_reminder_message": "Ledgerデバイスが次の状態であることを確認してください:", + "ledger_reminder_message_step_one": "1. Ledgerデバイスのロックを解除します", "ledger_reminder_message_step_two": "2. イーサリアムアプリをインストールして開きます", "ledger_reminder_message_step_three": "3. Bluetoothを有効にします", "ledger_reminder_message_step_four": "4. 位置情報は有効で、正確な位置情報の使用がオンになっています", "ledger_reminder_message_step_four_Androidv12plus": "4. 近くのデバイスは有効です", "ledger_reminder_message_step_five": "5. サイレントモードがオフになっている必要があります", + "blind_signing_message": "6. Enable \"blind signing\" on your Ledger device.", "available_devices": "利用可能なデバイス", "retry": "再試行", "continue": "続行", - "confirm_transaction_on_ledger": "Confirm transaction on your Ledger", + "confirm_transaction_on_ledger": "Ledgerでトランザクションを確定します", "bluetooth_enabled_message": "Bluetoothが有効になっていることを確認してください", "device_unlocked_message": "デバイスのロックが解除されています", "ledger_disconnected": "デバイスの接続が切断されました", @@ -3162,11 +3182,19 @@ "not_supported": "操作がサポートされていません。", "not_supported_error": "型付きデータ署名のバージョン4のみがサポートされています。", "error_during_connection": "不明なエラーが発生しました", - "error_during_connection_message": "There's been a slight problem connecting your Ledger device, tap 'Retry' below to give this another go. Sometimes this occurs due to the ETH app on your Ledger device being open at the start of the pairing process with MetaMask Mobile.", + "error_during_connection_message": "Ledgerデバイスの接続中に若干の問題が発生しました。下の「再試行」をタップして、もう一度お試しください。この問題は、LedgerデバイスのETHアプリが、MetaMask Mobileとのペアリングプロセスの開始時に開いているのが原因で起こることがあります。", "how_to_install_eth_webview_title": "イーサリアムアプリのインストール方法", "nonce_too_low": "ナンスが低すぎます", "nonce_too_low_error": "設定されたナンスが低すぎます", - "select_accounts": "Select an Account" + "select_accounts": "Select an account", + "select_hd_path": "Select HD Path", + "select_hd_path_description": "If you don't see the accounts you expect, try switching the HD path or current selected network.", + "ledger_live_path": "Ledger Live", + "ledger_legacy_path": "Legacy(MEW/MyCrypto)", + "ledger_bip44_path": "BIP44(e.g. MetaMask, Trezor)", + "ledger_legacy_label": " (legacy)", + "blind_sign_error": "Blind signing error", + "blind_sign_error_message": "Blind signing is not enabled on your Ledger device. Please enable it in the settings." }, "account_actions": { "edit_name": "アカウント名を編集", @@ -3220,7 +3248,21 @@ "error_description": "{{snap}}のインストールに失敗しました。" }, "stake": { - "stake": "ステーク" + "stake": "ステーク", + "stake_eth": "Stake ETH", + "your_earnings": "Your earnings", + "annual_rate": "Annual rate", + "lifetime_rewards": "Lifetime rewards", + "estimated_annual_earnings": "Estimated annual earnings", + "accessibility_labels": { + "stake_annual_rate_tooltip": "Annual rate tooltip" + }, + "estimated_annual_rewards": "Estimated annual rewards", + "metamask_pool": "MetaMask Pool", + "enter_amount": "Enter amount", + "review": "Review", + "not_enough_eth": "Not enough ETH", + "balance": "Balance" }, "default_settings": { "title": "ウォレットの準備ができました", @@ -3239,7 +3281,7 @@ "title_off": "基本機能をオフにする", "description_off": "これは、MetaMaskでの時間が完全に最適化されないことを意味します。基本機能 (トークンの詳細、最適なガス設定など) は利用できません。", "title_on": "基本機能をオンにする", - "description_on": "To optimize your time on MetaMask, you’ll need to turn on this feature. Basic functions (like token details, optimal gas settings, notifications, and others) are important to the web3 experience.", + "description_on": "MetaMaskでの時間を最適化するには、この機能をオンにする必要があります。基本機能 (トークンの詳細、最適なガス設定、通知など) は、Web3エクスペリエンスに重要です。", "checkbox_label": "理解したうえで続行します", "buttons": { "cancel": "キャンセル", @@ -3269,6 +3311,14 @@ "title": "複数の要求が検出されました" }, "common": { - "please_wait": "お待ちください" + "please_wait": "お待ちください", + "disconnect_you_from": "This will disconnect you from {{dappUrl}}", + "disconnect": "Disconnect" + }, + "tooltip_modal": { + "reward_rate": { + "title": "Reward rate", + "tooltip": "Expected yearly increase in the value of your stake, based on the reward rate over the past week." + } } } diff --git a/locales/languages/ko.json b/locales/languages/ko.json index af70019cf9b..19c4e3a89bc 100644 --- a/locales/languages/ko.json +++ b/locales/languages/ko.json @@ -686,9 +686,8 @@ "suggest_transactions": "승인할 트랜잭션 추천", "disconnect": "연결 해제", "disconnect_all": "모두 연결 해제", - "reconnect_notice": "{{dappUrl}}에서 계정의 연결을 끊은 경우, 다시 사용하려면 다시 연결해야 합니다.", + "reconnect_notice": "If you disconnect from {{dappUrl}}, you’ll need to reconnect your accounts and networks to use this site again.", "disconnect_all_accounts": "모든 계정 연결 해제", - "disconnect_you_from": "이렇게 하면 {{dappUrl}}에서의 연결이 해제됩니다", "deceptive_site_ahead": "의심스러운 사이트 주의", "deceptive_site_desc": "방문하려는 사이트가 안전하지 않습니다. 공격자가 위험한 작업을 하도록 유도할 수 있습니다.", "learn_more": "자세히 알아보기", @@ -1066,7 +1065,12 @@ "simulation_details_description": "이 기능을 켜면 트랜잭션을 확정하기 전에 트랜잭션의 잔액 변동을 추정할 수 있습니다. 이 기능이 트랜잭션의 최종 결과를 보장하지는 않습니다. ", "simulation_details_learn_more": "더 알아보기.", "aes_crypto_test_form_title": "AES 암호화 - 테스트 양식", - "aes_crypto_test_form_description": "E2E 테스트 전용으로 개발된 섹션입니다. 앱에서 이 섹션이 표시되면 MetaMask 지원팀에 보고하세요." + "aes_crypto_test_form_description": "E2E 테스트 전용으로 개발된 섹션입니다. 앱에서 이 섹션이 표시되면 MetaMask 지원팀에 보고하세요.", + "developer_options": { + "title": "Developer Options", + "generate_trace_test": "Generate Trace Test", + "generate_trace_test_desc": "Generate a Developer Test Sentry trace." + } }, "aes_crypto_test_form": { "generate_random_salt": "무작위 솔트 생성", @@ -1444,7 +1448,8 @@ "hex_data_copied": "헥스 데이터를 클립보드에 복사했습니다", "invalid_recipient": "잘못된 수신자", "invalid_recipient_description": "주소가 올바른지 확인하세요", - "swap_tokens": "토큰 스왑" + "swap_tokens": "토큰 스왑", + "fromWithColon": "From:" }, "custom_gas": { "total": "총합", @@ -1731,6 +1736,7 @@ "continue": "계속", "cancel": "취소", "approve": "승인", + "update": "Update", "edit_network_details": "네트워크 세부 정보 편집", "malicious_network_warning": "악성 네트워크 공급업체는 블록체인 상태를 거짓으로 보고하고 네트워크 활동을 기록할 수 있습니다. 신뢰하는 커스텀 네트워크만 추가하세요.", "security_link": "https://support.metamask.io/networks-and-sidechains/managing-networks/user-guide-custom-networks-and-sidechains/", @@ -1738,10 +1744,12 @@ "additional_network_information_title": "추가 네트워크 정보", "network_warning_desc": "이 네트워크 연결은 타사 서비스를 이용합니다. 연결의 신뢰성이 낮거나 타사가 활동을 추적할 수 있습니다.", "additonial_network_information_desc": "이러한 네트워크 중 일부는 제삼자에 의존합니다. 이러한 연결은 안정성이 떨어지거나 제삼자가 활동을 추적할 수 있습니다.", + "connect_more_networks": "Connect more networks", "learn_more": "더 보기", "learn_more_url": "https://support.metamask.io/networks-and-sidechains/managing-networks/the-risks-of-connecting-to-an-unknown-network/", "switch_network": "네트워크 전환", "switch": "전환", + "select_all": "Select all", "new_network": "신규 네트워크 추가됨", "network_name": "{{networkName}} 네트워크", "network_added": " 를 네트워크 셀렉터에서 사용할 수 있습니다.", @@ -1758,7 +1766,19 @@ "show_test_networks": "테스트 네트워크 표시", "deprecated_goerli": "이더리움의 프로토콜 변경으로 인해 Goerli 테스트 네트워크가 안정적으로 작동하지 않을 수 있으며, 곧 사용 중지될 예정입니다.", "network_deprecated_title": "이 네트워크는 더 이상 지원되지 않습니다", - "network_deprecated_description": "연결하려는 네트워크는 Metamask에서 더 이상 지원되지 않습니다." + "network_deprecated_description": "연결하려는 네트워크는 Metamask에서 더 이상 지원되지 않습니다.", + "edit_networks_title": "Edit networks" + }, + "permissions": { + "title_this_site_wants_to": "This site wants to:", + "title_dapp_url_wants_to": "{{dappUrl}} wants to:", + "title_dapp_url_has_approval_to": "{{dappUrl}} has approval to:", + "use_enabled_networks": "Use your enabled networks", + "wants_to_see_your_accounts": "See your accounts and suggest transactions", + "requesting_for": "Requesting for ", + "manage_permissions": "Manage Permissions", + "edit": "Edit", + "cancel": "Cancel" }, "select": { "cancel": "취소", @@ -2022,6 +2042,8 @@ "back_to_safety": "안전성으로 돌아가기" }, "notifications": { + "no_date": "Unknown", + "yesterday": "Yesterday", "staked": "스테이킹됨", "received": "받음", "unstaked": "언스테이킹됨", @@ -2097,6 +2119,8 @@ "success_withdrawal_title": "인출 완료!", "error_title": "앗, 문제가 발생했습니다 :/", "received_title": "{{amount}}의 {{assetType}}(을)를 받았습니다", + "default_message_title": "New transaction notification", + "default_message_description": "Tap to view this transaction", "received_payment_title": "즉시 결제됨", "pending_message": "컨펌을 기다리는 중", "pending_deposit_message": "입금 완료 대기 중", @@ -2112,12 +2136,12 @@ "cancelled_message": "눌러서 이 트랜잭션을 확인하세요", "received_message": "눌러서 이 트랜잭션을 확인하세요", "received_payment_message": "{{amount}} DAI를 받았습니다", - "prompt_title": "공지 알림 활성화", + "prompt_title": "Receive Push Notifications", "notifications_enabled_error_title": "문제가 발생했습니다", "notifications_enabled_error_desc": "알림을 활성화할 수 없습니다. 나중에 다시 시도하세요.", - "prompt_desc": "공지 알림을 활성화해서 MetaMask가 언제 ETH를 받았는지 혹은 언제 트랜잭션이 컨펌되었는지 등의 알림을 받으세요.", - "prompt_ok": "예", - "prompt_cancel": "아니요, 괜찮습니다", + "prompt_desc": "Turn on notifications from Settings to get important alerts on wallet activity and more.", + "prompt_ok": "Turn on", + "prompt_cancel": "Maybe later", "wc_connected_title": "{{title}}에 연결", "wc_signed_title": "서명함", "wc_sent_tx_title": "보낸 트랜잭션", @@ -2798,13 +2822,8 @@ }, "switch_custom_network": { "title_existing_network": "이 사이트는 네트워크 전환을 원합니다", - "title_enabled_network": "이 사이트에서 요청하는 사항:", "title_new_network": "신규 네트워크 추가됨", "switch_warning": "이는 MetaMask에서 선택한 네트워크를 이전에 추가한 다음 네트워크로 전환하게 됩니다:", - "use_enabled_networks": "활성화된 네트워크 사용", - "wants_to_see_your_accounts": "계정 보기 및 거래 제안", - "requesting_for": "다음을 요청: ", - "edit": "편집", "add_network_and_give_dapp_permission_warning": "MetaMask에 이 네트워크를 추가하고 {{dapp_origin}}에 사용 권한을 허용합니다.", "available": "이것은 이제 네트워크 선택자 내에서 이용 가능합니다.", "cancel": "취소", @@ -2896,7 +2915,7 @@ }, "transaction_review_eip1559": { "estimated_gas_fee": "예상 가스비", - "suggested_gas_fee": "%{origin}(이)가 가스비를 추천했습니다", + "network_fee": "Network fee", "max_fee": "최대 수수료", "total": "총", "max_amount": "최대 금액", @@ -3136,6 +3155,7 @@ "ledger_reminder_message_step_four": "4. 정확한 위치 사용으로 위치 권한이 활성화되었습니다", "ledger_reminder_message_step_four_Androidv12plus": "4. 주변 장치가 활성화되었습니다", "ledger_reminder_message_step_five": "5. 방해 금지 모드는 꺼져 있어야 합니다", + "blind_signing_message": "6. Enable \"blind signing\" on your Ledger device.", "available_devices": "이용 가능한 기기", "retry": "재시도", "continue": "계속", @@ -3166,7 +3186,15 @@ "how_to_install_eth_webview_title": "이더리움 앱 설치 방법", "nonce_too_low": "논스가 너무 낮습니다", "nonce_too_low_error": "설정한 논스가 너무 낮습니다", - "select_accounts": "계정 선택" + "select_accounts": "Select an account", + "select_hd_path": "Select HD Path", + "select_hd_path_description": "If you don't see the accounts you expect, try switching the HD path or current selected network.", + "ledger_live_path": "Ledger Live", + "ledger_legacy_path": "Legacy(MEW/MyCrypto)", + "ledger_bip44_path": "BIP44(e.g. MetaMask, Trezor)", + "ledger_legacy_label": " (legacy)", + "blind_sign_error": "Blind signing error", + "blind_sign_error_message": "Blind signing is not enabled on your Ledger device. Please enable it in the settings." }, "account_actions": { "edit_name": "계정 이름 편집", @@ -3220,7 +3248,21 @@ "error_description": "{{snap}} 설치에 실패했습니다." }, "stake": { - "stake": "스테이크" + "stake": "스테이크", + "stake_eth": "Stake ETH", + "your_earnings": "Your earnings", + "annual_rate": "Annual rate", + "lifetime_rewards": "Lifetime rewards", + "estimated_annual_earnings": "Estimated annual earnings", + "accessibility_labels": { + "stake_annual_rate_tooltip": "Annual rate tooltip" + }, + "estimated_annual_rewards": "Estimated annual rewards", + "metamask_pool": "MetaMask Pool", + "enter_amount": "Enter amount", + "review": "Review", + "not_enough_eth": "Not enough ETH", + "balance": "Balance" }, "default_settings": { "title": "지갑이 준비되었습니다", @@ -3269,6 +3311,14 @@ "title": "여러 건의 요청을 확인했습니다" }, "common": { - "please_wait": "잠시만 기다려주세요" + "please_wait": "잠시만 기다려주세요", + "disconnect_you_from": "This will disconnect you from {{dappUrl}}", + "disconnect": "Disconnect" + }, + "tooltip_modal": { + "reward_rate": { + "title": "Reward rate", + "tooltip": "Expected yearly increase in the value of your stake, based on the reward rate over the past week." + } } } diff --git a/locales/languages/pt.json b/locales/languages/pt.json index 307f590e7f8..2b38bd88226 100644 --- a/locales/languages/pt.json +++ b/locales/languages/pt.json @@ -686,9 +686,8 @@ "suggest_transactions": "Sugerir transações para aprovar", "disconnect": "Desconectar", "disconnect_all": "Desconectar todas", - "reconnect_notice": "Se você desconectar suas contas do {{dappUrl}}, precisará reconectar para usá-las novamente.", + "reconnect_notice": "If you disconnect from {{dappUrl}}, you’ll need to reconnect your accounts and networks to use this site again.", "disconnect_all_accounts": "Desconectar todas as contas", - "disconnect_you_from": "Isso desconectará você do {{dappUrl}}", "deceptive_site_ahead": "Site enganoso à vista", "deceptive_site_desc": "O site que você está tentando visitar não é seguro. Invasores podem induzir você a fazer algo perigoso por engano.", "learn_more": "Saiba mais", @@ -1066,7 +1065,12 @@ "simulation_details_description": "Ative esta opção para estimar as alterações de saldo pelas transações antes de confirmá-las. Isso não garante o resultado de suas transações. ", "simulation_details_learn_more": "Saiba mais.", "aes_crypto_test_form_title": "AES Crypto - Formulário de teste", - "aes_crypto_test_form_description": "Seção desenvolvida exclusivamente para testes E2E. Se isso for exibido em seu aplicativo, informe ao suporte da MetaMask." + "aes_crypto_test_form_description": "Seção desenvolvida exclusivamente para testes E2E. Se isso for exibido em seu aplicativo, informe ao suporte da MetaMask.", + "developer_options": { + "title": "Developer Options", + "generate_trace_test": "Generate Trace Test", + "generate_trace_test_desc": "Generate a Developer Test Sentry trace." + } }, "aes_crypto_test_form": { "generate_random_salt": "Gerar sal aleatório", @@ -1444,7 +1448,8 @@ "hex_data_copied": "Dados hexa copiados para a área de transferência", "invalid_recipient": "Destinatário inválido", "invalid_recipient_description": "Verifique o endereço e certifique-se de que ele é válido", - "swap_tokens": "Trocar tokens" + "swap_tokens": "Trocar tokens", + "fromWithColon": "From:" }, "custom_gas": { "total": "Total", @@ -1731,6 +1736,7 @@ "continue": "Continuar", "cancel": "Cancelar", "approve": "Aprovar", + "update": "Update", "edit_network_details": "Editar os dados da rede", "malicious_network_warning": "Um provedor de rede mal-intencionado pode mentir sobre o estado da blockchain e registrar sua atividade na rede. Adicione apenas redes personalizadas em que você confia.", "security_link": "https://support.metamask.io/networks-and-sidechains/managing-networks/user-guide-custom-networks-and-sidechains/", @@ -1738,10 +1744,12 @@ "additional_network_information_title": "Informações sobre redes adicionais", "network_warning_desc": "Essa conexão de rede depende de terceiros. Ela pode ser menos confiável ou permitir que eles rastreiem as atividades.", "additonial_network_information_desc": "Algumas dessas redes dependem de terceiros. As conexões podem ser menos confiáveis ​​ou permitir que terceiros rastreiem atividades.", + "connect_more_networks": "Connect more networks", "learn_more": "Saiba mais", "learn_more_url": "https://support.metamask.io/networks-and-sidechains/managing-networks/the-risks-of-connecting-to-an-unknown-network/", "switch_network": "Alternar para rede", "switch": "Alternar", + "select_all": "Select all", "new_network": "Nova rede adicionada", "network_name": "Rede {{networkName}}", "network_added": " agora está disponível no seletor de redes.", @@ -1758,7 +1766,19 @@ "show_test_networks": "Exibir redes de teste", "deprecated_goerli": "Devido às alterações no protocolo Ethereum, a rede de teste Goerli pode não funcionar com a mesma confiabilidade e será descontinuada.", "network_deprecated_title": "Essa rede foi descontinuada", - "network_deprecated_description": "A rede à qual você está tentando se conectar não é mais suportada pela MetaMask." + "network_deprecated_description": "A rede à qual você está tentando se conectar não é mais suportada pela MetaMask.", + "edit_networks_title": "Edit networks" + }, + "permissions": { + "title_this_site_wants_to": "This site wants to:", + "title_dapp_url_wants_to": "{{dappUrl}} wants to:", + "title_dapp_url_has_approval_to": "{{dappUrl}} has approval to:", + "use_enabled_networks": "Use your enabled networks", + "wants_to_see_your_accounts": "See your accounts and suggest transactions", + "requesting_for": "Requesting for ", + "manage_permissions": "Manage Permissions", + "edit": "Edit", + "cancel": "Cancel" }, "select": { "cancel": "Cancelar", @@ -2022,6 +2042,8 @@ "back_to_safety": "Voltar para a segurança" }, "notifications": { + "no_date": "Unknown", + "yesterday": "Yesterday", "staked": "Em staking", "received": "Recebido", "unstaked": "Retirado de staking", @@ -2097,6 +2119,8 @@ "success_withdrawal_title": "Retirada concluída!", "error_title": "Ops, ocorreu um erro :/", "received_title": "Você recebeu {{amount}} {{assetType}}", + "default_message_title": "New transaction notification", + "default_message_description": "Tap to view this transaction", "received_payment_title": "Pagamento instantâneo recebido", "pending_message": "Aguardando confirmação", "pending_deposit_message": "Aguardando conclusão do depósito", @@ -2112,12 +2136,12 @@ "cancelled_message": "Toque para ver essa transação", "received_message": "Toque para ver essa transação", "received_payment_message": "Você recebeu {{amount}} DAI", - "prompt_title": "Ativar notificações push", + "prompt_title": "Receive Push Notifications", "notifications_enabled_error_title": "Algo deu errado", "notifications_enabled_error_desc": "Não foi possível ativar as notificações. Tente novamente mais tarde.", - "prompt_desc": "Ative as notificações para que a MetaMask informe quando você tiver recebido ETH ou quando suas transações forem confirmadas.", - "prompt_ok": "Sim", - "prompt_cancel": "Não, obrigado", + "prompt_desc": "Turn on notifications from Settings to get important alerts on wallet activity and more.", + "prompt_ok": "Turn on", + "prompt_cancel": "Maybe later", "wc_connected_title": "Conectado ao {{title}}", "wc_signed_title": "Assinado", "wc_sent_tx_title": "Enviou a transação", @@ -2798,13 +2822,8 @@ }, "switch_custom_network": { "title_existing_network": "Este site deseja alternar a rede", - "title_enabled_network": "Este site quer:", "title_new_network": "Nova rede adicionada", "switch_warning": "Isso alternará a rede selecionada dentro da MetaMask para uma rede adicionada anteriormente:", - "use_enabled_networks": "Usar suas redes habilitadas", - "wants_to_see_your_accounts": "Ver suas contas e sugerir transações", - "requesting_for": "Solicitando para ", - "edit": "Editar", "add_network_and_give_dapp_permission_warning": "Você está adicionando esta rede à MetaMask e dando à {{dapp_origin}} permissão para usá-la.", "available": "está disponível agora no seletor de redes.", "cancel": "Cancelar", @@ -2896,7 +2915,7 @@ }, "transaction_review_eip1559": { "estimated_gas_fee": "Taxa de gás estimada", - "suggested_gas_fee": "Taxa de gás sugerida por %{origin}", + "network_fee": "Network fee", "max_fee": "Taxa máxima", "total": "Total", "max_amount": "Valor máximo", @@ -3136,6 +3155,7 @@ "ledger_reminder_message_step_four": "4. O acesso à localização está ativado com o uso de localização precisa", "ledger_reminder_message_step_four_Androidv12plus": "4. O acesso a dispositivos próximos está ativado", "ledger_reminder_message_step_five": "5. Não perturbe precisa estar desligado", + "blind_signing_message": "6. Enable \"blind signing\" on your Ledger device.", "available_devices": "Dispositivos disponíveis", "retry": "Tentar novamente", "continue": "Continuar", @@ -3166,7 +3186,15 @@ "how_to_install_eth_webview_title": "Como instalar o app Ethereum", "nonce_too_low": "Nonce muito baixo", "nonce_too_low_error": "O nonce definido está muito baixo", - "select_accounts": "Selecione uma conta" + "select_accounts": "Select an account", + "select_hd_path": "Select HD Path", + "select_hd_path_description": "If you don't see the accounts you expect, try switching the HD path or current selected network.", + "ledger_live_path": "Ledger Live", + "ledger_legacy_path": "Legacy(MEW/MyCrypto)", + "ledger_bip44_path": "BIP44(e.g. MetaMask, Trezor)", + "ledger_legacy_label": " (legacy)", + "blind_sign_error": "Blind signing error", + "blind_sign_error_message": "Blind signing is not enabled on your Ledger device. Please enable it in the settings." }, "account_actions": { "edit_name": "Editar nome da conta", @@ -3220,7 +3248,21 @@ "error_description": "Ocorreu uma falha na instalação de {{snap}}." }, "stake": { - "stake": "Stake" + "stake": "Stake", + "stake_eth": "Stake ETH", + "your_earnings": "Your earnings", + "annual_rate": "Annual rate", + "lifetime_rewards": "Lifetime rewards", + "estimated_annual_earnings": "Estimated annual earnings", + "accessibility_labels": { + "stake_annual_rate_tooltip": "Annual rate tooltip" + }, + "estimated_annual_rewards": "Estimated annual rewards", + "metamask_pool": "MetaMask Pool", + "enter_amount": "Enter amount", + "review": "Review", + "not_enough_eth": "Not enough ETH", + "balance": "Balance" }, "default_settings": { "title": "Sua carteira está pronta", @@ -3269,6 +3311,14 @@ "title": "Percebemos várias solicitações" }, "common": { - "please_wait": "Aguarde" + "please_wait": "Aguarde", + "disconnect_you_from": "This will disconnect you from {{dappUrl}}", + "disconnect": "Disconnect" + }, + "tooltip_modal": { + "reward_rate": { + "title": "Reward rate", + "tooltip": "Expected yearly increase in the value of your stake, based on the reward rate over the past week." + } } } diff --git a/locales/languages/ru.json b/locales/languages/ru.json index 9c9e737b17a..d45d7385011 100644 --- a/locales/languages/ru.json +++ b/locales/languages/ru.json @@ -686,9 +686,8 @@ "suggest_transactions": "Предлагать транзакции для одобрения", "disconnect": "Отключиться", "disconnect_all": "Отключить все", - "reconnect_notice": "Если вы отключите свои счета от {{dappUrl}}, вам придется повторно подключиться, чтобы снова использовать их.", + "reconnect_notice": "If you disconnect from {{dappUrl}}, you’ll need to reconnect your accounts and networks to use this site again.", "disconnect_all_accounts": "Отключить все счета", - "disconnect_you_from": "Это отключит вас от {{dappUrl}}", "deceptive_site_ahead": "Впереди моешеннический сайт", "deceptive_site_desc": "Сайт, который вы пытаетесь посетить, небезопасен. Злоумышленники могут обманом заставить вас сделать что-то опасное.", "learn_more": "Подробнее", @@ -1066,7 +1065,12 @@ "simulation_details_description": "Включите эту опцию, чтобы спрогнозировать изменения баланса транзакций перед их подтверждением. Это не гарантирует окончательный результат ваших транзакций. ", "simulation_details_learn_more": "Узнайте подробнее.", "aes_crypto_test_form_title": "Криптовалюта AES — тестовая форма", - "aes_crypto_test_form_description": "Раздел разработан исключительно для тестирования E2E. Если его видно в вашем приложении, сообщите об этом в службу поддержки MetaMask." + "aes_crypto_test_form_description": "Раздел разработан исключительно для тестирования E2E. Если его видно в вашем приложении, сообщите об этом в службу поддержки MetaMask.", + "developer_options": { + "title": "Developer Options", + "generate_trace_test": "Generate Trace Test", + "generate_trace_test_desc": "Generate a Developer Test Sentry trace." + } }, "aes_crypto_test_form": { "generate_random_salt": "Генерировать случайный солт", @@ -1444,7 +1448,8 @@ "hex_data_copied": "Шестнадцатеричные данные скопированы в буфер обмена", "invalid_recipient": "Недействительный получатель", "invalid_recipient_description": "Проверьте адрес и убедитесь, что он действителен", - "swap_tokens": "Обменять токены" + "swap_tokens": "Обменять токены", + "fromWithColon": "From:" }, "custom_gas": { "total": "Итого", @@ -1731,6 +1736,7 @@ "continue": "Продолжить", "cancel": "Отмена", "approve": "Одобрить", + "update": "Update", "edit_network_details": "Изменить сведения о сети", "malicious_network_warning": "Вредоносный сетевой провайдер может дезинформировать о состоянии блокчейна и записывать ваши действия в сети. Добавляйте только те пользовательские сети, которым доверяете.", "security_link": "https://support.metamask.io/networks-and-sidechains/managing-networks/user-guide-custom-networks-and-sidechains/", @@ -1738,10 +1744,12 @@ "additional_network_information_title": "Дополнительная информация о сетях", "network_warning_desc": "Это сетевое подключение зависит от третьих сторон. Оно может быть менее надежным или позволять третьим лицам отслеживать активность.", "additonial_network_information_desc": "Некоторые из этих сетей являются зависимыми от третьих сторон. Соединения могут быть менее надежными или позволять третьим сторонам отслеживать активность.", + "connect_more_networks": "Connect more networks", "learn_more": "Подробнее", "learn_more_url": "https://support.metamask.io/networks-and-sidechains/managing-networks/the-risks-of-connecting-to-an-unknown-network/", "switch_network": "Переключиться на сеть", "switch": "Сменить", + "select_all": "Select all", "new_network": "Добавлена новая сеть", "network_name": "Сеть {{networkName}}", "network_added": " теперь доступна в селекторе сети.", @@ -1758,7 +1766,19 @@ "show_test_networks": "Показать тестнеты", "deprecated_goerli": "Из-за изменений протокола Ethereum: тестовая сеть Goerli может работать ненадежно и скоро будет выведена из эксплуатации.", "network_deprecated_title": "Эта сеть устарела", - "network_deprecated_description": "Сеть, к которой вы пытаетесь подключиться, больше не поддерживается в Metamask." + "network_deprecated_description": "Сеть, к которой вы пытаетесь подключиться, больше не поддерживается в Metamask.", + "edit_networks_title": "Edit networks" + }, + "permissions": { + "title_this_site_wants_to": "This site wants to:", + "title_dapp_url_wants_to": "{{dappUrl}} wants to:", + "title_dapp_url_has_approval_to": "{{dappUrl}} has approval to:", + "use_enabled_networks": "Use your enabled networks", + "wants_to_see_your_accounts": "See your accounts and suggest transactions", + "requesting_for": "Requesting for ", + "manage_permissions": "Manage Permissions", + "edit": "Edit", + "cancel": "Cancel" }, "select": { "cancel": "Отмена", @@ -2022,6 +2042,8 @@ "back_to_safety": "Назад в безопасное место" }, "notifications": { + "no_date": "Unknown", + "yesterday": "Yesterday", "staked": "В стейкинге", "received": "Получено", "unstaked": "Стейкинг отменен", @@ -2097,6 +2119,8 @@ "success_withdrawal_title": "Вывод средств завершен!", "error_title": "Ой, что-то пошло не так :/", "received_title": "Вы получили {{amount}} {{assetType}}", + "default_message_title": "New transaction notification", + "default_message_description": "Tap to view this transaction", "received_payment_title": "Получен мгновенный платеж", "pending_message": "Ожидает подтверждения", "pending_deposit_message": "Ожидание завершения внесения депозита", @@ -2112,12 +2136,12 @@ "cancelled_message": "Нажмите для просмотра этой транзакции", "received_message": "Нажмите для просмотра этой транзакции", "received_payment_message": "Вы получили {{amount}} DAI", - "prompt_title": "Включить push-уведомления", + "prompt_title": "Receive Push Notifications", "notifications_enabled_error_title": "Что-то пошло не так", "notifications_enabled_error_desc": "Нам не удалось включить уведомления. Повторите попытку позже.", - "prompt_desc": "Включите уведомления, чтобы MetaMask мог сообщить вам о получении вами ETH или подтверждении ваших транзакций.", - "prompt_ok": "Да", - "prompt_cancel": "Нет, спасибо", + "prompt_desc": "Turn on notifications from Settings to get important alerts on wallet activity and more.", + "prompt_ok": "Turn on", + "prompt_cancel": "Maybe later", "wc_connected_title": "Подключен к {{title}}", "wc_signed_title": "Подписано", "wc_sent_tx_title": "Отправленная транзакция", @@ -2798,13 +2822,8 @@ }, "switch_custom_network": { "title_existing_network": "Этот сайт хотел бы сменить сеть", - "title_enabled_network": "Этот сайт хочет:", "title_new_network": "Добавлена новая сеть", "switch_warning": "В результате этого сеть, выбранная в MetaMask, будет изменена на ранее добавленную:", - "use_enabled_networks": "Использовать ваши включенные сети", - "wants_to_see_your_accounts": "Просматривать ваши счета и предлагать транзакции", - "requesting_for": "Запрошено для ", - "edit": "Изменить", "add_network_and_give_dapp_permission_warning": "Вы добавляете эту сеть в MetaMask и даете разрешение {{dapp_origin}} на ее использование.", "available": "теперь доступно в селекторе сети.", "cancel": "Отмена", @@ -2896,7 +2915,7 @@ }, "transaction_review_eip1559": { "estimated_gas_fee": "Примерная плата за газ", - "suggested_gas_fee": "%{origin} рекомендуемой платы за газ", + "network_fee": "Network fee", "max_fee": "Максимальная комиссия", "total": "Итого", "max_amount": "Maкс. сумма", @@ -3136,6 +3155,7 @@ "ledger_reminder_message_step_four": "4. Включена опция «Местоположение» с использованием точных данных", "ledger_reminder_message_step_four_Androidv12plus": "4. Включена опция «устройства поблизости»", "ledger_reminder_message_step_five": "5. Режим «Не беспокоить» должен быть выключен", + "blind_signing_message": "6. Enable \"blind signing\" on your Ledger device.", "available_devices": "Доступные устройства", "retry": "Повтор", "continue": "Продолжить", @@ -3166,7 +3186,15 @@ "how_to_install_eth_webview_title": "Как установить приложение Ethereum", "nonce_too_low": "Одноразовый код слишком короткий", "nonce_too_low_error": "Установлен слишком короткий одноразовый код", - "select_accounts": "Выберите счет" + "select_accounts": "Select an account", + "select_hd_path": "Select HD Path", + "select_hd_path_description": "If you don't see the accounts you expect, try switching the HD path or current selected network.", + "ledger_live_path": "Ledger Live", + "ledger_legacy_path": "Legacy(MEW/MyCrypto)", + "ledger_bip44_path": "BIP44(e.g. MetaMask, Trezor)", + "ledger_legacy_label": " (legacy)", + "blind_sign_error": "Blind signing error", + "blind_sign_error_message": "Blind signing is not enabled on your Ledger device. Please enable it in the settings." }, "account_actions": { "edit_name": "Изменить имя счета", @@ -3220,7 +3248,21 @@ "error_description": "Не удалось установить {{snap}}." }, "stake": { - "stake": "Выполнить стейкинг" + "stake": "Выполнить стейкинг", + "stake_eth": "Stake ETH", + "your_earnings": "Your earnings", + "annual_rate": "Annual rate", + "lifetime_rewards": "Lifetime rewards", + "estimated_annual_earnings": "Estimated annual earnings", + "accessibility_labels": { + "stake_annual_rate_tooltip": "Annual rate tooltip" + }, + "estimated_annual_rewards": "Estimated annual rewards", + "metamask_pool": "MetaMask Pool", + "enter_amount": "Enter amount", + "review": "Review", + "not_enough_eth": "Not enough ETH", + "balance": "Balance" }, "default_settings": { "title": "Ваш кошелек готов", @@ -3269,6 +3311,14 @@ "title": "Мы заметили множество запросов" }, "common": { - "please_wait": "Пожалуйста, подождите" + "please_wait": "Пожалуйста, подождите", + "disconnect_you_from": "This will disconnect you from {{dappUrl}}", + "disconnect": "Disconnect" + }, + "tooltip_modal": { + "reward_rate": { + "title": "Reward rate", + "tooltip": "Expected yearly increase in the value of your stake, based on the reward rate over the past week." + } } } diff --git a/locales/languages/tl.json b/locales/languages/tl.json index 5409d37582e..931b7bdadca 100644 --- a/locales/languages/tl.json +++ b/locales/languages/tl.json @@ -449,10 +449,10 @@ "use_the_currency_symbol": "ginagamit ang simbolo ng salapi", "use_correct_symbol": "Tiyakin na ginagamit mo ang tamang simbolo bago magpatuloy", "chain_id_currently_used": "Ang ID ng Chain na ito ay kasalukuyang ginagamit ng", - "incorrect_network_name_warning": "According to our records, the network name may not correctly match this chain ID.", - "suggested_name": "Suggested name:", + "incorrect_network_name_warning": "Ayon sa aming mga rekord, maaaring hindi tumugma ang pangalan ng network sa ID ng chain na ito.", + "suggested_name": "Iminumungkahing pangalan:", "network_check_validation_desc": "binabawasan ang iyong tiyansa na kumonekta sa isang malicious o maling network.", - "cant_verify_custom_network_warning": "We can’t verify custom networks. To avoid malicious providers from recording your network activity, only add networks you trust.", + "cant_verify_custom_network_warning": "Hindi namin ma-verify ang mga custom na network. Para maiwasan ang mga mapaminsalang provider sa pagre-rekord ng iyong aktibidad sa network, idagdag lamang ang mga network na pinakakatiwalaan mo.", "nfts_autodetection_cta": "I-on ang pagtuklas ng NFT sa Mga Setting", "learn_more": "Matuto pa", "add_collectibles": "Mag-import ng mga NFT", @@ -501,10 +501,10 @@ "lists": "Mga Listahan ng Token", "hide_cta": "Itago ang token", "options": { - "view_on_portfolio": "View on Portfolio", + "view_on_portfolio": "Tingnan sa Portfolio", "view_on_block": "Tingnan sa block explorer", "token_details": "Mga detalye ng token", - "remove_token": "Remove token" + "remove_token": "Alisin ang token" } }, "activity_view": { @@ -551,7 +551,7 @@ "description_title": "Tulungan kaming mapahusay ang MetaMask", "description_content_1": "Nais naming mangalap ng data para sa batayang paggamit upang mapahusay ang MetaMask. Dapat mong malaman na hindi namin ibebenta ang data na iyong ibibigay rito.", "description_content_2": "Kapag kami ay nangangalap ng metrics, ito ay palaging...", - "description_content_3": "Learn how we protect your privacy while collecting usage data for your profile.", + "description_content_3": "Matuto kung paano namin pinoprotektahan ang iyong privacy habang nangongolekta ng data ng paggamit para sa iyong profile.", "checkbox": "Gagamitin namin ang data na ito upang matutunan paano ka nakikipag-ugnayan sa aming mga komunikasyon sa marketing. Maaari kaming magbahagi ng kaugnay na balita (tulad ng mga tampok ng produkto).", "action_description_1_prefix": "Pribado:", "action_description_2_prefix": "Pangkalahatan:", @@ -606,14 +606,14 @@ "billion_abbreviation": "B", "trillion_abbreviation": "T", "million_abbreviation": "M", - "token_details": "Token details", - "contract_address": "Contract address", - "token_list": "Token list", - "market_details": "Market details", + "token_details": "Mga detalye ng token", + "contract_address": "Address ng kontrata", + "token_list": "Listahan ng token", + "market_details": "Mga detalye ng market", "market_cap": "Market Cap", - "total_volume": "Total Volume (24h)", + "total_volume": "Kabuuang Volume (24h)", "volume_to_marketcap": "Volume / Market Cap", - "circulating_supply": "Circulating supply", + "circulating_supply": "Umiikot na supply", "all_time_high": "All time high", "all_time_low": "All time low", "fully_diluted": "Fully diluted" @@ -666,10 +666,10 @@ "accounts_title": "Mga Account", "connect_account_title": "Ikonekta ang account", "connect_accounts_title": "Ikonekta ang mga account", - "edit_accounts_title": "Edit accounts", + "edit_accounts_title": "I-edit ang mga account", "connected_accounts_title": "Mga konektadong account", "connect_description": "Ibahagi ang address ng iyong account, balanse, aktibidad, at payagan ang site na magsimula ng mga transaksyon.", - "select_accounts_description": "Select the account(s) to use on this site:", + "select_accounts_description": "Pumili ng (mga) account na gagamitin sa site na ito:", "connect_multiple_accounts": "Ikonekta ang maraming account", "connect_more_accounts": "Ikonekta ang higit pang mga account", "cancel": "Kanselahin", @@ -685,10 +685,9 @@ "address_balance_activity_permission": "Tingnan ang address, balanse ng account, at aktibidad", "suggest_transactions": "Magmungkahi ng mga transaksyong aaprubahan", "disconnect": "Idiskonekta", - "disconnect_all": "Disconnect all", - "reconnect_notice": "If you disconnect your accounts from {{dappUrl}}, you’ll need to reconnect to use them again.", + "disconnect_all": "Idiskonekta ang lahat", + "reconnect_notice": "If you disconnect from {{dappUrl}}, you’ll need to reconnect your accounts and networks to use this site again.", "disconnect_all_accounts": "Idiskonekta ang lahat ng account", - "disconnect_you_from": "This will disconnect you from {{dappUrl}}", "deceptive_site_ahead": "Papunta sa isang mapanlinlang na site", "deceptive_site_desc": "Ang site na iyong sinusubukang bisitahin ay hindi ligtas. Maaari kang linlangin ng mga umaatake na gumawa ng mapanganib na bagay.", "learn_more": "Matuto pa", @@ -706,7 +705,7 @@ "accounts_connected": "mga account na konektado.", "disconnected": "nadiskonekta.", "disconnected_all": "Nadiskonekta ang lahat ng account.", - "disconnected_from": "Disconnected from {{dappHostName}}", + "disconnected_from": "Nadiskonekta mula sa {{dappHostName}}", "nft_detection_enabled": "Pinagana ang autodetection ng NFT" }, "connect_qr_hardware": { @@ -717,13 +716,13 @@ "keystone": "Keystone", "ngravezero": "Ngrave Zero", "learnMore": "Matuto pa", - "buyNow": "Buy now", + "buyNow": "Bilhin Ngayon", "tutorial": "Pagtuturo", "description4": "Keystone (pagtuturo)", "description5": "1. I-unlock ang iyong Keystone", "description6": "2. I-tap ang ··· Menu, pagkatapos ay pumunta sa Sync", "button_continue": "Magpatuloy", - "hint_text": "Scan your hardware wallet to ", + "hint_text": "I-scan ang iyong wallet na hardware sa ", "purpose_connect": "kumonekta", "purpose_sign": "kumpirmahin ang transaksyon", "select_accounts": "Pumili ng Account" @@ -744,7 +743,7 @@ "enabling_profile_sync": "Pinapagana ang pag-sync ng profile...", "disabling_profile_sync": "Hindi pinapagana ang pag-sync ng profile...", "notifications_dismiss_modal": "I-dismiss", - "select_rpc_url": "Select RPC URL", + "select_rpc_url": "Pumili ng RPC URL", "title": "Mga Setting", "current_conversion": "Batayang Currency", "current_language": "Kasalukuyang Wika", @@ -826,7 +825,7 @@ "notifications_title": "Mga notipikasyon", "notifications_desc": "Pamahalaan ang mga notipikasyon mo", "allow_notifications": "Payagan ang mga notipikasyon", - "allow_notifications_desc": "Stay in the loop on what’s happening in your wallet with notifications. To use notifications, we use a profile to sync some settings across your devices.", + "allow_notifications_desc": "Manatiling may-alam sa nangyayari sa iyong wallet gamit ang mga notification. Para gamitin ang mga notification, gumagamit kami ng profile para i-sync ang ilang mga setting sa lahat ng iyong mga device.", "notifications_opts": { "customize_session_title": "I-customize ang mga notipikasyon mo", "customize_session_desc": "I-on ang mga uri ng notipikasyon na nais mong matanggap:", @@ -845,10 +844,10 @@ }, "contacts_title": "Mga Kontak", "contacts_desc": "Magdagdag, mag-edit, mag-alis, at mamahala ng iyong mga account", - "permissions_title": "Permissions", - "permissions_desc": "Manage the permissions given to sites and apps", - "no_permissions": "No permissions", - "no_permissions_desc": "If you connect an account to a site or an app, you’ll see it here.", + "permissions_title": "Mga Pahintulot", + "permissions_desc": "Pamahalaan ang mga pahintulot na ibinigay sa mga site at app", + "no_permissions": "Walang pahintulot", + "no_permissions_desc": "Kapag ikinonekta mo ang isang account sa isang site o app, makikita mo ito dito.", "security_title": "Seguridad at Pagkapribado", "back": "Bumalik", "security_desc": "Mga setting ng pagkapribado, MetaMetrics, pribadong key at Lihim na Parirala sa Pagbawi ng wallet", @@ -939,17 +938,17 @@ "popular": "Sikat", "delete": "Tanggalin", "account": "account", - "accounts": "accounts", + "accounts": "mga account", "network": "network", - "networks": "networks", + "networks": "mga network", "network_exists": "Naidagdag na ang network na ito.", - "unMatched_chain": "According to our records, this URL does not match a known provider for this chain ID.", - "unMatched_chain_name": "This chain ID doesn’t match the network name.", - "url_associated_to_another_chain_id": "This URL is associated with another chain ID.", - "chain_id_associated_with_another_network": "The information you have entered is associated with an existing chain ID. Update your information or", - "network_already_exist": "You already have a network with the same chain ID or RPC URL. Enter a new chain ID or RPC URL", - "edit_original_network": "edit the original network", - "find_the_right_one": "Find the right one on:", + "unMatched_chain": "Ayon sa aming mga rekord, hindi tumutugma ang URL na ito sa isang kilalang provider para sa ID ng chain na ito.", + "unMatched_chain_name": "Ang ID ng chain na ito ay hindi tugma sa pangalan ng network.", + "url_associated_to_another_chain_id": "Ang URL na ito ay nauugnay sa ibang ID ng chain.", + "chain_id_associated_with_another_network": "Ang impormasyon na iyong inilagay ay nauugnay sa isang umiiral na ID ng chain. I-update ang iyong impormasyon o", + "network_already_exist": "Mayroon ka nang network na may parehong ID ng chain o RPC URL. Maglagay ng bagong ID ng chain o URL", + "edit_original_network": "i-edit ang orihinal na network", + "find_the_right_one": "Hanapin ang tama sa:", "delete_metrics_title": "Tanggalin ang data ng MetaMetrics", "delete_metrics_description_part_one": "Buburahin nito ang historikal na", "delete_metrics_description_part_two": "MetaMetrics", @@ -1066,7 +1065,12 @@ "simulation_details_description": "I-on ito para gumawa ng pagtataya sa mga pagbabago sa balanse ng mga transaksyon bago mo kumpirmahin ang mga ito. Hindi nito iginagarantiya ang panghuling resulta ng iyong mga transaksyon. ", "simulation_details_learn_more": "Matuto pa.", "aes_crypto_test_form_title": "AES Crypto - Test Form", - "aes_crypto_test_form_description": "Bahagi na ekslusibong binuo para sa E2E testing. Kung ito ay nakikita sa iyong app, mangyaring i-ulat ito sa MetaMask support." + "aes_crypto_test_form_description": "Bahagi na ekslusibong binuo para sa E2E testing. Kung ito ay nakikita sa iyong app, mangyaring i-ulat ito sa MetaMask support.", + "developer_options": { + "title": "Developer Options", + "generate_trace_test": "Generate Trace Test", + "generate_trace_test_desc": "Generate a Developer Test Sentry trace." + } }, "aes_crypto_test_form": { "generate_random_salt": "Gumawa ng Random Salt", @@ -1179,7 +1183,7 @@ "text": "TEXT", "qr_code": "QR CODE", "hold_to_reveal_credential": "I-hold para ibunyag ang {{credentialName}}", - "reveal_credential": "Reveal {{credentialName}}", + "reveal_credential": "Ibunyag ang {{credentialName}}", "keep_credential_safe": "Panatilihing ligtas ang iyong {{credentialName}}", "srp_abbreviation_text": "SRP", "srp_text": "Lihim na Parirala sa Pagbawi", @@ -1306,14 +1310,14 @@ "error": "Error", "attempting_to_scan_with_wallet_locked": "Mukhang sinusubukan mong mag-scan ng QR code, kailangan mong i-unlock ang iyong wallet para magamit ito.", "attempting_sync_from_wallet_error": "Mukhang sinusubukan mong mag-sync sa extension. Para magawa ito, kakailanganin mong burahin ang iyong kasalukuyang wallet. \n\nSa oras na magbura o mag-install ka ng bagong bersyon ng app, piliin ang opsyong \"I-sync gamit ang MetaMask Extension\". Mahalaga! Bago burahin ang iyong wallet, tiyaking na-back up mo ang iyong Lihim na Parirala sa Pagbawi.", - "not_allowed_error_title": "Turn on camera access", - "not_allowed_error_desc": "To scan a QR code, you'll need to give MetaMask camera access from your device's settings menu.", + "not_allowed_error_title": "I-on ang access sa camera", + "not_allowed_error_desc": "Para mag-scan ng QR code, kailangan mong bigyan ang MetaMask ng access sa camera mula sa menu ng mga setting ng iyong device.", "unrecognized_address_qr_code_title": "Hindi nakikilalang QR Code", "unrecognized_address_qr_code_desc": "Paumanhin, ang QR code na ito ay hindi nauugnay sa isang address ng account o isang address ng kontrata.", "url_redirection_alert_title": "Bibisitahin mo ang isang panlabas na link", "url_redirection_alert_desc": "Maaaring gamitin ang mga link upang subukang manlinlang o mag-phish ng mga tao, kaya siguraduhing bisitahin lamang ang mga website na pinagkakatiwalaan mo.", "label": "Mag-scan ng QR code", - "open_settings": "Settings" + "open_settings": "Mga Setting" }, "action_view": { "cancel": "Kanselahin", @@ -1332,7 +1336,7 @@ "cancel": "Kanselahin", "save": "I-save", "speedup": "Pabilisin", - "sign_with_keystone": "Sign with hardware wallet", + "sign_with_keystone": "Pumirma gamit ang wallet na hardware", "sign_with_ledger": "Pumirma gamit ang Ledger", "from": "Mula kay/sa", "gas_fee": "Bayad sa gas", @@ -1406,7 +1410,7 @@ "token_id": "ID ng Token", "not_enough_for_gas": "Mayroon kang 0 {{ticker}} sa iyong account para bayaran ang mga transaksyon. Bumili ng {{ticker}} o magdeposito mula sa ibang account.", "send": "Magpadala", - "confirm_with_qr_hardware": "Confirm with hardware wallet", + "confirm_with_qr_hardware": "Kumpirmahin gamit ang wallet na hardware", "confirm_with_ledger_hardware": "Kumpirmahin gamit ang Ledger", "confirmed": "Nakumpirma", "pending": "Nakabinbin", @@ -1439,12 +1443,13 @@ "no_camera_permission": "Hindi awtorisado ang kamera. Mangyaring magbigay ng pahintulot at subukang muli", "invalid_qr_code_sign": "Di-wastong QR-code. Mangyaring suriin ang iyong hardware at subukang muli.", "no_camera_permission_android": "Kailangan mong bigyan ng access ang MetaMask sa iyong kamera upang magpatuloy. Maaaring kailanganin mo ring baguhin ang iyong mga setting ng sistema.", - "mismatched_qr_request_id": "Incongruent transaction data. Please use your hardware wallet to sign the QR code below and tap 'Get Signature'.", + "mismatched_qr_request_id": "Hindi tugmang data ng transaksyon. Mangyaring gamitin ang iyong wallet na hardware para lagdaan ang QR code sa ibaba at i-tap ang 'Kumuha ng Lagda'.", "fiat_conversion_not_available": "Hindi available ang mga palitan ng Fiat sa ngayon", "hex_data_copied": "Nakopya ang hex na data sa clipboard", "invalid_recipient": "Di-wastong tagatanggap", "invalid_recipient_description": "Tingnan ang address at tiyaking wasto ito", - "swap_tokens": "Ipagpalit ang mga token" + "swap_tokens": "Ipagpalit ang mga token", + "fromWithColon": "From:" }, "custom_gas": { "total": "Kabuuan", @@ -1640,8 +1645,8 @@ "import_wallet_label": "Naidagdag ang Account", "import_wallet_tip": "Ang lahat ng transaksyon sa hinaharap na gagawin mula sa device na ito ay may kasamang label na \"mula sa device na ito\" sa tabi ng timestamp. Para sa mga transaksyong may petsa bago idagdag ang account, hindi isasaad sa history na ito kung aling papalabas na transaksyon ang nagmula sa device na ito.", "sign_title_scan": "I-scan ", - "sign_title_device": "with your hardware wallet", - "sign_description_1": "After you have signed with your hardware wallet,", + "sign_title_device": "gamit ang iyong wallet na hardware", + "sign_description_1": "Pagkatapos mong pumirma gamit ang iyong wallet na hardware,", "sign_description_2": "i-tap ang Kumuha ng Lagda", "sign_get_signature": "Kumuha ng Lagda", "network_fee": "Bayad sa Network" @@ -1714,11 +1719,11 @@ "network_display_name": "Ipinapakitang pangalan", "network_chain_id": "ID ng Chain", "network_rpc_url": "URL ng Network", - "network_rpc_url_label": "Network RPC URL", - "new_default_network_url": "New default network URL", - "current_label": "Current", - "new_label": "New", - "review": "Review", + "network_rpc_url_label": "RPC URL ng Network", + "new_default_network_url": "Bagong URL ng default network", + "current_label": "Kasalukuyan", + "new_label": "Bago", + "review": "I-review", "view_details": "Tingnan ang mga detalye", "network_details": "Mga detalye ng network", "network_select_confirm_use_safe_check": "Ang pagpili ng Kumpirmahin ay magbubukas ng pagsusuri ng detalye ng network. Maaari mong i-off ang pagsusuri ng detalye ng network sa ", @@ -1731,6 +1736,7 @@ "continue": "Magpatuloy", "cancel": "Kanselahin", "approve": "Aprubahan", + "update": "Update", "edit_network_details": "I-edit ang mga detalye ng network", "malicious_network_warning": "Ang isang mapaminsalang network provider ay maaaring magsinungaling tungkol sa estado ng blockchain at itala ang iyong aktibidad sa network. Magdagdag lang ng mga custom na network na pinagkakatiwalaan mo.", "security_link": "https://support.metamask.io/networks-and-sidechains/managing-networks/user-guide-custom-networks-and-sidechains/", @@ -1738,10 +1744,12 @@ "additional_network_information_title": "Impormasyon sa mga Karagdagang Network", "network_warning_desc": "Ang koneksyon sa network na ito ay umaasa sa mga third party. Ang koneksyon na ito ay maaaring hindi gaanong maaasahan o binibigyang-daan ang mga third-party na subaybayan ang aktibidad.", "additonial_network_information_desc": "Ang ilan sa mga network na ito ay umaasa sa mga third party. Ang mga koneksyon na ito ay maaaring hindi gaanong maaasahan o binibigyang-daan ang mga third-party na mag-track ng aktibidad.", + "connect_more_networks": "Connect more networks", "learn_more": "Matuto pa", "learn_more_url": "https://support.metamask.io/networks-and-sidechains/managing-networks/the-risks-of-connecting-to-an-unknown-network/", "switch_network": "Lumipat sa network", "switch": "Lumipat", + "select_all": "Select all", "new_network": "Naidagdag ang bagong network", "network_name": "Ang {{networkName}} Network", "network_added": " ay available na ngayon sa network selector.", @@ -1751,14 +1759,26 @@ "add_other_network_here": "dito.", "you_can": "O maaari kang", "add_network": "magdagdag pa ng mga network nang manu-mano.", - "add_specific_network": "Add {{network_name}}", + "add_specific_network": "Idagdag ang {{network_name}}", "select_network": "Pumili ng network", "enabled_networks": "Naka-enable na mga network", "additional_networks": "Mga karagdagang network", "show_test_networks": "Ipakita ang mga test network", "deprecated_goerli": "Dahil sa mga pagbabago sa protocol ng Ethereum: Ang Goerli test network ay maaaring hindi gumana tulad ng inaasahan at malapit nang ihinto ang paggamit.", "network_deprecated_title": "Ang network na ito ay hindi na suportado", - "network_deprecated_description": "Ang network na sinusubukan mong ikonekta ay hindi na suportado ng Metamask." + "network_deprecated_description": "Ang network na sinusubukan mong ikonekta ay hindi na suportado ng Metamask.", + "edit_networks_title": "Edit networks" + }, + "permissions": { + "title_this_site_wants_to": "This site wants to:", + "title_dapp_url_wants_to": "{{dappUrl}} wants to:", + "title_dapp_url_has_approval_to": "{{dappUrl}} has approval to:", + "use_enabled_networks": "Use your enabled networks", + "wants_to_see_your_accounts": "See your accounts and suggest transactions", + "requesting_for": "Requesting for ", + "manage_permissions": "Manage Permissions", + "edit": "Edit", + "cancel": "Cancel" }, "select": { "cancel": "Kanselahin", @@ -2022,10 +2042,12 @@ "back_to_safety": "Bumalik sa ligtas" }, "notifications": { + "no_date": "Unknown", + "yesterday": "Yesterday", "staked": "Nai-stake", "received": "Natanggap", "unstaked": "Na-unstake", - "mark_all_as_read": "Mark all as read", + "mark_all_as_read": "Markahan ang lahat bilang nabasa na", "to": "Sa/Kay", "rate": "Halaga (kasama ang bayarin)", "unstaking_requested": "Hiniling ang pag-unstake", @@ -2038,7 +2060,7 @@ "swap": "Nai-swap", "sent": "Ipinadala sa {{address}}", "menu_item_title": { - "metamask_swap_completed": "Swapped {{symbolIn}} for {{symbolOut}}", + "metamask_swap_completed": "Nai-swap ang {{symbolIn}} sa {{symbolOut}}", "erc20_sent": "Ipinadala sa {{address}}", "erc20_received": "Natanggap mula sa {{address}}", "eth_sent": "Ipinadala sa {{address}}", @@ -2065,7 +2087,7 @@ "title_untake_ready": "Handa na ang pag-withdraw", "title_stake": "Nag-stake ng {{symbol}}", "title_unstake_completed": "Nakumpleto ang pag-unstake", - "title_swapped": "Swapped {{symbolIn}} to {{symbolOut}}", + "title_swapped": "Nai-swap ang {{symbolIn}} sa {{symbolOut}}", "label_address_to": "Sa/Kay", "label_address_from": "Mula sa/kay", "label_address_to_you": "Sa (Iyo)", @@ -2097,6 +2119,8 @@ "success_withdrawal_title": "Tapos nang Mag-withdraw!", "error_title": "Ooops, nagkaproblema :/", "received_title": "Nakatanggap ka ng {{amount}}{{assetType}}", + "default_message_title": "New transaction notification", + "default_message_description": "Tap to view this transaction", "received_payment_title": "Natanggap ang instant na bayad", "pending_message": "Naghihintay ng kumpirmasyon", "pending_deposit_message": "Hinihintay na matapos magdeposito", @@ -2112,12 +2136,12 @@ "cancelled_message": "Mag-tap para tingnan ang transaksyong ito", "received_message": "Mag-tap para tingnan ang transaksyong ito", "received_payment_message": "Nakatanggap ka ng {{amount}} DAI", - "prompt_title": "Paganahin ang Mga Push Notification", + "prompt_title": "Receive Push Notifications", "notifications_enabled_error_title": "Nagkaproblema", "notifications_enabled_error_desc": "Hindi namin mapagana ang mga notipikasyon. Mangyaring subukan muli mamaya.", - "prompt_desc": "Paganahin ang mga notification para maipaalam sa iyo ng MetaMask kapag nakatanggap ka ng ETH o kapag nakumpirma na ang iyong mga transaksyon.", - "prompt_ok": "Oo", - "prompt_cancel": "Huwag na lang", + "prompt_desc": "Turn on notifications from Settings to get important alerts on wallet activity and more.", + "prompt_ok": "Turn on", + "prompt_cancel": "Maybe later", "wc_connected_title": "Nakakonekta sa {{title}}", "wc_signed_title": "Nilagdaan", "wc_sent_tx_title": "Transaksyon ng pagpapadala", @@ -2144,9 +2168,9 @@ "transaction_id_copied_to_clipboard": "Nakopya ang ID ng transaksyon sa clipboard", "activation_card": { "title": "I-on ang mga notipikasyon", - "description_1": "Stay in the loop on what's happening in your wallet with notifications.", + "description_1": "Manatiling may-alam sa nangyayari sa iyong wallet gamit ang mga notification.", "description_2": "Para gamitin ang tampok na ito, kami ay gagawa ng isang hindi kilalang ID para sa iyong account. Ginagamit lang ito para sa pag-sync ng data sa MetaMask at hindi inuugnay ang iyong mga aktibidad o iba pang mga pagkakakilanlan, tinitiyak ang iyong pagkapribado.", - "learn_more": "Learn how we protect your privacy while using this feature.", + "learn_more": "Matuto kung paano namin pinoprotektahan ang iyong privacy habang ginagamit ang feature na ito.", "manage_preferences_1": "Maaari mong i-off ang mga notipikasypon anumang oras sa ", "manage_preferences_2": "Mga Setting > Mga notipikasyon.", "cancel": "Kanselahin", @@ -2539,7 +2563,7 @@ "sale_completed_description": "Matagumpay ang iyong order!.", "sale_pending_title": "Pinoproseso ang pagbenta ng {{currency}}", "sale_pending_description": "Pinoproseso na ang iyong order.", - "no_date": "Unknown" + "no_date": "Hindi Alam" } }, "swaps": { @@ -2798,14 +2822,9 @@ }, "switch_custom_network": { "title_existing_network": "Gusto ng site na ito na lumipat ng network", - "title_enabled_network": "This site wants to:", "title_new_network": "Nagdagdag ng bagong network", "switch_warning": "Ililipat nito ang napiling network sa loob ng MetaMask sa dating idinagdag na network:", - "use_enabled_networks": "Use your enabled networks", - "wants_to_see_your_accounts": "See your accounts and suggest transactions", - "requesting_for": "Requesting for ", - "edit": "Edit", - "add_network_and_give_dapp_permission_warning": "You're adding this network to MetaMask and giving {{dapp_origin}} permission to use it.", + "add_network_and_give_dapp_permission_warning": "Idinagdagdag mo ang network na ito sa MetaMask at binibigyan ng pahintulot ang {{dapp_origin}} na gamitin ito.", "available": "ay available na ngayon sa network selector.", "cancel": "Kanselahin", "switch": "Lumipat ng Network" @@ -2896,7 +2915,7 @@ }, "transaction_review_eip1559": { "estimated_gas_fee": "Tinantyang bayad sa gas", - "suggested_gas_fee": "%{origin} iminungkahing bayad sa gas", + "network_fee": "Network fee", "max_fee": "Pinakamataas na bayad", "total": "Kabuuan", "max_amount": "Pinakamataas na halaga", @@ -3129,17 +3148,18 @@ "cannot_get_account": "Hindi makuha ang account", "connect_ledger": "Ikonekta ang Ledger", "looking_for_device": "Naghahanap ng device", - "ledger_reminder_message": "Please make sure your Ledger device is:", - "ledger_reminder_message_step_one": "1. Unlock your Ledger device", + "ledger_reminder_message": "Siguraduhin na ang iyong Ledger device ay:", + "ledger_reminder_message_step_one": "1. I-unlock ang iyong Ledger device", "ledger_reminder_message_step_two": "2. I-install at buksan ang Ethereum app", "ledger_reminder_message_step_three": "3. I-enable ang Bluetooth", "ledger_reminder_message_step_four": "4. Ang Lokasyon ay pinapagana na naka-on ang eksaktong lokasyon", "ledger_reminder_message_step_four_Androidv12plus": "4. Pinapagana ang mga malapit na device", "ledger_reminder_message_step_five": "5. Dapat i-off ang do not disturb", + "blind_signing_message": "6. Enable \"blind signing\" on your Ledger device.", "available_devices": "Available na mga device", "retry": "Subukang muli", "continue": "Magpatuloy", - "confirm_transaction_on_ledger": "Confirm transaction on your Ledger", + "confirm_transaction_on_ledger": "Kumpirmahin ang transaksyon sa iyong Ledger", "bluetooth_enabled_message": "Siguraduhing gumagana ang Bluetooth", "device_unlocked_message": "Naka-unlock ang device", "ledger_disconnected": "Nadiskonekta ang iyong device", @@ -3162,11 +3182,19 @@ "not_supported": "Hindi sinusuportahan ang operasyon", "not_supported_error": "Ang bersyon 4 lamang ng naka-type na pagpirma ng data ang sinusuportahan.", "error_during_connection": "May naganap na hindi kilalang error", - "error_during_connection_message": "There's been a slight problem connecting your Ledger device, tap 'Retry' below to give this another go. Sometimes this occurs due to the ETH app on your Ledger device being open at the start of the pairing process with MetaMask Mobile.", + "error_during_connection_message": "Nagkaroon ng kaunting problema sa pagkonekta sa iyong Ledger device, i-tap ang 'Subukang muli' sa ibaba para subukan ulit ito. Minsan nangyayari ito dahil sa pagiging bukas ng ETH app sa iyong Ledger device sa simula ng proseso ng pagpapares sa MetaMask Mobile.", "how_to_install_eth_webview_title": "Paano i-install ang App ng Ethereum", "nonce_too_low": "Masyadong mababa ang nonce", "nonce_too_low_error": "Masyadong mababa ang itinakdang nonce", - "select_accounts": "Select an Account" + "select_accounts": "Select an account", + "select_hd_path": "Select HD Path", + "select_hd_path_description": "If you don't see the accounts you expect, try switching the HD path or current selected network.", + "ledger_live_path": "Ledger Live", + "ledger_legacy_path": "Legacy(MEW/MyCrypto)", + "ledger_bip44_path": "BIP44(e.g. MetaMask, Trezor)", + "ledger_legacy_label": " (legacy)", + "blind_sign_error": "Blind signing error", + "blind_sign_error_message": "Blind signing is not enabled on your Ledger device. Please enable it in the settings." }, "account_actions": { "edit_name": "I-edit ang pangalan ng account", @@ -3220,7 +3248,21 @@ "error_description": "Nabigo ang pag-install ng {{snap}}." }, "stake": { - "stake": "Mag-stake" + "stake": "Mag-stake", + "stake_eth": "Stake ETH", + "your_earnings": "Your earnings", + "annual_rate": "Annual rate", + "lifetime_rewards": "Lifetime rewards", + "estimated_annual_earnings": "Estimated annual earnings", + "accessibility_labels": { + "stake_annual_rate_tooltip": "Annual rate tooltip" + }, + "estimated_annual_rewards": "Estimated annual rewards", + "metamask_pool": "MetaMask Pool", + "enter_amount": "Enter amount", + "review": "Review", + "not_enough_eth": "Not enough ETH", + "balance": "Balance" }, "default_settings": { "title": "Handa na ang iyong Wallet", @@ -3239,7 +3281,7 @@ "title_off": "I-off ang batayang kapakinabangan", "description_off": "Ibig sabihin nito hindi mo lubos na mao-optimize ang iyong oras sa MetaMask. Ang mga batayang tampok (tulad ng mga detalye ng token, optimal na settings ng gas, at iba pa) ay hindi magiging available para sa iyo.", "title_on": "I-on ang batayang kapakinabangan", - "description_on": "To optimize your time on MetaMask, you’ll need to turn on this feature. Basic functions (like token details, optimal gas settings, notifications, and others) are important to the web3 experience.", + "description_on": "Para ma-optimize ang iyong oras sa MetaMask, kailangan mong i-on ang feature na ito. Ang mga basic na function (tulad ng mga detalye ng token, optimal na mga setting ng gas, mga notification at iba pa) ay mahalaga sa karanasan sa web3.", "checkbox_label": "Nauunawaan ko at nais kong magpatuloy", "buttons": { "cancel": "Kanselahin", @@ -3269,6 +3311,14 @@ "title": "Napansin namin ang maraming hiling" }, "common": { - "please_wait": "Mangyaring maghintay" + "please_wait": "Mangyaring maghintay", + "disconnect_you_from": "This will disconnect you from {{dappUrl}}", + "disconnect": "Disconnect" + }, + "tooltip_modal": { + "reward_rate": { + "title": "Reward rate", + "tooltip": "Expected yearly increase in the value of your stake, based on the reward rate over the past week." + } } } diff --git a/locales/languages/tr.json b/locales/languages/tr.json index 5b871cfc19a..5142c9acedf 100644 --- a/locales/languages/tr.json +++ b/locales/languages/tr.json @@ -504,7 +504,7 @@ "view_on_portfolio": "Portfolio'da görüntüle", "view_on_block": "Blok gezgininde görüntüle", "token_details": "Token bilgileri", - "remove_token": "Tokeni kaldır" + "remove_token": "Token'i kaldır" } }, "activity_view": { @@ -611,7 +611,7 @@ "token_list": "Token listesi", "market_details": "Piyasa bilgileri", "market_cap": "Piyasa Değeri", - "total_volume": "Toplam Hacim (24sa)", + "total_volume": "Toplam Hacim (24 sa)", "volume_to_marketcap": "Hacim / Piyasa Değeri", "circulating_supply": "Dolaşımdaki arz", "all_time_high": "Tüm zamanların en yükseği", @@ -686,9 +686,8 @@ "suggest_transactions": "Onaylanacak işlemleri önerme", "disconnect": "Bağlantıyı kes", "disconnect_all": "Tümünün bağlantısını kes", - "reconnect_notice": "Hesapların {{dappUrl}} ile bağlantısını keserseniz onları tekrar kullanmak için tekrar bağlamanız gerekir.", + "reconnect_notice": "If you disconnect from {{dappUrl}}, you’ll need to reconnect your accounts and networks to use this site again.", "disconnect_all_accounts": "Tüm hesapların bağlantısını kes", - "disconnect_you_from": "{{dappUrl}} ile bağlantınızı kesecekler", "deceptive_site_ahead": "Aldatıcı siteye gidiliyor", "deceptive_site_desc": "Ziyaret etmeye çalıştığınız sayfa güvenli değil. Saldırganlar tehlikeli bir şey yapmanız konusunda sizi aldatabilir.", "learn_more": "Daha fazla bilgi", @@ -717,7 +716,7 @@ "keystone": "Ana İlke", "ngravezero": "Ngrave Zero", "learnMore": "Daha fazla bilgi edinin", - "buyNow": "Şimdi al", + "buyNow": "Şimdi satın al", "tutorial": "Öğretici", "description4": "Temel (öğretici)", "description5": "1. Kilit Taşınızı Açın", @@ -948,7 +947,7 @@ "url_associated_to_another_chain_id": "Bu URL adresi başka bir zincir kimliği ile ilişkilidir.", "chain_id_associated_with_another_network": "Girdiğiniz bilgiler mevcut bir zincir kimliği ile ilişkilidir. Bilgilerinizi güncelleyin veya", "network_already_exist": "Zaten aynı zincir kimliği veya RPC URL adresi ile bir ağınız var. Yeni bir zincir kimliği veya RPC URL adresi girin", - "edit_original_network": "orijinal ağı düzenle", + "edit_original_network": "orijinal ağı düzenleyin", "find_the_right_one": "Doğrusunu şurada bulabilirsiniz:", "delete_metrics_title": "MetaMetrics verilerini sil", "delete_metrics_description_part_one": "Bu işlem cüzdanınızla ilişkili geçmiş", @@ -1066,7 +1065,12 @@ "simulation_details_description": "İşlemleri onaylamadan önce işlemlerin neden olacağı bakiye değişikliklerini tahmin etmek için bunu açın. İşlemlerinizin nihai sonucunu garanti etmez. ", "simulation_details_learn_more": "Daha fazla bilgi edinin.", "aes_crypto_test_form_title": "AES Crypto - Test Biçimi", - "aes_crypto_test_form_description": "Bu kısım özel olarak E2E testi için geliştirilmiştir. Bu, uygulamanızda görünüyorsa lütfen MetaMask destek bölümüne bildirin." + "aes_crypto_test_form_description": "Bu kısım özel olarak E2E testi için geliştirilmiştir. Bu, uygulamanızda görünüyorsa lütfen MetaMask destek bölümüne bildirin.", + "developer_options": { + "title": "Developer Options", + "generate_trace_test": "Generate Trace Test", + "generate_trace_test_desc": "Generate a Developer Test Sentry trace." + } }, "aes_crypto_test_form": { "generate_random_salt": "Rastgele Tuz Oluştur", @@ -1307,7 +1311,7 @@ "attempting_to_scan_with_wallet_locked": "Görünüşe göre bir QR kodunu taramaya çalışıyorsunuz, bunu kullanmak için cüzdanınızın kilidini açmanız gerekecek.", "attempting_sync_from_wallet_error": "Görünüşe göre uzantı ile senkronize etmeye çalışıyorsunuz. Bunu yapabilmek için mevcut cüzdanınızı silmeniz gerekecek. \n\nUygulamayı sildiğinizde veya uygulamanın yeni bir sürümünü tekrar yüklediğinizde \"MetaMask Uzantısı ile Senkronize Et\" seçeneğini seçin. Önemli! Cüzdanınızı silmeden önce Gizli Kurtarma İfadenizi yedeklediğinizden emin olun.", "not_allowed_error_title": "Kamera erişimini aç", - "not_allowed_error_desc": "Bir QR kodunu taramak için cihazınızın ayarlar menüsünden MetaMask'e kamera erişimi vermeniz gerekecek.", + "not_allowed_error_desc": "Bir QR kodunu taramak için cihazınızın ayarlar menüsünden MetaMask'e kamera erişimi vermeniz gerekecektir.", "unrecognized_address_qr_code_title": "Tanınmayan QR kodu", "unrecognized_address_qr_code_desc": "Üzgünüz, bu QR kodu bir hesap adresi ya da bir iletişim adresiyle ilişkilendirilmemiş.", "url_redirection_alert_title": "Harici bir bağlantıyı ziyaret etmek üzeresiniz", @@ -1439,12 +1443,13 @@ "no_camera_permission": "Kamera izni yok. Lütfen izin verin ve tekrar deneyin", "invalid_qr_code_sign": "Geçersiz QR kodu. Lütfen donanımınızı kontrol edin ve tekrar deneyin.", "no_camera_permission_android": "Devam etmek için kameranıza MetaMask erişim izni vermeniz gerekiyor. Ayrıca sistem ayarlarınızı değiştirmeniz gerekebilir.", - "mismatched_qr_request_id": "Uyumsuz işlem verisi. Lütfen aşağıdaki QR koduyla imzalamak için donanım cüzdanınızı kullanın ve 'İmzayı Al' tuşuna tıklayın.", + "mismatched_qr_request_id": "Uyumsuz işlem verisi. Lütfen aşağıdaki QR koduyla imzalamak için donanım cüzdanınızı kullanın ve \"İmzayı Al\" tuşuna tıklayın.", "fiat_conversion_not_available": "Fiat para dönüştürmeleri şu anda kullanılmıyor", "hex_data_copied": "On altılı veriler panoya kopyalandı", "invalid_recipient": "Alıcı geçersiz", "invalid_recipient_description": "Adresi kontrol edin ve geçerli olduğundan emin olun", - "swap_tokens": "Token swap işlemi yapın" + "swap_tokens": "Token swap işlemi yapın", + "fromWithColon": "From:" }, "custom_gas": { "total": "Toplam", @@ -1731,6 +1736,7 @@ "continue": "Devam et", "cancel": "İptal et", "approve": "Onayla", + "update": "Update", "edit_network_details": "Ağ bilgilerini düzenle", "malicious_network_warning": "Kötü amaçlı bir ağ sağlayıcı blokzincirinin durumu hakkında yalan söyleyebilir ve ağ aktivitenizi kaydedebilir. Sadece güvendiğiniz özel ağları ekleyin.", "security_link": "https://support.metamask.io/networks-and-sidechains/managing-networks/user-guide-custom-networks-and-sidechains/", @@ -1738,10 +1744,12 @@ "additional_network_information_title": "Diğer Ağ Bilgileri", "network_warning_desc": "Bu ağ bağlantısı üçüncü taraflara dayalıdır. Bu bağlantı daha az güvenilir olabilir ya da üçüncü tarafların aktiviteyi takip etmesini sağlayabilir.", "additonial_network_information_desc": "Bu ağların bazıları üçüncü taraflara dayalıdır. Bağlantılar daha az güvenilir olabilir veya üçüncü tarafların aktiviteleri takip etmesine olanak sağlayabilir.", + "connect_more_networks": "Connect more networks", "learn_more": "Daha fazlasını öğrenin", "learn_more_url": "https://support.metamask.io/networks-and-sidechains/managing-networks/the-risks-of-connecting-to-an-unknown-network/", "switch_network": "Ağ değiştir", "switch": "Değiştir", + "select_all": "Select all", "new_network": "Yeni ağ eklendi", "network_name": "{{networkName}} Ağı", "network_added": " artık ağ seçicide mevcut.", @@ -1758,7 +1766,19 @@ "show_test_networks": "Test ağlarını göster", "deprecated_goerli": "Ethereum'da yaşanan protokol değişikliklerinden dolayı: Goerli test ağı güvenilir bir şekilde çalışmayabilir ve yakında kullanım dışı olacak.", "network_deprecated_title": "Bu ağ artık kullanılmıyor", - "network_deprecated_description": "Bağlanmaya çalıştığınız ağ artık Metamask'te desteklenmiyor." + "network_deprecated_description": "Bağlanmaya çalıştığınız ağ artık Metamask'te desteklenmiyor.", + "edit_networks_title": "Edit networks" + }, + "permissions": { + "title_this_site_wants_to": "This site wants to:", + "title_dapp_url_wants_to": "{{dappUrl}} wants to:", + "title_dapp_url_has_approval_to": "{{dappUrl}} has approval to:", + "use_enabled_networks": "Use your enabled networks", + "wants_to_see_your_accounts": "See your accounts and suggest transactions", + "requesting_for": "Requesting for ", + "manage_permissions": "Manage Permissions", + "edit": "Edit", + "cancel": "Cancel" }, "select": { "cancel": "İptal", @@ -2022,6 +2042,8 @@ "back_to_safety": "Güvenliğe geri dön" }, "notifications": { + "no_date": "Unknown", + "yesterday": "Yesterday", "staked": "Stake edildi", "received": "Alınan", "unstaked": "Unstake edildi", @@ -2097,6 +2119,8 @@ "success_withdrawal_title": "Para Çekme İşlemi Tamamlandı!", "error_title": "Hay aksi, bir şeyler ters gitti :/", "received_title": "{{amount}} {{assetType}} aldınız", + "default_message_title": "New transaction notification", + "default_message_description": "Tap to view this transaction", "received_payment_title": "Anında ödeme alındı", "pending_message": "Onay bekleniyor", "pending_deposit_message": "Yatırma işleminin tamamlanması bekleniyor", @@ -2112,12 +2136,12 @@ "cancelled_message": "Bu işlemi görüntülemek için dokunun", "received_message": "Bu işlemi görüntülemek için dokunun", "received_payment_message": "{{amount}} DAI aldınız", - "prompt_title": "Anlık Bildirimleri Etkinleştir", + "prompt_title": "Receive Push Notifications", "notifications_enabled_error_title": "Bir şeyler ters gitti", "notifications_enabled_error_desc": "Bildirimleri etkinleştiremedik. Lütfen daha sonra tekrar deneyin.", - "prompt_desc": "MetaMask'ın ne zaman ETH aldığınızı veya işlemlerinizin ne zaman onaylandığını size bildirebilmesi için bildirimleri etkinleştirin.", - "prompt_ok": "Evet", - "prompt_cancel": "Hayır, teşekkürler", + "prompt_desc": "Turn on notifications from Settings to get important alerts on wallet activity and more.", + "prompt_ok": "Turn on", + "prompt_cancel": "Maybe later", "wc_connected_title": "{{title}} alanına bağlanıldı", "wc_signed_title": "İmzalandı", "wc_sent_tx_title": "İşlem gönderdi", @@ -2798,13 +2822,8 @@ }, "switch_custom_network": { "title_existing_network": "Bu site ağ değiştirmek istiyor", - "title_enabled_network": "Bu site şunları yapmak istiyor:", "title_new_network": "Yeni ağ eklendi", "switch_warning": "Bu işlem, MetaMask'te seçili bir ağı önceden eklenmiş bir ağ ile değiştirecek:", - "use_enabled_networks": "Etkin ağlarınızı kullanmak", - "wants_to_see_your_accounts": "Hesaplarınızı görmek ve işlem önermek", - "requesting_for": "Talep: ", - "edit": "Düzenle", "add_network_and_give_dapp_permission_warning": "Bu ağı MetaMask'e ekliyor ve {{dapp_origin}} uygulamasına bunu kullanma izni veriyorsunuz.", "available": "artık ağ seçicide mevcut.", "cancel": "İptal", @@ -2896,7 +2915,7 @@ }, "transaction_review_eip1559": { "estimated_gas_fee": "Tahmini gaz ücreti", - "suggested_gas_fee": "%{origin} önerilen gaz ücreti", + "network_fee": "Network fee", "max_fee": "Maks. ücret", "total": "Toplam", "max_amount": "Maks. tutar", @@ -3136,6 +3155,7 @@ "ledger_reminder_message_step_four": "4. Konum, hassas konum kullan özelliği açık olarak etkinleştirildi", "ledger_reminder_message_step_four_Androidv12plus": "4. Yakındaki cihazlar etkinleştirildi", "ledger_reminder_message_step_five": "5. Rahatsız etme modu kapatılmalı", + "blind_signing_message": "6. Enable \"blind signing\" on your Ledger device.", "available_devices": "Kullanılabilir cihazlar", "retry": "Tekrar Dene", "continue": "Devam", @@ -3166,7 +3186,15 @@ "how_to_install_eth_webview_title": "Ethereum uygulaması nasıl yüklenir?", "nonce_too_low": "Nonce çok düşük", "nonce_too_low_error": "Ayarlanan nonce çok düşük", - "select_accounts": "Hesap Seç" + "select_accounts": "Select an account", + "select_hd_path": "Select HD Path", + "select_hd_path_description": "If you don't see the accounts you expect, try switching the HD path or current selected network.", + "ledger_live_path": "Ledger Live", + "ledger_legacy_path": "Legacy(MEW/MyCrypto)", + "ledger_bip44_path": "BIP44(e.g. MetaMask, Trezor)", + "ledger_legacy_label": " (legacy)", + "blind_sign_error": "Blind signing error", + "blind_sign_error_message": "Blind signing is not enabled on your Ledger device. Please enable it in the settings." }, "account_actions": { "edit_name": "Hesap adını düzenleyin", @@ -3220,7 +3248,21 @@ "error_description": "{{snap}} yüklemesi başarısız oldu." }, "stake": { - "stake": "Pay" + "stake": "Pay", + "stake_eth": "Stake ETH", + "your_earnings": "Your earnings", + "annual_rate": "Annual rate", + "lifetime_rewards": "Lifetime rewards", + "estimated_annual_earnings": "Estimated annual earnings", + "accessibility_labels": { + "stake_annual_rate_tooltip": "Annual rate tooltip" + }, + "estimated_annual_rewards": "Estimated annual rewards", + "metamask_pool": "MetaMask Pool", + "enter_amount": "Enter amount", + "review": "Review", + "not_enough_eth": "Not enough ETH", + "balance": "Balance" }, "default_settings": { "title": "Cüzdanınız hazır", @@ -3269,6 +3311,14 @@ "title": "Birden fazla talep fark ettik" }, "common": { - "please_wait": "Lütfen bekleyin" + "please_wait": "Lütfen bekleyin", + "disconnect_you_from": "This will disconnect you from {{dappUrl}}", + "disconnect": "Disconnect" + }, + "tooltip_modal": { + "reward_rate": { + "title": "Reward rate", + "tooltip": "Expected yearly increase in the value of your stake, based on the reward rate over the past week." + } } } diff --git a/locales/languages/vi.json b/locales/languages/vi.json index abe4f037122..2cb8f85eef7 100644 --- a/locales/languages/vi.json +++ b/locales/languages/vi.json @@ -686,9 +686,8 @@ "suggest_transactions": "Đề xuất các giao dịch cần chấp thuận", "disconnect": "Ngắt kết nối", "disconnect_all": "Ngắt kết nối tất cả", - "reconnect_notice": "Nếu bạn ngắt kết nối tài khoản của mình khỏi {{dappUrl}}, bạn sẽ cần kết nối lại để sử dụng lại.", + "reconnect_notice": "If you disconnect from {{dappUrl}}, you’ll need to reconnect your accounts and networks to use this site again.", "disconnect_all_accounts": "Ngắt kết nối tất cả các tài khoản", - "disconnect_you_from": "Hành động này sẽ ngắt kết nối bạn khỏi {{dappUrl}}", "deceptive_site_ahead": "Phía trước là trang web lừa đảo", "deceptive_site_desc": "Trang web mà bạn đang cố gắng truy cập không an toàn. Những kẻ tấn công có thể lừa bạn thực hiện một hành động nguy hiểm.", "learn_more": "Tìm hiểu thêm", @@ -1066,7 +1065,12 @@ "simulation_details_description": "Bật tính năng này để ước tính thay đổi số dư của các giao dịch trước khi bạn xác nhận. Điều này không đảm bảo cho kết quả cuối cùng của giao dịch. ", "simulation_details_learn_more": "Tìm hiểu thêm.", "aes_crypto_test_form_title": "Tiền mã hóa AES - Mẫu thử nghiệm", - "aes_crypto_test_form_description": "Phần được phát triển dành riêng cho thử nghiệm E2E. Nếu bạn thấy phần này trong ứng dụng, vui lòng báo cáo với bộ phận hỗ trợ của MetaMask." + "aes_crypto_test_form_description": "Phần được phát triển dành riêng cho thử nghiệm E2E. Nếu bạn thấy phần này trong ứng dụng, vui lòng báo cáo với bộ phận hỗ trợ của MetaMask.", + "developer_options": { + "title": "Developer Options", + "generate_trace_test": "Generate Trace Test", + "generate_trace_test_desc": "Generate a Developer Test Sentry trace." + } }, "aes_crypto_test_form": { "generate_random_salt": "Tạo Salt ngẫu nhiên", @@ -1444,7 +1448,8 @@ "hex_data_copied": "Đã sao chép dữ liệu thập lục phân vào bộ nhớ đệm", "invalid_recipient": "Người nhận không hợp lệ", "invalid_recipient_description": "Kiểm tra địa chỉ và đảm bảo địa chỉ hợp lệ", - "swap_tokens": "Hoán đổi token" + "swap_tokens": "Hoán đổi token", + "fromWithColon": "From:" }, "custom_gas": { "total": "Tổng", @@ -1731,6 +1736,7 @@ "continue": "Tiếp tục", "cancel": "Hủy", "approve": "Chấp thuận", + "update": "Update", "edit_network_details": "Chỉnh sửa thông tin mạng", "malicious_network_warning": "Một nhà cung cấp mạng độc hại có thể nói dối về trạng thái của chuỗi khối và ghi lại hoạt động của bạn trên mạng. Chỉ thêm các mạng tùy chỉnh mà bạn tin tưởng.", "security_link": "https://support.metamask.io/networks-and-sidechains/managing-networks/user-guide-custom-networks-and-sidechains/", @@ -1738,10 +1744,12 @@ "additional_network_information_title": "Thông tin mạng bổ sung", "network_warning_desc": "Kết nối mạng này dựa vào các bên thứ ba. Kết nối này có thể kém tin cậy hơn hoặc cho phép các bên thứ ba theo dõi hoạt động.", "additonial_network_information_desc": "Một vài mạng trong số này phụ thuộc vào bên thứ ba. Kết nối có thể kém tin cậy hơn hoặc cho phép bên thứ ba theo dõi hoạt động.", + "connect_more_networks": "Connect more networks", "learn_more": "Tìm hiểu thêm", "learn_more_url": "https://support.metamask.io/networks-and-sidechains/managing-networks/the-risks-of-connecting-to-an-unknown-network/", "switch_network": "Chuyển sang mạng", "switch": "Chuyển", + "select_all": "Select all", "new_network": "Đã thêm mạng mới", "network_name": "Mạng {{networkName}}", "network_added": " hiện có sẵn trong trình chọn mạng.", @@ -1758,7 +1766,19 @@ "show_test_networks": "Hiển thị các mạng thử nghiệm", "deprecated_goerli": "Do những thay đổi trong giao thức của Ethereum: Mạng thử nghiệm Goerli có thể không hoạt động đáng tin cậy và sẽ sớm bị ngừng sử dụng.", "network_deprecated_title": "Mạng này đã ngừng sử dụng", - "network_deprecated_description": "Mạng bạn đang cố gắng kết nối không còn được hỗ trợ trên MetaMask." + "network_deprecated_description": "Mạng bạn đang cố gắng kết nối không còn được hỗ trợ trên MetaMask.", + "edit_networks_title": "Edit networks" + }, + "permissions": { + "title_this_site_wants_to": "This site wants to:", + "title_dapp_url_wants_to": "{{dappUrl}} wants to:", + "title_dapp_url_has_approval_to": "{{dappUrl}} has approval to:", + "use_enabled_networks": "Use your enabled networks", + "wants_to_see_your_accounts": "See your accounts and suggest transactions", + "requesting_for": "Requesting for ", + "manage_permissions": "Manage Permissions", + "edit": "Edit", + "cancel": "Cancel" }, "select": { "cancel": "Hủy", @@ -2022,6 +2042,8 @@ "back_to_safety": "Quay lại mục an toàn" }, "notifications": { + "no_date": "Unknown", + "yesterday": "Yesterday", "staked": "Đã ký gửi", "received": "Đã nhận", "unstaked": "Đã hủy ký gửi", @@ -2097,6 +2119,8 @@ "success_withdrawal_title": "Rút tiền hoàn tất!", "error_title": "Rất tiếc, đã xảy ra lỗi :/", "received_title": "Bạn đã nhận {{amount}} {{assetType}}", + "default_message_title": "New transaction notification", + "default_message_description": "Tap to view this transaction", "received_payment_title": "Đã nhận tiền thanh toán ngay", "pending_message": "Đang đợi xác nhận", "pending_deposit_message": "Đang đợi hoàn tất quá trình nạp tiền", @@ -2112,12 +2136,12 @@ "cancelled_message": "Chạm để xem giao dịch này", "received_message": "Chạm để xem giao dịch này", "received_payment_message": "Bạn đã nhận {{amount}} DAI", - "prompt_title": "Bật thông báo đẩy", + "prompt_title": "Receive Push Notifications", "notifications_enabled_error_title": "Đã xảy ra sự cố", "notifications_enabled_error_desc": "Chúng tôi không thể bật thông báo. Vui lòng thử lại sau.", - "prompt_desc": "Bật thông báo để MetaMask có thể thông báo cho bạn biết khi bạn nhận ETH hoặc khi giao dịch của bạn được xác nhận.", - "prompt_ok": "Có", - "prompt_cancel": "Không, cảm ơn", + "prompt_desc": "Turn on notifications from Settings to get important alerts on wallet activity and more.", + "prompt_ok": "Turn on", + "prompt_cancel": "Maybe later", "wc_connected_title": "Đã kết nối với {{title}}", "wc_signed_title": "Đã ký", "wc_sent_tx_title": "Giao dịch đã gửi", @@ -2798,13 +2822,8 @@ }, "switch_custom_network": { "title_existing_network": "Trang web này muốn chuyển mạng", - "title_enabled_network": "Trang web này muốn:", "title_new_network": "Đã thêm mạng mới", "switch_warning": "Thao tác này sẽ chuyển mạng được chọn trong MetaMask sang một mạng đã thêm trước đó:", - "use_enabled_networks": "Sử dụng mạng đã kích hoạt của bạn", - "wants_to_see_your_accounts": "Xem tài khoản của bạn và đề xuất giao dịch", - "requesting_for": "Yêu cầu cho ", - "edit": "Chỉnh sửa", "add_network_and_give_dapp_permission_warning": "Bạn đang thêm mạng này vào MetaMask và cấp cho {{dapp_origin}} quyền sử dụng mạng này.", "available": "hiện có sẵn trong trình chọn mạng.", "cancel": "Hủy", @@ -2896,7 +2915,7 @@ }, "transaction_review_eip1559": { "estimated_gas_fee": "Phí gas ước tính", - "suggested_gas_fee": "%{origin} phí gas được đề xuất", + "network_fee": "Network fee", "max_fee": "Phí tối đa", "total": "Tổng", "max_amount": "Số tiền tối đa", @@ -3136,6 +3155,7 @@ "ledger_reminder_message_step_four": "4. Vị trí đã được bật cùng với sử dụng vị trí chính xác", "ledger_reminder_message_step_four_Androidv12plus": "4. Thiết bị lân cận đã được bật", "ledger_reminder_message_step_five": "5. Phải tắt chế độ Không làm phiền", + "blind_signing_message": "6. Enable \"blind signing\" on your Ledger device.", "available_devices": "Thiết bị có sẵn", "retry": "Thử lại", "continue": "Tiếp tục", @@ -3166,7 +3186,15 @@ "how_to_install_eth_webview_title": "Cách cài đặt Ứng dụng Ethereum", "nonce_too_low": "Số nonce quá thấp", "nonce_too_low_error": "Số nonce đã đặt quá thấp", - "select_accounts": "Chọn một tài khoản" + "select_accounts": "Select an account", + "select_hd_path": "Select HD Path", + "select_hd_path_description": "If you don't see the accounts you expect, try switching the HD path or current selected network.", + "ledger_live_path": "Ledger Live", + "ledger_legacy_path": "Legacy(MEW/MyCrypto)", + "ledger_bip44_path": "BIP44(e.g. MetaMask, Trezor)", + "ledger_legacy_label": " (legacy)", + "blind_sign_error": "Blind signing error", + "blind_sign_error_message": "Blind signing is not enabled on your Ledger device. Please enable it in the settings." }, "account_actions": { "edit_name": "Chỉnh sửa tên tài khoản", @@ -3220,7 +3248,21 @@ "error_description": "Cài đặt {{snap}} thất bại." }, "stake": { - "stake": "Ký gửi" + "stake": "Ký gửi", + "stake_eth": "Stake ETH", + "your_earnings": "Your earnings", + "annual_rate": "Annual rate", + "lifetime_rewards": "Lifetime rewards", + "estimated_annual_earnings": "Estimated annual earnings", + "accessibility_labels": { + "stake_annual_rate_tooltip": "Annual rate tooltip" + }, + "estimated_annual_rewards": "Estimated annual rewards", + "metamask_pool": "MetaMask Pool", + "enter_amount": "Enter amount", + "review": "Review", + "not_enough_eth": "Not enough ETH", + "balance": "Balance" }, "default_settings": { "title": "Ví của bạn đã sẵn sàng", @@ -3269,6 +3311,14 @@ "title": "Chúng tôi nhận thấy có nhiều yêu cầu" }, "common": { - "please_wait": "Vui lòng chờ" + "please_wait": "Vui lòng chờ", + "disconnect_you_from": "This will disconnect you from {{dappUrl}}", + "disconnect": "Disconnect" + }, + "tooltip_modal": { + "reward_rate": { + "title": "Reward rate", + "tooltip": "Expected yearly increase in the value of your stake, based on the reward rate over the past week." + } } } diff --git a/locales/languages/zh.json b/locales/languages/zh.json index 8ef9ceeaf70..f2eb8ebfed1 100644 --- a/locales/languages/zh.json +++ b/locales/languages/zh.json @@ -686,9 +686,8 @@ "suggest_transactions": "建议要批准的交易", "disconnect": "断开连接", "disconnect_all": "断开所有连接", - "reconnect_notice": "如果将您的账户与 {{dappUrl}} 断开连接,则需要重新连接才能再次使用。", + "reconnect_notice": "If you disconnect from {{dappUrl}}, you’ll need to reconnect your accounts and networks to use this site again.", "disconnect_all_accounts": "断开所有账户的连接", - "disconnect_you_from": "这将断开与 {{dappUrl}} 的连接", "deceptive_site_ahead": "前方为欺骗性网站", "deceptive_site_desc": "您尝试访问的网站并不安全。攻击者可能会诱使您做一些危险的事情", "learn_more": "了解详情", @@ -1066,7 +1065,12 @@ "simulation_details_description": "开启此选项,以便在确认交易之前估计交易余额的变化。这并不能保证交易的最终结果。 ", "simulation_details_learn_more": "了解更多。", "aes_crypto_test_form_title": "AES 加密 - 测试表", - "aes_crypto_test_form_description": "专为 E2E 测试开发的部分。如果这在您的应用程序中可见,请向 MetaMask 支持团队报告。" + "aes_crypto_test_form_description": "专为 E2E 测试开发的部分。如果这在您的应用程序中可见,请向 MetaMask 支持团队报告。", + "developer_options": { + "title": "Developer Options", + "generate_trace_test": "Generate Trace Test", + "generate_trace_test_desc": "Generate a Developer Test Sentry trace." + } }, "aes_crypto_test_form": { "generate_random_salt": "生成随机盐", @@ -1444,7 +1448,8 @@ "hex_data_copied": "十六进制数据已复制到剪贴板", "invalid_recipient": "收件人无效", "invalid_recipient_description": "请检查地址,确保其为有效地址", - "swap_tokens": "兑换代币" + "swap_tokens": "兑换代币", + "fromWithColon": "From:" }, "custom_gas": { "total": "总计", @@ -1731,6 +1736,7 @@ "continue": "继续", "cancel": "取消", "approve": "批准", + "update": "Update", "edit_network_details": "编辑网络详情", "malicious_network_warning": "恶意网络提供商可能会谎报区块链的状态,并记录您的网络活动。仅添加您信任的自定义网络。", "security_link": "https://support.metamask.io/networks-and-sidechains/managing-networks/user-guide-custom-networks-and-sidechains/", @@ -1738,10 +1744,12 @@ "additional_network_information_title": "其他网络信息", "network_warning_desc": "此网络连接依赖于第三方。这种连接可靠程度可能会较低,或会使第三方能够跟踪活动。", "additonial_network_information_desc": "这些网络中的其中一些依赖于第三方。此连接可能不太可靠,或使第三方可进行活动跟踪。", + "connect_more_networks": "Connect more networks", "learn_more": "了解详情", "learn_more_url": "https://support.metamask.io/networks-and-sidechains/managing-networks/the-risks-of-connecting-to-an-unknown-network/", "switch_network": "切换到网络", "switch": "切换", + "select_all": "Select all", "new_network": "已添加新网络", "network_name": "{{networkName}}网络", "network_added": " 现已在网络选择工具中。", @@ -1758,7 +1766,19 @@ "show_test_networks": "显示测试网络", "deprecated_goerli": "由于以太坊协议的变化:Goerli 测试网络可能无法可靠工作,很快就会弃用。", "network_deprecated_title": "该网络已弃用", - "network_deprecated_description": "您尝试连接的网络在 Metamask 上不再受支持。" + "network_deprecated_description": "您尝试连接的网络在 Metamask 上不再受支持。", + "edit_networks_title": "Edit networks" + }, + "permissions": { + "title_this_site_wants_to": "This site wants to:", + "title_dapp_url_wants_to": "{{dappUrl}} wants to:", + "title_dapp_url_has_approval_to": "{{dappUrl}} has approval to:", + "use_enabled_networks": "Use your enabled networks", + "wants_to_see_your_accounts": "See your accounts and suggest transactions", + "requesting_for": "Requesting for ", + "manage_permissions": "Manage Permissions", + "edit": "Edit", + "cancel": "Cancel" }, "select": { "cancel": "取消", @@ -2022,6 +2042,8 @@ "back_to_safety": "返回以确保安全" }, "notifications": { + "no_date": "Unknown", + "yesterday": "Yesterday", "staked": "已质押", "received": "已收到", "unstaked": "已解除质押", @@ -2097,6 +2119,8 @@ "success_withdrawal_title": "提取完成!", "error_title": "糟糕,出问题了:/", "received_title": "您已收到 {{amount}} {{assetType}}", + "default_message_title": "New transaction notification", + "default_message_description": "Tap to view this transaction", "received_payment_title": "已收到即时付款", "pending_message": "正在等待确认", "pending_deposit_message": "正在等待保证金完成", @@ -2112,12 +2136,12 @@ "cancelled_message": "点按以查看此交易", "received_message": "点按以查看此交易", "received_payment_message": "您已收到 {{amount}} DAI", - "prompt_title": "启用推送通知", + "prompt_title": "Receive Push Notifications", "notifications_enabled_error_title": "出错了", "notifications_enabled_error_desc": "我们无法启用通知。请稍后再试。", - "prompt_desc": "启用通知,以便在您收到 ETH 或您的交易被确认时,MetaMask 能够让您知道。", - "prompt_ok": "是", - "prompt_cancel": "不,谢谢", + "prompt_desc": "Turn on notifications from Settings to get important alerts on wallet activity and more.", + "prompt_ok": "Turn on", + "prompt_cancel": "Maybe later", "wc_connected_title": "已连接 {{title}}", "wc_signed_title": "已签名", "wc_sent_tx_title": "已发送交易", @@ -2798,13 +2822,8 @@ }, "switch_custom_network": { "title_existing_network": "本站点希望切换网络", - "title_enabled_network": "此网站想要:", "title_new_network": "已增加新网络", "switch_warning": "这将把 MetaMask 中选择的网络切换到先前添加的网络:", - "use_enabled_networks": "使用您启用的网络", - "wants_to_see_your_accounts": "查看您的账户并建议交易", - "requesting_for": "请求 ", - "edit": "编辑", "add_network_and_give_dapp_permission_warning": "您正在将此网络添加到 MetaMask 并授予 {{dapp_origin}} 其使用许可。", "available": "现在可以在网络选择工具中使用。", "cancel": "取消", @@ -2896,7 +2915,7 @@ }, "transaction_review_eip1559": { "estimated_gas_fee": "估算的燃料费", - "suggested_gas_fee": "%{origin} 建议燃料费", + "network_fee": "Network fee", "max_fee": "最大费用", "total": "共计", "max_amount": "最大金额", @@ -3136,6 +3155,7 @@ "ledger_reminder_message_step_four": "4. 使用下述精确定位后,定位已启用:", "ledger_reminder_message_step_four_Androidv12plus": "4. 附近设备已启用", "ledger_reminder_message_step_five": "5. 必须关闭请勿打扰", + "blind_signing_message": "6. Enable \"blind signing\" on your Ledger device.", "available_devices": "可用设备", "retry": "重试", "continue": "继续", @@ -3166,7 +3186,15 @@ "how_to_install_eth_webview_title": "如何安装以太坊应用程序", "nonce_too_low": "唯一交易标识号安全性过低", "nonce_too_low_error": "设置的唯一交易标识号安全性过低", - "select_accounts": "选择一个账户" + "select_accounts": "Select an account", + "select_hd_path": "Select HD Path", + "select_hd_path_description": "If you don't see the accounts you expect, try switching the HD path or current selected network.", + "ledger_live_path": "Ledger Live", + "ledger_legacy_path": "Legacy(MEW/MyCrypto)", + "ledger_bip44_path": "BIP44(e.g. MetaMask, Trezor)", + "ledger_legacy_label": " (legacy)", + "blind_sign_error": "Blind signing error", + "blind_sign_error_message": "Blind signing is not enabled on your Ledger device. Please enable it in the settings." }, "account_actions": { "edit_name": "编辑账户名", @@ -3220,7 +3248,21 @@ "error_description": "{{snap}}安装失败。" }, "stake": { - "stake": "质押" + "stake": "质押", + "stake_eth": "Stake ETH", + "your_earnings": "Your earnings", + "annual_rate": "Annual rate", + "lifetime_rewards": "Lifetime rewards", + "estimated_annual_earnings": "Estimated annual earnings", + "accessibility_labels": { + "stake_annual_rate_tooltip": "Annual rate tooltip" + }, + "estimated_annual_rewards": "Estimated annual rewards", + "metamask_pool": "MetaMask Pool", + "enter_amount": "Enter amount", + "review": "Review", + "not_enough_eth": "Not enough ETH", + "balance": "Balance" }, "default_settings": { "title": "您的钱包已准备就绪", @@ -3269,6 +3311,14 @@ "title": "我们注意到有多个请求" }, "common": { - "please_wait": "请稍候" + "please_wait": "请稍候", + "disconnect_you_from": "This will disconnect you from {{dappUrl}}", + "disconnect": "Disconnect" + }, + "tooltip_modal": { + "reward_rate": { + "title": "Reward rate", + "tooltip": "Expected yearly increase in the value of your stake, based on the reward rate over the past week." + } } } From d80fae9cdee469e677f590e5a4baeca7027a61c1 Mon Sep 17 00:00:00 2001 From: sethkfman <10342624+sethkfman@users.noreply.github.com> Date: Mon, 23 Sep 2024 11:48:35 -0600 Subject: [PATCH 05/27] Update CHANGELOG.md --- CHANGELOG.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbe1547d548..97b9b7095f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,8 @@ ## 7.32.0 - Sep 19, 2024 ### Added -- [#11093](https://github.com/MetaMask/metamask-mobile/pull/11093): feat: 7.31.0 (#11093) + - [#10294](https://github.com/MetaMask/metamask-mobile/pull/10294): feat: create redux slice for featureFlags (#10294) -- [#11310](https://github.com/MetaMask/metamask-mobile/pull/11310): fix: quick fix on feature flag & notification state (#11310) -- [#11200](https://github.com/MetaMask/metamask-mobile/pull/11200): fix: add feature flag on profile sync (#11200) - [#11314](https://github.com/MetaMask/metamask-mobile/pull/11314): feat: reject connection properly (#11314) - [#11132](https://github.com/MetaMask/metamask-mobile/pull/11132): feat: Add performance tracing infrastructure (#11132) - [#10061](https://github.com/MetaMask/metamask-mobile/pull/10061): feat: new receive flow (#10061) @@ -17,9 +15,8 @@ - [#10988](https://github.com/MetaMask/metamask-mobile/pull/10988): feat(2808): add a mocked UI checkbox list that will later allow adding the ability to edit network permission (#10988) - [#11168](https://github.com/MetaMask/metamask-mobile/pull/11168): feat: add pooled staking input flow screen (#11168) - [#10964](https://github.com/MetaMask/metamask-mobile/pull/10964): feat: build your earnings component stub in eth token details (#10964) -- [#11117](https://github.com/MetaMask/metamask-mobile/pull/11117): fix: add feat flag (#11117) - [#11051](https://github.com/MetaMask/metamask-mobile/pull/11051): feat: add brand evo font files (#11051) -- [#11285](https://github.com/MetaMask/metamask-mobile/pull/11285): Feat/notifications add analytics (#11285) +- [#11285](https://github.com/MetaMask/metamask-mobile/pull/11285): feat: notifications add analytics (#11285) - [#10755](https://github.com/MetaMask/metamask-mobile/pull/10755): feat: ledger account selection screen add hd options to sync with extension (#10755) - [#11195](https://github.com/MetaMask/metamask-mobile/pull/11195): feat: add AppState dependency to load notifications (#11195) - [#11175](https://github.com/MetaMask/metamask-mobile/pull/11175): feat: add product announcements toggle (#11175) @@ -51,7 +48,6 @@ - [#11235](https://github.com/MetaMask/metamask-mobile/pull/11235): ci: avoid running release pipeline on every commit to the release branch (#11235) - [#11094](https://github.com/MetaMask/metamask-mobile/pull/11094): chore: chore/7.31.0-Changelog (#11094) - [#10788](https://github.com/MetaMask/metamask-mobile/pull/10788): chore: Add `@metamask/selected-network-controller` & integrate (#10788) -- [#11084](https://github.com/MetaMask/metamask-mobile/pull/11084): fix: locks api spec version for api spec tests (#11084) - [#11122](https://github.com/MetaMask/metamask-mobile/pull/11122): test: e2e for auto-lock (#11122) - [#11143](https://github.com/MetaMask/metamask-mobile/pull/11143): chore: bump react native webview to 14.0.3 version (#11143) - [#11284](https://github.com/MetaMask/metamask-mobile/pull/11284): chore: add notifications state awareness inapp badge (#11284) @@ -64,12 +60,16 @@ - [#10821](https://github.com/MetaMask/metamask-mobile/pull/10821): chore(deps): bump `accounts-controller` to v18.1.0 and `keyring-api` to v8.1.0 (#10821) ### Fixed +- [#11117](https://github.com/MetaMask/metamask-mobile/pull/11117): fix: add feat flag (#11117) +- [#11084](https://github.com/MetaMask/metamask-mobile/pull/11084): fix: locks api spec version for api spec tests (#11084) +- [#11310](https://github.com/MetaMask/metamask-mobile/pull/11310): fix: quick fix on feature flag & notification state (#11310) +- [#11200](https://github.com/MetaMask/metamask-mobile/pull/11200): fix: add feature flag on profile sync (#11200) - [#11302](https://github.com/MetaMask/metamask-mobile/pull/11302): fix: cp & resolve merge conflict (#11302) - [#11130](https://github.com/MetaMask/metamask-mobile/pull/11130): fix(action): add a workaround for known bots (#11130) - [#11173](https://github.com/MetaMask/metamask-mobile/pull/11173): fix: dset version (#11173) - [#10899](https://github.com/MetaMask/metamask-mobile/pull/10899): fix: Android crash when svgs use the " html entity (#10899) - [#11126](https://github.com/MetaMask/metamask-mobile/pull/11126): fix: Skip sonar cloud gate in step instead (#11126) -- [#11121](https://github.com/MetaMask/metamask-mobile/pull/11121): "fix: Add new job to verify ""All jobs pass"" job for required PR check (#11121)" +- [#11121](https://github.com/MetaMask/metamask-mobile/pull/11121): fix: Add new job to verify ""All jobs pass"" job for required PR check (#11121) - [#11266](https://github.com/MetaMask/metamask-mobile/pull/11266): fix: notification permission flow (#11266) - [#11252](https://github.com/MetaMask/metamask-mobile/pull/11252): fix: notification permission request message (#11252) - [#11155](https://github.com/MetaMask/metamask-mobile/pull/11155): fix: android crashing on date formating Intl usage. (#11155) From afc872f1a2080d4882bcc94347c2923fef59503a Mon Sep 17 00:00:00 2001 From: metamaskbot Date: Mon, 23 Sep 2024 15:34:22 -0600 Subject: [PATCH 06/27] bump version to 1436 RC 2 --- android/app/build.gradle | 2 +- bitrise.yml | 4 ++-- ios/MetaMask.xcodeproj/project.pbxproj | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index ae26808e691..a8ee2bda369 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -173,7 +173,7 @@ android { applicationId "io.metamask" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1435 + versionCode 1436 versionName "7.32.0" testBuildType System.getProperty('testBuildType', 'debug') missingDimensionStrategy 'react-native-camera', 'general' diff --git a/bitrise.yml b/bitrise.yml index f8c598290ae..6f0c5be06ac 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -1500,13 +1500,13 @@ app: VERSION_NAME: 7.32.0 - opts: is_expand: false - VERSION_NUMBER: 1435 + VERSION_NUMBER: 1436 - opts: is_expand: false FLASK_VERSION_NAME: 7.32.0 - opts: is_expand: false - FLASK_VERSION_NUMBER: 1435 + FLASK_VERSION_NUMBER: 1436 - opts: is_expand: false ANDROID_APK_LINK: '' diff --git a/ios/MetaMask.xcodeproj/project.pbxproj b/ios/MetaMask.xcodeproj/project.pbxproj index 1134d2b444d..2bcb3d845c0 100644 --- a/ios/MetaMask.xcodeproj/project.pbxproj +++ b/ios/MetaMask.xcodeproj/project.pbxproj @@ -1273,7 +1273,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1435; + CURRENT_PROJECT_VERSION = 1436; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1338,7 +1338,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1435; + CURRENT_PROJECT_VERSION = 1436; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1401,7 +1401,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1435; + CURRENT_PROJECT_VERSION = 1436; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1462,7 +1462,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1435; + CURRENT_PROJECT_VERSION = 1436; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1624,7 +1624,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1435; + CURRENT_PROJECT_VERSION = 1436; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1692,7 +1692,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1435; + CURRENT_PROJECT_VERSION = 1436; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; From 0520cfb8ffce8d113e3aa868123469f40dd0247c Mon Sep 17 00:00:00 2001 From: "runway-github[bot]" <73448015+runway-github[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2024 15:35:29 -0600 Subject: [PATCH 07/27] chore(runway): cherry-pick feat: app event manager and attribution id parameters (#11382) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - feat: app event manager and attribution id parameters (#11318) ## **Description** When a user is redirected to MetaMask Mobile app thanks to a deep link, including an attributionId parameter: metamask://open.browser/website_url?attributionId=xyz (deep link above is just a placeholder) Then MM Mobile app should attach attributionId as a property to an event emitted right after Mobile app opens. Given Mobile app would open and directly redirect the user to the in-app browser, the "App Opened" event might be a good candidate. ## **Related issues** Files: [jira issue: ](https://id.atlassian.com/login?continue=https%3A%2F%2Fid.atlassian.com%2Fjoin%2Fuser-access%3Fresource%3Dari%253Acloud%253Ajira%253A%253Asite%252F8831f492-a12c-460e-ac1b-400f1b09e935%26continue%3Dhttps%253A%252F%252Fconsensyssoftware.atlassian.net%252Fbrowse%252FSDK-18%253FatlOrigin%253DeyJpIjoiNzI2OWEwNTczYTZmNGEyZjgxYmFjYjkxMzJlZmE4MTYiLCJwIjoiaiJ9&application=jira) ## **Manual testing steps** 1. Open deeplink container url and check event on segment ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [x] 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). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] 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. --------- Co-authored-by: Cal Leung [e99b5ca](https://github.com/MetaMask/metamask-mobile/commit/e99b5ca63a6890c2e90cc67040839050c58ac9ad) Co-authored-by: abretonc7s <107169956+abretonc7s@users.noreply.github.com> Co-authored-by: Cal Leung --- app/components/Nav/App/index.js | 5 + app/core/Analytics/MetaMetrics.events.ts | 4 + app/core/AppStateEventListener.test.ts | 168 ++++++++++++++++++ app/core/AppStateEventListener.ts | 71 ++++++++ .../ParseManager/extractURLParams.test.ts | 4 + .../ParseManager/extractURLParams.ts | 2 + app/core/processAttribution.test.tsx | 59 ++++++ app/core/processAttribution.tsx | 21 +++ app/store/index.ts | 2 + 9 files changed, 336 insertions(+) create mode 100644 app/core/AppStateEventListener.test.ts create mode 100644 app/core/AppStateEventListener.ts create mode 100644 app/core/processAttribution.test.tsx create mode 100644 app/core/processAttribution.tsx diff --git a/app/components/Nav/App/index.js b/app/components/Nav/App/index.js index 8f14a0497f6..8a58b6f45d1 100644 --- a/app/components/Nav/App/index.js +++ b/app/components/Nav/App/index.js @@ -125,6 +125,7 @@ import { SnapsExecutionWebView } from '../../../lib/snaps'; ///: END:ONLY_INCLUDE_IF import OptionsSheet from '../../UI/SelectOptionSheet/OptionsSheet'; import FoxLoader from '../../../components/UI/FoxLoader'; +import { AppStateEventProcessor } from '../../../core/AppStateEventListener'; const clearStackNavigatorOptions = { headerShown: false, @@ -317,6 +318,7 @@ const App = (props) => { const dispatch = useDispatch(); const sdkInit = useRef(); const [onboarded, setOnboarded] = useState(false); + const triggerSetCurrentRoute = (route) => { dispatch(setCurrentRoute(route)); if (route === 'Wallet' || route === 'BrowserView') { @@ -369,6 +371,7 @@ const App = (props) => { const deeplink = params?.['+non_branch_link'] || uri || null; try { if (deeplink) { + AppStateEventProcessor.setCurrentDeeplink(deeplink); SharedDeeplinkManager.parse(deeplink, { origin: AppConstants.DEEPLINKS.ORIGIN_DEEPLINK, }); @@ -392,6 +395,7 @@ const App = (props) => { }); }, [handleDeeplink]); + useEffect(() => { if (navigator) { // Initialize deep link manager @@ -406,6 +410,7 @@ const App = (props) => { }, dispatch, }); + if (!prevNavigator.current) { // Setup navigator with Sentry instrumentation routingInstrumentation.registerNavigationContainer(navigator); diff --git a/app/core/Analytics/MetaMetrics.events.ts b/app/core/Analytics/MetaMetrics.events.ts index 86a00f456c0..0a61dbc6f16 100644 --- a/app/core/Analytics/MetaMetrics.events.ts +++ b/app/core/Analytics/MetaMetrics.events.ts @@ -36,6 +36,9 @@ const ONBOARDING_WIZARD_STEP_DESCRIPTION: { [key: number]: string } = { * Analytics Tracking Events */ enum EVENT_NAME { + // App + APP_OPENED = 'App Opened', + // Error ERROR = 'Error occurred', ERROR_SCREEN_VIEWED = 'Error Screen Viewed', @@ -439,6 +442,7 @@ enum ACTIONS { } const events = { + APP_OPENED: generateOpt(EVENT_NAME.APP_OPENED), ERROR: generateOpt(EVENT_NAME.ERROR), ERROR_SCREEN_VIEWED: generateOpt(EVENT_NAME.ERROR_SCREEN_VIEWED), APPROVAL_STARTED: generateOpt(EVENT_NAME.APPROVAL_STARTED), diff --git a/app/core/AppStateEventListener.test.ts b/app/core/AppStateEventListener.test.ts new file mode 100644 index 00000000000..3cf93c94aac --- /dev/null +++ b/app/core/AppStateEventListener.test.ts @@ -0,0 +1,168 @@ +import { AppState, AppStateStatus } from 'react-native'; +import { store } from '../store'; +import Logger from '../util/Logger'; +import { MetaMetrics, MetaMetricsEvents } from './Analytics'; +import { AppStateEventListener } from './AppStateEventListener'; +import extractURLParams from './DeeplinkManager/ParseManager/extractURLParams'; + +jest.mock('react-native', () => ({ + AppState: { + addEventListener: jest.fn(), + currentState: 'active', + }, +})); + +jest.mock('./Analytics', () => ({ + MetaMetrics: { + getInstance: jest.fn(), + }, + MetaMetricsEvents: { + APP_OPENED: 'APP_OPENED', + }, +})); + +jest.mock('../store', () => ({ + store: { + getState: jest.fn(), + }, +})); + +jest.mock('./DeeplinkManager/ParseManager/extractURLParams', () => jest.fn()); + +jest.mock('../util/Logger', () => ({ + error: jest.fn(), +})); + +describe('AppStateEventListener', () => { + let appStateManager: AppStateEventListener; + let mockAppStateListener: (state: AppStateStatus) => void; + let mockTrackEvent: jest.Mock; + + beforeEach(() => { + jest.clearAllMocks(); + jest.useFakeTimers(); + mockTrackEvent = jest.fn(); + (MetaMetrics.getInstance as jest.Mock).mockReturnValue({ + trackEvent: mockTrackEvent, + }); + (AppState.addEventListener as jest.Mock).mockImplementation((_, listener) => { + mockAppStateListener = listener; + return { remove: jest.fn() }; + }); + appStateManager = new AppStateEventListener(); + appStateManager.init(store); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('subscribes to AppState changes on instantiation', () => { + expect(AppState.addEventListener).toHaveBeenCalledWith('change', expect.any(Function)); + }); + + it('throws error if store is initialized more than once', () => { + expect(() => appStateManager.init(store)).toThrow('store is already initialized'); + expect(Logger.error).toHaveBeenCalledWith(new Error('store is already initialized')); + }); + + it('tracks event when app becomes active and conditions are met', () => { + (store.getState as jest.Mock).mockReturnValue({ + security: { dataCollectionForMarketing: true }, + }); + (extractURLParams as jest.Mock).mockReturnValue({ params: { attributionId: 'test123' } }); + + appStateManager.setCurrentDeeplink('metamask://connect?attributionId=test123'); + mockAppStateListener('active'); + jest.advanceTimersByTime(2000); + + expect(mockTrackEvent).toHaveBeenCalledWith( + MetaMetricsEvents.APP_OPENED, + { attributionId: 'test123' }, + true + ); + }); + + it('does not track event when data collection is disabled', () => { + (store.getState as jest.Mock).mockReturnValue({ + security: { dataCollectionForMarketing: false }, + }); + + mockAppStateListener('active'); + jest.advanceTimersByTime(2000); + + expect(mockTrackEvent).toHaveBeenCalledWith( + MetaMetricsEvents.APP_OPENED, + {}, + true + ); + }); + + it('does not track event when there is no deeplink', () => { + (store.getState as jest.Mock).mockReturnValue({ + security: { dataCollectionForMarketing: true }, + }); + + mockAppStateListener('active'); + jest.advanceTimersByTime(2000); + + expect(mockTrackEvent).toHaveBeenCalledWith( + MetaMetricsEvents.APP_OPENED, + { attributionId: undefined }, + true + ); + }); + + it('handles errors gracefully', () => { + (store.getState as jest.Mock).mockImplementation(() => { + throw new Error('Test error'); + }); + + mockAppStateListener('active'); + jest.advanceTimersByTime(2000); + + expect(Logger.error).toHaveBeenCalledWith( + expect.any(Error), + 'AppStateManager: Error processing app state change' + ); + expect(mockTrackEvent).not.toHaveBeenCalled(); + }); + + it('cleans up the AppState listener on cleanup', () => { + const mockRemove = jest.fn(); + (AppState.addEventListener as jest.Mock).mockReturnValue({ remove: mockRemove }); + + appStateManager = new AppStateEventListener(); + appStateManager.init(store); + appStateManager.cleanup(); + + expect(mockRemove).toHaveBeenCalled(); + }); + + it('should not process app state change when app is not becoming active', () => { + mockAppStateListener('background'); + jest.advanceTimersByTime(2000); + + expect(mockTrackEvent).not.toHaveBeenCalled(); + }); + + it('should not process app state change when app state has not changed', () => { + mockAppStateListener('active'); + jest.advanceTimersByTime(2000); + mockTrackEvent.mockClear(); + + mockAppStateListener('active'); + jest.advanceTimersByTime(2000); + + expect(mockTrackEvent).not.toHaveBeenCalled(); + }); + + it('should handle undefined store gracefully', () => { + appStateManager = new AppStateEventListener(); + mockAppStateListener('active'); + jest.advanceTimersByTime(2000); + + expect(mockTrackEvent).not.toHaveBeenCalled(); + expect(Logger.error).toHaveBeenCalledWith(new Error('store is not initialized')); + }); +}); diff --git a/app/core/AppStateEventListener.ts b/app/core/AppStateEventListener.ts new file mode 100644 index 00000000000..46752626e2a --- /dev/null +++ b/app/core/AppStateEventListener.ts @@ -0,0 +1,71 @@ +import { AppState, AppStateStatus } from 'react-native'; +import { Store } from 'redux'; +import { RootState } from '../reducers'; +import Logger from '../util/Logger'; +import { MetaMetrics, MetaMetricsEvents } from './Analytics'; +import { processAttribution } from './processAttribution'; +import DevLogger from './SDKConnect/utils/DevLogger'; + +export class AppStateEventListener { + private appStateSubscription: ReturnType; + private currentDeeplink: string | null = null; + private lastAppState: AppStateStatus = AppState.currentState; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private store: Store | undefined; + + constructor() { + this.lastAppState = AppState.currentState; + this.appStateSubscription = AppState.addEventListener('change', this.handleAppStateChange); + } + + init(store: Store) { + if(this.store) { + Logger.error(new Error('store is already initialized')); + throw new Error('store is already initialized'); + } + this.store = store; + } + + public setCurrentDeeplink(deeplink: string | null) { + this.currentDeeplink = deeplink; + } + + private handleAppStateChange = (nextAppState: AppStateStatus) => { + if ( + nextAppState === 'active' && + this.lastAppState !== nextAppState + ) { + // delay to allow time for the deeplink to be set + setTimeout(() => { + this.processAppStateChange(); + }, 2000); + } + this.lastAppState = nextAppState; + }; + + private processAppStateChange = () => { + if (!this.store) { + Logger.error(new Error('store is not initialized')); + return; + } + + try { + const attributionId = processAttribution({ currentDeeplink: this.currentDeeplink, store: this.store }); + DevLogger.log(`AppStateManager:: processAppStateChange:: sending event 'APP_OPENED' attributionId=${attributionId}`); + MetaMetrics.getInstance().trackEvent( + MetaMetricsEvents.APP_OPENED, + { attributionId }, + true + ); + } catch (error) { + Logger.error(error as Error, 'AppStateManager: Error processing app state change'); + } + }; + + public cleanup() { + this.appStateSubscription.remove(); + } +} + +export const AppStateEventProcessor = new AppStateEventListener(); diff --git a/app/core/DeeplinkManager/ParseManager/extractURLParams.test.ts b/app/core/DeeplinkManager/ParseManager/extractURLParams.test.ts index 5f43baeb696..50b942a1366 100644 --- a/app/core/DeeplinkManager/ParseManager/extractURLParams.test.ts +++ b/app/core/DeeplinkManager/ParseManager/extractURLParams.test.ts @@ -42,6 +42,7 @@ describe('extractURLParams', () => { channelId: '123', comm: 'test', v: '2', + attributionId: '', }; mockUrlParser.mockImplementation( @@ -81,6 +82,7 @@ describe('extractURLParams', () => { comm: '', pubkey: '', v: '', + attributionId: '', }); }); @@ -113,6 +115,7 @@ describe('extractURLParams', () => { comm: '', pubkey: '', v: '', + attributionId: '', }); expect(alertSpy).toHaveBeenCalledWith( @@ -133,6 +136,7 @@ describe('extractURLParams', () => { rpc: '', sdkVersion: '', pubkey: 'xyz', + attributionId: '', }; mockUrlParser.mockImplementation( diff --git a/app/core/DeeplinkManager/ParseManager/extractURLParams.ts b/app/core/DeeplinkManager/ParseManager/extractURLParams.ts index 6f797ae109d..723ce7148b7 100644 --- a/app/core/DeeplinkManager/ParseManager/extractURLParams.ts +++ b/app/core/DeeplinkManager/ParseManager/extractURLParams.ts @@ -19,6 +19,7 @@ export interface DeeplinkUrlParams { message?: string; originatorInfo?: string; request?: string; + attributionId?: string; account?: string; // This is the format => "address@chainId" } @@ -39,6 +40,7 @@ function extractURLParams(url: string) { originatorInfo: '', channelId: '', comm: '', + attributionId: '', }; DevLogger.log(`extractParams:: urlObj`, urlObj); diff --git a/app/core/processAttribution.test.tsx b/app/core/processAttribution.test.tsx new file mode 100644 index 00000000000..fa4a196a2e2 --- /dev/null +++ b/app/core/processAttribution.test.tsx @@ -0,0 +1,59 @@ +import { store } from '../store'; +import extractURLParams from './DeeplinkManager/ParseManager/extractURLParams'; +import { processAttribution } from './processAttribution'; + +jest.mock('../store', () => ({ + store: { + getState: jest.fn(), + }, +})); + +jest.mock('./DeeplinkManager/ParseManager/extractURLParams', () => jest.fn()); + +describe('processAttribution', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('returns attributionId when marketing is enabled and deeplink is provided', () => { + (store.getState as jest.Mock).mockReturnValue({ + security: { dataCollectionForMarketing: true }, + }); + (extractURLParams as jest.Mock).mockReturnValue({ + params: { attributionId: 'test123' }, + }); + + const result = processAttribution({ currentDeeplink: 'metamask://connect?attributionId=test123', store }); + expect(result).toBe('test123'); + }); + + it('returns undefined when marketing is disabled', () => { + (store.getState as jest.Mock).mockReturnValue({ + security: { dataCollectionForMarketing: false }, + }); + + const result = processAttribution({ currentDeeplink: 'metamask://connect?attributionId=test123', store }); + expect(result).toBeUndefined(); + }); + + it('returns undefined when deeplink is null', () => { + (store.getState as jest.Mock).mockReturnValue({ + security: { dataCollectionForMarketing: true }, + }); + + const result = processAttribution({ currentDeeplink: null, store }); + expect(result).toBeUndefined(); + }); + + it('returns undefined when attributionId is not present in params', () => { + (store.getState as jest.Mock).mockReturnValue({ + security: { dataCollectionForMarketing: true }, + }); + (extractURLParams as jest.Mock).mockReturnValue({ + params: {}, + }); + + const result = processAttribution({ currentDeeplink: 'metamask://connect', store }); + expect(result).toBeUndefined(); + }); +}); diff --git a/app/core/processAttribution.tsx b/app/core/processAttribution.tsx new file mode 100644 index 00000000000..f3a604e3cf5 --- /dev/null +++ b/app/core/processAttribution.tsx @@ -0,0 +1,21 @@ +import extractURLParams from './DeeplinkManager/ParseManager/extractURLParams'; +import { RootState } from '../reducers'; +import { Store } from 'redux'; + +interface ProcessAttributionParams { + currentDeeplink: string | null; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + store: Store; +} + +export function processAttribution({ currentDeeplink, store }: ProcessAttributionParams): string | undefined { + const state = store.getState(); + const isMarketingEnabled = state.security.dataCollectionForMarketing; + + if (isMarketingEnabled && currentDeeplink) { + const { params } = extractURLParams(currentDeeplink); + return params.attributionId || undefined; // Force undefined to be returned as extractUrlParams default to empty string on error. + } + + return undefined; +} diff --git a/app/store/index.ts b/app/store/index.ts index 9b1746c929b..aa7a4df512f 100644 --- a/app/store/index.ts +++ b/app/store/index.ts @@ -12,6 +12,7 @@ import { isE2E } from '../util/test/utils'; import thunk from 'redux-thunk'; import persistConfig from './persistConfig'; +import { AppStateEventProcessor } from '../core/AppStateEventListener'; // TODO: Improve type safety by using real Action types instead of `any` // TODO: Replace "any" with type @@ -84,6 +85,7 @@ const createStoreAndPersistor = async () => { }); EngineService.initalizeEngine(store); Authentication.init(store); + AppStateEventProcessor.init(store); LockManagerService.init(store); }; From cdaf63eacd91e67d2a0f165277ed68c18632060a Mon Sep 17 00:00:00 2001 From: "runway-github[bot]" <73448015+runway-github[bot]@users.noreply.github.com> Date: Tue, 24 Sep 2024 12:30:34 -0600 Subject: [PATCH 08/27] chore(runway): cherry-pick fix: Unreadable Asset options (#11416) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fix: Unreadable Asset options (#11380) ## **Description** On darkmode, asset options in bottom drawer were unreabable. Additionally, when pressing on new "balance" list item, the button didn't do anything. This PR also adds the navigation to TokenDetails. ## **Related issues** Fixes: https://github.com/MetaMask/metamask-mobile/issues/11376 ## **Manual testing steps** 1. Toggle dark mode in system settings 2. Navigate to token detail page 3. Press ellipsis in top right to open drawer. Text should be white on darkmode, black on light mode 4. Additionally, when pressing balance from token detail screen, it should be pressable and navigate to the token detail view. This should not navigate anywhere for native gas token, and only for erc20 tokens on each network (DAI, USDC, SushiSwap, etc) ## **Screenshots/Recordings** ### **Before** ![IMG_26837C390124-1](https://github.com/user-attachments/assets/cbf7ee10-7c4c-41a5-93e5-7a9f0a8984ca) ### **After** https://github.com/user-attachments/assets/52d73fea-81ef-4f26-bdfa-cfc8b198b44a ## **Pre-merge author checklist** - [x] 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). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] 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. [a2adb20](https://github.com/MetaMask/metamask-mobile/commit/a2adb205f4b4a2d53cb64562a1b8d7c99146061d) Co-authored-by: Nick Gambino <35090461+gambinish@users.noreply.github.com> --- .../UI/AssetOverview/Balance/Balance.tsx | 3 + .../UI/AssetOverview/Balance/index.test.tsx | 70 +++++++++++++++++-- .../Views/AssetOptions/AssetOptions.styles.ts | 1 + 3 files changed, 70 insertions(+), 4 deletions(-) diff --git a/app/components/UI/AssetOverview/Balance/Balance.tsx b/app/components/UI/AssetOverview/Balance/Balance.tsx index abbaa021a1f..75fe3d4efac 100644 --- a/app/components/UI/AssetOverview/Balance/Balance.tsx +++ b/app/components/UI/AssetOverview/Balance/Balance.tsx @@ -25,6 +25,7 @@ import Text, { TextVariant, } from '../../../../component-library/components/Texts/Text'; import { TokenI } from '../../Tokens/types'; +import { useNavigation } from '@react-navigation/native'; interface BalanceProps { asset: TokenI; mainBalance: string; @@ -46,6 +47,7 @@ const NetworkBadgeSource = (chainId: string, ticker: string) => { const Balance = ({ asset, mainBalance, secondaryBalance }: BalanceProps) => { const { styles } = useStyles(styleSheet, {}); + const navigation = useNavigation(); const networkName = useSelector(selectNetworkName); const chainId = useSelector(selectChainId); @@ -58,6 +60,7 @@ const Balance = ({ asset, mainBalance, secondaryBalance }: BalanceProps) => { asset={asset} mainBalance={mainBalance} balance={secondaryBalance} + onPress={() => !asset.isETH && navigation.navigate('AssetDetails')} > ({ ...jest.requireActual('react-redux'), useSelector: jest.fn(), })); +const mockNavigate = jest.fn(); + +jest.mock('@react-navigation/native', () => ({ + ...jest.requireActual('@react-navigation/native'), + useNavigation: () => ({ + navigate: mockNavigate, + }), +})); + const mockDAI = { address: '0x6b175474e89094c44da98b954eedeac495271d0f', aggregators: ['Metamask', 'Coinmarketcap'], @@ -25,7 +37,35 @@ const mockDAI = { logo: 'image-path', }; +const mockETH = { + address: '0x0000000000000000000000000000', + aggregators: [], + balanceError: null, + balance: '100', + balanceFiat: '$10000', + decimals: 18, + image: + 'https://static.cx.metamask.io/api/v1/tokenIcons/1/0x6b175474e89094c44da98b954eedeac495271d0f.png', + name: 'Ethereum', + symbol: 'ETH', + isETH: true, + logo: 'image-path', +}; + +const mockInitialState = { + engine: { + backgroundState, + }, +}; + describe('Balance', () => { + const mockStore = configureMockStore(); + const store = mockStore(mockInitialState); + + Image.getSize = jest.fn((_uri, success) => { + success(100, 100); // Mock successful response for ETH native Icon Image + }); + beforeEach(() => { (useSelector as jest.Mock).mockImplementation((selector) => { switch (selector) { @@ -38,9 +78,11 @@ describe('Balance', () => { } }); }); - beforeAll(() => { - jest.resetAllMocks(); + + afterEach(() => { + jest.clearAllMocks(); }); + it('should render correctly with a fiat balance', () => { const wrapper = render( , @@ -58,4 +100,24 @@ describe('Balance', () => { ); expect(wrapper).toMatchSnapshot(); }); + + it('should fire navigation event for non native tokens', () => { + const { queryByTestId } = render( + , + ); + const assetElement = queryByTestId('asset-DAI'); + fireEvent.press(assetElement); + expect(mockNavigate).toHaveBeenCalledTimes(1); + }); + + it('should not fire navigation event for native tokens', () => { + const { queryByTestId } = render( + + + , + ); + const assetElement = queryByTestId('asset-ETH'); + fireEvent.press(assetElement); + expect(mockNavigate).toHaveBeenCalledTimes(0); + }); }); diff --git a/app/components/Views/AssetOptions/AssetOptions.styles.ts b/app/components/Views/AssetOptions/AssetOptions.styles.ts index 4ca2e5004ac..31dec7ced07 100644 --- a/app/components/Views/AssetOptions/AssetOptions.styles.ts +++ b/app/components/Views/AssetOptions/AssetOptions.styles.ts @@ -31,6 +31,7 @@ const styleSheet = (params: { theme: Theme }) => { }, optionLabel: { ...typography.sBodyLGMedium, + color: colors.text.default, } as TextStyle, }); }; From ca92f2e1de2161127b9e1b57f03846e5ce47009a Mon Sep 17 00:00:00 2001 From: "runway-github[bot]" <73448015+runway-github[bot]@users.noreply.github.com> Date: Tue, 24 Sep 2024 12:30:51 -0600 Subject: [PATCH 09/27] chore(runway): cherry-pick fix: push notifications (#11417) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fix: push notifications (#11250) ## **Description** This PR re-enable push notifications (background/foreground) based on notifee approach. ## **Related issues** Fixes: [NOTIFY-1103](https://consensyssoftware.atlassian.net/issues/?jql=project+%3D+%22NOTIFY%22+AND+assignee+%3D+712020%3A5eed27f4-5f0b-4c08-92a0-9879b5f69a8d+ORDER+BY+created+DESC&atlOrigin=eyJpIjoiYmFjNDg1NDY1Y2ZlNDRiMGEwNTI0N2MzNzcxZjlmZjkiLCJwIjoiaiJ9) ## **Manual testing steps** 1. Install the MetaMask wallet 2. Click on the Bell Icon on the top-right header 3.Turn On Notifications 4. Trigger any transaction and leave the app. Expected behaviour: A device push notification should appear on your device. ## **Screenshots/Recordings** ### **Before** ### **After** https://github.com/user-attachments/assets/59938cbf-4a2c-4409-9b68-085ee67dcc9d ## **Pre-merge author checklist** - [x] 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). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] 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** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] 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. [NOTIFY-1103]: https://consensyssoftware.atlassian.net/browse/NOTIFY-1103?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ [dcca986](https://github.com/MetaMask/metamask-mobile/commit/dcca986dec8cff18842d6ecbd508296561cdc886) [NOTIFY-1103]: https://consensyssoftware.atlassian.net/browse/NOTIFY-1103?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ [NOTIFY-1103]: https://consensyssoftware.atlassian.net/browse/NOTIFY-1103?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ Co-authored-by: Jonathan Ferreira <44679989+Jonathansoufer@users.noreply.github.com> --- app/components/Nav/Main/index.js | 30 +- .../BasicFunctionalityModal.tsx | 21 +- app/components/UI/Notification/List/index.tsx | 8 +- .../Views/Notifications/OptIn/index.test.tsx | 90 ++++- .../Views/Notifications/OptIn/index.tsx | 20 +- app/components/Views/Notifications/index.tsx | 4 +- .../NotificationsSettings/index.test.tsx | 160 +++++++- .../Settings/NotificationsSettings/index.tsx | 15 +- app/components/Views/Wallet/index.tsx | 6 +- app/core/NotificationManager.js | 17 +- app/core/NotificationsManager.test.ts | 40 +- .../notifications/androidChannels.test.ts | 50 +++ app/util/notifications/androidChannels.ts | 32 ++ app/util/notifications/hooks/index.test.ts | 90 +++-- app/util/notifications/hooks/index.ts | 89 ++--- app/util/notifications/index.ts | 4 +- app/util/notifications/methods/common.ts | 4 + .../notifications/pushPermissions.test.ts | 113 ------ app/util/notifications/pushPermissions.ts | 123 ------ .../services/NotificationService.test.ts | 134 +++++++ .../services/NotificationService.ts | 236 +++++++++++ app/util/notifications/services/index.ts | 1 + .../settings/storage/constants.ts | 2 +- .../settings/storage/contants.test.ts | 2 +- .../setupAndroidChannels.test.ts | 25 -- .../notifications/setupAndroidChannels.ts | 12 - .../notifications/types/notification/index.ts | 13 - e2e/helpers.js | 2 +- e2e/pages/EnableDeviceNotificationsAlert.js | 27 ++ e2e/specs/quarantine/import-nft.failing.js | 2 +- e2e/utils/Matchers.js | 13 +- e2e/viewHelper.js | 7 +- index.js | 16 - ios/Podfile.lock | 90 ++--- package.json | 9 +- ...eDeviceNotificationsChecksAlert.testIds.js | 6 + yarn.lock | 371 +++++++++++++++++- 37 files changed, 1315 insertions(+), 569 deletions(-) create mode 100644 app/util/notifications/androidChannels.test.ts create mode 100644 app/util/notifications/androidChannels.ts delete mode 100644 app/util/notifications/pushPermissions.test.ts delete mode 100644 app/util/notifications/pushPermissions.ts create mode 100644 app/util/notifications/services/NotificationService.test.ts create mode 100644 app/util/notifications/services/NotificationService.ts create mode 100644 app/util/notifications/services/index.ts delete mode 100644 app/util/notifications/setupAndroidChannels.test.ts delete mode 100644 app/util/notifications/setupAndroidChannels.ts create mode 100644 e2e/pages/EnableDeviceNotificationsAlert.js create mode 100644 wdio/screen-objects/testIDs/Screens/EnableDeviceNotificationsChecksAlert.testIds.js diff --git a/app/components/Nav/Main/index.js b/app/components/Nav/Main/index.js index 1353c66b04a..d800e78ce30 100644 --- a/app/components/Nav/Main/index.js +++ b/app/components/Nav/Main/index.js @@ -21,14 +21,11 @@ import BackgroundTimer from 'react-native-background-timer'; import NotificationManager from '../../../core/NotificationManager'; import Engine from '../../../core/Engine'; import AppConstants from '../../../core/AppConstants'; -import notifee from '@notifee/react-native'; import I18n, { strings } from '../../../../locales/i18n'; import FadeOutOverlay from '../../UI/FadeOutOverlay'; import BackupAlert from '../../UI/BackupAlert'; import Notification from '../../UI/Notification'; import RampOrders from '../../UI/Ramp'; -import Device from '../../../util/device'; -import Routes from '../../../constants/navigation/Routes'; import { showTransactionNotification, hideCurrentNotification, @@ -36,12 +33,12 @@ import { removeNotificationById, removeNotVisibleNotifications, } from '../../../actions/notification'; + import ProtectYourWalletModal from '../../UI/ProtectYourWalletModal'; import MainNavigator from './MainNavigator'; import SkipAccountSecurityModal from '../../UI/SkipAccountSecurityModal'; import { query } from '@metamask/controller-utils'; import SwapsLiveness from '../../UI/Swaps/SwapsLiveness'; -import useNotificationHandler from '../../../util/notifications/hooks'; import { setInfuraAvailabilityBlocked, @@ -70,6 +67,8 @@ import { selectNetworkImageSource, } from '../../../selectors/networkInfos'; import { selectShowIncomingTransactionNetworks } from '../../../selectors/preferencesController'; + +import useNotificationHandler from '../../../util/notifications/hooks'; import { DEPRECATED_NETWORKS, NETWORKS_CHAIN_ID, @@ -105,16 +104,18 @@ const Main = (props) => { const [showDeprecatedAlert, setShowDeprecatedAlert] = useState(true); const { colors } = useTheme(); const styles = createStyles(colors); - const backgroundMode = useRef(false); const locale = useRef(I18n.locale); const removeConnectionStatusListener = useRef(); const removeNotVisibleNotifications = props.removeNotVisibleNotifications; - + useNotificationHandler(props.navigation); useEnableAutomaticSecurityChecks(); useMinimumVersions(); + + + useEffect(() => { if (DEPRECATED_NETWORKS.includes(props.chainId)) { setShowDeprecatedAlert(true); @@ -267,23 +268,8 @@ const Main = (props) => { initForceReload(); return; } - }); - - const bootstrapAndroidInitialNotification = useCallback(async () => { - if (Device.isAndroid()) { - const initialNotification = await notifee.getInitialNotification(); - - if ( - initialNotification?.data?.action === 'tx' && - initialNotification.data.id - ) { - NotificationManager.setTransactionToView(initialNotification.data.id); - props.navigation.navigate(Routes.TRANSACTIONS_VIEW); - } - } - }, [props.navigation]); - useNotificationHandler(bootstrapAndroidInitialNotification, props.navigation); + }); // Remove all notifications that aren't visible useEffect(() => { diff --git a/app/components/UI/BasicFunctionality/BasicFunctionalityModal/BasicFunctionalityModal.tsx b/app/components/UI/BasicFunctionality/BasicFunctionalityModal/BasicFunctionalityModal.tsx index b2d26f68838..e01d3afdccb 100644 --- a/app/components/UI/BasicFunctionality/BasicFunctionalityModal/BasicFunctionalityModal.tsx +++ b/app/components/UI/BasicFunctionality/BasicFunctionalityModal/BasicFunctionalityModal.tsx @@ -26,10 +26,7 @@ import Icon, { IconSize, } from '../../../../component-library/components/Icons/Icon'; import Routes from '../../../../constants/navigation/Routes'; -import { - asyncAlert, - requestPushNotificationsPermission, -} from '../../../../util/notifications'; +import NotificationsService from '../../../../util/notifications/services/NotificationService'; import { MetaMetricsEvents } from '../../../../core/Analytics'; import { useEnableNotifications } from '../../../../util/notifications/hooks/useNotifications'; import { useMetrics } from '../../../hooks/useMetrics'; @@ -37,7 +34,6 @@ import { selectIsProfileSyncingEnabled, selectIsMetamaskNotificationsEnabled, } from '../../../../selectors/notifications'; -import { AuthorizationStatus } from '@notifee/react-native'; interface Props { route: { @@ -65,18 +61,11 @@ const BasicFunctionalityModal = ({ route }: Props) => { const { enableNotifications } = useEnableNotifications(); const enableNotificationsFromModal = useCallback(async () => { - const nativeNotificationStatus = await requestPushNotificationsPermission( - asyncAlert, - ); - - if (nativeNotificationStatus?.authorizationStatus === AuthorizationStatus.AUTHORIZED) { - /** - * Although this is an async function, we are dispatching an action (firing & forget) - * to emulate optimistic UI. - * - */ - enableNotifications(); + const { permission } = await NotificationsService.getAllPermissions(false); + if (permission !== 'authorized') { + return; } + enableNotifications(); }, [enableNotifications]); const closeBottomSheet = async () => { diff --git a/app/components/UI/Notification/List/index.tsx b/app/components/UI/Notification/List/index.tsx index edac15cf843..fcfd61a8002 100644 --- a/app/components/UI/Notification/List/index.tsx +++ b/app/components/UI/Notification/List/index.tsx @@ -1,6 +1,6 @@ import { NavigationProp, ParamListBase } from '@react-navigation/native'; import React, { useCallback, useMemo } from 'react'; -import notifee from '@notifee/react-native'; +import NotificationsService from '../../../../util/notifications/services/NotificationService'; import { ActivityIndicator, FlatList, FlatListProps, View } from 'react-native'; import ScrollableTabView, { DefaultTabBar, @@ -75,11 +75,11 @@ function NotificationsListItem(props: NotificationsListItemProps) { }); } - notifee.getBadgeCount().then((count) => { + NotificationsService.getBadgeCount().then((count) => { if (count > 0) { - notifee.setBadgeCount(count - 1); + NotificationsService.decrementBadgeCount(count - 1); } else { - notifee.setBadgeCount(0); + NotificationsService.setBadgeCount(0); } }); diff --git a/app/components/Views/Notifications/OptIn/index.test.tsx b/app/components/Views/Notifications/OptIn/index.test.tsx index bd62c3d961e..b643daaa7fb 100644 --- a/app/components/Views/Notifications/OptIn/index.test.tsx +++ b/app/components/Views/Notifications/OptIn/index.test.tsx @@ -1,13 +1,16 @@ import React from 'react'; -import OptIn from './'; +import OptIn from '.'; import { RootState } from '../../../../reducers'; import { backgroundState } from '../../../../util/test/initial-root-state'; import renderWithProvider, { DeepPartial, } from '../../../../util/test/renderWithProvider'; +import { strings } from '../../../../../locales/i18n'; const mockedDispatch = jest.fn(); + + const mockInitialState: DeepPartial = { settings: {}, engine: { @@ -18,14 +21,12 @@ const mockInitialState: DeepPartial = { }, }, }, -}; + }; -jest.mock('react-redux', () => ({ - ...jest.requireActual('react-redux'), - // TODO: Replace "any" with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - useSelector: (fn: any) => fn(mockInitialState), -})); + jest.mock('react-redux', () => ({ + ...jest.requireActual('react-redux'), + useSelector: jest.fn().mockImplementation((selector) => selector(mockInitialState)), + })); jest.mock('@react-navigation/native', () => { const actualNav = jest.requireActual('@react-navigation/native'); @@ -38,9 +39,82 @@ jest.mock('@react-navigation/native', () => { }; }); +jest.mock('../../../../actions/notification/helpers', () => ({ + enableNotificationServices: jest.fn(), +})); + +jest.mock('../../../../components/hooks/useMetrics', () => ({ + useMetrics: () => ({ + trackEvent: jest.fn(), + }), +})); + +jest.mock('../../../../util/notifications/hooks/useNotifications', () => ({ + useEnableNotifications: () => ({ + enableNotifications: jest.fn(), + }), +})); + +jest.mock('react-native', () => ({ + Linking: { + openURL: jest.fn(), + }, +})); + +jest.mock('../../../../selectors/notifications', () => ({ + selectIsMetamaskNotificationsEnabled: jest.fn(), +})); + +jest.mock('../../../../core/Analytics', () => ({ + MetaMetricsEvents: { + NOTIFICATIONS_ACTIVATED: 'notifications_activated', + }, +})); + +jest.mock('../../../../util/theme', () => ({ + useTheme: jest.fn(), +})); + +jest.mock('../../../../selectors/notifications', () => ({ + selectIsProfileSyncingEnabled: jest.fn(), +})); + describe('OptIn', () => { + + beforeEach(() => { + jest.resetAllMocks(); + }); + it('should render correctly', () => { const { toJSON } = renderWithProvider(); expect(toJSON()).toMatchSnapshot(); }); + + it('calls enableNotifications when the button is pressed', async () => { + const { getByText } = renderWithProvider( + + ); + + const button = getByText(strings('notifications.activation_card.cta')); + expect(button).toBeDefined(); + }); + + it('calls navigate when the cancel button is pressed', async () => { + const { getByText } = renderWithProvider( + + ); + + const button = getByText(strings('notifications.activation_card.cancel')); + expect(button).toBeDefined(); + }); + + it('calls trackEvent when the button is pressed', async () => { + const { getByText } = renderWithProvider( + + ); + + const button = getByText(strings('notifications.activation_card.cta')); + expect(button).toBeDefined(); + }); }); + diff --git a/app/components/Views/Notifications/OptIn/index.tsx b/app/components/Views/Notifications/OptIn/index.tsx index d256d687df8..4c69eb7d7dd 100644 --- a/app/components/Views/Notifications/OptIn/index.tsx +++ b/app/components/Views/Notifications/OptIn/index.tsx @@ -4,7 +4,6 @@ import { useNavigation } from '@react-navigation/native'; import { useMetrics } from '../../../../components/hooks/useMetrics'; import { MetaMetricsEvents } from '../../../../core/Analytics'; -import { AuthorizationStatus } from '@notifee/react-native'; import Button, { ButtonVariants, } from '../../../../component-library/components/Buttons/Button'; @@ -18,10 +17,7 @@ import EnableNotificationsCardPlaceholder from '../../../../images/enableNotific import { createStyles } from './styles'; import Routes from '../../../../constants/navigation/Routes'; import { useSelector } from 'react-redux'; -import { - asyncAlert, - requestPushNotificationsPermission, -} from '../../../../util/notifications'; +import NotificationsService from '../../../../util/notifications/services/NotificationService'; import AppConstants from '../../../../core/AppConstants'; import { RootState } from '../../../../reducers'; import { useEnableNotifications } from '../../../../util/notifications/hooks/useNotifications'; @@ -76,14 +72,11 @@ const OptIn = () => { }, }); } else { - const nativeNotificationStatus = await requestPushNotificationsPermission( - asyncAlert, - ); - - if ( - nativeNotificationStatus?.authorizationStatus === - AuthorizationStatus.AUTHORIZED - ) { + const { permission } = await NotificationsService.getAllPermissions(); + + if (permission !== 'authorized') { + return; + } /** * Although this is an async function, we are dispatching an action (firing & forget) * to emulate optimistic UI. @@ -103,7 +96,6 @@ const OptIn = () => { action_type: 'activated', is_profile_syncing_enabled: isProfileSyncingEnabled, }); - } }, [ basicFunctionalityEnabled, enableNotifications, diff --git a/app/components/Views/Notifications/index.tsx b/app/components/Views/Notifications/index.tsx index a38ffeb1b50..2a3ee9be996 100644 --- a/app/components/Views/Notifications/index.tsx +++ b/app/components/Views/Notifications/index.tsx @@ -1,7 +1,6 @@ import React, { useCallback, useMemo } from 'react'; import { View } from 'react-native'; import { useSelector } from 'react-redux'; -import notifee from '@notifee/react-native'; import { useMetrics } from '../../../components/hooks/useMetrics'; import { NotificationsViewSelectorsIDs } from '../../../../e2e/selectors/NotificationsView.selectors'; import styles from './styles'; @@ -29,6 +28,7 @@ import { useMarkNotificationAsRead, } from '../../../util/notifications/hooks/useNotifications'; import { NavigationProp, ParamListBase } from '@react-navigation/native'; +import NotificationsService from '../../../util/notifications/services/NotificationService'; import ButtonIcon, { ButtonIconSizes, } from '../../../component-library/components/Buttons/ButtonIcon'; @@ -53,7 +53,7 @@ const NotificationsView = ({ const handleMarkAllAsRead = useCallback(() => { markNotificationAsRead(notifications); - notifee.setBadgeCount(0); + NotificationsService.setBadgeCount(0); trackEvent(MetaMetricsEvents.NOTIFICATIONS_MARKED_ALL_AS_READ); }, [markNotificationAsRead, notifications, trackEvent]); diff --git a/app/components/Views/Settings/NotificationsSettings/index.test.tsx b/app/components/Views/Settings/NotificationsSettings/index.test.tsx index 292c0906062..ff8b0928e70 100644 --- a/app/components/Views/Settings/NotificationsSettings/index.test.tsx +++ b/app/components/Views/Settings/NotificationsSettings/index.test.tsx @@ -1,11 +1,16 @@ -import React from 'react'; +import React, { useCallback } from 'react'; +import { renderHook, act } from '@testing-library/react-hooks'; import renderWithProvider from '../../../../util/test/renderWithProvider'; - +import NotificationsService from '../../../../util/notifications/services/NotificationService'; import { backgroundState } from '../../../../util/test/initial-root-state'; import NotificationsSettings from '.'; + +import Routes from '../../../../constants/navigation/Routes'; import { Props } from './NotificationsSettings.types'; import { MOCK_ACCOUNTS_CONTROLLER_STATE } from '../../../../util/test/accountsControllerTestUtils'; +import { MetaMetricsEvents } from '../../../../core/Analytics/MetaMetrics.events'; +import { NavigationProp, ParamListBase } from '@react-navigation/native'; // Mock store.getState let mockGetState: jest.Mock; @@ -38,8 +43,142 @@ const mockInitialState = { }, }; +jest.mock('@react-navigation/native', () => { + const actualNav = jest.requireActual('@react-navigation/native'); + return { + ...actualNav, + useNavigation: () => ({ + navigate: jest.fn(), + }), + }; +}); + +jest.mock('../../../../util/notifications/services/NotificationService', () => ({ + getAllPermissions: jest.fn(), +})); + +jest.mock('../../../../core/Analytics/MetaMetrics.events', () => ({ + MetaMetricsEvents: { + NOTIFICATIONS_SETTINGS_UPDATED: 'NOTIFICATIONS_SETTINGS_UPDATED', + }, +})); + +const mockDisableNotifications = jest.fn(); +const mockEnableNotifications = jest.fn(); +const mockSetUiNotificationStatus = jest.fn(); +const mockTrackEvent = jest.fn(); + +const mockNavigation = { + navigate: jest.fn(), +} as unknown as NavigationProp; + const setOptions = jest.fn(); +describe('toggleNotificationsEnabled', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + const setup = (basicFunctionalityEnabled: boolean, isMetamaskNotificationsEnabled: boolean, isProfileSyncingEnabled: boolean) => renderHook(() => + useCallback(async () => { + if (!basicFunctionalityEnabled) { + mockNavigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { + screen: Routes.SHEET.BASIC_FUNCTIONALITY, + params: { + caller: Routes.SETTINGS.NOTIFICATIONS, + }, + }); + } else if (isMetamaskNotificationsEnabled) { + mockDisableNotifications(); + mockSetUiNotificationStatus(false); + } else { + const { permission } = await NotificationsService.getAllPermissions(false); + if (permission !== 'authorized') { + return; + } + + mockEnableNotifications(); + mockSetUiNotificationStatus(true); + } + mockTrackEvent(MetaMetricsEvents.NOTIFICATIONS_SETTINGS_UPDATED, { + settings_type: 'notifications', + old_value: isMetamaskNotificationsEnabled, + new_value: !isMetamaskNotificationsEnabled, + was_profile_syncing_on: isMetamaskNotificationsEnabled ? true : isProfileSyncingEnabled, + }); + }, []) + ); + + it('should navigate to basic functionality screen if basicFunctionalityEnabled is false', async () => { + const { result } = setup(false, false, false); + + await act(async () => { + await result.current(); + }); + + expect(mockNavigation.navigate).toHaveBeenCalledWith(Routes.MODAL.ROOT_MODAL_FLOW, { + screen: Routes.SHEET.BASIC_FUNCTIONALITY, + params: { + caller: Routes.SETTINGS.NOTIFICATIONS, + }, + }); + }); + + it('should disable notifications if isMetamaskNotificationsEnabled is true', async () => { + const { result } = setup(true, true, false); + + await act(async () => { + await result.current(); + }); + + expect(mockDisableNotifications).toHaveBeenCalled(); + expect(mockSetUiNotificationStatus).toHaveBeenCalledWith(false); + }); + + it('should enable notifications if isMetamaskNotificationsEnabled is false and permission is authorized', async () => { + (NotificationsService.getAllPermissions as jest.Mock).mockResolvedValue({ permission: 'authorized' }); + + const { result } = setup(true, false, false); + + await act(async () => { + await result.current(); + }); + + expect(mockEnableNotifications).toHaveBeenCalled(); + expect(mockSetUiNotificationStatus).toHaveBeenCalledWith(true); + }); + + it('should not enable notifications if permission is not authorized', async () => { + (NotificationsService.getAllPermissions as jest.Mock).mockResolvedValue({ permission: 'denied' }); + + const { result } = setup(true, false, false); + + await act(async () => { + await result.current(); + }); + + expect(mockEnableNotifications).not.toHaveBeenCalled(); + expect(mockSetUiNotificationStatus).not.toHaveBeenCalled(); + }); + + it('should track event when notifications settings are updated', async () => { + (NotificationsService.getAllPermissions as jest.Mock).mockResolvedValue({ permission: 'authorized' }); + + const { result } = setup(true, false, true); + + await act(async () => { + await result.current(); + }); + + expect(mockTrackEvent).toHaveBeenCalledWith(MetaMetricsEvents.NOTIFICATIONS_SETTINGS_UPDATED, { + settings_type: 'notifications', + old_value: false, + new_value: true, + was_profile_syncing_on: true, + }); + }); +}); + describe('NotificationsSettings', () => { it('should render correctly', () => { mockGetState.mockImplementation(() => ({ @@ -60,4 +199,21 @@ describe('NotificationsSettings', () => { ); expect(toJSON()).toMatchSnapshot(); }); + + it('should toggle notifications and handle permission correctly', async () => { + const isMetamaskNotificationsEnabled = true; + const basicFunctionalityEnabled = true; + const isProfileSyncingEnabled = true; + + const toggleNotificationsEnabledImpl = jest.fn(() => Promise.resolve({ + isMetamaskNotificationsEnabled, + basicFunctionalityEnabled, + isProfileSyncingEnabled, + })); + + await toggleNotificationsEnabledImpl(); + + expect(NotificationsService.getAllPermissions).toHaveBeenCalledTimes(1); + expect(NotificationsService.getAllPermissions).toHaveBeenCalledWith(false); + }); }); diff --git a/app/components/Views/Settings/NotificationsSettings/index.tsx b/app/components/Views/Settings/NotificationsSettings/index.tsx index 9dd493d57e6..707d2a24b7e 100644 --- a/app/components/Views/Settings/NotificationsSettings/index.tsx +++ b/app/components/Views/Settings/NotificationsSettings/index.tsx @@ -31,10 +31,7 @@ import { selectIsProfileSyncingEnabled, } from '../../../../selectors/notifications'; -import { - requestPushNotificationsPermission, - asyncAlert, -} from '../../../../util/notifications'; +import NotificationsService from '../../../../util/notifications/services/NotificationService'; import Routes from '../../../../constants/navigation/Routes'; import ButtonIcon, { @@ -54,7 +51,6 @@ import AppConstants from '../../../../core/AppConstants'; import notificationsRows from './notificationsRows'; import { IconName } from '../../../../component-library/components/Icons/Icon'; import { MetaMetricsEvents } from '../../../../core/Analytics/MetaMetrics.events'; -import { AuthorizationStatus } from '@notifee/react-native'; interface MainNotificationSettingsProps extends Props { toggleNotificationsEnabled: () => void; @@ -187,18 +183,17 @@ const NotificationsSettings = ({ navigation, route }: Props) => { disableNotifications(); setUiNotificationStatus(false); } else { - const nativeNotificationStatus = await requestPushNotificationsPermission( - asyncAlert, - ); + const { permission } = await NotificationsService.getAllPermissions(false); + if (permission !== 'authorized') { + return; + } - if (nativeNotificationStatus?.authorizationStatus === AuthorizationStatus.AUTHORIZED) { /** * Although this is an async function, we are dispatching an action (firing & forget) * to emulate optimistic UI. */ enableNotifications(); setUiNotificationStatus(true); - } } trackEvent(MetaMetricsEvents.NOTIFICATIONS_SETTINGS_UPDATED, { settings_type: 'notifications', diff --git a/app/components/Views/Wallet/index.tsx b/app/components/Views/Wallet/index.tsx index 5efbd2e5b90..522affc03fd 100644 --- a/app/components/Views/Wallet/index.tsx +++ b/app/components/Views/Wallet/index.tsx @@ -35,9 +35,7 @@ import { ToastContext, ToastVariants, } from '../../../component-library/components/Toast'; -import { - isDeviceNotificationEnabled, -} from '../../../util/notifications'; +import NotificationsService from '../../../util/notifications/services/NotificationService'; import Engine from '../../../core/Engine'; import CollectibleContracts from '../../UI/CollectibleContracts'; import { MetaMetricsEvents } from '../../../core/Analytics'; @@ -343,7 +341,7 @@ const Wallet = ({ } async function checkIfNotificationsAreEnabled() { - await isDeviceNotificationEnabled(); + await NotificationsService.isDeviceNotificationEnabled(); } checkIfNotificationsAreEnabled(); }); diff --git a/app/core/NotificationManager.js b/app/core/NotificationManager.js index c01a4e1dbfb..3bcf35ee01b 100644 --- a/app/core/NotificationManager.js +++ b/app/core/NotificationManager.js @@ -1,9 +1,9 @@ 'use strict'; -import notifee from '@notifee/react-native'; import Engine from './Engine'; import { hexToBN, renderFromWei } from '../util/number'; import Device from '../util/device'; +import notifee from '@notifee/react-native'; import { STORAGE_IDS } from '../util/notifications/settings/storage/constants'; import { strings } from '../../locales/i18n'; import { AppState } from 'react-native'; @@ -11,9 +11,9 @@ import { AppState } from 'react-native'; import { NotificationTransactionTypes, isNotificationsFeatureEnabled, - requestPushNotificationsPermission, - asyncAlert, + } from '../util/notifications'; + import { safeToChecksumAddress } from '../util/address'; import ReviewManager from './ReviewManager'; import { selectChainId } from '../selectors/networkController'; @@ -152,7 +152,7 @@ class NotificationManager { title, body: message, android: { - lightUpScreen: false, + lightUpScreen: true, channelId, smallIcon: 'ic_notification_small', largeIcon: 'ic_notification', @@ -179,7 +179,6 @@ class NotificationManager { } else { pushData.userInfo = extraData; // check if is still needed } - isNotificationsFeatureEnabled() && notifee.displayNotification(pushData); } else { this._showTransactionNotification({ @@ -256,11 +255,6 @@ class NotificationManager { } Promise.all(pollPromises); - Device.isIos() && - setTimeout(() => { - requestPushNotificationsPermission(asyncAlert); - }, 5000); - // Prompt review ReviewManager.promptReview(); @@ -491,9 +485,6 @@ export default { gotIncomingTransaction(lastBlock) { return instance?.gotIncomingTransaction(lastBlock); }, - requestPushNotificationsPermission() { - return instance?.requestPushNotificationsPermission(asyncAlert); - }, showSimpleNotification(data) { return instance?.showSimpleNotification(data); }, diff --git a/app/core/NotificationsManager.test.ts b/app/core/NotificationsManager.test.ts index bf4e31eab4a..2af79c085b5 100644 --- a/app/core/NotificationsManager.test.ts +++ b/app/core/NotificationsManager.test.ts @@ -1,4 +1,5 @@ import { NotificationTransactionTypes } from '../util/notifications'; + import NotificationManager, { constructTitleAndMessage, } from './NotificationManager'; @@ -44,6 +45,26 @@ describe('NotificationManager', () => { expect(notificationManager._backgroundMode).toBe(true); }); + it('calling NotificationManager in _failedCallback mode should call _showNotification', () => { + notificationManager._failedCallback({ + id: 1, + txParams: { + nonce: 1, + }, + }); + expect(notificationManager._showNotification).toBeInstanceOf(Function); + }); + + it('calling NotificationManager onMessageReceived', () => { + notificationManager.onMessageReceived({ + data: { + title: 'title', + shortDescription: 'shortDescription', + }, + }); + expect(notificationManager.onMessageReceived).toBeInstanceOf(Function); + }); + it('calling NotificationManager in background mode OFF should be falsy', () => { notificationManager._handleAppStateChange('active'); expect(notificationManager._backgroundMode).toBe(false); @@ -69,15 +90,16 @@ describe('NotificationManager', () => { expect(NotificationManager.getTransactionToView()).toBeTruthy(); }); - const selectedNotificationTypes: (keyof typeof NotificationTransactionTypes)[] = [ - 'pending', - 'pending_deposit', - 'pending_withdrawal', - 'success_withdrawal', - 'success_deposit', - 'error', - 'cancelled', - ]; + const selectedNotificationTypes: (keyof typeof NotificationTransactionTypes)[] = + [ + 'pending', + 'pending_deposit', + 'pending_withdrawal', + 'success_withdrawal', + 'success_deposit', + 'error', + 'cancelled', + ]; selectedNotificationTypes.forEach((type) => { it(`should construct title and message for ${type}`, () => { const { title, message } = constructTitleAndMessage({ diff --git a/app/util/notifications/androidChannels.test.ts b/app/util/notifications/androidChannels.test.ts new file mode 100644 index 00000000000..49e3a28b8ad --- /dev/null +++ b/app/util/notifications/androidChannels.test.ts @@ -0,0 +1,50 @@ +import { AndroidImportance } from '@notifee/react-native'; +import { + ChannelId, + MetaMaskAndroidChannel, + notificationChannels, +} from './androidChannels'; + +describe('notificationChannels', () => { + it('should have two channels', () => { + expect(notificationChannels).toHaveLength(2); + }); + + it('should have the correct properties for the first channel', () => { + const firstChannel: MetaMaskAndroidChannel = notificationChannels[0]; + expect(firstChannel).toEqual({ + id: ChannelId.DEFAULT_NOTIFICATION_CHANNEL_ID, + name: 'Transaction Complete', + lights: false, + vibration: false, + importance: AndroidImportance.DEFAULT, + title: 'Transaction', + subtitle: 'Transaction Complete', + }); + }); + + it('should have the correct properties for the second channel', () => { + const secondChannel: MetaMaskAndroidChannel = notificationChannels[1]; + expect(secondChannel).toEqual({ + id: ChannelId.DEFAULT_NOTIFICATION_CHANNEL_ID, + name: 'MetaMask Announcement', + lights: false, + vibration: false, + importance: AndroidImportance.DEFAULT, + title: 'Announcement', + subtitle: 'MetaMask Announcement', + }); + }); + + it('should have unique titles for each channel', () => { + const titles = notificationChannels.map((channel) => channel.title); + const uniqueTitles = new Set(titles); + expect(uniqueTitles.size).toBe(titles.length); + }); + + it('should have unique subtitles for each channel', () => { + const subtitles = notificationChannels.map((channel) => channel.subtitle); + const uniqueSubtitles = new Set(subtitles); + expect(uniqueSubtitles.size).toBe(subtitles.length); + }); +}); diff --git a/app/util/notifications/androidChannels.ts b/app/util/notifications/androidChannels.ts new file mode 100644 index 00000000000..1bc1de98c72 --- /dev/null +++ b/app/util/notifications/androidChannels.ts @@ -0,0 +1,32 @@ +import { AndroidChannel, AndroidImportance } from '@notifee/react-native'; + +export enum ChannelId { + DEFAULT_NOTIFICATION_CHANNEL_ID = 'DEFAULT_NOTIFICATION_CHANNEL_ID', +} + +export interface MetaMaskAndroidChannel extends AndroidChannel { + id: ChannelId; + title: string; + subtitle: string; +} + +export const notificationChannels = [ + { + id: ChannelId.DEFAULT_NOTIFICATION_CHANNEL_ID, + name: 'Transaction Complete', + lights: false, + vibration: false, + importance: AndroidImportance.DEFAULT, + title: 'Transaction', + subtitle: 'Transaction Complete', + } as MetaMaskAndroidChannel, + { + id: ChannelId.DEFAULT_NOTIFICATION_CHANNEL_ID, + name: 'MetaMask Announcement', + lights: false, + vibration: false, + importance: AndroidImportance.DEFAULT, + title: 'Announcement', + subtitle: 'MetaMask Announcement', + } as MetaMaskAndroidChannel, +]; diff --git a/app/util/notifications/hooks/index.test.ts b/app/util/notifications/hooks/index.test.ts index c826067a63a..bbe94a97dc3 100644 --- a/app/util/notifications/hooks/index.test.ts +++ b/app/util/notifications/hooks/index.test.ts @@ -6,6 +6,9 @@ import notifee, { import useNotificationHandler from './index'; import { NavigationProp, ParamListBase } from '@react-navigation/native'; +import Routes from '../../../constants/navigation/Routes'; +import { Notification } from '../../../util/notifications/types'; +import { TRIGGER_TYPES } from '../constants'; jest.mock('../../../util/device'); jest.mock('../../../core/NotificationManager', () => ({ @@ -26,51 +29,67 @@ jest.mock('@notifee/react-native', () => ({ }, })); +jest.mock('../../../core/NotificationManager', () => ({ + setTransactionToView: jest.fn(), +})); + +jest.mock('../../../util/device', () => ({ + isAndroid: jest.fn(), +})); + const mockNavigate = jest.fn(); const mockNavigation = { navigate: mockNavigate, } as unknown as NavigationProp; -const bootstrapAndroidInitialNotification = jest - .fn() - .mockResolvedValue(undefined); +const notification = { + id: 1, + type: TRIGGER_TYPES.ERC1155_RECEIVED, + data: { + id: 1, + trigger_id: '1', + chain_id: 1, + block_number: 1, + block_timestamp: '', + tx_hash: '', + unread: false, + created_at: '', + address: '', + type: TRIGGER_TYPES.ERC1155_RECEIVED, + data: {}, + createdAt: '', + isRead: false, + }, +} as unknown as Notification; const mockNotificationEvent = (event: NotifeeEvent) => ({ type: event.type, detail: { - notification: { - body: 'notificationTest', - data: { - action: 'tx', - id: '123', - }, - }, + notification, }, }); + describe('useNotificationHandler', () => { beforeEach(() => { jest.clearAllMocks(); }); - it('sets initial badge count and initializes Android notifications on mount', async () => { - renderHook(() => - useNotificationHandler( - bootstrapAndroidInitialNotification, - mockNavigation, - ), - ); + it('should navigate to NOTIFICATIONS.DETAILS if notification is pressed', async () => { + const { result } = renderHook(() => useNotificationHandler(mockNavigation)); - expect(bootstrapAndroidInitialNotification).toHaveBeenCalled(); + await result.current.handlePressedNotification(notification); - jest.runAllTimers(); + expect(mockNavigation.navigate).toHaveBeenCalledWith( + Routes.NOTIFICATIONS.DETAILS, + { + notificationId: notification.id, + }, + ); }); it('should handle notifications correctly', async () => { const { waitFor } = renderHook(() => - useNotificationHandler( - bootstrapAndroidInitialNotification, - mockNavigation, - ), + useNotificationHandler(mockNavigation), ); await act(async () => { @@ -96,10 +115,7 @@ describe('useNotificationHandler', () => { it('should do nothing if the EventType is DISMISSED', async () => { const { waitFor } = renderHook(() => - useNotificationHandler( - bootstrapAndroidInitialNotification, - mockNavigation, - ), + useNotificationHandler(mockNavigation), ); await act(async () => { @@ -126,10 +142,7 @@ describe('useNotificationHandler', () => { it('should do nothing if data.action is not tx', async () => { const { waitFor } = renderHook(() => - useNotificationHandler( - bootstrapAndroidInitialNotification, - mockNavigation, - ), + useNotificationHandler(mockNavigation), ); await act(async () => { @@ -158,10 +171,7 @@ describe('useNotificationHandler', () => { it('handleOpenedNotification should do nothing if notification is null', async () => { const { waitFor } = renderHook(() => - useNotificationHandler( - bootstrapAndroidInitialNotification, - mockNavigation, - ), + useNotificationHandler(mockNavigation), ); await act(async () => { @@ -183,10 +193,7 @@ describe('useNotificationHandler', () => { it('should navigate to the transaction view when the notification action is "tx"', async () => { const { waitFor } = renderHook(() => - useNotificationHandler( - bootstrapAndroidInitialNotification, - mockNavigation, - ), + useNotificationHandler(mockNavigation), ); await act(async () => { @@ -216,10 +223,7 @@ describe('useNotificationHandler', () => { })); const { waitFor } = renderHook(() => - useNotificationHandler( - bootstrapAndroidInitialNotification, - mockNavigation, - ), + useNotificationHandler(mockNavigation), ); await act(async () => { diff --git a/app/util/notifications/hooks/index.ts b/app/util/notifications/hooks/index.ts index 796d8c3b19f..6c6eb0d8b7d 100644 --- a/app/util/notifications/hooks/index.ts +++ b/app/util/notifications/hooks/index.ts @@ -1,39 +1,22 @@ import { useCallback, useEffect } from 'react'; -import notifee, { - Event as NotifeeEvent, - EventType, -} from '@notifee/react-native'; -import NotificationManager from '../../../core/NotificationManager'; -import Routes from '../../../constants/navigation/Routes'; -import { setupAndroidChannel } from '../setupAndroidChannels'; -import { SimpleNotification } from '../types'; -import Device from '../../../util/device'; import { NavigationProp, ParamListBase } from '@react-navigation/native'; +import NotificationsService from '../../../util/notifications/services/NotificationService'; +import Routes from '../../../constants/navigation/Routes'; +import { isNotificationsFeatureEnabled } from '../../../util/notifications'; +import { Notification } from '../../../util/notifications/types'; -const useNotificationHandler = ( - bootstrapAndroidInitialNotification: () => Promise, - navigation: NavigationProp, -) => { +const useNotificationHandler = (navigation: NavigationProp) => { const performActionBasedOnOpenedNotificationType = useCallback( - async (notification: SimpleNotification) => { - const { data } = notification; - - if (data && data.action === 'tx') { - if (data.id) { - NotificationManager.setTransactionToView(data.id); - } - if (navigation) { - navigation.navigate(Routes.TRANSACTIONS_VIEW); - } - } else { - navigation.navigate(Routes.NOTIFICATIONS.VIEW); - } + async (notification: Notification) => { + navigation.navigate(Routes.NOTIFICATIONS.DETAILS, { + notificationId: notification.id, + }); }, [navigation], ); - const handleOpenedNotification = useCallback( - (notification?: SimpleNotification) => { + const handlePressedNotification = useCallback( + (notification?: Notification) => { if (!notification) { return; } @@ -42,28 +25,36 @@ const useNotificationHandler = ( [performActionBasedOnOpenedNotificationType], ); - const handleNotificationPressed = useCallback( - (event: NotifeeEvent) => { - if (event.type === EventType.PRESS) { - handleOpenedNotification(event.detail.notification); - } - }, - [handleOpenedNotification], - ); - useEffect(() => { - bootstrapAndroidInitialNotification(); - setTimeout(() => { - if (Device.isAndroid()) { - setupAndroidChannel(); - } - notifee.onForegroundEvent(handleNotificationPressed); - }, 1000); - }, [ - bootstrapAndroidInitialNotification, - navigation, - handleNotificationPressed, - ]); + if (!isNotificationsFeatureEnabled()) return; + + const unsubscribeForegroundEvent = NotificationsService.onForegroundEvent( + async ({ type, detail }) => { + await NotificationsService.handleNotificationEvent({ + type, + detail, + callback: handlePressedNotification, + }); + }, + ); + + NotificationsService.onBackgroundEvent(async ({ type, detail }) => { + await NotificationsService.handleNotificationEvent({ + type, + detail, + callback: handlePressedNotification, + }); + }); + + return () => { + unsubscribeForegroundEvent(); + }; + }, [handlePressedNotification]); + + return { + performActionBasedOnOpenedNotificationType, + handlePressedNotification, + }; }; export default useNotificationHandler; diff --git a/app/util/notifications/index.ts b/app/util/notifications/index.ts index ec78eb1f67a..d1e6201e1e9 100644 --- a/app/util/notifications/index.ts +++ b/app/util/notifications/index.ts @@ -1,7 +1,7 @@ export * from './types'; export * from './constants'; -export * from './setupAndroidChannels'; +export * from './androidChannels'; export * from './settings'; export * from './hooks'; export * from './methods'; -export * from './pushPermissions'; +export * from './services'; diff --git a/app/util/notifications/methods/common.ts b/app/util/notifications/methods/common.ts index c80bc3fdb80..c7ac73eeacd 100644 --- a/app/util/notifications/methods/common.ts +++ b/app/util/notifications/methods/common.ts @@ -2,6 +2,7 @@ import dayjs, { Dayjs } from 'dayjs'; import isYesterday from 'dayjs/plugin/isYesterday'; import relativeTime from 'dayjs/plugin/relativeTime'; +import notifee from '@notifee/react-native'; import localeData from 'dayjs/plugin/localeData'; import { Web3Provider } from '@ethersproject/providers'; import { toHex } from '@metamask/controller-utils'; @@ -470,3 +471,6 @@ export const getUsdAmount = (amount: string, decimals: string, usd: string) => { return formatAmount(numericAmount); }; + +export const hasInitialNotification = async () => + Boolean(await notifee.getInitialNotification()); diff --git a/app/util/notifications/pushPermissions.test.ts b/app/util/notifications/pushPermissions.test.ts deleted file mode 100644 index b9f80882f2f..00000000000 --- a/app/util/notifications/pushPermissions.test.ts +++ /dev/null @@ -1,113 +0,0 @@ -import notifee, { - NotificationSettings, - AuthorizationStatus, - IOSNotificationSetting, - IOSNotificationSettings, - AndroidNotificationSettings, - AndroidNotificationSetting, -} from '@notifee/react-native'; - -import { strings } from '../../../locales/i18n'; -import { requestPushNotificationsPermission } from './pushPermissions'; - -jest.mock('@notifee/react-native', () => ({ - getNotificationSettings: jest.fn(), - AuthorizationStatus: { - AUTHORIZED: 1, - DENIED: 2, - }, - IOSNotificationSetting: { - ENABLED: 1, - DISABLED: 2, - }, - AndroidNotificationSetting: { - ENABLED: 1, - DISABLED: 2, - }, -})); - -jest.mock('../Logger', () => ({ - error: jest.fn(), -})); - -jest.mock('../../../locales/i18n', () => ({ - strings: jest.fn(), -})); - -jest.mock('./pushPermissions', () => ({ - ...jest.requireActual('./pushPermissions'), - AsyncAlert: jest.fn(), -})); - -describe('requestPushNotificationsPermission', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - const mockIOSSettings: IOSNotificationSettings = { - authorizationStatus: AuthorizationStatus.AUTHORIZED, - alert: IOSNotificationSetting.ENABLED, - badge: IOSNotificationSetting.ENABLED, - sound: IOSNotificationSetting.ENABLED, - carPlay: IOSNotificationSetting.DISABLED, - criticalAlert: IOSNotificationSetting.DISABLED, - inAppNotificationSettings: IOSNotificationSetting.DISABLED, - lockScreen: IOSNotificationSetting.ENABLED, - notificationCenter: IOSNotificationSetting.ENABLED, - showPreviews: 1, - announcement: 1, - }; - - const mockAndroidSettings: AndroidNotificationSettings = { - alarm: AndroidNotificationSetting.ENABLED, - }; - - it('should return notification settings if already authorized', async () => { - const mockSettings: NotificationSettings = { - authorizationStatus: AuthorizationStatus.AUTHORIZED, - ios: mockIOSSettings, - android: mockAndroidSettings, - web: {}, - }; - - (notifee.getNotificationSettings as jest.Mock).mockResolvedValue( - mockSettings, - ); - - const result = await requestPushNotificationsPermission(); - - expect(notifee.getNotificationSettings).toHaveBeenCalledTimes(1); - expect(result).toBe(mockSettings); - }); - - it('should prompt the user with AsyncAlert and simulate a click', async () => { - const mockSettings: NotificationSettings = { - authorizationStatus: AuthorizationStatus.DENIED, - ios: mockIOSSettings, - android: mockAndroidSettings, - web: {}, - }; - - const updatedSettings: NotificationSettings = { - ...mockSettings, - authorizationStatus: AuthorizationStatus.AUTHORIZED, - }; - - (notifee.getNotificationSettings as jest.Mock) - .mockResolvedValueOnce(mockSettings) - .mockResolvedValueOnce(updatedSettings); - (strings as jest.Mock).mockImplementation((key: string) => key); - - const mockAsyncAlert = jest.fn().mockResolvedValue(true); - - await requestPushNotificationsPermission(mockAsyncAlert); - - expect(mockAsyncAlert).toHaveBeenCalledWith( - 'notifications.prompt_title', - 'notifications.prompt_desc', - ); - expect(notifee.getNotificationSettings).toHaveBeenCalledTimes(1); - expect(strings).toHaveBeenCalledWith('notifications.prompt_title'); - expect(strings).toHaveBeenCalledWith('notifications.prompt_desc'); - }); -}); diff --git a/app/util/notifications/pushPermissions.ts b/app/util/notifications/pushPermissions.ts deleted file mode 100644 index 6610803e1b5..00000000000 --- a/app/util/notifications/pushPermissions.ts +++ /dev/null @@ -1,123 +0,0 @@ -import notifee, { - AuthorizationStatus, - NotificationSettings, -} from '@notifee/react-native'; -import { Linking, Alert as NativeAlert, Platform } from 'react-native'; -import { strings } from '../../../locales/i18n'; -import { mmStorage } from './settings'; -import { STORAGE_IDS } from './settings/storage/constants'; -import Logger from '../Logger'; -import Device from '../device'; -import { store } from '../../store'; - -interface AlertButton { - text: string; - onPress: () => void | Promise; -} - -export const openSystemSettings = () => { - if (Platform.OS === 'ios') { - Linking.openSettings(); - } else { - notifee.openNotificationSettings(); - } -}; - -export const isDeviceNotificationEnabled = async () => { - const settings = await notifee.getNotificationSettings(); - switch (settings.authorizationStatus) { - case AuthorizationStatus.AUTHORIZED: - case AuthorizationStatus.PROVISIONAL: - store.dispatch({ - type: 'TOGGLE_DEVICE_NOTIFICATIONS', - deviceNotificationEnabled: true, - }); - return true; - default: - store.dispatch({ - type: 'TOGGLE_DEVICE_NOTIFICATIONS', - deviceNotificationEnabled: false, - }); - return false; - } -}; - -const defaultButtons = (resolve: (value: boolean) => void): AlertButton[] => [ - { - text: strings('notifications.prompt_cancel'), - onPress: () => { - const promptCount = mmStorage.getLocal( - STORAGE_IDS.PUSH_NOTIFICATIONS_PROMPT_COUNT, - ); - mmStorage.saveLocal( - STORAGE_IDS.PUSH_NOTIFICATIONS_PROMPT_COUNT, - promptCount + 1, - ); - mmStorage.saveLocal( - STORAGE_IDS.PUSH_NOTIFICATIONS_PROMPT_TIME, - Date.now().toString(), - ); - - resolve(false); - }, - }, - { - text: strings('notifications.prompt_ok'), - onPress: async () => { - openSystemSettings(); - resolve(true); - }, - }, -]; - -export const asyncAlert = ( - title: string, - msg: string, - getButtons: ( - resolve: (value: boolean) => void, - ) => AlertButton[] = defaultButtons, -): Promise => - new Promise((resolve) => { - NativeAlert.alert(title, msg, getButtons(resolve), { - cancelable: false, - }); - }); - -export const requestPushNotificationsPermission = async ( - alertFunction: typeof asyncAlert = asyncAlert, -): Promise => { - let permissionStatus: NotificationSettings | undefined; - permissionStatus = await notifee.getNotificationSettings(); - if ( - permissionStatus.authorizationStatus === AuthorizationStatus.AUTHORIZED || - permissionStatus.authorizationStatus === AuthorizationStatus.PROVISIONAL - ) { - return permissionStatus; - } - try { - await alertFunction( - strings('notifications.prompt_title'), - strings('notifications.prompt_desc'), - ); - - if (Device.isIos()) { - permissionStatus = await notifee.requestPermission({ - provisional: true, - }); - } else { - permissionStatus = await notifee.requestPermission(); - } - - return permissionStatus; - } catch (e) { - if (e instanceof Error) { - Logger.error(e, strings('notifications.error_checking_permission')); - } else { - Logger.error( - new Error(strings('notifications.error_checking_permission')), - ); - } - } -}; - -export default requestPushNotificationsPermission; diff --git a/app/util/notifications/services/NotificationService.test.ts b/app/util/notifications/services/NotificationService.test.ts new file mode 100644 index 00000000000..9816f9f99f4 --- /dev/null +++ b/app/util/notifications/services/NotificationService.test.ts @@ -0,0 +1,134 @@ +import notifee, { + AuthorizationStatus, + AndroidChannel, + AndroidImportance, + EventType, +} from '@notifee/react-native'; +import { Linking } from 'react-native'; +import { ChannelId } from '../../../util/notifications/androidChannels'; +import NotificationsService from './NotificationService'; + +jest.mock('@notifee/react-native'); +jest.mock('react-native', () => ({ + Linking: { openSettings: jest.fn() }, + Platform: { OS: 'ios' }, + Alert: { alert: jest.fn() }, +})); +jest.mock('../settings', () => ({ + mmStorage: { + getLocal: jest.fn(), + saveLocal: jest.fn(), + }, +})); +jest.mock('../../../store', () => ({ + store: { + dispatch: jest.fn(), + }, +})); +jest.mock('../../../util/Logger', () => ({ + error: jest.fn(), +})); + +describe('NotificationsService', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should get blocked notifications', async () => { + (notifee.getNotificationSettings as jest.Mock).mockResolvedValue({ + authorizationStatus: AuthorizationStatus.AUTHORIZED, + }); + (notifee.getChannels as jest.Mock).mockResolvedValue([ + { id: ChannelId.DEFAULT_NOTIFICATION_CHANNEL_ID, blocked: true }, + ]); + + const blockedNotifications = + await NotificationsService.getBlockedNotifications(); + + expect( + blockedNotifications.get(ChannelId.DEFAULT_NOTIFICATION_CHANNEL_ID), + ).toBe(true); + }); + + it('should handle notification press', async () => { + const detail = { + notification: { + id: 'test-id', + data: { url: 'https://example.com' }, + }, + }; + const callback = jest.fn(); + + await NotificationsService.handleNotificationPress({ detail, callback }); + + expect(notifee.cancelTriggerNotification).toHaveBeenCalledWith('test-id'); + expect(callback).toHaveBeenCalledWith(detail.notification); + }); + + it('should open system settings on iOS', () => { + NotificationsService.openSystemSettings(); + + expect(Linking.openSettings).toHaveBeenCalled(); + }); + + it('should create notification channels', async () => { + const channel: AndroidChannel = { + id: ChannelId.DEFAULT_NOTIFICATION_CHANNEL_ID, + name: 'Test Channel', + importance: AndroidImportance.HIGH, + }; + + await NotificationsService.createChannel(channel); + + expect(notifee.createChannel).toHaveBeenCalledWith(channel); + }); + + it('should return authorized from getAllPermissions', async () => { + const result = await NotificationsService.getAllPermissions(); + expect(result.permission).toBe('authorized'); + }); + + it('should return authorized from requestPermission ', async () => { + const result = await NotificationsService.requestPermission(); + expect(result).toBe('authorized'); + }); + + it('should return denied from requestPermission', async () => { + (notifee.requestPermission as jest.Mock).mockResolvedValue({ + authorizationStatus: AuthorizationStatus.DENIED, + }); + const result = await NotificationsService.requestPermission(); + expect(result).toBe('denied'); + }); + + it('should handle notification event', async () => { + const callback = jest.fn(); + + await NotificationsService.handleNotificationEvent({ + type: EventType.DELIVERED, + detail: { + notification: { + id: '123', + }, + }, + callback, + }); + + expect(NotificationsService.incrementBadgeCount).toBeInstanceOf(Function); + + await NotificationsService.handleNotificationEvent({ + type: EventType.PRESS, + detail: { + notification: { + id: '123', + }, + }, + callback, + }); + + expect(NotificationsService.decrementBadgeCount).toBeInstanceOf(Function); + expect(NotificationsService.cancelTriggerNotification).toBeInstanceOf( + Function, + ); + }); +}); diff --git a/app/util/notifications/services/NotificationService.ts b/app/util/notifications/services/NotificationService.ts new file mode 100644 index 00000000000..d8d6c53ae0f --- /dev/null +++ b/app/util/notifications/services/NotificationService.ts @@ -0,0 +1,236 @@ +import notifee, { + AuthorizationStatus, + Event as NotifeeEvent, + EventType, + EventDetail, + AndroidChannel, +} from '@notifee/react-native'; + +import { Notification } from '../types'; + +import { Linking, Platform, Alert as NativeAlert } from 'react-native'; +import { + ChannelId, + notificationChannels, +} from '../../../util/notifications/androidChannels'; + +import { strings } from '../../../../locales/i18n'; +import { mmStorage } from '../settings'; +import { STORAGE_IDS } from '../settings/storage/constants'; +import { store } from '../../../store'; +import Logger from '../../../util/Logger'; + +interface AlertButton { + text: string; + onPress: () => void | Promise; +} + +class NotificationsService { + async getBlockedNotifications(): Promise> { + try { + const settings = await notifee.getNotificationSettings(); + const channels = await notifee.getChannels(); + + switch (settings.authorizationStatus) { + case AuthorizationStatus.NOT_DETERMINED: + case AuthorizationStatus.DENIED: + return notificationChannels.reduce((map, next) => { + map.set(next.id as ChannelId, true); + return map; + }, new Map()); + } + + return channels.reduce((map, next) => { + if (next.blocked) { + map.set(next.id as ChannelId, true); + } + return map; + }, new Map()); + } catch (e) { + if (e instanceof Error) { + Logger.error(e, strings('notifications.error_checking_permission')); + } else { + Logger.error( + new Error(strings('notifications.error_checking_permission')), + ); + } + return new Map(); + } + } + + async getAllPermissions(shouldOpenSettings = true) { + const promises = [] as Promise[]; + notificationChannels.forEach((channel: AndroidChannel) => { + promises.push(this.createChannel(channel)); + }); + await Promise.allSettled(promises); + const permission = await this.requestPermission(); + const blockedNotifications = await this.getBlockedNotifications(); + if ( + (permission !== 'authorized' || blockedNotifications.size !== 0) && + shouldOpenSettings + ) { + this.requestPushNotificationsPermission(); + } + return { permission, blockedNotifications }; + } + + async isDeviceNotificationEnabled() { + const permission = await notifee.getNotificationSettings(); + + const isAuthorized = + permission.authorizationStatus === AuthorizationStatus.AUTHORIZED; + + store.dispatch({ + type: 'TOGGLE_DEVICE_NOTIFICATIONS', + deviceNotificationEnabled: isAuthorized, + }); + return isAuthorized; + } + + defaultButtons = (resolve: (value: boolean) => void): AlertButton[] => [ + { + text: strings('notifications.prompt_cancel'), + onPress: () => { + const promptCount = mmStorage.getLocal( + STORAGE_IDS.PUSH_NOTIFICATIONS_PROMPT_COUNT, + ); + mmStorage.saveLocal( + STORAGE_IDS.PUSH_NOTIFICATIONS_PROMPT_COUNT, + promptCount + 1, + ); + mmStorage.saveLocal( + STORAGE_IDS.PUSH_NOTIFICATIONS_PROMPT_TIME, + Date.now().toString(), + ); + + resolve(false); + }, + }, + { + text: strings('notifications.prompt_ok'), + onPress: async () => { + this.openSystemSettings(); + resolve(true); + }, + }, + ]; + + asyncAlert = ( + title: string, + msg: string, + getButtons: (resolve: (value: boolean) => void) => AlertButton[] = this + .defaultButtons, + ): Promise => + new Promise((resolve) => { + NativeAlert.alert(title, msg, getButtons(resolve), { + cancelable: false, + }); + }); + + async requestPushNotificationsPermission(): Promise { + try { + await this.asyncAlert( + strings('notifications.prompt_title'), + strings('notifications.prompt_desc'), + ); + } catch (e) { + if (e instanceof Error) { + Logger.error(e, strings('notifications.error_checking_permission')); + } else { + Logger.error( + new Error(strings('notifications.error_checking_permission')), + ); + } + } + } + openSystemSettings() { + if (Platform.OS === 'ios') { + Linking.openSettings(); + } else { + notifee.openNotificationSettings(); + } + } + + async requestPermission() { + const settings = await notifee.requestPermission(); + return settings.authorizationStatus === AuthorizationStatus.AUTHORIZED || + settings.authorizationStatus === AuthorizationStatus.PROVISIONAL + ? 'authorized' + : 'denied'; + } + + onForegroundEvent = ( + observer: (event: NotifeeEvent) => Promise, + ): (() => void) => notifee.onForegroundEvent(observer); + + onBackgroundEvent = (observer: (event: NotifeeEvent) => Promise) => + notifee.onBackgroundEvent(observer); + + incrementBadgeCount = async (incrementBy?: number) => { + notifee.incrementBadgeCount(incrementBy); + }; + + decrementBadgeCount = async (decrementBy?: number) => { + notifee.decrementBadgeCount(decrementBy); + }; + + setBadgeCount = async (count: number) => { + notifee.setBadgeCount(count); + }; + + getBadgeCount = async () => notifee.getBadgeCount(); + + handleNotificationPress = async ({ + detail, + callback, + }: { + detail: EventDetail; + callback?: (notification: Notification) => void; + }) => { + this.decrementBadgeCount(1); + if (detail?.notification?.id) { + await this.cancelTriggerNotification(detail.notification.id); + } + + if (detail?.notification?.data?.url) { + callback?.(detail.notification as Notification); + } + }; + + handleNotificationEvent = async ({ + type, + detail, + callback, + }: NotifeeEvent & { + callback?: (notification: Notification) => void; + }) => { + switch (type as unknown as EventType) { + case EventType.DELIVERED: + this.incrementBadgeCount(1); + break; + case EventType.PRESS: + this.handleNotificationPress({ + detail, + callback, + }); + break; + } + }; + + cancelTriggerNotification = async (id?: string) => { + if (!id) return; + await notifee.cancelTriggerNotification(id); + }; + + getInitialNotification = async () => notifee.getInitialNotification(); + + cancelAllNotifications = async () => { + await notifee.cancelAllNotifications(); + }; + + createChannel = async (channel: AndroidChannel): Promise => + notifee.createChannel(channel); +} + +export default new NotificationsService(); diff --git a/app/util/notifications/services/index.ts b/app/util/notifications/services/index.ts new file mode 100644 index 00000000000..529a6c3f602 --- /dev/null +++ b/app/util/notifications/services/index.ts @@ -0,0 +1 @@ +export * from './NotificationService'; diff --git a/app/util/notifications/settings/storage/constants.ts b/app/util/notifications/settings/storage/constants.ts index fd34f849f6b..fcf857060a4 100644 --- a/app/util/notifications/settings/storage/constants.ts +++ b/app/util/notifications/settings/storage/constants.ts @@ -5,7 +5,7 @@ export const STORAGE_IDS = { PUSH_NOTIFICATIONS_PROMPT_COUNT: 'pushNotificationsPromptCount', PUSH_NOTIFICATIONS_PROMPT_TIME: 'pushNotificationsPromptTime', DEVICE_ID_STORAGE_KEY: 'pns:deviceId', - ANDROID_DEFAULT_CHANNEL_ID: 'ANDROID_DEFAULT_CHANNEL_ID', + DEFAULT_NOTIFICATION_CHANNEL_ID: 'DEFAULT_NOTIFICATION_CHANNEL_ID', DEFAULT_PUSH_NOTIFICATION_CHANNEL_PRIORITY: 'high', REQUEST_PERMISSION_ASKED: 'REQUEST_PERMISSION_ASKED', REQUEST_PERMISSION_GRANTED: 'REQUEST_PERMISSION_GRANTED', diff --git a/app/util/notifications/settings/storage/contants.test.ts b/app/util/notifications/settings/storage/contants.test.ts index ec76db3a580..347cfb4b87f 100644 --- a/app/util/notifications/settings/storage/contants.test.ts +++ b/app/util/notifications/settings/storage/contants.test.ts @@ -9,7 +9,7 @@ describe('constants', () => { PUSH_NOTIFICATIONS_PROMPT_COUNT: 'pushNotificationsPromptCount', PUSH_NOTIFICATIONS_PROMPT_TIME: 'pushNotificationsPromptTime', DEVICE_ID_STORAGE_KEY: 'pns:deviceId', - ANDROID_DEFAULT_CHANNEL_ID: 'ANDROID_DEFAULT_CHANNEL_ID', + DEFAULT_NOTIFICATION_CHANNEL_ID: 'DEFAULT_NOTIFICATION_CHANNEL_ID', DEFAULT_PUSH_NOTIFICATION_CHANNEL_PRIORITY: 'high', REQUEST_PERMISSION_ASKED: 'REQUEST_PERMISSION_ASKED', REQUEST_PERMISSION_GRANTED: 'REQUEST_PERMISSION_GRANTED', diff --git a/app/util/notifications/setupAndroidChannels.test.ts b/app/util/notifications/setupAndroidChannels.test.ts deleted file mode 100644 index e2e917f6e4f..00000000000 --- a/app/util/notifications/setupAndroidChannels.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* eslint-disable import/prefer-default-export */ -import notifee, { AndroidImportance } from '@notifee/react-native'; -import { setupAndroidChannel } from './setupAndroidChannels'; -import { STORAGE_IDS } from './settings/storage/constants'; - -jest.mock('@notifee/react-native'); -const mockedNotifee = jest.mocked(notifee); - -describe('setupAndroidChannel', () => { - beforeEach(() => { - mockedNotifee.createChannel.mockClear(); - }); - - it('should create android channel', async () => { - await setupAndroidChannel(); - - expect(mockedNotifee.createChannel).toHaveBeenCalledTimes(1); - expect(mockedNotifee.createChannel).toHaveBeenCalledWith({ - id: STORAGE_IDS.ANDROID_DEFAULT_CHANNEL_ID, - importance: AndroidImportance.HIGH, - name: 'Default', - badge: true, - }); - }); -}); diff --git a/app/util/notifications/setupAndroidChannels.ts b/app/util/notifications/setupAndroidChannels.ts deleted file mode 100644 index d938acbc1e3..00000000000 --- a/app/util/notifications/setupAndroidChannels.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* eslint-disable import/prefer-default-export */ -import notifee, { AndroidImportance } from '@notifee/react-native'; -import { STORAGE_IDS } from './settings/storage/constants'; - -export async function setupAndroidChannel() { - await notifee.createChannel({ - id: STORAGE_IDS.ANDROID_DEFAULT_CHANNEL_ID, - importance: AndroidImportance.HIGH, - name: 'Default', - badge: true, - }); -} diff --git a/app/util/notifications/types/notification/index.ts b/app/util/notifications/types/notification/index.ts index 7bfd0b2af0c..6b091239414 100644 --- a/app/util/notifications/types/notification/index.ts +++ b/app/util/notifications/types/notification/index.ts @@ -108,14 +108,6 @@ export const STAKING_PROVIDER_MAP: Record< [TRIGGER_TYPES.ROCKETPOOL_UNSTAKE_COMPLETED]: 'Rocket Pool-staked ETH', }; -export const networkFeeDetails: Record = { - 'transactions.gas_limit': 'gasLimitUnits', - 'transactions.gas_used': 'gasUsedUnits', - 'transactions.base_fee': 'baseFee', - 'transactions.priority_fee': 'priorityFee', - 'transactions.max_fee': 'maxFeePerGas', -}; - export interface SimpleNotification { title?: string; body?: string; @@ -123,8 +115,3 @@ export interface SimpleNotification { [key: string]: string | object | number; }; } - -export enum NotificationsKindTypes { - transaction = 'transaction', - announcements = 'announcements', -} diff --git a/e2e/helpers.js b/e2e/helpers.js index d1b169fb58d..89c665fdec9 100644 --- a/e2e/helpers.js +++ b/e2e/helpers.js @@ -1,4 +1,4 @@ -import { waitFor, web } from 'detox'; +import { waitFor, web, system } from 'detox'; import { getFixturesServerPort, getGanachePort, diff --git a/e2e/pages/EnableDeviceNotificationsAlert.js b/e2e/pages/EnableDeviceNotificationsAlert.js new file mode 100644 index 00000000000..3c9eebe0c55 --- /dev/null +++ b/e2e/pages/EnableDeviceNotificationsAlert.js @@ -0,0 +1,27 @@ +import TestHelpers from '../helpers'; + +import { + ENABLE_DEVICE_NOTIFICATIONS_CONTAINER_ID, + ENABLE_DEVICE_NOTIFICATIONS_NO_THANKS_BUTTON_ID, + ENABLE_DEVICE_NOTIFICATIONS_YES_BUTTON_ID +} from '../../wdio/screen-objects/testIDs/Screens/EnableDeviceNotificationsChecksAlert.testIds'; + +export default class EnableDeviceNotificationsAlert { + static async tapNoThanks() { + await TestHelpers.waitAndTapByLabel( + ENABLE_DEVICE_NOTIFICATIONS_NO_THANKS_BUTTON_ID + ); + } + + static async tapYes() { + await TestHelpers.waitAndTapByLabel( + ENABLE_DEVICE_NOTIFICATIONS_YES_BUTTON_ID, + ); + } + + static async isVisible() { + await TestHelpers.checkIfElementWithTextIsVisible( + ENABLE_DEVICE_NOTIFICATIONS_CONTAINER_ID, + ); + } +} diff --git a/e2e/specs/quarantine/import-nft.failing.js b/e2e/specs/quarantine/import-nft.failing.js index 004ffef6158..e7e2fed937e 100644 --- a/e2e/specs/quarantine/import-nft.failing.js +++ b/e2e/specs/quarantine/import-nft.failing.js @@ -12,7 +12,7 @@ import { SMART_CONTRACTS } from '../../../app/util/test/smart-contracts'; import FixtureBuilder from '../../fixtures/fixture-builder'; describe(SmokeAssets('Import NFT'), () => { - beforeAll(async () => { + beforeAll(() => { jest.setTimeout(150000); }); diff --git a/e2e/utils/Matchers.js b/e2e/utils/Matchers.js index cd9255de69e..536a6c0c171 100644 --- a/e2e/utils/Matchers.js +++ b/e2e/utils/Matchers.js @@ -1,4 +1,4 @@ -import { web } from 'detox'; +import { web, system } from 'detox'; /** * Utility class for matching (locating) UI elements @@ -152,6 +152,17 @@ class Matchers { static async getIdentifier(selectorString) { return by.id(selectorString); } + + + /** + * Get system dialogs in the system-level (e.g. permissions, alerts, etc.), by text. + * + * @param {string} text - Match elements with the specified text + * @return {Promise} - Resolves to the located element + */ + static async getSystemElementByText(text) { + return system.element(by.system.label(text)); + } } export default Matchers; diff --git a/e2e/viewHelper.js b/e2e/viewHelper.js index 3b75cd49de4..ebcf468596a 100644 --- a/e2e/viewHelper.js +++ b/e2e/viewHelper.js @@ -83,9 +83,11 @@ export const importWalletWithRecoveryPhrase = async () => { await ImportWalletView.enterPassword(validAccount.password); await ImportWalletView.reEnterPassword(validAccount.password); - // Should dismiss Automatic Security checks screen + + //'Should dismiss Enable device Notifications checks alert' await TestHelpers.delay(3500); await OnboardingSuccessView.tapDone(); + // Should dismiss Automatic Security checks screen await EnableAutomaticSecurityChecksView.isVisible(); await EnableAutomaticSecurityChecksView.tapNoThanks(); @@ -120,9 +122,10 @@ export const CreateNewWallet = async () => { await device.enableSynchronization(); await Assertions.checkIfVisible(WalletView.container); - //'Should dismiss Automatic Security checks screen' + //'Should dismiss Enable device Notifications checks alert' await TestHelpers.delay(3500); await OnboardingSuccessView.tapDone(); + //'Should dismiss Automatic Security checks screen' await EnableAutomaticSecurityChecksView.isVisible(); await EnableAutomaticSecurityChecksView.tapNoThanks(); diff --git a/index.js b/index.js index f24e95556c6..ece1f59ea50 100644 --- a/index.js +++ b/index.js @@ -14,15 +14,11 @@ import * as Sentry from '@sentry/react-native'; // eslint-disable-line import/no import { setupSentry } from './app/util/sentry/utils'; setupSentry(); -import notifee, { EventType } from '@notifee/react-native'; - import { AppRegistry, LogBox } from 'react-native'; import Root from './app/components/Views/Root'; import { name } from './app.json'; import { isTest } from './app/util/test/utils.js'; -import NotificationManager from './app/core/NotificationManager'; -import { isNotificationsFeatureEnabled } from './app/util/notifications'; import { Performance } from './app/core/Performance'; Performance.setupPerformanceObservers(); @@ -85,18 +81,6 @@ if (IGNORE_BOXLOGS_DEVELOPMENT === 'true') { LogBox.ignoreAllLogs(); } -isNotificationsFeatureEnabled() && - notifee.onBackgroundEvent(async ({ type, detail }) => { - const { notification, pressAction } = detail; - - if (type === EventType.ACTION_PRESS && pressAction.id === 'mark-as-read') { - await notifee.decrementBadgeCount(1); - await notifee.cancelNotification(notification.id); - } else { - await NotificationManager.onMessageReceived(notification); - } - }); - /* Uncomment and comment regular registration below */ // import Storybook from './.storybook'; // AppRegistry.registerComponent(name, () => Storybook); diff --git a/ios/Podfile.lock b/ios/Podfile.lock index de82cd678de..3a2ab1ef655 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -15,18 +15,18 @@ PODS: - React-Core (= 0.72.15) - React-jsi (= 0.72.15) - ReactCommon/turbomodule/core (= 0.72.15) - - Firebase (10.27.0): - - Firebase/Core (= 10.27.0) - - Firebase/Core (10.27.0): + - Firebase (10.29.0): + - Firebase/Core (= 10.29.0) + - Firebase/Core (10.29.0): - Firebase/CoreOnly - - FirebaseAnalytics (~> 10.27.0) - - Firebase/CoreOnly (10.27.0): - - FirebaseCore (= 10.27.0) - - Firebase/Messaging (10.27.0): + - FirebaseAnalytics (~> 10.29.0) + - Firebase/CoreOnly (10.29.0): + - FirebaseCore (= 10.29.0) + - Firebase/Messaging (10.29.0): - Firebase/CoreOnly - - FirebaseMessaging (~> 10.27.0) - - FirebaseAnalytics (10.27.0): - - FirebaseAnalytics/AdIdSupport (= 10.27.0) + - FirebaseMessaging (~> 10.29.0) + - FirebaseAnalytics (10.29.0): + - FirebaseAnalytics/AdIdSupport (= 10.29.0) - FirebaseCore (~> 10.0) - FirebaseInstallations (~> 10.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.11) @@ -34,29 +34,29 @@ PODS: - GoogleUtilities/Network (~> 7.11) - "GoogleUtilities/NSData+zlib (~> 7.11)" - nanopb (< 2.30911.0, >= 2.30908.0) - - FirebaseAnalytics/AdIdSupport (10.27.0): + - FirebaseAnalytics/AdIdSupport (10.29.0): - FirebaseCore (~> 10.0) - FirebaseInstallations (~> 10.0) - - GoogleAppMeasurement (= 10.27.0) + - GoogleAppMeasurement (= 10.29.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - GoogleUtilities/MethodSwizzler (~> 7.11) - GoogleUtilities/Network (~> 7.11) - "GoogleUtilities/NSData+zlib (~> 7.11)" - nanopb (< 2.30911.0, >= 2.30908.0) - - FirebaseCore (10.27.0): + - FirebaseCore (10.29.0): - FirebaseCoreInternal (~> 10.0) - GoogleUtilities/Environment (~> 7.12) - GoogleUtilities/Logger (~> 7.12) - - FirebaseCoreExtension (10.28.0): + - FirebaseCoreExtension (10.29.0): - FirebaseCore (~> 10.0) - - FirebaseCoreInternal (10.28.0): + - FirebaseCoreInternal (10.29.0): - "GoogleUtilities/NSData+zlib (~> 7.8)" - - FirebaseInstallations (10.28.0): + - FirebaseInstallations (10.29.0): - FirebaseCore (~> 10.0) - GoogleUtilities/Environment (~> 7.8) - GoogleUtilities/UserDefaults (~> 7.8) - PromisesObjC (~> 2.1) - - FirebaseMessaging (10.27.0): + - FirebaseMessaging (10.29.0): - FirebaseCore (~> 10.0) - FirebaseInstallations (~> 10.0) - GoogleDataTransport (~> 9.3) @@ -125,21 +125,21 @@ PODS: - FlipperKit/FlipperKitNetworkPlugin - fmt (6.2.1) - glog (0.3.5) - - GoogleAppMeasurement (10.27.0): - - GoogleAppMeasurement/AdIdSupport (= 10.27.0) + - GoogleAppMeasurement (10.29.0): + - GoogleAppMeasurement/AdIdSupport (= 10.29.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - GoogleUtilities/MethodSwizzler (~> 7.11) - GoogleUtilities/Network (~> 7.11) - "GoogleUtilities/NSData+zlib (~> 7.11)" - nanopb (< 2.30911.0, >= 2.30908.0) - - GoogleAppMeasurement/AdIdSupport (10.27.0): - - GoogleAppMeasurement/WithoutAdIdSupport (= 10.27.0) + - GoogleAppMeasurement/AdIdSupport (10.29.0): + - GoogleAppMeasurement/WithoutAdIdSupport (= 10.29.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - GoogleUtilities/MethodSwizzler (~> 7.11) - GoogleUtilities/Network (~> 7.11) - "GoogleUtilities/NSData+zlib (~> 7.11)" - nanopb (< 2.30911.0, >= 2.30908.0) - - GoogleAppMeasurement/WithoutAdIdSupport (10.27.0): + - GoogleAppMeasurement/WithoutAdIdSupport (10.29.0): - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - GoogleUtilities/MethodSwizzler (~> 7.11) - GoogleUtilities/Network (~> 7.11) @@ -200,9 +200,9 @@ PODS: - lottie-react-native (5.1.5): - lottie-ios (~> 3.4.0) - React-Core - - MMKV (1.3.5): - - MMKVCore (~> 1.3.5) - - MMKVCore (1.3.5) + - MMKV (1.3.9): + - MMKVCore (~> 1.3.9) + - MMKVCore (1.3.9) - MultiplatformBleAdapter (0.2.0) - nanopb (2.30910.0): - nanopb/decode (= 2.30910.0) @@ -706,11 +706,11 @@ PODS: - React - RNDeviceInfo (9.0.2): - React-Core - - RNFBApp (20.1.0): - - Firebase/CoreOnly (= 10.27.0) + - RNFBApp (20.5.0): + - Firebase/CoreOnly (= 10.29.0) - React-Core - - RNFBMessaging (20.1.0): - - Firebase/Messaging (= 10.27.0) + - RNFBMessaging (20.5.0): + - Firebase/Messaging (= 10.29.0) - FirebaseCoreExtension - React-Core - RNFBApp @@ -724,10 +724,10 @@ PODS: - React-Core - RNKeychain (8.0.0): - React-Core - - RNNotifee (7.8.2): + - RNNotifee (9.0.2): - React-Core - - RNNotifee/NotifeeCore (= 7.8.2) - - RNNotifee/NotifeeCore (7.8.2): + - RNNotifee/NotifeeCore (= 9.0.2) + - RNNotifee/NotifeeCore (9.0.2): - React-Core - RNOS (1.2.6): - React @@ -1170,13 +1170,13 @@ SPEC CHECKSUMS: DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 FBLazyVector: 25cbffbaec517695d376ab4bc428948cd0f08088 FBReactNativeSpec: e03b22fbf7017a6f76641ea4472e73c915dcdda7 - Firebase: 26b040b20866a55f55eb3611b9fcf3ae64816b86 - FirebaseAnalytics: f9211b719db260cc91aebee8bb539cb367d0dfd1 - FirebaseCore: a2b95ae4ce7c83ceecfbbbe3b6f1cddc7415a808 - FirebaseCoreExtension: f63147b723e2a700fe0f34ec6fb7f358d6fe83e0 - FirebaseCoreInternal: 58d07f1362fddeb0feb6a857d1d1d1c5e558e698 - FirebaseInstallations: 60c1d3bc1beef809fd1ad1189a8057a040c59f2e - FirebaseMessaging: 585984d0a1df120617eb10b44cad8968b859815e + Firebase: cec914dab6fd7b1bd8ab56ea07ce4e03dd251c2d + FirebaseAnalytics: 23717de130b779aa506e757edb9713d24b6ffeda + FirebaseCore: 30e9c1cbe3d38f5f5e75f48bfcea87d7c358ec16 + FirebaseCoreExtension: 705ca5b14bf71d2564a0ddc677df1fc86ffa600f + FirebaseCoreInternal: df84dd300b561c27d5571684f389bf60b0a5c934 + FirebaseInstallations: 913cf60d0400ebd5d6b63a28b290372ab44590dd + FirebaseMessaging: 7b5d8033e183ab59eb5b852a53201559e976d366 Flipper: 6edb735e6c3e332975d1b17956bcc584eccf5818 Flipper-Boost-iOSX: fd1e2b8cbef7e662a122412d7ac5f5bea715403c Flipper-DoubleConversion: 2dc99b02f658daf147069aad9dbd29d8feb06d30 @@ -1187,7 +1187,7 @@ SPEC CHECKSUMS: FlipperKit: 2efad7007d6745a3f95e4034d547be637f89d3f6 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b - GoogleAppMeasurement: f65fc137531af9ad647f1c0a42f3b6a4d3a98049 + GoogleAppMeasurement: f9de05ee17401e3355f68e8fc8b5064d429f5918 GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15 GZIP: 3c0abf794bfce8c7cb34ea05a1837752416c8868 @@ -1195,8 +1195,8 @@ SPEC CHECKSUMS: libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 lottie-ios: 016449b5d8be0c3dcbcfa0a9988469999cd04c5d lottie-react-native: 3e722c63015fdb9c27638b0a77969fc412067c18 - MMKV: 506311d0494023c2f7e0b62cc1f31b7370fa3cfb - MMKVCore: 9e2e5fd529b64a9fe15f1a7afb3d73b2e27b4db9 + MMKV: 817ba1eea17421547e01e087285606eb270a8dcb + MMKVCore: af055b00e27d88cd92fad301c5fecd1ff9b26dd9 MultiplatformBleAdapter: b1fddd0d499b96b607e00f0faa8e60648343dc1d nanopb: 438bc412db1928dac798aa6fd75726007be04262 OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c @@ -1272,14 +1272,14 @@ SPEC CHECKSUMS: RNDateTimePicker: 4f3c4dbd4f908be32ec8c93f086e8924bd4a2e07 RNDefaultPreference: 2f8d6d54230edbd78708ada8d63bb275e5a8415b RNDeviceInfo: 1e3f62b9ec32f7754fac60bd06b8f8a27124e7f0 - RNFBApp: 1ae7462cddf74a49df206d3418bc0170f8fa53e5 - RNFBMessaging: 85f661b9f16e2b081e6809ef63d3daa4458b9042 + RNFBApp: 5f87753a8d8b37d229adf85cd0ff37709ffdf008 + RNFBMessaging: 3fa1114c0868dd21f20dfe186adf42297ea316b1 RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNGestureHandler: a479ebd5ed4221a810967000735517df0d2db211 RNI18n: e2f7e76389fcc6e84f2c8733ea89b92502351fd8 RNInAppBrowser: e36d6935517101ccba0e875bac8ad7b0cb655364 RNKeychain: 4f63aada75ebafd26f4bc2c670199461eab85d94 - RNNotifee: 8e2d3df3f0e9ce8f5d1fe4c967431138190b6175 + RNNotifee: 2b7df6e32a9cc24b9af6b410fa7db1cd2f411d6d RNOS: 6f2f9a70895bbbfbdad7196abd952e7b01d45027 RNPermissions: 4e3714e18afe7141d000beae3755e5b5fb2f5e05 RNReanimated: f8379347f71248607d530a21e31e4140c5910c25 diff --git a/package.json b/package.json index b82cfbb8c55..85827600684 100644 --- a/package.json +++ b/package.json @@ -198,7 +198,7 @@ "@metamask/transaction-controller": "^35.0.0", "@metamask/utils": "^9.1.0", "@ngraveio/bc-ur": "^1.1.6", - "@notifee/react-native": "^7.8.2", + "@notifee/react-native": "^9.0.0", "@react-native-async-storage/async-storage": "^1.23.1", "@react-native-clipboard/clipboard": "1.8.4", "@react-native-community/blur": "^4.4.0", @@ -207,8 +207,8 @@ "@react-native-community/netinfo": "^9.5.0", "@react-native-community/slider": "^4.4.3", "@react-native-cookies/cookies": "^6.2.1", - "@react-native-firebase/app": "^20.1.0", - "@react-native-firebase/messaging": "^20.1.0", + "@react-native-firebase/app": "^20.5.0", + "@react-native-firebase/messaging": "^20.5.0", "@react-native-masked-view/masked-view": "^0.3.1", "@react-native-picker/picker": "^2.2.1", "@react-native/eslint-config": "^0.75.2", @@ -589,7 +589,8 @@ "@metamask/notification-services-controller>firebase>@firebase/firestore>@grpc/proto-loader>protobufjs": false, "@metamask/sdk-communication-layer>eciesjs>secp256k1": false, "detox>ws>utf-8-validate": false, - "ganache>@trufflesuite/uws-js-unofficial>utf-8-validate": false + "ganache>@trufflesuite/uws-js-unofficial>utf-8-validate": false, + "@react-native-firebase/app>firebase>@firebase/firestore>@grpc/proto-loader>protobufjs": false } }, "packageManager": "yarn@1.22.22" diff --git a/wdio/screen-objects/testIDs/Screens/EnableDeviceNotificationsChecksAlert.testIds.js b/wdio/screen-objects/testIDs/Screens/EnableDeviceNotificationsChecksAlert.testIds.js new file mode 100644 index 00000000000..c12c78557f5 --- /dev/null +++ b/wdio/screen-objects/testIDs/Screens/EnableDeviceNotificationsChecksAlert.testIds.js @@ -0,0 +1,6 @@ +export const ENABLE_DEVICE_NOTIFICATION_NO_THANKS_BUTTON_ID = + "Don't Allow"; +export const ENABLE_DEVICE_NOTIFICATIONS_CONTAINER_ID = + 'Would Like to Send You Notifications'; +export const ENABLE_DEVICE_NOTIFICATIONS_YES_BUTTON_ID = 'Allow'; + diff --git a/yarn.lock b/yarn.lock index a98e130a51a..87583d03a75 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2299,6 +2299,17 @@ resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d" integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA== +"@firebase/analytics-compat@0.2.10": + version "0.2.10" + resolved "https://registry.yarnpkg.com/@firebase/analytics-compat/-/analytics-compat-0.2.10.tgz#c98005075c019eb8255764a5279f0ff86b36b863" + integrity sha512-ia68RcLQLLMFWrM10JfmFod7eJGwqr4/uyrtzHpTDnxGX/6gNCBTOuxdAbyWIqXI5XmcMQdz9hDijGKOHgDfPw== + dependencies: + "@firebase/analytics" "0.10.4" + "@firebase/analytics-types" "0.8.2" + "@firebase/component" "0.6.7" + "@firebase/util" "1.9.6" + tslib "^2.1.0" + "@firebase/analytics-compat@0.2.11": version "0.2.11" resolved "https://registry.yarnpkg.com/@firebase/analytics-compat/-/analytics-compat-0.2.11.tgz#82995b29805f306ad862773e2cd907ae8fb7b7e5" @@ -2315,6 +2326,17 @@ resolved "https://registry.yarnpkg.com/@firebase/analytics-types/-/analytics-types-0.8.2.tgz#947f85346e404332aac6c996d71fd4a89cd7f87a" integrity sha512-EnzNNLh+9/sJsimsA/FGqzakmrAUKLeJvjRHlg8df1f97NLUlFidk9600y0ZgWOp3CAxn6Hjtk+08tixlUOWyw== +"@firebase/analytics@0.10.4": + version "0.10.4" + resolved "https://registry.yarnpkg.com/@firebase/analytics/-/analytics-0.10.4.tgz#dc68a86774f9ee4f980708e824157617fd2b8ef7" + integrity sha512-OJEl/8Oye/k+vJ1zV/1L6eGpc1XzAj+WG2TPznJ7PszL7sOFLBXkL9IjHfOCGDGpXeO3btozy/cYUqv4zgNeHg== + dependencies: + "@firebase/component" "0.6.7" + "@firebase/installations" "0.6.7" + "@firebase/logger" "0.4.2" + "@firebase/util" "1.9.6" + tslib "^2.1.0" + "@firebase/analytics@0.10.5": version "0.10.5" resolved "https://registry.yarnpkg.com/@firebase/analytics/-/analytics-0.10.5.tgz#a455028952bdc25b9da2b0070ebb09ca487ee09f" @@ -2326,6 +2348,18 @@ "@firebase/util" "1.9.7" tslib "^2.1.0" +"@firebase/app-check-compat@0.3.11": + version "0.3.11" + resolved "https://registry.yarnpkg.com/@firebase/app-check-compat/-/app-check-compat-0.3.11.tgz#0a5d1c72c91ba239e4dabf6fd698b27f082030ca" + integrity sha512-t01zaH3RJpKEey0nGduz3Is+uSz7Sj4U5nwOV6lWb+86s5xtxpIvBJzu/lKxJfYyfZ29eJwpdjEgT1/lm4iQyA== + dependencies: + "@firebase/app-check" "0.8.4" + "@firebase/app-check-types" "0.5.2" + "@firebase/component" "0.6.7" + "@firebase/logger" "0.4.2" + "@firebase/util" "1.9.6" + tslib "^2.1.0" + "@firebase/app-check-compat@0.3.12": version "0.3.12" resolved "https://registry.yarnpkg.com/@firebase/app-check-compat/-/app-check-compat-0.3.12.tgz#34d826f72e058baf1aad11713fda337046fb863c" @@ -2348,6 +2382,16 @@ resolved "https://registry.yarnpkg.com/@firebase/app-check-types/-/app-check-types-0.5.2.tgz#1221bd09b471e11bb149252f16640a0a51043cbc" integrity sha512-FSOEzTzL5bLUbD2co3Zut46iyPWML6xc4x+78TeaXMSuJap5QObfb+rVvZJtla3asN4RwU7elaQaduP+HFizDA== +"@firebase/app-check@0.8.4": + version "0.8.4" + resolved "https://registry.yarnpkg.com/@firebase/app-check/-/app-check-0.8.4.tgz#1c965d34527d1b924fc7bd51789119b3f817bf94" + integrity sha512-2tjRDaxcM5G7BEpytiDcIl+NovV99q8yEqRMKDbn4J4i/XjjuThuB4S+4PkmTnZiCbdLXQiBhkVxNlUDcfog5Q== + dependencies: + "@firebase/component" "0.6.7" + "@firebase/logger" "0.4.2" + "@firebase/util" "1.9.6" + tslib "^2.1.0" + "@firebase/app-check@0.8.5": version "0.8.5" resolved "https://registry.yarnpkg.com/@firebase/app-check/-/app-check-0.8.5.tgz#e8b0a6d603592f6a04f2d429029f5adfe1a4d2ca" @@ -2358,6 +2402,17 @@ "@firebase/util" "1.9.7" tslib "^2.1.0" +"@firebase/app-compat@0.2.35": + version "0.2.35" + resolved "https://registry.yarnpkg.com/@firebase/app-compat/-/app-compat-0.2.35.tgz#ca918736e6b06bdd63eaed24ba213059ecd55f88" + integrity sha512-vgay/WRjeH0r97/Q6L6df2CMx7oyNFDsE5yPQ9oR1G+zx2eT0s8vNNh0WlKqQxUEWaOLRnXhQ8gy7uu0cBgTRg== + dependencies: + "@firebase/app" "0.10.5" + "@firebase/component" "0.6.7" + "@firebase/logger" "0.4.2" + "@firebase/util" "1.9.6" + tslib "^2.1.0" + "@firebase/app-compat@0.2.36": version "0.2.36" resolved "https://registry.yarnpkg.com/@firebase/app-compat/-/app-compat-0.2.36.tgz#46926ee9ba0d54fc5ec4695e62588b63e2f7584a" @@ -2374,6 +2429,17 @@ resolved "https://registry.yarnpkg.com/@firebase/app-types/-/app-types-0.9.2.tgz#8cbcceba784753a7c0066a4809bc22f93adee080" integrity sha512-oMEZ1TDlBz479lmABwWsWjzHwheQKiAgnuKxE0pz0IXCVx7/rtlkx1fQ6GfgK24WCrxDKMplZrT50Kh04iMbXQ== +"@firebase/app@0.10.5": + version "0.10.5" + resolved "https://registry.yarnpkg.com/@firebase/app/-/app-0.10.5.tgz#84d3c99b253366844335a411b568dd258800c794" + integrity sha512-iY/fNot+hWPk9sTX8aHMqlcX9ynRvpGkskWAdUZ2eQQdLo8d1hSFYcYNwPv0Q/frGMasw8udKWMcFOEpC9fG8g== + dependencies: + "@firebase/component" "0.6.7" + "@firebase/logger" "0.4.2" + "@firebase/util" "1.9.6" + idb "7.1.1" + tslib "^2.1.0" + "@firebase/app@0.10.6": version "0.10.6" resolved "https://registry.yarnpkg.com/@firebase/app/-/app-0.10.6.tgz#0f96a573c18d75723ddeedb45c02c5471d9de695" @@ -2397,6 +2463,18 @@ tslib "^2.1.0" undici "5.28.4" +"@firebase/auth-compat@0.5.9": + version "0.5.9" + resolved "https://registry.yarnpkg.com/@firebase/auth-compat/-/auth-compat-0.5.9.tgz#ab925dbe8baf0911fb4836c14403706132d751e8" + integrity sha512-RX8Zh/3zz2CsVbmYfgHkfUm4fAEPCl+KHVIImNygV5jTGDF6oKOhBIpf4Yigclyu8ESQKZ4elyN0MBYm9/7zGw== + dependencies: + "@firebase/auth" "1.7.4" + "@firebase/auth-types" "0.12.2" + "@firebase/component" "0.6.7" + "@firebase/util" "1.9.6" + tslib "^2.1.0" + undici "5.28.4" + "@firebase/auth-interop-types@0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@firebase/auth-interop-types/-/auth-interop-types-0.2.3.tgz#927f1f2139a680b55fef0bddbff2c982b08587e8" @@ -2407,6 +2485,17 @@ resolved "https://registry.yarnpkg.com/@firebase/auth-types/-/auth-types-0.12.2.tgz#f12d890585866e53b6ab18b16fa4d425c52eee6e" integrity sha512-qsEBaRMoGvHO10unlDJhaKSuPn4pyoTtlQuP1ghZfzB6rNQPuhp/N/DcFZxm9i4v0SogjCbf9reWupwIvfmH6w== +"@firebase/auth@1.7.4": + version "1.7.4" + resolved "https://registry.yarnpkg.com/@firebase/auth/-/auth-1.7.4.tgz#0dc8083314a61598c91cfe00cb96cf2cb3d74336" + integrity sha512-d2Fw17s5QesojwebrA903el20Li9/YGgkoOGJjagM4I1qAT36APa/FcZ+OX86KxbYKCtQKTMqraU8pxG7C2JWA== + dependencies: + "@firebase/component" "0.6.7" + "@firebase/logger" "0.4.2" + "@firebase/util" "1.9.6" + tslib "^2.1.0" + undici "5.28.4" + "@firebase/auth@1.7.5": version "1.7.5" resolved "https://registry.yarnpkg.com/@firebase/auth/-/auth-1.7.5.tgz#8135e0933e874231d7ebafc94f5796a19f5df39b" @@ -2418,6 +2507,14 @@ tslib "^2.1.0" undici "5.28.4" +"@firebase/component@0.6.7": + version "0.6.7" + resolved "https://registry.yarnpkg.com/@firebase/component/-/component-0.6.7.tgz#6fbffddb26833e1ed58bf296ad587cb330aee716" + integrity sha512-baH1AA5zxfaz4O8w0vDwETByrKTQqB5CDjRls79Sa4eAGAoERw4Tnung7XbMl3jbJ4B/dmmtsMrdki0KikwDYA== + dependencies: + "@firebase/util" "1.9.6" + tslib "^2.1.0" + "@firebase/component@0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@firebase/component/-/component-0.6.8.tgz#899b9318c0ce0586580e8cda7eaf61296f7fb43b" @@ -2426,6 +2523,18 @@ "@firebase/util" "1.9.7" tslib "^2.1.0" +"@firebase/database-compat@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@firebase/database-compat/-/database-compat-1.0.5.tgz#18c2314f169942ac315e46b68f86cbe64bafe063" + integrity sha512-NDSMaDjQ+TZEMDMmzJwlTL05kh1+0Y84C+kVMaOmNOzRGRM7VHi29I6YUhCetXH+/b1Wh4ZZRyp1CuWkd8s6hg== + dependencies: + "@firebase/component" "0.6.7" + "@firebase/database" "1.0.5" + "@firebase/database-types" "1.0.3" + "@firebase/logger" "0.4.2" + "@firebase/util" "1.9.6" + tslib "^2.1.0" + "@firebase/database-compat@1.0.6": version "1.0.6" resolved "https://registry.yarnpkg.com/@firebase/database-compat/-/database-compat-1.0.6.tgz#6a4966fe4a9d8bc2cb11ee98a1bb01ab954d7d66" @@ -2438,6 +2547,14 @@ "@firebase/util" "1.9.7" tslib "^2.1.0" +"@firebase/database-types@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@firebase/database-types/-/database-types-1.0.3.tgz#1b764212dce88eca74b16da9d220cfea6814858e" + integrity sha512-39V/Riv2R3O/aUjYKh0xypj7NTNXNAK1bcgY5Kx+hdQPRS/aPTS8/5c0CGFYKgVuFbYlnlnhrCTYsh2uNhGwzA== + dependencies: + "@firebase/app-types" "0.9.2" + "@firebase/util" "1.9.6" + "@firebase/database-types@1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@firebase/database-types/-/database-types-1.0.4.tgz#dc507f7838ed29ac3235c68ebae5fd42a562e3e8" @@ -2446,6 +2563,19 @@ "@firebase/app-types" "0.9.2" "@firebase/util" "1.9.7" +"@firebase/database@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@firebase/database/-/database-1.0.5.tgz#09d7162b7dbc4533f17498ac6a76d5e757ab45be" + integrity sha512-cAfwBqMQuW6HbhwI3Cb/gDqZg7aR0OmaJ85WUxlnoYW2Tm4eR0hFl5FEijI3/gYPUiUcUPQvTkGV222VkT7KPw== + dependencies: + "@firebase/app-check-interop-types" "0.3.2" + "@firebase/auth-interop-types" "0.2.3" + "@firebase/component" "0.6.7" + "@firebase/logger" "0.4.2" + "@firebase/util" "1.9.6" + faye-websocket "0.11.4" + tslib "^2.1.0" + "@firebase/database@1.0.6": version "1.0.6" resolved "https://registry.yarnpkg.com/@firebase/database/-/database-1.0.6.tgz#cf0592b140e207e35c14efe6776fc92266ac408a" @@ -2459,6 +2589,17 @@ faye-websocket "0.11.4" tslib "^2.1.0" +"@firebase/firestore-compat@0.3.32": + version "0.3.32" + resolved "https://registry.yarnpkg.com/@firebase/firestore-compat/-/firestore-compat-0.3.32.tgz#1357ba5f80b83f33210d4fb49a1cd346cf95b291" + integrity sha512-at71mwK7a/mUXH0OgyY0+gUzedm/EUydDFYSFsBoO8DYowZ23Mgd6P4Rzq/Ll3zI/3xJN7LGe7Qp4iE/V/3Arg== + dependencies: + "@firebase/component" "0.6.7" + "@firebase/firestore" "4.6.3" + "@firebase/firestore-types" "3.0.2" + "@firebase/util" "1.9.6" + tslib "^2.1.0" + "@firebase/firestore-compat@0.3.33": version "0.3.33" resolved "https://registry.yarnpkg.com/@firebase/firestore-compat/-/firestore-compat-0.3.33.tgz#8e591bfafb574c695b09101b98c1a1057f55c60e" @@ -2475,6 +2616,20 @@ resolved "https://registry.yarnpkg.com/@firebase/firestore-types/-/firestore-types-3.0.2.tgz#75c301acc5fa33943eaaa9570b963c55398cad2a" integrity sha512-wp1A+t5rI2Qc/2q7r2ZpjUXkRVPtGMd6zCLsiWurjsQpqPgFin3AhNibKcIzoF2rnToNa/XYtyWXuifjOOwDgg== +"@firebase/firestore@4.6.3": + version "4.6.3" + resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-4.6.3.tgz#87ad38dfd0a0f16e79682177102ee1328d59af44" + integrity sha512-d/+N2iUsiJ/Dc7fApdpdmmTXzwuTCromsdA1lKwYfZtMIOd1fI881NSLwK2wV4I38wkLnvfKJUV6WpU1f3/ONg== + dependencies: + "@firebase/component" "0.6.7" + "@firebase/logger" "0.4.2" + "@firebase/util" "1.9.6" + "@firebase/webchannel-wrapper" "1.0.0" + "@grpc/grpc-js" "~1.9.0" + "@grpc/proto-loader" "^0.7.8" + tslib "^2.1.0" + undici "5.28.4" + "@firebase/firestore@4.6.4": version "4.6.4" resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-4.6.4.tgz#f53fcfc3ecfeb844f2147a43382d013d21e64968" @@ -2489,6 +2644,17 @@ tslib "^2.1.0" undici "5.28.4" +"@firebase/functions-compat@0.3.11": + version "0.3.11" + resolved "https://registry.yarnpkg.com/@firebase/functions-compat/-/functions-compat-0.3.11.tgz#9fdff8b174879b404501df7b8b519e5fb6d0b8ec" + integrity sha512-Qn+ts/M6Lj2/6i1cp5V5TRR+Hi9kyXyHbo+w9GguINJ87zxrCe6ulx3TI5AGQkoQa8YFHUhT3DMGmLFiJjWTSQ== + dependencies: + "@firebase/component" "0.6.7" + "@firebase/functions" "0.11.5" + "@firebase/functions-types" "0.6.2" + "@firebase/util" "1.9.6" + tslib "^2.1.0" + "@firebase/functions-compat@0.3.12": version "0.3.12" resolved "https://registry.yarnpkg.com/@firebase/functions-compat/-/functions-compat-0.3.12.tgz#aae387eb48466df1d031fc5bb755c657cfeb5994" @@ -2505,6 +2671,19 @@ resolved "https://registry.yarnpkg.com/@firebase/functions-types/-/functions-types-0.6.2.tgz#03b4ec9259d2f57548a3909d6a35ae35ad243552" integrity sha512-0KiJ9lZ28nS2iJJvimpY4nNccV21rkQyor5Iheu/nq8aKXJqtJdeSlZDspjPSBBiHRzo7/GMUttegnsEITqR+w== +"@firebase/functions@0.11.5": + version "0.11.5" + resolved "https://registry.yarnpkg.com/@firebase/functions/-/functions-0.11.5.tgz#e4187ae3ae262b0482114f7ad418600ca84f3459" + integrity sha512-qrHJ+l62mZiU5UZiVi84t/iLXZlhRuSvBQsa2qvNLgPsEWR7wdpWhRmVdB7AU8ndkSHJjGlMICqrVnz47sgU7Q== + dependencies: + "@firebase/app-check-interop-types" "0.3.2" + "@firebase/auth-interop-types" "0.2.3" + "@firebase/component" "0.6.7" + "@firebase/messaging-interop-types" "0.2.2" + "@firebase/util" "1.9.6" + tslib "^2.1.0" + undici "5.28.4" + "@firebase/functions@0.11.6": version "0.11.6" resolved "https://registry.yarnpkg.com/@firebase/functions/-/functions-0.11.6.tgz#607991a3a870051e6456d7ccb0217fac6305db89" @@ -2518,6 +2697,17 @@ tslib "^2.1.0" undici "5.28.4" +"@firebase/installations-compat@0.2.7": + version "0.2.7" + resolved "https://registry.yarnpkg.com/@firebase/installations-compat/-/installations-compat-0.2.7.tgz#c430f34bfcfc008c92ca32fd11d6c84ab5dd7888" + integrity sha512-RPcbD+3nqHbnhVjIOpWK2H5qzZ8pAAAScceiWph0VNTqpKyPQ5tDcp4V5fS0ELpfgsHYvroMLDKfeHxpfvm8cw== + dependencies: + "@firebase/component" "0.6.7" + "@firebase/installations" "0.6.7" + "@firebase/installations-types" "0.5.2" + "@firebase/util" "1.9.6" + tslib "^2.1.0" + "@firebase/installations-compat@0.2.8": version "0.2.8" resolved "https://registry.yarnpkg.com/@firebase/installations-compat/-/installations-compat-0.2.8.tgz#ebc908afe84db2754b19a62f7655608911e13819" @@ -2534,6 +2724,16 @@ resolved "https://registry.yarnpkg.com/@firebase/installations-types/-/installations-types-0.5.2.tgz#4d4949e0e83ced7f36cbee009355cd305a36e158" integrity sha512-que84TqGRZJpJKHBlF2pkvc1YcXrtEDOVGiDjovP/a3s6W4nlbohGXEsBJo0JCeeg/UG9A+DEZVDUV9GpklUzA== +"@firebase/installations@0.6.7": + version "0.6.7" + resolved "https://registry.yarnpkg.com/@firebase/installations/-/installations-0.6.7.tgz#4fc60ca86e838d7c45dfd1d4926d000060bd1079" + integrity sha512-i6iGoXRu5mX4rTsiMSSKrgh9pSEzD4hwBEzRh5kEhOTr8xN/wvQcCPZDSMVYKwM2XyCPBLVq0JzjyerwL0Rihg== + dependencies: + "@firebase/component" "0.6.7" + "@firebase/util" "1.9.6" + idb "7.1.1" + tslib "^2.1.0" + "@firebase/installations@0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@firebase/installations/-/installations-0.6.8.tgz#f9c9d493bce04b04ca28814e926ef3ed71f033d6" @@ -2561,6 +2761,16 @@ "@firebase/util" "1.9.7" tslib "^2.1.0" +"@firebase/messaging-compat@0.2.9": + version "0.2.9" + resolved "https://registry.yarnpkg.com/@firebase/messaging-compat/-/messaging-compat-0.2.9.tgz#a4cae54c9caf10a3a6c811152d5bd58f165337b7" + integrity sha512-5jN6wyhwPgBH02zOtmmoOeyfsmoD7ty48D1m0vVPsFg55RqN2Z3Q9gkZ5GmPklFPjTPLcxB1ObcHOZvThTkm7g== + dependencies: + "@firebase/component" "0.6.7" + "@firebase/messaging" "0.12.9" + "@firebase/util" "1.9.6" + tslib "^2.1.0" + "@firebase/messaging-interop-types@0.2.2": version "0.2.2" resolved "https://registry.yarnpkg.com/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.2.tgz#81042f7e9739733fa4571d17f6eb6869522754d0" @@ -2578,6 +2788,30 @@ idb "7.1.1" tslib "^2.1.0" +"@firebase/messaging@0.12.9": + version "0.12.9" + resolved "https://registry.yarnpkg.com/@firebase/messaging/-/messaging-0.12.9.tgz#c3cb7a26a3488161273839bf65237f8c485ba661" + integrity sha512-IH+JJmzbFGZXV3+TDyKdqqKPVfKRqBBg2BfYYOy7cm7J+SwV+uJMe8EnDKYeQLEQhtpwciPfJ3qQXJs2lbxDTw== + dependencies: + "@firebase/component" "0.6.7" + "@firebase/installations" "0.6.7" + "@firebase/messaging-interop-types" "0.2.2" + "@firebase/util" "1.9.6" + idb "7.1.1" + tslib "^2.1.0" + +"@firebase/performance-compat@0.2.7": + version "0.2.7" + resolved "https://registry.yarnpkg.com/@firebase/performance-compat/-/performance-compat-0.2.7.tgz#30e29934326888b164c67e5f3709c3a8e580a8d6" + integrity sha512-cb8ge/5iTstxfIGW+iiY+7l3FtN8gobNh9JSQNZgLC9xmcfBYWEs8IeEWMI6S8T+At0oHc3lv+b2kpRMUWr8zQ== + dependencies: + "@firebase/component" "0.6.7" + "@firebase/logger" "0.4.2" + "@firebase/performance" "0.6.7" + "@firebase/performance-types" "0.2.2" + "@firebase/util" "1.9.6" + tslib "^2.1.0" + "@firebase/performance-compat@0.2.8": version "0.2.8" resolved "https://registry.yarnpkg.com/@firebase/performance-compat/-/performance-compat-0.2.8.tgz#d97bab3fd0c147c7f796e9b8f78712bc0b83699c" @@ -2595,6 +2829,17 @@ resolved "https://registry.yarnpkg.com/@firebase/performance-types/-/performance-types-0.2.2.tgz#7b78cd2ab2310bac89a63348d93e67e01eb06dd7" integrity sha512-gVq0/lAClVH5STrIdKnHnCo2UcPLjJlDUoEB/tB4KM+hAeHUxWKnpT0nemUPvxZ5nbdY/pybeyMe8Cs29gEcHA== +"@firebase/performance@0.6.7": + version "0.6.7" + resolved "https://registry.yarnpkg.com/@firebase/performance/-/performance-0.6.7.tgz#7d6c4e5ec61df7369d87fb4a5c0af4e0cedee69b" + integrity sha512-d+Q4ltjdJZqjzcdms5i0UC9KLYX7vKGcygZ+7zHA/Xk+bAbMD2CPU0nWTnlNFWifZWIcXZ/2mAMvaGMW3lypUA== + dependencies: + "@firebase/component" "0.6.7" + "@firebase/installations" "0.6.7" + "@firebase/logger" "0.4.2" + "@firebase/util" "1.9.6" + tslib "^2.1.0" + "@firebase/performance@0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@firebase/performance/-/performance-0.6.8.tgz#668b0fc207389f7829fd3bfb6614fe819b7db124" @@ -2606,6 +2851,18 @@ "@firebase/util" "1.9.7" tslib "^2.1.0" +"@firebase/remote-config-compat@0.2.7": + version "0.2.7" + resolved "https://registry.yarnpkg.com/@firebase/remote-config-compat/-/remote-config-compat-0.2.7.tgz#8a7ac7658a7c9cc29a4ad5884bc224eaae950c38" + integrity sha512-Fq0oneQ4SluLnfr5/HfzRS1TZf1ANj1rWbCCW3+oC98An3nE+sCdp+FSuHsEVNwgMg4Tkwx9Oom2lkKeU+Vn+w== + dependencies: + "@firebase/component" "0.6.7" + "@firebase/logger" "0.4.2" + "@firebase/remote-config" "0.4.7" + "@firebase/remote-config-types" "0.3.2" + "@firebase/util" "1.9.6" + tslib "^2.1.0" + "@firebase/remote-config-compat@0.2.8": version "0.2.8" resolved "https://registry.yarnpkg.com/@firebase/remote-config-compat/-/remote-config-compat-0.2.8.tgz#a6df065c1fd0a943e84ee0e76acfc6c1bede42f9" @@ -2623,6 +2880,17 @@ resolved "https://registry.yarnpkg.com/@firebase/remote-config-types/-/remote-config-types-0.3.2.tgz#a5d1009c6fd08036c5cd4f28764e3cd694f966d5" integrity sha512-0BC4+Ud7y2aPTyhXJTMTFfrGGLqdYXrUB9sJVAB8NiqJswDTc4/2qrE/yfUbnQJhbSi6ZaTTBKyG3n1nplssaA== +"@firebase/remote-config@0.4.7": + version "0.4.7" + resolved "https://registry.yarnpkg.com/@firebase/remote-config/-/remote-config-0.4.7.tgz#1afd6f3089e3c66ed6909eb60d0eb1329d27c9ff" + integrity sha512-5oPNrPFLsbsjpq0lUEIXoDF2eJK7vAbyXe/DEuZQxnwJlfR7aQbtUlEkRgQWcicXpyDmAmDLo7q7lDbCYa6CpA== + dependencies: + "@firebase/component" "0.6.7" + "@firebase/installations" "0.6.7" + "@firebase/logger" "0.4.2" + "@firebase/util" "1.9.6" + tslib "^2.1.0" + "@firebase/remote-config@0.4.8": version "0.4.8" resolved "https://registry.yarnpkg.com/@firebase/remote-config/-/remote-config-0.4.8.tgz#b6a79acdf73554e0ee31c278162b85592fc8c1f3" @@ -2634,6 +2902,17 @@ "@firebase/util" "1.9.7" tslib "^2.1.0" +"@firebase/storage-compat@0.3.8": + version "0.3.8" + resolved "https://registry.yarnpkg.com/@firebase/storage-compat/-/storage-compat-0.3.8.tgz#0d6d66a683713953b2bd24494e83bddcbb562f3a" + integrity sha512-qDfY9kMb6Ch2hZb40sBjDQ8YPxbjGOxuT+gU1Z0iIVSSpSX0f4YpGJCypUXiA0T11n6InCXB+T/Dknh2yxVTkg== + dependencies: + "@firebase/component" "0.6.7" + "@firebase/storage" "0.12.5" + "@firebase/storage-types" "0.8.2" + "@firebase/util" "1.9.6" + tslib "^2.1.0" + "@firebase/storage-compat@0.3.9": version "0.3.9" resolved "https://registry.yarnpkg.com/@firebase/storage-compat/-/storage-compat-0.3.9.tgz#42496a7b5f7c384f0ea590d704934465102b4527" @@ -2650,6 +2929,16 @@ resolved "https://registry.yarnpkg.com/@firebase/storage-types/-/storage-types-0.8.2.tgz#edb321b8a3872a9f74e1f27de046f160021c8e1f" integrity sha512-0vWu99rdey0g53lA7IShoA2Lol1jfnPovzLDUBuon65K7uKG9G+L5uO05brD9pMw+l4HRFw23ah3GwTGpEav6g== +"@firebase/storage@0.12.5": + version "0.12.5" + resolved "https://registry.yarnpkg.com/@firebase/storage/-/storage-0.12.5.tgz#9277b4f838572ba78f017aa6207c6d7545400846" + integrity sha512-nGWBOGFNr10j0LA4NJ3/Yh3us/lb0Q1xSIKZ38N6FcS+vY54nqJ7k3zE3PENregHC8+8txRow++A568G3v8hOA== + dependencies: + "@firebase/component" "0.6.7" + "@firebase/util" "1.9.6" + tslib "^2.1.0" + undici "5.28.4" + "@firebase/storage@0.12.6": version "0.12.6" resolved "https://registry.yarnpkg.com/@firebase/storage/-/storage-0.12.6.tgz#49b2c77f10fd97da913a93e37c86cdff92a805eb" @@ -2660,6 +2949,13 @@ tslib "^2.1.0" undici "5.28.4" +"@firebase/util@1.9.6": + version "1.9.6" + resolved "https://registry.yarnpkg.com/@firebase/util/-/util-1.9.6.tgz#56dc34e20fcbc0dd07b11b800f95f5d0417cbfb4" + integrity sha512-IBr1MZbp4d5MjBCXL3TW1dK/PDXX4yOGbiwRNh1oAbE/+ci5Uuvy9KIrsFYY80as1I0iOaD5oOMA9Q8j4TJWcw== + dependencies: + tslib "^2.1.0" + "@firebase/util@1.9.7": version "1.9.7" resolved "https://registry.yarnpkg.com/@firebase/util/-/util-1.9.7.tgz#c03b0ae065b3bba22800da0bd5314ef030848038" @@ -2667,6 +2963,17 @@ dependencies: tslib "^2.1.0" +"@firebase/vertexai-preview@0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@firebase/vertexai-preview/-/vertexai-preview-0.0.2.tgz#a17454e4899bf4b3fa07322fb204659e7cfa5868" + integrity sha512-NOOL63kFQRq45ioi5P+hlqj/4LNmvn1URhGjQdvyV54c1Irvoq26aW861PRRLjrSMIeNeiLtCLD5pe+ediepAg== + dependencies: + "@firebase/app-check-interop-types" "0.3.2" + "@firebase/component" "0.6.7" + "@firebase/logger" "0.4.2" + "@firebase/util" "1.9.6" + tslib "^2.1.0" + "@firebase/vertexai-preview@0.0.3": version "0.0.3" resolved "https://registry.yarnpkg.com/@firebase/vertexai-preview/-/vertexai-preview-0.0.3.tgz#73dea839439ebdbb5ccd946f297ede5b57e6e7e9" @@ -2678,6 +2985,11 @@ "@firebase/util" "1.9.7" tslib "^2.1.0" +"@firebase/webchannel-wrapper@1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.0.tgz#a0e11b39fa3ef56ed5333bf321f581037aeda033" + integrity sha512-zuWxyfXNbsKbm96HhXzainONPFqRcoZblQ++e9cAIGUuHfl2cFSBzW01jtesqWG/lqaUyX3H8O1y9oWboGNQBA== + "@firebase/webchannel-wrapper@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.1.tgz#0b62c9f47f557a5b4adc073bb0a47542ce6af4c4" @@ -5581,10 +5893,10 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@notifee/react-native@^7.8.2": - version "7.8.2" - resolved "https://registry.yarnpkg.com/@notifee/react-native/-/react-native-7.8.2.tgz#72d3199ae830b4128ddaef3c1c2f11604759c9c4" - integrity sha512-VG4IkWJIlOKqXwa3aExC3WFCVCGCC9BA55Ivg0SMRfEs+ruvYy/zlLANcrVGiPtgkUEryXDhA8SXx9+JcO8oLA== +"@notifee/react-native@^9.0.0": + version "9.0.2" + resolved "https://registry.yarnpkg.com/@notifee/react-native/-/react-native-9.0.2.tgz#28e2b11afa10b13717518d2fd6377b45a754bbba" + integrity sha512-fuStKItKNg+X8ke8qkFC/NjAFuI4Gvt0J8r8LbuXYHyPUj+YiauQSEyVKejQp+a98UyJrAJtKrgJ7XDru+vNiA== "@npmcli/agent@^2.0.0": version "2.2.2" @@ -6627,18 +6939,18 @@ dependencies: invariant "^2.2.4" -"@react-native-firebase/app@^20.1.0": - version "20.1.0" - resolved "https://registry.yarnpkg.com/@react-native-firebase/app/-/app-20.1.0.tgz#86b9371290f92d51821b7299eede95336949f214" - integrity sha512-FCcTtmfz/Bk2laOEKOiUrQUkAnzerkRml7d3kZzJSxaBWLFxpWJQnnXqGZmD8hNWio2QEauB8llUD71KiDk+sw== +"@react-native-firebase/app@^20.5.0": + version "20.5.0" + resolved "https://registry.yarnpkg.com/@react-native-firebase/app/-/app-20.5.0.tgz#9dc2fbc6eee7fb00eefc9f88e404411068befd29" + integrity sha512-zW6jc9R8Ikxxo5qXv25No6QxdWOx37D4U0ppKZQoY6CzwGUzqc4oQUU9MclbUcx+H5p+GEzvT1EIGV6JG7/FAg== dependencies: - opencollective-postinstall "^2.0.3" + firebase "10.12.2" superstruct "^0.6.2" -"@react-native-firebase/messaging@^20.1.0": - version "20.1.0" - resolved "https://registry.yarnpkg.com/@react-native-firebase/messaging/-/messaging-20.1.0.tgz#02026259c74d1725dfc5216158b05bc6655e7951" - integrity sha512-y9FtQ9dIQSyueuLeJghvfLYnay5BqPVgl9T94p+HtUlkxinOgNDjquQFtV/QlzVOyVpLrVPmknMohvBj/fvBzg== +"@react-native-firebase/messaging@^20.5.0": + version "20.5.0" + resolved "https://registry.yarnpkg.com/@react-native-firebase/messaging/-/messaging-20.5.0.tgz#90eeed62d1a069b4fd56746afcfb33ee2a61ba0f" + integrity sha512-S3M9zHJ3zKRH6PuxxjOrV9i0uRO3S5rGYaY3s64BTh4iLEG46UDgJ5uA+WsY+9IsJtSpN4HYDcvnwoxasiEUfw== "@react-native-masked-view/masked-view@^0.3.1": version "0.3.1" @@ -18089,6 +18401,39 @@ findup-sync@^5.0.0: micromatch "^4.0.4" resolve-dir "^1.0.1" +firebase@10.12.2: + version "10.12.2" + resolved "https://registry.yarnpkg.com/firebase/-/firebase-10.12.2.tgz#9049286c5fafb6d686bb19ad93c7bb4a9e8756c0" + integrity sha512-ZxEdtSvP1I9su1yf32D8TIdgxtPgxwr6z3jYAR1TXS/t+fVfpoPc/N1/N2bxOco9mNjUoc+od34v5Fn4GeKs6Q== + dependencies: + "@firebase/analytics" "0.10.4" + "@firebase/analytics-compat" "0.2.10" + "@firebase/app" "0.10.5" + "@firebase/app-check" "0.8.4" + "@firebase/app-check-compat" "0.3.11" + "@firebase/app-compat" "0.2.35" + "@firebase/app-types" "0.9.2" + "@firebase/auth" "1.7.4" + "@firebase/auth-compat" "0.5.9" + "@firebase/database" "1.0.5" + "@firebase/database-compat" "1.0.5" + "@firebase/firestore" "4.6.3" + "@firebase/firestore-compat" "0.3.32" + "@firebase/functions" "0.11.5" + "@firebase/functions-compat" "0.3.11" + "@firebase/installations" "0.6.7" + "@firebase/installations-compat" "0.2.7" + "@firebase/messaging" "0.12.9" + "@firebase/messaging-compat" "0.2.9" + "@firebase/performance" "0.6.7" + "@firebase/performance-compat" "0.2.7" + "@firebase/remote-config" "0.4.7" + "@firebase/remote-config-compat" "0.2.7" + "@firebase/storage" "0.12.5" + "@firebase/storage-compat" "0.3.8" + "@firebase/util" "1.9.6" + "@firebase/vertexai-preview" "0.0.2" + firebase@^10.11.0: version "10.12.3" resolved "https://registry.yarnpkg.com/firebase/-/firebase-10.12.3.tgz#b94510728f603a15367b95e12a00b366700ba7f8" From 20d8e33fb083a174ab754e29528970c6d8b7d5af Mon Sep 17 00:00:00 2001 From: "runway-github[bot]" <73448015+runway-github[bot]@users.noreply.github.com> Date: Tue, 24 Sep 2024 15:49:11 -0600 Subject: [PATCH 10/27] chore(runway): cherry-pick fix: fix detect tokens performance (#11420) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fix: fix detect tokens performance (#11321) ## **Description** This PR removes not needed calls in the codebase to `TokenDetectionController.detectTokens(); ` that was making the app slower; Note: This performance issue is more noticeable on Android than IOS. This PR remove this [call](https://github.com/MetaMask/metamask-mobile/blob/6fc1d9d3ae3fc2f7e447349f8b110e9520fc9e15/app/core/Engine.ts#L1698) in Engine.ts. This call is executed whenever the user switches networks; I think this is not needed since `TokenDetectionController` in core is already subscribed to `NetworkController:networkDidChange`. Similarly, This PR also also removes this [call](https://github.com/MetaMask/metamask-mobile/blob/6fc1d9d3ae3fc2f7e447349f8b110e9520fc9e15/app/components/Views/Wallet/index.tsx#L401) in wallet/index.tsx; I have also noticed that switching accounts in android is a bit slow; that was due to calling detectTokens function twice. One call was made because TokenDetectionController is subscribed to `AccountsController:selectedAccountChange` the other one was happening because of the subscription to `PreferencesController:stateChange` which is redundant and was happening because this [PR](https://github.com/MetaMask/core/pull/4219/files#diff-3336d65a4d2c1e5af90f22e16feb058f327960796b135308bca9ed8415418f5dR297) update was not patched; When testing with simulator, after adding logs inside [onNetworkChange](https://github.com/MetaMask/metamask-mobile/blob/726ca826662ea6044cd5da671555c379e19a7864/app/components/Views/NetworkSelector/NetworkSelector.tsx#L191) This is how much it take to go from linea to Ethereum network on this PR vs main Screenshot 2024-09-19 at 17 29 51 Screenshot 2024-09-19 at 18 27 51 Testing on physical: https://github.com/user-attachments/assets/146cee68-75ba-459b-9e80-4b8f9bfb54b6 ## **Related issues** Fixes: https://github.com/MetaMask/mobile-planning/issues/1927 ## **Manual testing steps** (I found it easier to debug when you add console.log inside detect tokens function to make sure it is not being called multiple times) 1. Go to home page and select Linea network; then go back to Ethereum mainnet; switch back and forth multiple times and you should notice the switch is smoother now; you can check your console.log in your terminal and see that when you switch between networks; you only see it calling detectTokens once. 2. Switch accounts back and forth and it should also be faster; you can also check logs and make sure detectTokens is only triggered once when switching accounts ## **Screenshots/Recordings** ### **Before** https://github.com/user-attachments/assets/721d1f56-6e1e-4940-9544-5d4b5c3232c0 ### **After** https://github.com/user-attachments/assets/c6287f8c-3091-4267-b82a-4c6bc02527fb ## **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. [6e8d831](https://github.com/MetaMask/metamask-mobile/commit/6e8d83177337f03bcbbc867b560ac262dd88cee3) Co-authored-by: sahar-fehri --- app/components/Views/Wallet/index.tsx | 3 +- app/core/Engine.ts | 2 -- .../@metamask+assets-controllers+30.0.0.patch | 34 ++++++++++++++++--- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/app/components/Views/Wallet/index.tsx b/app/components/Views/Wallet/index.tsx index 522affc03fd..886a36d645a 100644 --- a/app/components/Views/Wallet/index.tsx +++ b/app/components/Views/Wallet/index.tsx @@ -406,9 +406,8 @@ const Wallet = ({ useEffect( () => { requestAnimationFrame(async () => { - const { TokenDetectionController, AccountTrackerController } = + const { AccountTrackerController } = Engine.context; - TokenDetectionController.detectTokens(); AccountTrackerController.refresh(); }); }, diff --git a/app/core/Engine.ts b/app/core/Engine.ts index 295788e58d7..9ba99efe7f9 100644 --- a/app/core/Engine.ts +++ b/app/core/Engine.ts @@ -1673,7 +1673,6 @@ class Engine { const { AccountTrackerController, AssetsContractController, - TokenDetectionController, NetworkController, SwapsController, } = this.context; @@ -1695,7 +1694,6 @@ class Engine { ).configuration.chainId, pollCountLimit: AppConstants.SWAPS.POLL_COUNT_LIMIT, }); - TokenDetectionController.detectTokens(); AccountTrackerController.refresh(); } diff --git a/patches/@metamask+assets-controllers+30.0.0.patch b/patches/@metamask+assets-controllers+30.0.0.patch index 0e0b88768f3..a9f3c0d37f5 100644 --- a/patches/@metamask+assets-controllers+30.0.0.patch +++ b/patches/@metamask+assets-controllers+30.0.0.patch @@ -1,5 +1,5 @@ diff --git a/node_modules/@metamask/assets-controllers/dist/chunk-4ODKGWYQ.js b/node_modules/@metamask/assets-controllers/dist/chunk-4ODKGWYQ.js -index 542e3f6..6c656aa 100644 +index 542e3f6..af49a85 100644 --- a/node_modules/@metamask/assets-controllers/dist/chunk-4ODKGWYQ.js +++ b/node_modules/@metamask/assets-controllers/dist/chunk-4ODKGWYQ.js @@ -69,9 +69,10 @@ var AssetsContractController = class extends _basecontroller.BaseControllerV1 { @@ -381,7 +381,7 @@ index ee6155c..06a3a04 100644 }; var NftDetectionController_default = NftDetectionController; diff --git a/node_modules/@metamask/assets-controllers/dist/chunk-HDI4L2DD.js b/node_modules/@metamask/assets-controllers/dist/chunk-HDI4L2DD.js -index 76e3362..5ab79a4 100644 +index 76e3362..057c90e 100644 --- a/node_modules/@metamask/assets-controllers/dist/chunk-HDI4L2DD.js +++ b/node_modules/@metamask/assets-controllers/dist/chunk-HDI4L2DD.js @@ -34,7 +34,7 @@ var STATIC_MAINNET_TOKEN_LIST = Object.entries( @@ -417,7 +417,18 @@ index 76e3362..5ab79a4 100644 _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _trackMetaMetricsEvent, trackMetaMetricsEvent); const { isUnlocked } = this.messagingSystem.call( "KeyringController:getState" -@@ -203,6 +206,7 @@ _isDetectionEnabledFromPreferences = new WeakMap(); +@@ -165,7 +168,9 @@ var TokenDetectionController = class extends _pollingcontroller.StaticIntervalPo + if (!this.isActive) { + return; + } +- const addressAgainstWhichToDetect = selectedAddress ?? _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _selectedAddress); ++ const currentAddress = _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _selectedAddress); ++ const currentAddressChecksum = _controllerutils.toChecksumHexAddress.call(void 0, currentAddress) ++ const addressAgainstWhichToDetect = _controllerutils.toChecksumHexAddress.call(void 0, selectedAddress) ?? currentAddressChecksum; + const { chainId, networkClientId: selectedNetworkClientId } = _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getCorrectChainIdAndNetworkClientId, getCorrectChainIdAndNetworkClientId_fn).call(this, networkClientId); + const chainIdAgainstWhichToDetect = chainId; + const networkClientIdAgainstWhichToDetect = selectedNetworkClientId; +@@ -203,6 +208,7 @@ _isDetectionEnabledFromPreferences = new WeakMap(); _isDetectionEnabledForNetwork = new WeakMap(); _getBalancesInSingleCall = new WeakMap(); _trackMetaMetricsEvent = new WeakMap(); @@ -425,6 +436,21 @@ index 76e3362..5ab79a4 100644 _registerEventListeners = new WeakSet(); registerEventListeners_fn = function() { this.messagingSystem.subscribe("KeyringController:unlock", async () => { +@@ -224,12 +230,10 @@ registerEventListeners_fn = function() { + ); + this.messagingSystem.subscribe( + "PreferencesController:stateChange", +- async ({ selectedAddress: newSelectedAddress, useTokenDetection }) => { +- const isSelectedAddressChanged = _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _selectedAddress) !== newSelectedAddress; ++ async ({ useTokenDetection }) => { + const isDetectionChangedFromPreferences = _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _isDetectionEnabledFromPreferences) !== useTokenDetection; +- _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _selectedAddress, newSelectedAddress); + _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _isDetectionEnabledFromPreferences, useTokenDetection); +- if (isSelectedAddressChanged || isDetectionChangedFromPreferences) { ++ if (isDetectionChangedFromPreferences) { + await _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _restartTokenDetection, restartTokenDetection_fn).call(this, { + selectedAddress: _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _selectedAddress) + }); @@ -324,7 +328,7 @@ getSlicesOfTokensToDetect_fn = function({ chainId, selectedAddress @@ -826,7 +852,7 @@ index cd8f792..b20db8a 100644 diff --git a/node_modules/@metamask/assets-controllers/dist/chunk-MBCN3MNX.js b/node_modules/@metamask/assets-controllers/dist/chunk-MBCN3MNX.js -index b8722af..535b648 100644 +index b8722af..dbff48b 100644 --- a/node_modules/@metamask/assets-controllers/dist/chunk-MBCN3MNX.js +++ b/node_modules/@metamask/assets-controllers/dist/chunk-MBCN3MNX.js @@ -103,10 +103,14 @@ var TokensController = class extends _basecontroller.BaseControllerV1 { From 0a321dca8caff131e7f543890c26baf4514c9680 Mon Sep 17 00:00:00 2001 From: metamaskbot Date: Tue, 24 Sep 2024 15:54:24 -0600 Subject: [PATCH 11/27] Revert "chore(runway): cherry-pick fix: push notifications (#11417)" This reverts commit ca92f2e1de2161127b9e1b57f03846e5ce47009a. --- app/components/Nav/Main/index.js | 30 +- .../BasicFunctionalityModal.tsx | 21 +- app/components/UI/Notification/List/index.tsx | 8 +- .../Views/Notifications/OptIn/index.test.tsx | 90 +---- .../Views/Notifications/OptIn/index.tsx | 20 +- app/components/Views/Notifications/index.tsx | 4 +- .../NotificationsSettings/index.test.tsx | 160 +------- .../Settings/NotificationsSettings/index.tsx | 15 +- app/components/Views/Wallet/index.tsx | 6 +- app/core/NotificationManager.js | 17 +- app/core/NotificationsManager.test.ts | 40 +- .../notifications/androidChannels.test.ts | 50 --- app/util/notifications/androidChannels.ts | 32 -- app/util/notifications/hooks/index.test.ts | 90 ++--- app/util/notifications/hooks/index.ts | 89 +++-- app/util/notifications/index.ts | 4 +- app/util/notifications/methods/common.ts | 4 - .../notifications/pushPermissions.test.ts | 113 ++++++ app/util/notifications/pushPermissions.ts | 123 ++++++ .../services/NotificationService.test.ts | 134 ------- .../services/NotificationService.ts | 236 ----------- app/util/notifications/services/index.ts | 1 - .../settings/storage/constants.ts | 2 +- .../settings/storage/contants.test.ts | 2 +- .../setupAndroidChannels.test.ts | 25 ++ .../notifications/setupAndroidChannels.ts | 12 + .../notifications/types/notification/index.ts | 13 + e2e/helpers.js | 2 +- e2e/pages/EnableDeviceNotificationsAlert.js | 27 -- e2e/specs/quarantine/import-nft.failing.js | 2 +- e2e/utils/Matchers.js | 13 +- e2e/viewHelper.js | 7 +- index.js | 16 + ios/Podfile.lock | 90 ++--- package.json | 9 +- ...eDeviceNotificationsChecksAlert.testIds.js | 6 - yarn.lock | 371 +----------------- 37 files changed, 569 insertions(+), 1315 deletions(-) delete mode 100644 app/util/notifications/androidChannels.test.ts delete mode 100644 app/util/notifications/androidChannels.ts create mode 100644 app/util/notifications/pushPermissions.test.ts create mode 100644 app/util/notifications/pushPermissions.ts delete mode 100644 app/util/notifications/services/NotificationService.test.ts delete mode 100644 app/util/notifications/services/NotificationService.ts delete mode 100644 app/util/notifications/services/index.ts create mode 100644 app/util/notifications/setupAndroidChannels.test.ts create mode 100644 app/util/notifications/setupAndroidChannels.ts delete mode 100644 e2e/pages/EnableDeviceNotificationsAlert.js delete mode 100644 wdio/screen-objects/testIDs/Screens/EnableDeviceNotificationsChecksAlert.testIds.js diff --git a/app/components/Nav/Main/index.js b/app/components/Nav/Main/index.js index d800e78ce30..1353c66b04a 100644 --- a/app/components/Nav/Main/index.js +++ b/app/components/Nav/Main/index.js @@ -21,11 +21,14 @@ import BackgroundTimer from 'react-native-background-timer'; import NotificationManager from '../../../core/NotificationManager'; import Engine from '../../../core/Engine'; import AppConstants from '../../../core/AppConstants'; +import notifee from '@notifee/react-native'; import I18n, { strings } from '../../../../locales/i18n'; import FadeOutOverlay from '../../UI/FadeOutOverlay'; import BackupAlert from '../../UI/BackupAlert'; import Notification from '../../UI/Notification'; import RampOrders from '../../UI/Ramp'; +import Device from '../../../util/device'; +import Routes from '../../../constants/navigation/Routes'; import { showTransactionNotification, hideCurrentNotification, @@ -33,12 +36,12 @@ import { removeNotificationById, removeNotVisibleNotifications, } from '../../../actions/notification'; - import ProtectYourWalletModal from '../../UI/ProtectYourWalletModal'; import MainNavigator from './MainNavigator'; import SkipAccountSecurityModal from '../../UI/SkipAccountSecurityModal'; import { query } from '@metamask/controller-utils'; import SwapsLiveness from '../../UI/Swaps/SwapsLiveness'; +import useNotificationHandler from '../../../util/notifications/hooks'; import { setInfuraAvailabilityBlocked, @@ -67,8 +70,6 @@ import { selectNetworkImageSource, } from '../../../selectors/networkInfos'; import { selectShowIncomingTransactionNetworks } from '../../../selectors/preferencesController'; - -import useNotificationHandler from '../../../util/notifications/hooks'; import { DEPRECATED_NETWORKS, NETWORKS_CHAIN_ID, @@ -104,18 +105,16 @@ const Main = (props) => { const [showDeprecatedAlert, setShowDeprecatedAlert] = useState(true); const { colors } = useTheme(); const styles = createStyles(colors); + const backgroundMode = useRef(false); const locale = useRef(I18n.locale); const removeConnectionStatusListener = useRef(); const removeNotVisibleNotifications = props.removeNotVisibleNotifications; - useNotificationHandler(props.navigation); + useEnableAutomaticSecurityChecks(); useMinimumVersions(); - - - useEffect(() => { if (DEPRECATED_NETWORKS.includes(props.chainId)) { setShowDeprecatedAlert(true); @@ -268,9 +267,24 @@ const Main = (props) => { initForceReload(); return; } - }); + const bootstrapAndroidInitialNotification = useCallback(async () => { + if (Device.isAndroid()) { + const initialNotification = await notifee.getInitialNotification(); + + if ( + initialNotification?.data?.action === 'tx' && + initialNotification.data.id + ) { + NotificationManager.setTransactionToView(initialNotification.data.id); + props.navigation.navigate(Routes.TRANSACTIONS_VIEW); + } + } + }, [props.navigation]); + + useNotificationHandler(bootstrapAndroidInitialNotification, props.navigation); + // Remove all notifications that aren't visible useEffect(() => { removeNotVisibleNotifications(); diff --git a/app/components/UI/BasicFunctionality/BasicFunctionalityModal/BasicFunctionalityModal.tsx b/app/components/UI/BasicFunctionality/BasicFunctionalityModal/BasicFunctionalityModal.tsx index e01d3afdccb..b2d26f68838 100644 --- a/app/components/UI/BasicFunctionality/BasicFunctionalityModal/BasicFunctionalityModal.tsx +++ b/app/components/UI/BasicFunctionality/BasicFunctionalityModal/BasicFunctionalityModal.tsx @@ -26,7 +26,10 @@ import Icon, { IconSize, } from '../../../../component-library/components/Icons/Icon'; import Routes from '../../../../constants/navigation/Routes'; -import NotificationsService from '../../../../util/notifications/services/NotificationService'; +import { + asyncAlert, + requestPushNotificationsPermission, +} from '../../../../util/notifications'; import { MetaMetricsEvents } from '../../../../core/Analytics'; import { useEnableNotifications } from '../../../../util/notifications/hooks/useNotifications'; import { useMetrics } from '../../../hooks/useMetrics'; @@ -34,6 +37,7 @@ import { selectIsProfileSyncingEnabled, selectIsMetamaskNotificationsEnabled, } from '../../../../selectors/notifications'; +import { AuthorizationStatus } from '@notifee/react-native'; interface Props { route: { @@ -61,11 +65,18 @@ const BasicFunctionalityModal = ({ route }: Props) => { const { enableNotifications } = useEnableNotifications(); const enableNotificationsFromModal = useCallback(async () => { - const { permission } = await NotificationsService.getAllPermissions(false); - if (permission !== 'authorized') { - return; - } + const nativeNotificationStatus = await requestPushNotificationsPermission( + asyncAlert, + ); + + if (nativeNotificationStatus?.authorizationStatus === AuthorizationStatus.AUTHORIZED) { + /** + * Although this is an async function, we are dispatching an action (firing & forget) + * to emulate optimistic UI. + * + */ enableNotifications(); + } }, [enableNotifications]); const closeBottomSheet = async () => { diff --git a/app/components/UI/Notification/List/index.tsx b/app/components/UI/Notification/List/index.tsx index fcfd61a8002..edac15cf843 100644 --- a/app/components/UI/Notification/List/index.tsx +++ b/app/components/UI/Notification/List/index.tsx @@ -1,6 +1,6 @@ import { NavigationProp, ParamListBase } from '@react-navigation/native'; import React, { useCallback, useMemo } from 'react'; -import NotificationsService from '../../../../util/notifications/services/NotificationService'; +import notifee from '@notifee/react-native'; import { ActivityIndicator, FlatList, FlatListProps, View } from 'react-native'; import ScrollableTabView, { DefaultTabBar, @@ -75,11 +75,11 @@ function NotificationsListItem(props: NotificationsListItemProps) { }); } - NotificationsService.getBadgeCount().then((count) => { + notifee.getBadgeCount().then((count) => { if (count > 0) { - NotificationsService.decrementBadgeCount(count - 1); + notifee.setBadgeCount(count - 1); } else { - NotificationsService.setBadgeCount(0); + notifee.setBadgeCount(0); } }); diff --git a/app/components/Views/Notifications/OptIn/index.test.tsx b/app/components/Views/Notifications/OptIn/index.test.tsx index b643daaa7fb..bd62c3d961e 100644 --- a/app/components/Views/Notifications/OptIn/index.test.tsx +++ b/app/components/Views/Notifications/OptIn/index.test.tsx @@ -1,16 +1,13 @@ import React from 'react'; -import OptIn from '.'; +import OptIn from './'; import { RootState } from '../../../../reducers'; import { backgroundState } from '../../../../util/test/initial-root-state'; import renderWithProvider, { DeepPartial, } from '../../../../util/test/renderWithProvider'; -import { strings } from '../../../../../locales/i18n'; const mockedDispatch = jest.fn(); - - const mockInitialState: DeepPartial = { settings: {}, engine: { @@ -21,12 +18,14 @@ const mockInitialState: DeepPartial = { }, }, }, - }; +}; - jest.mock('react-redux', () => ({ - ...jest.requireActual('react-redux'), - useSelector: jest.fn().mockImplementation((selector) => selector(mockInitialState)), - })); +jest.mock('react-redux', () => ({ + ...jest.requireActual('react-redux'), + // TODO: Replace "any" with type + // eslint-disable-next-line @typescript-eslint/no-explicit-any + useSelector: (fn: any) => fn(mockInitialState), +})); jest.mock('@react-navigation/native', () => { const actualNav = jest.requireActual('@react-navigation/native'); @@ -39,82 +38,9 @@ jest.mock('@react-navigation/native', () => { }; }); -jest.mock('../../../../actions/notification/helpers', () => ({ - enableNotificationServices: jest.fn(), -})); - -jest.mock('../../../../components/hooks/useMetrics', () => ({ - useMetrics: () => ({ - trackEvent: jest.fn(), - }), -})); - -jest.mock('../../../../util/notifications/hooks/useNotifications', () => ({ - useEnableNotifications: () => ({ - enableNotifications: jest.fn(), - }), -})); - -jest.mock('react-native', () => ({ - Linking: { - openURL: jest.fn(), - }, -})); - -jest.mock('../../../../selectors/notifications', () => ({ - selectIsMetamaskNotificationsEnabled: jest.fn(), -})); - -jest.mock('../../../../core/Analytics', () => ({ - MetaMetricsEvents: { - NOTIFICATIONS_ACTIVATED: 'notifications_activated', - }, -})); - -jest.mock('../../../../util/theme', () => ({ - useTheme: jest.fn(), -})); - -jest.mock('../../../../selectors/notifications', () => ({ - selectIsProfileSyncingEnabled: jest.fn(), -})); - describe('OptIn', () => { - - beforeEach(() => { - jest.resetAllMocks(); - }); - it('should render correctly', () => { const { toJSON } = renderWithProvider(); expect(toJSON()).toMatchSnapshot(); }); - - it('calls enableNotifications when the button is pressed', async () => { - const { getByText } = renderWithProvider( - - ); - - const button = getByText(strings('notifications.activation_card.cta')); - expect(button).toBeDefined(); - }); - - it('calls navigate when the cancel button is pressed', async () => { - const { getByText } = renderWithProvider( - - ); - - const button = getByText(strings('notifications.activation_card.cancel')); - expect(button).toBeDefined(); - }); - - it('calls trackEvent when the button is pressed', async () => { - const { getByText } = renderWithProvider( - - ); - - const button = getByText(strings('notifications.activation_card.cta')); - expect(button).toBeDefined(); - }); }); - diff --git a/app/components/Views/Notifications/OptIn/index.tsx b/app/components/Views/Notifications/OptIn/index.tsx index 4c69eb7d7dd..d256d687df8 100644 --- a/app/components/Views/Notifications/OptIn/index.tsx +++ b/app/components/Views/Notifications/OptIn/index.tsx @@ -4,6 +4,7 @@ import { useNavigation } from '@react-navigation/native'; import { useMetrics } from '../../../../components/hooks/useMetrics'; import { MetaMetricsEvents } from '../../../../core/Analytics'; +import { AuthorizationStatus } from '@notifee/react-native'; import Button, { ButtonVariants, } from '../../../../component-library/components/Buttons/Button'; @@ -17,7 +18,10 @@ import EnableNotificationsCardPlaceholder from '../../../../images/enableNotific import { createStyles } from './styles'; import Routes from '../../../../constants/navigation/Routes'; import { useSelector } from 'react-redux'; -import NotificationsService from '../../../../util/notifications/services/NotificationService'; +import { + asyncAlert, + requestPushNotificationsPermission, +} from '../../../../util/notifications'; import AppConstants from '../../../../core/AppConstants'; import { RootState } from '../../../../reducers'; import { useEnableNotifications } from '../../../../util/notifications/hooks/useNotifications'; @@ -72,11 +76,14 @@ const OptIn = () => { }, }); } else { - const { permission } = await NotificationsService.getAllPermissions(); - - if (permission !== 'authorized') { - return; - } + const nativeNotificationStatus = await requestPushNotificationsPermission( + asyncAlert, + ); + + if ( + nativeNotificationStatus?.authorizationStatus === + AuthorizationStatus.AUTHORIZED + ) { /** * Although this is an async function, we are dispatching an action (firing & forget) * to emulate optimistic UI. @@ -96,6 +103,7 @@ const OptIn = () => { action_type: 'activated', is_profile_syncing_enabled: isProfileSyncingEnabled, }); + } }, [ basicFunctionalityEnabled, enableNotifications, diff --git a/app/components/Views/Notifications/index.tsx b/app/components/Views/Notifications/index.tsx index 2a3ee9be996..a38ffeb1b50 100644 --- a/app/components/Views/Notifications/index.tsx +++ b/app/components/Views/Notifications/index.tsx @@ -1,6 +1,7 @@ import React, { useCallback, useMemo } from 'react'; import { View } from 'react-native'; import { useSelector } from 'react-redux'; +import notifee from '@notifee/react-native'; import { useMetrics } from '../../../components/hooks/useMetrics'; import { NotificationsViewSelectorsIDs } from '../../../../e2e/selectors/NotificationsView.selectors'; import styles from './styles'; @@ -28,7 +29,6 @@ import { useMarkNotificationAsRead, } from '../../../util/notifications/hooks/useNotifications'; import { NavigationProp, ParamListBase } from '@react-navigation/native'; -import NotificationsService from '../../../util/notifications/services/NotificationService'; import ButtonIcon, { ButtonIconSizes, } from '../../../component-library/components/Buttons/ButtonIcon'; @@ -53,7 +53,7 @@ const NotificationsView = ({ const handleMarkAllAsRead = useCallback(() => { markNotificationAsRead(notifications); - NotificationsService.setBadgeCount(0); + notifee.setBadgeCount(0); trackEvent(MetaMetricsEvents.NOTIFICATIONS_MARKED_ALL_AS_READ); }, [markNotificationAsRead, notifications, trackEvent]); diff --git a/app/components/Views/Settings/NotificationsSettings/index.test.tsx b/app/components/Views/Settings/NotificationsSettings/index.test.tsx index ff8b0928e70..292c0906062 100644 --- a/app/components/Views/Settings/NotificationsSettings/index.test.tsx +++ b/app/components/Views/Settings/NotificationsSettings/index.test.tsx @@ -1,16 +1,11 @@ -import React, { useCallback } from 'react'; -import { renderHook, act } from '@testing-library/react-hooks'; +import React from 'react'; import renderWithProvider from '../../../../util/test/renderWithProvider'; -import NotificationsService from '../../../../util/notifications/services/NotificationService'; + import { backgroundState } from '../../../../util/test/initial-root-state'; import NotificationsSettings from '.'; - -import Routes from '../../../../constants/navigation/Routes'; import { Props } from './NotificationsSettings.types'; import { MOCK_ACCOUNTS_CONTROLLER_STATE } from '../../../../util/test/accountsControllerTestUtils'; -import { MetaMetricsEvents } from '../../../../core/Analytics/MetaMetrics.events'; -import { NavigationProp, ParamListBase } from '@react-navigation/native'; // Mock store.getState let mockGetState: jest.Mock; @@ -43,142 +38,8 @@ const mockInitialState = { }, }; -jest.mock('@react-navigation/native', () => { - const actualNav = jest.requireActual('@react-navigation/native'); - return { - ...actualNav, - useNavigation: () => ({ - navigate: jest.fn(), - }), - }; -}); - -jest.mock('../../../../util/notifications/services/NotificationService', () => ({ - getAllPermissions: jest.fn(), -})); - -jest.mock('../../../../core/Analytics/MetaMetrics.events', () => ({ - MetaMetricsEvents: { - NOTIFICATIONS_SETTINGS_UPDATED: 'NOTIFICATIONS_SETTINGS_UPDATED', - }, -})); - -const mockDisableNotifications = jest.fn(); -const mockEnableNotifications = jest.fn(); -const mockSetUiNotificationStatus = jest.fn(); -const mockTrackEvent = jest.fn(); - -const mockNavigation = { - navigate: jest.fn(), -} as unknown as NavigationProp; - const setOptions = jest.fn(); -describe('toggleNotificationsEnabled', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - const setup = (basicFunctionalityEnabled: boolean, isMetamaskNotificationsEnabled: boolean, isProfileSyncingEnabled: boolean) => renderHook(() => - useCallback(async () => { - if (!basicFunctionalityEnabled) { - mockNavigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { - screen: Routes.SHEET.BASIC_FUNCTIONALITY, - params: { - caller: Routes.SETTINGS.NOTIFICATIONS, - }, - }); - } else if (isMetamaskNotificationsEnabled) { - mockDisableNotifications(); - mockSetUiNotificationStatus(false); - } else { - const { permission } = await NotificationsService.getAllPermissions(false); - if (permission !== 'authorized') { - return; - } - - mockEnableNotifications(); - mockSetUiNotificationStatus(true); - } - mockTrackEvent(MetaMetricsEvents.NOTIFICATIONS_SETTINGS_UPDATED, { - settings_type: 'notifications', - old_value: isMetamaskNotificationsEnabled, - new_value: !isMetamaskNotificationsEnabled, - was_profile_syncing_on: isMetamaskNotificationsEnabled ? true : isProfileSyncingEnabled, - }); - }, []) - ); - - it('should navigate to basic functionality screen if basicFunctionalityEnabled is false', async () => { - const { result } = setup(false, false, false); - - await act(async () => { - await result.current(); - }); - - expect(mockNavigation.navigate).toHaveBeenCalledWith(Routes.MODAL.ROOT_MODAL_FLOW, { - screen: Routes.SHEET.BASIC_FUNCTIONALITY, - params: { - caller: Routes.SETTINGS.NOTIFICATIONS, - }, - }); - }); - - it('should disable notifications if isMetamaskNotificationsEnabled is true', async () => { - const { result } = setup(true, true, false); - - await act(async () => { - await result.current(); - }); - - expect(mockDisableNotifications).toHaveBeenCalled(); - expect(mockSetUiNotificationStatus).toHaveBeenCalledWith(false); - }); - - it('should enable notifications if isMetamaskNotificationsEnabled is false and permission is authorized', async () => { - (NotificationsService.getAllPermissions as jest.Mock).mockResolvedValue({ permission: 'authorized' }); - - const { result } = setup(true, false, false); - - await act(async () => { - await result.current(); - }); - - expect(mockEnableNotifications).toHaveBeenCalled(); - expect(mockSetUiNotificationStatus).toHaveBeenCalledWith(true); - }); - - it('should not enable notifications if permission is not authorized', async () => { - (NotificationsService.getAllPermissions as jest.Mock).mockResolvedValue({ permission: 'denied' }); - - const { result } = setup(true, false, false); - - await act(async () => { - await result.current(); - }); - - expect(mockEnableNotifications).not.toHaveBeenCalled(); - expect(mockSetUiNotificationStatus).not.toHaveBeenCalled(); - }); - - it('should track event when notifications settings are updated', async () => { - (NotificationsService.getAllPermissions as jest.Mock).mockResolvedValue({ permission: 'authorized' }); - - const { result } = setup(true, false, true); - - await act(async () => { - await result.current(); - }); - - expect(mockTrackEvent).toHaveBeenCalledWith(MetaMetricsEvents.NOTIFICATIONS_SETTINGS_UPDATED, { - settings_type: 'notifications', - old_value: false, - new_value: true, - was_profile_syncing_on: true, - }); - }); -}); - describe('NotificationsSettings', () => { it('should render correctly', () => { mockGetState.mockImplementation(() => ({ @@ -199,21 +60,4 @@ describe('NotificationsSettings', () => { ); expect(toJSON()).toMatchSnapshot(); }); - - it('should toggle notifications and handle permission correctly', async () => { - const isMetamaskNotificationsEnabled = true; - const basicFunctionalityEnabled = true; - const isProfileSyncingEnabled = true; - - const toggleNotificationsEnabledImpl = jest.fn(() => Promise.resolve({ - isMetamaskNotificationsEnabled, - basicFunctionalityEnabled, - isProfileSyncingEnabled, - })); - - await toggleNotificationsEnabledImpl(); - - expect(NotificationsService.getAllPermissions).toHaveBeenCalledTimes(1); - expect(NotificationsService.getAllPermissions).toHaveBeenCalledWith(false); - }); }); diff --git a/app/components/Views/Settings/NotificationsSettings/index.tsx b/app/components/Views/Settings/NotificationsSettings/index.tsx index 707d2a24b7e..9dd493d57e6 100644 --- a/app/components/Views/Settings/NotificationsSettings/index.tsx +++ b/app/components/Views/Settings/NotificationsSettings/index.tsx @@ -31,7 +31,10 @@ import { selectIsProfileSyncingEnabled, } from '../../../../selectors/notifications'; -import NotificationsService from '../../../../util/notifications/services/NotificationService'; +import { + requestPushNotificationsPermission, + asyncAlert, +} from '../../../../util/notifications'; import Routes from '../../../../constants/navigation/Routes'; import ButtonIcon, { @@ -51,6 +54,7 @@ import AppConstants from '../../../../core/AppConstants'; import notificationsRows from './notificationsRows'; import { IconName } from '../../../../component-library/components/Icons/Icon'; import { MetaMetricsEvents } from '../../../../core/Analytics/MetaMetrics.events'; +import { AuthorizationStatus } from '@notifee/react-native'; interface MainNotificationSettingsProps extends Props { toggleNotificationsEnabled: () => void; @@ -183,17 +187,18 @@ const NotificationsSettings = ({ navigation, route }: Props) => { disableNotifications(); setUiNotificationStatus(false); } else { - const { permission } = await NotificationsService.getAllPermissions(false); - if (permission !== 'authorized') { - return; - } + const nativeNotificationStatus = await requestPushNotificationsPermission( + asyncAlert, + ); + if (nativeNotificationStatus?.authorizationStatus === AuthorizationStatus.AUTHORIZED) { /** * Although this is an async function, we are dispatching an action (firing & forget) * to emulate optimistic UI. */ enableNotifications(); setUiNotificationStatus(true); + } } trackEvent(MetaMetricsEvents.NOTIFICATIONS_SETTINGS_UPDATED, { settings_type: 'notifications', diff --git a/app/components/Views/Wallet/index.tsx b/app/components/Views/Wallet/index.tsx index 886a36d645a..e127c3ca2d3 100644 --- a/app/components/Views/Wallet/index.tsx +++ b/app/components/Views/Wallet/index.tsx @@ -35,7 +35,9 @@ import { ToastContext, ToastVariants, } from '../../../component-library/components/Toast'; -import NotificationsService from '../../../util/notifications/services/NotificationService'; +import { + isDeviceNotificationEnabled, +} from '../../../util/notifications'; import Engine from '../../../core/Engine'; import CollectibleContracts from '../../UI/CollectibleContracts'; import { MetaMetricsEvents } from '../../../core/Analytics'; @@ -341,7 +343,7 @@ const Wallet = ({ } async function checkIfNotificationsAreEnabled() { - await NotificationsService.isDeviceNotificationEnabled(); + await isDeviceNotificationEnabled(); } checkIfNotificationsAreEnabled(); }); diff --git a/app/core/NotificationManager.js b/app/core/NotificationManager.js index 3bcf35ee01b..c01a4e1dbfb 100644 --- a/app/core/NotificationManager.js +++ b/app/core/NotificationManager.js @@ -1,9 +1,9 @@ 'use strict'; +import notifee from '@notifee/react-native'; import Engine from './Engine'; import { hexToBN, renderFromWei } from '../util/number'; import Device from '../util/device'; -import notifee from '@notifee/react-native'; import { STORAGE_IDS } from '../util/notifications/settings/storage/constants'; import { strings } from '../../locales/i18n'; import { AppState } from 'react-native'; @@ -11,9 +11,9 @@ import { AppState } from 'react-native'; import { NotificationTransactionTypes, isNotificationsFeatureEnabled, - + requestPushNotificationsPermission, + asyncAlert, } from '../util/notifications'; - import { safeToChecksumAddress } from '../util/address'; import ReviewManager from './ReviewManager'; import { selectChainId } from '../selectors/networkController'; @@ -152,7 +152,7 @@ class NotificationManager { title, body: message, android: { - lightUpScreen: true, + lightUpScreen: false, channelId, smallIcon: 'ic_notification_small', largeIcon: 'ic_notification', @@ -179,6 +179,7 @@ class NotificationManager { } else { pushData.userInfo = extraData; // check if is still needed } + isNotificationsFeatureEnabled() && notifee.displayNotification(pushData); } else { this._showTransactionNotification({ @@ -255,6 +256,11 @@ class NotificationManager { } Promise.all(pollPromises); + Device.isIos() && + setTimeout(() => { + requestPushNotificationsPermission(asyncAlert); + }, 5000); + // Prompt review ReviewManager.promptReview(); @@ -485,6 +491,9 @@ export default { gotIncomingTransaction(lastBlock) { return instance?.gotIncomingTransaction(lastBlock); }, + requestPushNotificationsPermission() { + return instance?.requestPushNotificationsPermission(asyncAlert); + }, showSimpleNotification(data) { return instance?.showSimpleNotification(data); }, diff --git a/app/core/NotificationsManager.test.ts b/app/core/NotificationsManager.test.ts index 2af79c085b5..bf4e31eab4a 100644 --- a/app/core/NotificationsManager.test.ts +++ b/app/core/NotificationsManager.test.ts @@ -1,5 +1,4 @@ import { NotificationTransactionTypes } from '../util/notifications'; - import NotificationManager, { constructTitleAndMessage, } from './NotificationManager'; @@ -45,26 +44,6 @@ describe('NotificationManager', () => { expect(notificationManager._backgroundMode).toBe(true); }); - it('calling NotificationManager in _failedCallback mode should call _showNotification', () => { - notificationManager._failedCallback({ - id: 1, - txParams: { - nonce: 1, - }, - }); - expect(notificationManager._showNotification).toBeInstanceOf(Function); - }); - - it('calling NotificationManager onMessageReceived', () => { - notificationManager.onMessageReceived({ - data: { - title: 'title', - shortDescription: 'shortDescription', - }, - }); - expect(notificationManager.onMessageReceived).toBeInstanceOf(Function); - }); - it('calling NotificationManager in background mode OFF should be falsy', () => { notificationManager._handleAppStateChange('active'); expect(notificationManager._backgroundMode).toBe(false); @@ -90,16 +69,15 @@ describe('NotificationManager', () => { expect(NotificationManager.getTransactionToView()).toBeTruthy(); }); - const selectedNotificationTypes: (keyof typeof NotificationTransactionTypes)[] = - [ - 'pending', - 'pending_deposit', - 'pending_withdrawal', - 'success_withdrawal', - 'success_deposit', - 'error', - 'cancelled', - ]; + const selectedNotificationTypes: (keyof typeof NotificationTransactionTypes)[] = [ + 'pending', + 'pending_deposit', + 'pending_withdrawal', + 'success_withdrawal', + 'success_deposit', + 'error', + 'cancelled', + ]; selectedNotificationTypes.forEach((type) => { it(`should construct title and message for ${type}`, () => { const { title, message } = constructTitleAndMessage({ diff --git a/app/util/notifications/androidChannels.test.ts b/app/util/notifications/androidChannels.test.ts deleted file mode 100644 index 49e3a28b8ad..00000000000 --- a/app/util/notifications/androidChannels.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { AndroidImportance } from '@notifee/react-native'; -import { - ChannelId, - MetaMaskAndroidChannel, - notificationChannels, -} from './androidChannels'; - -describe('notificationChannels', () => { - it('should have two channels', () => { - expect(notificationChannels).toHaveLength(2); - }); - - it('should have the correct properties for the first channel', () => { - const firstChannel: MetaMaskAndroidChannel = notificationChannels[0]; - expect(firstChannel).toEqual({ - id: ChannelId.DEFAULT_NOTIFICATION_CHANNEL_ID, - name: 'Transaction Complete', - lights: false, - vibration: false, - importance: AndroidImportance.DEFAULT, - title: 'Transaction', - subtitle: 'Transaction Complete', - }); - }); - - it('should have the correct properties for the second channel', () => { - const secondChannel: MetaMaskAndroidChannel = notificationChannels[1]; - expect(secondChannel).toEqual({ - id: ChannelId.DEFAULT_NOTIFICATION_CHANNEL_ID, - name: 'MetaMask Announcement', - lights: false, - vibration: false, - importance: AndroidImportance.DEFAULT, - title: 'Announcement', - subtitle: 'MetaMask Announcement', - }); - }); - - it('should have unique titles for each channel', () => { - const titles = notificationChannels.map((channel) => channel.title); - const uniqueTitles = new Set(titles); - expect(uniqueTitles.size).toBe(titles.length); - }); - - it('should have unique subtitles for each channel', () => { - const subtitles = notificationChannels.map((channel) => channel.subtitle); - const uniqueSubtitles = new Set(subtitles); - expect(uniqueSubtitles.size).toBe(subtitles.length); - }); -}); diff --git a/app/util/notifications/androidChannels.ts b/app/util/notifications/androidChannels.ts deleted file mode 100644 index 1bc1de98c72..00000000000 --- a/app/util/notifications/androidChannels.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { AndroidChannel, AndroidImportance } from '@notifee/react-native'; - -export enum ChannelId { - DEFAULT_NOTIFICATION_CHANNEL_ID = 'DEFAULT_NOTIFICATION_CHANNEL_ID', -} - -export interface MetaMaskAndroidChannel extends AndroidChannel { - id: ChannelId; - title: string; - subtitle: string; -} - -export const notificationChannels = [ - { - id: ChannelId.DEFAULT_NOTIFICATION_CHANNEL_ID, - name: 'Transaction Complete', - lights: false, - vibration: false, - importance: AndroidImportance.DEFAULT, - title: 'Transaction', - subtitle: 'Transaction Complete', - } as MetaMaskAndroidChannel, - { - id: ChannelId.DEFAULT_NOTIFICATION_CHANNEL_ID, - name: 'MetaMask Announcement', - lights: false, - vibration: false, - importance: AndroidImportance.DEFAULT, - title: 'Announcement', - subtitle: 'MetaMask Announcement', - } as MetaMaskAndroidChannel, -]; diff --git a/app/util/notifications/hooks/index.test.ts b/app/util/notifications/hooks/index.test.ts index bbe94a97dc3..c826067a63a 100644 --- a/app/util/notifications/hooks/index.test.ts +++ b/app/util/notifications/hooks/index.test.ts @@ -6,9 +6,6 @@ import notifee, { import useNotificationHandler from './index'; import { NavigationProp, ParamListBase } from '@react-navigation/native'; -import Routes from '../../../constants/navigation/Routes'; -import { Notification } from '../../../util/notifications/types'; -import { TRIGGER_TYPES } from '../constants'; jest.mock('../../../util/device'); jest.mock('../../../core/NotificationManager', () => ({ @@ -29,67 +26,51 @@ jest.mock('@notifee/react-native', () => ({ }, })); -jest.mock('../../../core/NotificationManager', () => ({ - setTransactionToView: jest.fn(), -})); - -jest.mock('../../../util/device', () => ({ - isAndroid: jest.fn(), -})); - const mockNavigate = jest.fn(); const mockNavigation = { navigate: mockNavigate, } as unknown as NavigationProp; -const notification = { - id: 1, - type: TRIGGER_TYPES.ERC1155_RECEIVED, - data: { - id: 1, - trigger_id: '1', - chain_id: 1, - block_number: 1, - block_timestamp: '', - tx_hash: '', - unread: false, - created_at: '', - address: '', - type: TRIGGER_TYPES.ERC1155_RECEIVED, - data: {}, - createdAt: '', - isRead: false, - }, -} as unknown as Notification; +const bootstrapAndroidInitialNotification = jest + .fn() + .mockResolvedValue(undefined); const mockNotificationEvent = (event: NotifeeEvent) => ({ type: event.type, detail: { - notification, + notification: { + body: 'notificationTest', + data: { + action: 'tx', + id: '123', + }, + }, }, }); - describe('useNotificationHandler', () => { beforeEach(() => { jest.clearAllMocks(); }); - it('should navigate to NOTIFICATIONS.DETAILS if notification is pressed', async () => { - const { result } = renderHook(() => useNotificationHandler(mockNavigation)); + it('sets initial badge count and initializes Android notifications on mount', async () => { + renderHook(() => + useNotificationHandler( + bootstrapAndroidInitialNotification, + mockNavigation, + ), + ); - await result.current.handlePressedNotification(notification); + expect(bootstrapAndroidInitialNotification).toHaveBeenCalled(); - expect(mockNavigation.navigate).toHaveBeenCalledWith( - Routes.NOTIFICATIONS.DETAILS, - { - notificationId: notification.id, - }, - ); + jest.runAllTimers(); }); it('should handle notifications correctly', async () => { const { waitFor } = renderHook(() => - useNotificationHandler(mockNavigation), + useNotificationHandler( + bootstrapAndroidInitialNotification, + mockNavigation, + ), ); await act(async () => { @@ -115,7 +96,10 @@ describe('useNotificationHandler', () => { it('should do nothing if the EventType is DISMISSED', async () => { const { waitFor } = renderHook(() => - useNotificationHandler(mockNavigation), + useNotificationHandler( + bootstrapAndroidInitialNotification, + mockNavigation, + ), ); await act(async () => { @@ -142,7 +126,10 @@ describe('useNotificationHandler', () => { it('should do nothing if data.action is not tx', async () => { const { waitFor } = renderHook(() => - useNotificationHandler(mockNavigation), + useNotificationHandler( + bootstrapAndroidInitialNotification, + mockNavigation, + ), ); await act(async () => { @@ -171,7 +158,10 @@ describe('useNotificationHandler', () => { it('handleOpenedNotification should do nothing if notification is null', async () => { const { waitFor } = renderHook(() => - useNotificationHandler(mockNavigation), + useNotificationHandler( + bootstrapAndroidInitialNotification, + mockNavigation, + ), ); await act(async () => { @@ -193,7 +183,10 @@ describe('useNotificationHandler', () => { it('should navigate to the transaction view when the notification action is "tx"', async () => { const { waitFor } = renderHook(() => - useNotificationHandler(mockNavigation), + useNotificationHandler( + bootstrapAndroidInitialNotification, + mockNavigation, + ), ); await act(async () => { @@ -223,7 +216,10 @@ describe('useNotificationHandler', () => { })); const { waitFor } = renderHook(() => - useNotificationHandler(mockNavigation), + useNotificationHandler( + bootstrapAndroidInitialNotification, + mockNavigation, + ), ); await act(async () => { diff --git a/app/util/notifications/hooks/index.ts b/app/util/notifications/hooks/index.ts index 6c6eb0d8b7d..796d8c3b19f 100644 --- a/app/util/notifications/hooks/index.ts +++ b/app/util/notifications/hooks/index.ts @@ -1,22 +1,39 @@ import { useCallback, useEffect } from 'react'; -import { NavigationProp, ParamListBase } from '@react-navigation/native'; -import NotificationsService from '../../../util/notifications/services/NotificationService'; +import notifee, { + Event as NotifeeEvent, + EventType, +} from '@notifee/react-native'; +import NotificationManager from '../../../core/NotificationManager'; import Routes from '../../../constants/navigation/Routes'; -import { isNotificationsFeatureEnabled } from '../../../util/notifications'; -import { Notification } from '../../../util/notifications/types'; +import { setupAndroidChannel } from '../setupAndroidChannels'; +import { SimpleNotification } from '../types'; +import Device from '../../../util/device'; +import { NavigationProp, ParamListBase } from '@react-navigation/native'; -const useNotificationHandler = (navigation: NavigationProp) => { +const useNotificationHandler = ( + bootstrapAndroidInitialNotification: () => Promise, + navigation: NavigationProp, +) => { const performActionBasedOnOpenedNotificationType = useCallback( - async (notification: Notification) => { - navigation.navigate(Routes.NOTIFICATIONS.DETAILS, { - notificationId: notification.id, - }); + async (notification: SimpleNotification) => { + const { data } = notification; + + if (data && data.action === 'tx') { + if (data.id) { + NotificationManager.setTransactionToView(data.id); + } + if (navigation) { + navigation.navigate(Routes.TRANSACTIONS_VIEW); + } + } else { + navigation.navigate(Routes.NOTIFICATIONS.VIEW); + } }, [navigation], ); - const handlePressedNotification = useCallback( - (notification?: Notification) => { + const handleOpenedNotification = useCallback( + (notification?: SimpleNotification) => { if (!notification) { return; } @@ -25,36 +42,28 @@ const useNotificationHandler = (navigation: NavigationProp) => { [performActionBasedOnOpenedNotificationType], ); - useEffect(() => { - if (!isNotificationsFeatureEnabled()) return; - - const unsubscribeForegroundEvent = NotificationsService.onForegroundEvent( - async ({ type, detail }) => { - await NotificationsService.handleNotificationEvent({ - type, - detail, - callback: handlePressedNotification, - }); - }, - ); - - NotificationsService.onBackgroundEvent(async ({ type, detail }) => { - await NotificationsService.handleNotificationEvent({ - type, - detail, - callback: handlePressedNotification, - }); - }); - - return () => { - unsubscribeForegroundEvent(); - }; - }, [handlePressedNotification]); + const handleNotificationPressed = useCallback( + (event: NotifeeEvent) => { + if (event.type === EventType.PRESS) { + handleOpenedNotification(event.detail.notification); + } + }, + [handleOpenedNotification], + ); - return { - performActionBasedOnOpenedNotificationType, - handlePressedNotification, - }; + useEffect(() => { + bootstrapAndroidInitialNotification(); + setTimeout(() => { + if (Device.isAndroid()) { + setupAndroidChannel(); + } + notifee.onForegroundEvent(handleNotificationPressed); + }, 1000); + }, [ + bootstrapAndroidInitialNotification, + navigation, + handleNotificationPressed, + ]); }; export default useNotificationHandler; diff --git a/app/util/notifications/index.ts b/app/util/notifications/index.ts index d1e6201e1e9..ec78eb1f67a 100644 --- a/app/util/notifications/index.ts +++ b/app/util/notifications/index.ts @@ -1,7 +1,7 @@ export * from './types'; export * from './constants'; -export * from './androidChannels'; +export * from './setupAndroidChannels'; export * from './settings'; export * from './hooks'; export * from './methods'; -export * from './services'; +export * from './pushPermissions'; diff --git a/app/util/notifications/methods/common.ts b/app/util/notifications/methods/common.ts index c7ac73eeacd..c80bc3fdb80 100644 --- a/app/util/notifications/methods/common.ts +++ b/app/util/notifications/methods/common.ts @@ -2,7 +2,6 @@ import dayjs, { Dayjs } from 'dayjs'; import isYesterday from 'dayjs/plugin/isYesterday'; import relativeTime from 'dayjs/plugin/relativeTime'; -import notifee from '@notifee/react-native'; import localeData from 'dayjs/plugin/localeData'; import { Web3Provider } from '@ethersproject/providers'; import { toHex } from '@metamask/controller-utils'; @@ -471,6 +470,3 @@ export const getUsdAmount = (amount: string, decimals: string, usd: string) => { return formatAmount(numericAmount); }; - -export const hasInitialNotification = async () => - Boolean(await notifee.getInitialNotification()); diff --git a/app/util/notifications/pushPermissions.test.ts b/app/util/notifications/pushPermissions.test.ts new file mode 100644 index 00000000000..b9f80882f2f --- /dev/null +++ b/app/util/notifications/pushPermissions.test.ts @@ -0,0 +1,113 @@ +import notifee, { + NotificationSettings, + AuthorizationStatus, + IOSNotificationSetting, + IOSNotificationSettings, + AndroidNotificationSettings, + AndroidNotificationSetting, +} from '@notifee/react-native'; + +import { strings } from '../../../locales/i18n'; +import { requestPushNotificationsPermission } from './pushPermissions'; + +jest.mock('@notifee/react-native', () => ({ + getNotificationSettings: jest.fn(), + AuthorizationStatus: { + AUTHORIZED: 1, + DENIED: 2, + }, + IOSNotificationSetting: { + ENABLED: 1, + DISABLED: 2, + }, + AndroidNotificationSetting: { + ENABLED: 1, + DISABLED: 2, + }, +})); + +jest.mock('../Logger', () => ({ + error: jest.fn(), +})); + +jest.mock('../../../locales/i18n', () => ({ + strings: jest.fn(), +})); + +jest.mock('./pushPermissions', () => ({ + ...jest.requireActual('./pushPermissions'), + AsyncAlert: jest.fn(), +})); + +describe('requestPushNotificationsPermission', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + const mockIOSSettings: IOSNotificationSettings = { + authorizationStatus: AuthorizationStatus.AUTHORIZED, + alert: IOSNotificationSetting.ENABLED, + badge: IOSNotificationSetting.ENABLED, + sound: IOSNotificationSetting.ENABLED, + carPlay: IOSNotificationSetting.DISABLED, + criticalAlert: IOSNotificationSetting.DISABLED, + inAppNotificationSettings: IOSNotificationSetting.DISABLED, + lockScreen: IOSNotificationSetting.ENABLED, + notificationCenter: IOSNotificationSetting.ENABLED, + showPreviews: 1, + announcement: 1, + }; + + const mockAndroidSettings: AndroidNotificationSettings = { + alarm: AndroidNotificationSetting.ENABLED, + }; + + it('should return notification settings if already authorized', async () => { + const mockSettings: NotificationSettings = { + authorizationStatus: AuthorizationStatus.AUTHORIZED, + ios: mockIOSSettings, + android: mockAndroidSettings, + web: {}, + }; + + (notifee.getNotificationSettings as jest.Mock).mockResolvedValue( + mockSettings, + ); + + const result = await requestPushNotificationsPermission(); + + expect(notifee.getNotificationSettings).toHaveBeenCalledTimes(1); + expect(result).toBe(mockSettings); + }); + + it('should prompt the user with AsyncAlert and simulate a click', async () => { + const mockSettings: NotificationSettings = { + authorizationStatus: AuthorizationStatus.DENIED, + ios: mockIOSSettings, + android: mockAndroidSettings, + web: {}, + }; + + const updatedSettings: NotificationSettings = { + ...mockSettings, + authorizationStatus: AuthorizationStatus.AUTHORIZED, + }; + + (notifee.getNotificationSettings as jest.Mock) + .mockResolvedValueOnce(mockSettings) + .mockResolvedValueOnce(updatedSettings); + (strings as jest.Mock).mockImplementation((key: string) => key); + + const mockAsyncAlert = jest.fn().mockResolvedValue(true); + + await requestPushNotificationsPermission(mockAsyncAlert); + + expect(mockAsyncAlert).toHaveBeenCalledWith( + 'notifications.prompt_title', + 'notifications.prompt_desc', + ); + expect(notifee.getNotificationSettings).toHaveBeenCalledTimes(1); + expect(strings).toHaveBeenCalledWith('notifications.prompt_title'); + expect(strings).toHaveBeenCalledWith('notifications.prompt_desc'); + }); +}); diff --git a/app/util/notifications/pushPermissions.ts b/app/util/notifications/pushPermissions.ts new file mode 100644 index 00000000000..6610803e1b5 --- /dev/null +++ b/app/util/notifications/pushPermissions.ts @@ -0,0 +1,123 @@ +import notifee, { + AuthorizationStatus, + NotificationSettings, +} from '@notifee/react-native'; +import { Linking, Alert as NativeAlert, Platform } from 'react-native'; +import { strings } from '../../../locales/i18n'; +import { mmStorage } from './settings'; +import { STORAGE_IDS } from './settings/storage/constants'; +import Logger from '../Logger'; +import Device from '../device'; +import { store } from '../../store'; + +interface AlertButton { + text: string; + onPress: () => void | Promise; +} + +export const openSystemSettings = () => { + if (Platform.OS === 'ios') { + Linking.openSettings(); + } else { + notifee.openNotificationSettings(); + } +}; + +export const isDeviceNotificationEnabled = async () => { + const settings = await notifee.getNotificationSettings(); + switch (settings.authorizationStatus) { + case AuthorizationStatus.AUTHORIZED: + case AuthorizationStatus.PROVISIONAL: + store.dispatch({ + type: 'TOGGLE_DEVICE_NOTIFICATIONS', + deviceNotificationEnabled: true, + }); + return true; + default: + store.dispatch({ + type: 'TOGGLE_DEVICE_NOTIFICATIONS', + deviceNotificationEnabled: false, + }); + return false; + } +}; + +const defaultButtons = (resolve: (value: boolean) => void): AlertButton[] => [ + { + text: strings('notifications.prompt_cancel'), + onPress: () => { + const promptCount = mmStorage.getLocal( + STORAGE_IDS.PUSH_NOTIFICATIONS_PROMPT_COUNT, + ); + mmStorage.saveLocal( + STORAGE_IDS.PUSH_NOTIFICATIONS_PROMPT_COUNT, + promptCount + 1, + ); + mmStorage.saveLocal( + STORAGE_IDS.PUSH_NOTIFICATIONS_PROMPT_TIME, + Date.now().toString(), + ); + + resolve(false); + }, + }, + { + text: strings('notifications.prompt_ok'), + onPress: async () => { + openSystemSettings(); + resolve(true); + }, + }, +]; + +export const asyncAlert = ( + title: string, + msg: string, + getButtons: ( + resolve: (value: boolean) => void, + ) => AlertButton[] = defaultButtons, +): Promise => + new Promise((resolve) => { + NativeAlert.alert(title, msg, getButtons(resolve), { + cancelable: false, + }); + }); + +export const requestPushNotificationsPermission = async ( + alertFunction: typeof asyncAlert = asyncAlert, +): Promise => { + let permissionStatus: NotificationSettings | undefined; + permissionStatus = await notifee.getNotificationSettings(); + if ( + permissionStatus.authorizationStatus === AuthorizationStatus.AUTHORIZED || + permissionStatus.authorizationStatus === AuthorizationStatus.PROVISIONAL + ) { + return permissionStatus; + } + try { + await alertFunction( + strings('notifications.prompt_title'), + strings('notifications.prompt_desc'), + ); + + if (Device.isIos()) { + permissionStatus = await notifee.requestPermission({ + provisional: true, + }); + } else { + permissionStatus = await notifee.requestPermission(); + } + + return permissionStatus; + } catch (e) { + if (e instanceof Error) { + Logger.error(e, strings('notifications.error_checking_permission')); + } else { + Logger.error( + new Error(strings('notifications.error_checking_permission')), + ); + } + } +}; + +export default requestPushNotificationsPermission; diff --git a/app/util/notifications/services/NotificationService.test.ts b/app/util/notifications/services/NotificationService.test.ts deleted file mode 100644 index 9816f9f99f4..00000000000 --- a/app/util/notifications/services/NotificationService.test.ts +++ /dev/null @@ -1,134 +0,0 @@ -import notifee, { - AuthorizationStatus, - AndroidChannel, - AndroidImportance, - EventType, -} from '@notifee/react-native'; -import { Linking } from 'react-native'; -import { ChannelId } from '../../../util/notifications/androidChannels'; -import NotificationsService from './NotificationService'; - -jest.mock('@notifee/react-native'); -jest.mock('react-native', () => ({ - Linking: { openSettings: jest.fn() }, - Platform: { OS: 'ios' }, - Alert: { alert: jest.fn() }, -})); -jest.mock('../settings', () => ({ - mmStorage: { - getLocal: jest.fn(), - saveLocal: jest.fn(), - }, -})); -jest.mock('../../../store', () => ({ - store: { - dispatch: jest.fn(), - }, -})); -jest.mock('../../../util/Logger', () => ({ - error: jest.fn(), -})); - -describe('NotificationsService', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should get blocked notifications', async () => { - (notifee.getNotificationSettings as jest.Mock).mockResolvedValue({ - authorizationStatus: AuthorizationStatus.AUTHORIZED, - }); - (notifee.getChannels as jest.Mock).mockResolvedValue([ - { id: ChannelId.DEFAULT_NOTIFICATION_CHANNEL_ID, blocked: true }, - ]); - - const blockedNotifications = - await NotificationsService.getBlockedNotifications(); - - expect( - blockedNotifications.get(ChannelId.DEFAULT_NOTIFICATION_CHANNEL_ID), - ).toBe(true); - }); - - it('should handle notification press', async () => { - const detail = { - notification: { - id: 'test-id', - data: { url: 'https://example.com' }, - }, - }; - const callback = jest.fn(); - - await NotificationsService.handleNotificationPress({ detail, callback }); - - expect(notifee.cancelTriggerNotification).toHaveBeenCalledWith('test-id'); - expect(callback).toHaveBeenCalledWith(detail.notification); - }); - - it('should open system settings on iOS', () => { - NotificationsService.openSystemSettings(); - - expect(Linking.openSettings).toHaveBeenCalled(); - }); - - it('should create notification channels', async () => { - const channel: AndroidChannel = { - id: ChannelId.DEFAULT_NOTIFICATION_CHANNEL_ID, - name: 'Test Channel', - importance: AndroidImportance.HIGH, - }; - - await NotificationsService.createChannel(channel); - - expect(notifee.createChannel).toHaveBeenCalledWith(channel); - }); - - it('should return authorized from getAllPermissions', async () => { - const result = await NotificationsService.getAllPermissions(); - expect(result.permission).toBe('authorized'); - }); - - it('should return authorized from requestPermission ', async () => { - const result = await NotificationsService.requestPermission(); - expect(result).toBe('authorized'); - }); - - it('should return denied from requestPermission', async () => { - (notifee.requestPermission as jest.Mock).mockResolvedValue({ - authorizationStatus: AuthorizationStatus.DENIED, - }); - const result = await NotificationsService.requestPermission(); - expect(result).toBe('denied'); - }); - - it('should handle notification event', async () => { - const callback = jest.fn(); - - await NotificationsService.handleNotificationEvent({ - type: EventType.DELIVERED, - detail: { - notification: { - id: '123', - }, - }, - callback, - }); - - expect(NotificationsService.incrementBadgeCount).toBeInstanceOf(Function); - - await NotificationsService.handleNotificationEvent({ - type: EventType.PRESS, - detail: { - notification: { - id: '123', - }, - }, - callback, - }); - - expect(NotificationsService.decrementBadgeCount).toBeInstanceOf(Function); - expect(NotificationsService.cancelTriggerNotification).toBeInstanceOf( - Function, - ); - }); -}); diff --git a/app/util/notifications/services/NotificationService.ts b/app/util/notifications/services/NotificationService.ts deleted file mode 100644 index d8d6c53ae0f..00000000000 --- a/app/util/notifications/services/NotificationService.ts +++ /dev/null @@ -1,236 +0,0 @@ -import notifee, { - AuthorizationStatus, - Event as NotifeeEvent, - EventType, - EventDetail, - AndroidChannel, -} from '@notifee/react-native'; - -import { Notification } from '../types'; - -import { Linking, Platform, Alert as NativeAlert } from 'react-native'; -import { - ChannelId, - notificationChannels, -} from '../../../util/notifications/androidChannels'; - -import { strings } from '../../../../locales/i18n'; -import { mmStorage } from '../settings'; -import { STORAGE_IDS } from '../settings/storage/constants'; -import { store } from '../../../store'; -import Logger from '../../../util/Logger'; - -interface AlertButton { - text: string; - onPress: () => void | Promise; -} - -class NotificationsService { - async getBlockedNotifications(): Promise> { - try { - const settings = await notifee.getNotificationSettings(); - const channels = await notifee.getChannels(); - - switch (settings.authorizationStatus) { - case AuthorizationStatus.NOT_DETERMINED: - case AuthorizationStatus.DENIED: - return notificationChannels.reduce((map, next) => { - map.set(next.id as ChannelId, true); - return map; - }, new Map()); - } - - return channels.reduce((map, next) => { - if (next.blocked) { - map.set(next.id as ChannelId, true); - } - return map; - }, new Map()); - } catch (e) { - if (e instanceof Error) { - Logger.error(e, strings('notifications.error_checking_permission')); - } else { - Logger.error( - new Error(strings('notifications.error_checking_permission')), - ); - } - return new Map(); - } - } - - async getAllPermissions(shouldOpenSettings = true) { - const promises = [] as Promise[]; - notificationChannels.forEach((channel: AndroidChannel) => { - promises.push(this.createChannel(channel)); - }); - await Promise.allSettled(promises); - const permission = await this.requestPermission(); - const blockedNotifications = await this.getBlockedNotifications(); - if ( - (permission !== 'authorized' || blockedNotifications.size !== 0) && - shouldOpenSettings - ) { - this.requestPushNotificationsPermission(); - } - return { permission, blockedNotifications }; - } - - async isDeviceNotificationEnabled() { - const permission = await notifee.getNotificationSettings(); - - const isAuthorized = - permission.authorizationStatus === AuthorizationStatus.AUTHORIZED; - - store.dispatch({ - type: 'TOGGLE_DEVICE_NOTIFICATIONS', - deviceNotificationEnabled: isAuthorized, - }); - return isAuthorized; - } - - defaultButtons = (resolve: (value: boolean) => void): AlertButton[] => [ - { - text: strings('notifications.prompt_cancel'), - onPress: () => { - const promptCount = mmStorage.getLocal( - STORAGE_IDS.PUSH_NOTIFICATIONS_PROMPT_COUNT, - ); - mmStorage.saveLocal( - STORAGE_IDS.PUSH_NOTIFICATIONS_PROMPT_COUNT, - promptCount + 1, - ); - mmStorage.saveLocal( - STORAGE_IDS.PUSH_NOTIFICATIONS_PROMPT_TIME, - Date.now().toString(), - ); - - resolve(false); - }, - }, - { - text: strings('notifications.prompt_ok'), - onPress: async () => { - this.openSystemSettings(); - resolve(true); - }, - }, - ]; - - asyncAlert = ( - title: string, - msg: string, - getButtons: (resolve: (value: boolean) => void) => AlertButton[] = this - .defaultButtons, - ): Promise => - new Promise((resolve) => { - NativeAlert.alert(title, msg, getButtons(resolve), { - cancelable: false, - }); - }); - - async requestPushNotificationsPermission(): Promise { - try { - await this.asyncAlert( - strings('notifications.prompt_title'), - strings('notifications.prompt_desc'), - ); - } catch (e) { - if (e instanceof Error) { - Logger.error(e, strings('notifications.error_checking_permission')); - } else { - Logger.error( - new Error(strings('notifications.error_checking_permission')), - ); - } - } - } - openSystemSettings() { - if (Platform.OS === 'ios') { - Linking.openSettings(); - } else { - notifee.openNotificationSettings(); - } - } - - async requestPermission() { - const settings = await notifee.requestPermission(); - return settings.authorizationStatus === AuthorizationStatus.AUTHORIZED || - settings.authorizationStatus === AuthorizationStatus.PROVISIONAL - ? 'authorized' - : 'denied'; - } - - onForegroundEvent = ( - observer: (event: NotifeeEvent) => Promise, - ): (() => void) => notifee.onForegroundEvent(observer); - - onBackgroundEvent = (observer: (event: NotifeeEvent) => Promise) => - notifee.onBackgroundEvent(observer); - - incrementBadgeCount = async (incrementBy?: number) => { - notifee.incrementBadgeCount(incrementBy); - }; - - decrementBadgeCount = async (decrementBy?: number) => { - notifee.decrementBadgeCount(decrementBy); - }; - - setBadgeCount = async (count: number) => { - notifee.setBadgeCount(count); - }; - - getBadgeCount = async () => notifee.getBadgeCount(); - - handleNotificationPress = async ({ - detail, - callback, - }: { - detail: EventDetail; - callback?: (notification: Notification) => void; - }) => { - this.decrementBadgeCount(1); - if (detail?.notification?.id) { - await this.cancelTriggerNotification(detail.notification.id); - } - - if (detail?.notification?.data?.url) { - callback?.(detail.notification as Notification); - } - }; - - handleNotificationEvent = async ({ - type, - detail, - callback, - }: NotifeeEvent & { - callback?: (notification: Notification) => void; - }) => { - switch (type as unknown as EventType) { - case EventType.DELIVERED: - this.incrementBadgeCount(1); - break; - case EventType.PRESS: - this.handleNotificationPress({ - detail, - callback, - }); - break; - } - }; - - cancelTriggerNotification = async (id?: string) => { - if (!id) return; - await notifee.cancelTriggerNotification(id); - }; - - getInitialNotification = async () => notifee.getInitialNotification(); - - cancelAllNotifications = async () => { - await notifee.cancelAllNotifications(); - }; - - createChannel = async (channel: AndroidChannel): Promise => - notifee.createChannel(channel); -} - -export default new NotificationsService(); diff --git a/app/util/notifications/services/index.ts b/app/util/notifications/services/index.ts deleted file mode 100644 index 529a6c3f602..00000000000 --- a/app/util/notifications/services/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './NotificationService'; diff --git a/app/util/notifications/settings/storage/constants.ts b/app/util/notifications/settings/storage/constants.ts index fcf857060a4..fd34f849f6b 100644 --- a/app/util/notifications/settings/storage/constants.ts +++ b/app/util/notifications/settings/storage/constants.ts @@ -5,7 +5,7 @@ export const STORAGE_IDS = { PUSH_NOTIFICATIONS_PROMPT_COUNT: 'pushNotificationsPromptCount', PUSH_NOTIFICATIONS_PROMPT_TIME: 'pushNotificationsPromptTime', DEVICE_ID_STORAGE_KEY: 'pns:deviceId', - DEFAULT_NOTIFICATION_CHANNEL_ID: 'DEFAULT_NOTIFICATION_CHANNEL_ID', + ANDROID_DEFAULT_CHANNEL_ID: 'ANDROID_DEFAULT_CHANNEL_ID', DEFAULT_PUSH_NOTIFICATION_CHANNEL_PRIORITY: 'high', REQUEST_PERMISSION_ASKED: 'REQUEST_PERMISSION_ASKED', REQUEST_PERMISSION_GRANTED: 'REQUEST_PERMISSION_GRANTED', diff --git a/app/util/notifications/settings/storage/contants.test.ts b/app/util/notifications/settings/storage/contants.test.ts index 347cfb4b87f..ec76db3a580 100644 --- a/app/util/notifications/settings/storage/contants.test.ts +++ b/app/util/notifications/settings/storage/contants.test.ts @@ -9,7 +9,7 @@ describe('constants', () => { PUSH_NOTIFICATIONS_PROMPT_COUNT: 'pushNotificationsPromptCount', PUSH_NOTIFICATIONS_PROMPT_TIME: 'pushNotificationsPromptTime', DEVICE_ID_STORAGE_KEY: 'pns:deviceId', - DEFAULT_NOTIFICATION_CHANNEL_ID: 'DEFAULT_NOTIFICATION_CHANNEL_ID', + ANDROID_DEFAULT_CHANNEL_ID: 'ANDROID_DEFAULT_CHANNEL_ID', DEFAULT_PUSH_NOTIFICATION_CHANNEL_PRIORITY: 'high', REQUEST_PERMISSION_ASKED: 'REQUEST_PERMISSION_ASKED', REQUEST_PERMISSION_GRANTED: 'REQUEST_PERMISSION_GRANTED', diff --git a/app/util/notifications/setupAndroidChannels.test.ts b/app/util/notifications/setupAndroidChannels.test.ts new file mode 100644 index 00000000000..e2e917f6e4f --- /dev/null +++ b/app/util/notifications/setupAndroidChannels.test.ts @@ -0,0 +1,25 @@ +/* eslint-disable import/prefer-default-export */ +import notifee, { AndroidImportance } from '@notifee/react-native'; +import { setupAndroidChannel } from './setupAndroidChannels'; +import { STORAGE_IDS } from './settings/storage/constants'; + +jest.mock('@notifee/react-native'); +const mockedNotifee = jest.mocked(notifee); + +describe('setupAndroidChannel', () => { + beforeEach(() => { + mockedNotifee.createChannel.mockClear(); + }); + + it('should create android channel', async () => { + await setupAndroidChannel(); + + expect(mockedNotifee.createChannel).toHaveBeenCalledTimes(1); + expect(mockedNotifee.createChannel).toHaveBeenCalledWith({ + id: STORAGE_IDS.ANDROID_DEFAULT_CHANNEL_ID, + importance: AndroidImportance.HIGH, + name: 'Default', + badge: true, + }); + }); +}); diff --git a/app/util/notifications/setupAndroidChannels.ts b/app/util/notifications/setupAndroidChannels.ts new file mode 100644 index 00000000000..d938acbc1e3 --- /dev/null +++ b/app/util/notifications/setupAndroidChannels.ts @@ -0,0 +1,12 @@ +/* eslint-disable import/prefer-default-export */ +import notifee, { AndroidImportance } from '@notifee/react-native'; +import { STORAGE_IDS } from './settings/storage/constants'; + +export async function setupAndroidChannel() { + await notifee.createChannel({ + id: STORAGE_IDS.ANDROID_DEFAULT_CHANNEL_ID, + importance: AndroidImportance.HIGH, + name: 'Default', + badge: true, + }); +} diff --git a/app/util/notifications/types/notification/index.ts b/app/util/notifications/types/notification/index.ts index 6b091239414..7bfd0b2af0c 100644 --- a/app/util/notifications/types/notification/index.ts +++ b/app/util/notifications/types/notification/index.ts @@ -108,6 +108,14 @@ export const STAKING_PROVIDER_MAP: Record< [TRIGGER_TYPES.ROCKETPOOL_UNSTAKE_COMPLETED]: 'Rocket Pool-staked ETH', }; +export const networkFeeDetails: Record = { + 'transactions.gas_limit': 'gasLimitUnits', + 'transactions.gas_used': 'gasUsedUnits', + 'transactions.base_fee': 'baseFee', + 'transactions.priority_fee': 'priorityFee', + 'transactions.max_fee': 'maxFeePerGas', +}; + export interface SimpleNotification { title?: string; body?: string; @@ -115,3 +123,8 @@ export interface SimpleNotification { [key: string]: string | object | number; }; } + +export enum NotificationsKindTypes { + transaction = 'transaction', + announcements = 'announcements', +} diff --git a/e2e/helpers.js b/e2e/helpers.js index 89c665fdec9..d1b169fb58d 100644 --- a/e2e/helpers.js +++ b/e2e/helpers.js @@ -1,4 +1,4 @@ -import { waitFor, web, system } from 'detox'; +import { waitFor, web } from 'detox'; import { getFixturesServerPort, getGanachePort, diff --git a/e2e/pages/EnableDeviceNotificationsAlert.js b/e2e/pages/EnableDeviceNotificationsAlert.js deleted file mode 100644 index 3c9eebe0c55..00000000000 --- a/e2e/pages/EnableDeviceNotificationsAlert.js +++ /dev/null @@ -1,27 +0,0 @@ -import TestHelpers from '../helpers'; - -import { - ENABLE_DEVICE_NOTIFICATIONS_CONTAINER_ID, - ENABLE_DEVICE_NOTIFICATIONS_NO_THANKS_BUTTON_ID, - ENABLE_DEVICE_NOTIFICATIONS_YES_BUTTON_ID -} from '../../wdio/screen-objects/testIDs/Screens/EnableDeviceNotificationsChecksAlert.testIds'; - -export default class EnableDeviceNotificationsAlert { - static async tapNoThanks() { - await TestHelpers.waitAndTapByLabel( - ENABLE_DEVICE_NOTIFICATIONS_NO_THANKS_BUTTON_ID - ); - } - - static async tapYes() { - await TestHelpers.waitAndTapByLabel( - ENABLE_DEVICE_NOTIFICATIONS_YES_BUTTON_ID, - ); - } - - static async isVisible() { - await TestHelpers.checkIfElementWithTextIsVisible( - ENABLE_DEVICE_NOTIFICATIONS_CONTAINER_ID, - ); - } -} diff --git a/e2e/specs/quarantine/import-nft.failing.js b/e2e/specs/quarantine/import-nft.failing.js index e7e2fed937e..004ffef6158 100644 --- a/e2e/specs/quarantine/import-nft.failing.js +++ b/e2e/specs/quarantine/import-nft.failing.js @@ -12,7 +12,7 @@ import { SMART_CONTRACTS } from '../../../app/util/test/smart-contracts'; import FixtureBuilder from '../../fixtures/fixture-builder'; describe(SmokeAssets('Import NFT'), () => { - beforeAll(() => { + beforeAll(async () => { jest.setTimeout(150000); }); diff --git a/e2e/utils/Matchers.js b/e2e/utils/Matchers.js index 536a6c0c171..cd9255de69e 100644 --- a/e2e/utils/Matchers.js +++ b/e2e/utils/Matchers.js @@ -1,4 +1,4 @@ -import { web, system } from 'detox'; +import { web } from 'detox'; /** * Utility class for matching (locating) UI elements @@ -152,17 +152,6 @@ class Matchers { static async getIdentifier(selectorString) { return by.id(selectorString); } - - - /** - * Get system dialogs in the system-level (e.g. permissions, alerts, etc.), by text. - * - * @param {string} text - Match elements with the specified text - * @return {Promise} - Resolves to the located element - */ - static async getSystemElementByText(text) { - return system.element(by.system.label(text)); - } } export default Matchers; diff --git a/e2e/viewHelper.js b/e2e/viewHelper.js index ebcf468596a..3b75cd49de4 100644 --- a/e2e/viewHelper.js +++ b/e2e/viewHelper.js @@ -83,11 +83,9 @@ export const importWalletWithRecoveryPhrase = async () => { await ImportWalletView.enterPassword(validAccount.password); await ImportWalletView.reEnterPassword(validAccount.password); - - //'Should dismiss Enable device Notifications checks alert' + // Should dismiss Automatic Security checks screen await TestHelpers.delay(3500); await OnboardingSuccessView.tapDone(); - // Should dismiss Automatic Security checks screen await EnableAutomaticSecurityChecksView.isVisible(); await EnableAutomaticSecurityChecksView.tapNoThanks(); @@ -122,10 +120,9 @@ export const CreateNewWallet = async () => { await device.enableSynchronization(); await Assertions.checkIfVisible(WalletView.container); - //'Should dismiss Enable device Notifications checks alert' + //'Should dismiss Automatic Security checks screen' await TestHelpers.delay(3500); await OnboardingSuccessView.tapDone(); - //'Should dismiss Automatic Security checks screen' await EnableAutomaticSecurityChecksView.isVisible(); await EnableAutomaticSecurityChecksView.tapNoThanks(); diff --git a/index.js b/index.js index ece1f59ea50..f24e95556c6 100644 --- a/index.js +++ b/index.js @@ -14,11 +14,15 @@ import * as Sentry from '@sentry/react-native'; // eslint-disable-line import/no import { setupSentry } from './app/util/sentry/utils'; setupSentry(); +import notifee, { EventType } from '@notifee/react-native'; + import { AppRegistry, LogBox } from 'react-native'; import Root from './app/components/Views/Root'; import { name } from './app.json'; import { isTest } from './app/util/test/utils.js'; +import NotificationManager from './app/core/NotificationManager'; +import { isNotificationsFeatureEnabled } from './app/util/notifications'; import { Performance } from './app/core/Performance'; Performance.setupPerformanceObservers(); @@ -81,6 +85,18 @@ if (IGNORE_BOXLOGS_DEVELOPMENT === 'true') { LogBox.ignoreAllLogs(); } +isNotificationsFeatureEnabled() && + notifee.onBackgroundEvent(async ({ type, detail }) => { + const { notification, pressAction } = detail; + + if (type === EventType.ACTION_PRESS && pressAction.id === 'mark-as-read') { + await notifee.decrementBadgeCount(1); + await notifee.cancelNotification(notification.id); + } else { + await NotificationManager.onMessageReceived(notification); + } + }); + /* Uncomment and comment regular registration below */ // import Storybook from './.storybook'; // AppRegistry.registerComponent(name, () => Storybook); diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 3a2ab1ef655..de82cd678de 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -15,18 +15,18 @@ PODS: - React-Core (= 0.72.15) - React-jsi (= 0.72.15) - ReactCommon/turbomodule/core (= 0.72.15) - - Firebase (10.29.0): - - Firebase/Core (= 10.29.0) - - Firebase/Core (10.29.0): + - Firebase (10.27.0): + - Firebase/Core (= 10.27.0) + - Firebase/Core (10.27.0): - Firebase/CoreOnly - - FirebaseAnalytics (~> 10.29.0) - - Firebase/CoreOnly (10.29.0): - - FirebaseCore (= 10.29.0) - - Firebase/Messaging (10.29.0): + - FirebaseAnalytics (~> 10.27.0) + - Firebase/CoreOnly (10.27.0): + - FirebaseCore (= 10.27.0) + - Firebase/Messaging (10.27.0): - Firebase/CoreOnly - - FirebaseMessaging (~> 10.29.0) - - FirebaseAnalytics (10.29.0): - - FirebaseAnalytics/AdIdSupport (= 10.29.0) + - FirebaseMessaging (~> 10.27.0) + - FirebaseAnalytics (10.27.0): + - FirebaseAnalytics/AdIdSupport (= 10.27.0) - FirebaseCore (~> 10.0) - FirebaseInstallations (~> 10.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.11) @@ -34,29 +34,29 @@ PODS: - GoogleUtilities/Network (~> 7.11) - "GoogleUtilities/NSData+zlib (~> 7.11)" - nanopb (< 2.30911.0, >= 2.30908.0) - - FirebaseAnalytics/AdIdSupport (10.29.0): + - FirebaseAnalytics/AdIdSupport (10.27.0): - FirebaseCore (~> 10.0) - FirebaseInstallations (~> 10.0) - - GoogleAppMeasurement (= 10.29.0) + - GoogleAppMeasurement (= 10.27.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - GoogleUtilities/MethodSwizzler (~> 7.11) - GoogleUtilities/Network (~> 7.11) - "GoogleUtilities/NSData+zlib (~> 7.11)" - nanopb (< 2.30911.0, >= 2.30908.0) - - FirebaseCore (10.29.0): + - FirebaseCore (10.27.0): - FirebaseCoreInternal (~> 10.0) - GoogleUtilities/Environment (~> 7.12) - GoogleUtilities/Logger (~> 7.12) - - FirebaseCoreExtension (10.29.0): + - FirebaseCoreExtension (10.28.0): - FirebaseCore (~> 10.0) - - FirebaseCoreInternal (10.29.0): + - FirebaseCoreInternal (10.28.0): - "GoogleUtilities/NSData+zlib (~> 7.8)" - - FirebaseInstallations (10.29.0): + - FirebaseInstallations (10.28.0): - FirebaseCore (~> 10.0) - GoogleUtilities/Environment (~> 7.8) - GoogleUtilities/UserDefaults (~> 7.8) - PromisesObjC (~> 2.1) - - FirebaseMessaging (10.29.0): + - FirebaseMessaging (10.27.0): - FirebaseCore (~> 10.0) - FirebaseInstallations (~> 10.0) - GoogleDataTransport (~> 9.3) @@ -125,21 +125,21 @@ PODS: - FlipperKit/FlipperKitNetworkPlugin - fmt (6.2.1) - glog (0.3.5) - - GoogleAppMeasurement (10.29.0): - - GoogleAppMeasurement/AdIdSupport (= 10.29.0) + - GoogleAppMeasurement (10.27.0): + - GoogleAppMeasurement/AdIdSupport (= 10.27.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - GoogleUtilities/MethodSwizzler (~> 7.11) - GoogleUtilities/Network (~> 7.11) - "GoogleUtilities/NSData+zlib (~> 7.11)" - nanopb (< 2.30911.0, >= 2.30908.0) - - GoogleAppMeasurement/AdIdSupport (10.29.0): - - GoogleAppMeasurement/WithoutAdIdSupport (= 10.29.0) + - GoogleAppMeasurement/AdIdSupport (10.27.0): + - GoogleAppMeasurement/WithoutAdIdSupport (= 10.27.0) - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - GoogleUtilities/MethodSwizzler (~> 7.11) - GoogleUtilities/Network (~> 7.11) - "GoogleUtilities/NSData+zlib (~> 7.11)" - nanopb (< 2.30911.0, >= 2.30908.0) - - GoogleAppMeasurement/WithoutAdIdSupport (10.29.0): + - GoogleAppMeasurement/WithoutAdIdSupport (10.27.0): - GoogleUtilities/AppDelegateSwizzler (~> 7.11) - GoogleUtilities/MethodSwizzler (~> 7.11) - GoogleUtilities/Network (~> 7.11) @@ -200,9 +200,9 @@ PODS: - lottie-react-native (5.1.5): - lottie-ios (~> 3.4.0) - React-Core - - MMKV (1.3.9): - - MMKVCore (~> 1.3.9) - - MMKVCore (1.3.9) + - MMKV (1.3.5): + - MMKVCore (~> 1.3.5) + - MMKVCore (1.3.5) - MultiplatformBleAdapter (0.2.0) - nanopb (2.30910.0): - nanopb/decode (= 2.30910.0) @@ -706,11 +706,11 @@ PODS: - React - RNDeviceInfo (9.0.2): - React-Core - - RNFBApp (20.5.0): - - Firebase/CoreOnly (= 10.29.0) + - RNFBApp (20.1.0): + - Firebase/CoreOnly (= 10.27.0) - React-Core - - RNFBMessaging (20.5.0): - - Firebase/Messaging (= 10.29.0) + - RNFBMessaging (20.1.0): + - Firebase/Messaging (= 10.27.0) - FirebaseCoreExtension - React-Core - RNFBApp @@ -724,10 +724,10 @@ PODS: - React-Core - RNKeychain (8.0.0): - React-Core - - RNNotifee (9.0.2): + - RNNotifee (7.8.2): - React-Core - - RNNotifee/NotifeeCore (= 9.0.2) - - RNNotifee/NotifeeCore (9.0.2): + - RNNotifee/NotifeeCore (= 7.8.2) + - RNNotifee/NotifeeCore (7.8.2): - React-Core - RNOS (1.2.6): - React @@ -1170,13 +1170,13 @@ SPEC CHECKSUMS: DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 FBLazyVector: 25cbffbaec517695d376ab4bc428948cd0f08088 FBReactNativeSpec: e03b22fbf7017a6f76641ea4472e73c915dcdda7 - Firebase: cec914dab6fd7b1bd8ab56ea07ce4e03dd251c2d - FirebaseAnalytics: 23717de130b779aa506e757edb9713d24b6ffeda - FirebaseCore: 30e9c1cbe3d38f5f5e75f48bfcea87d7c358ec16 - FirebaseCoreExtension: 705ca5b14bf71d2564a0ddc677df1fc86ffa600f - FirebaseCoreInternal: df84dd300b561c27d5571684f389bf60b0a5c934 - FirebaseInstallations: 913cf60d0400ebd5d6b63a28b290372ab44590dd - FirebaseMessaging: 7b5d8033e183ab59eb5b852a53201559e976d366 + Firebase: 26b040b20866a55f55eb3611b9fcf3ae64816b86 + FirebaseAnalytics: f9211b719db260cc91aebee8bb539cb367d0dfd1 + FirebaseCore: a2b95ae4ce7c83ceecfbbbe3b6f1cddc7415a808 + FirebaseCoreExtension: f63147b723e2a700fe0f34ec6fb7f358d6fe83e0 + FirebaseCoreInternal: 58d07f1362fddeb0feb6a857d1d1d1c5e558e698 + FirebaseInstallations: 60c1d3bc1beef809fd1ad1189a8057a040c59f2e + FirebaseMessaging: 585984d0a1df120617eb10b44cad8968b859815e Flipper: 6edb735e6c3e332975d1b17956bcc584eccf5818 Flipper-Boost-iOSX: fd1e2b8cbef7e662a122412d7ac5f5bea715403c Flipper-DoubleConversion: 2dc99b02f658daf147069aad9dbd29d8feb06d30 @@ -1187,7 +1187,7 @@ SPEC CHECKSUMS: FlipperKit: 2efad7007d6745a3f95e4034d547be637f89d3f6 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b - GoogleAppMeasurement: f9de05ee17401e3355f68e8fc8b5064d429f5918 + GoogleAppMeasurement: f65fc137531af9ad647f1c0a42f3b6a4d3a98049 GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15 GZIP: 3c0abf794bfce8c7cb34ea05a1837752416c8868 @@ -1195,8 +1195,8 @@ SPEC CHECKSUMS: libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 lottie-ios: 016449b5d8be0c3dcbcfa0a9988469999cd04c5d lottie-react-native: 3e722c63015fdb9c27638b0a77969fc412067c18 - MMKV: 817ba1eea17421547e01e087285606eb270a8dcb - MMKVCore: af055b00e27d88cd92fad301c5fecd1ff9b26dd9 + MMKV: 506311d0494023c2f7e0b62cc1f31b7370fa3cfb + MMKVCore: 9e2e5fd529b64a9fe15f1a7afb3d73b2e27b4db9 MultiplatformBleAdapter: b1fddd0d499b96b607e00f0faa8e60648343dc1d nanopb: 438bc412db1928dac798aa6fd75726007be04262 OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c @@ -1272,14 +1272,14 @@ SPEC CHECKSUMS: RNDateTimePicker: 4f3c4dbd4f908be32ec8c93f086e8924bd4a2e07 RNDefaultPreference: 2f8d6d54230edbd78708ada8d63bb275e5a8415b RNDeviceInfo: 1e3f62b9ec32f7754fac60bd06b8f8a27124e7f0 - RNFBApp: 5f87753a8d8b37d229adf85cd0ff37709ffdf008 - RNFBMessaging: 3fa1114c0868dd21f20dfe186adf42297ea316b1 + RNFBApp: 1ae7462cddf74a49df206d3418bc0170f8fa53e5 + RNFBMessaging: 85f661b9f16e2b081e6809ef63d3daa4458b9042 RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNGestureHandler: a479ebd5ed4221a810967000735517df0d2db211 RNI18n: e2f7e76389fcc6e84f2c8733ea89b92502351fd8 RNInAppBrowser: e36d6935517101ccba0e875bac8ad7b0cb655364 RNKeychain: 4f63aada75ebafd26f4bc2c670199461eab85d94 - RNNotifee: 2b7df6e32a9cc24b9af6b410fa7db1cd2f411d6d + RNNotifee: 8e2d3df3f0e9ce8f5d1fe4c967431138190b6175 RNOS: 6f2f9a70895bbbfbdad7196abd952e7b01d45027 RNPermissions: 4e3714e18afe7141d000beae3755e5b5fb2f5e05 RNReanimated: f8379347f71248607d530a21e31e4140c5910c25 diff --git a/package.json b/package.json index 85827600684..b82cfbb8c55 100644 --- a/package.json +++ b/package.json @@ -198,7 +198,7 @@ "@metamask/transaction-controller": "^35.0.0", "@metamask/utils": "^9.1.0", "@ngraveio/bc-ur": "^1.1.6", - "@notifee/react-native": "^9.0.0", + "@notifee/react-native": "^7.8.2", "@react-native-async-storage/async-storage": "^1.23.1", "@react-native-clipboard/clipboard": "1.8.4", "@react-native-community/blur": "^4.4.0", @@ -207,8 +207,8 @@ "@react-native-community/netinfo": "^9.5.0", "@react-native-community/slider": "^4.4.3", "@react-native-cookies/cookies": "^6.2.1", - "@react-native-firebase/app": "^20.5.0", - "@react-native-firebase/messaging": "^20.5.0", + "@react-native-firebase/app": "^20.1.0", + "@react-native-firebase/messaging": "^20.1.0", "@react-native-masked-view/masked-view": "^0.3.1", "@react-native-picker/picker": "^2.2.1", "@react-native/eslint-config": "^0.75.2", @@ -589,8 +589,7 @@ "@metamask/notification-services-controller>firebase>@firebase/firestore>@grpc/proto-loader>protobufjs": false, "@metamask/sdk-communication-layer>eciesjs>secp256k1": false, "detox>ws>utf-8-validate": false, - "ganache>@trufflesuite/uws-js-unofficial>utf-8-validate": false, - "@react-native-firebase/app>firebase>@firebase/firestore>@grpc/proto-loader>protobufjs": false + "ganache>@trufflesuite/uws-js-unofficial>utf-8-validate": false } }, "packageManager": "yarn@1.22.22" diff --git a/wdio/screen-objects/testIDs/Screens/EnableDeviceNotificationsChecksAlert.testIds.js b/wdio/screen-objects/testIDs/Screens/EnableDeviceNotificationsChecksAlert.testIds.js deleted file mode 100644 index c12c78557f5..00000000000 --- a/wdio/screen-objects/testIDs/Screens/EnableDeviceNotificationsChecksAlert.testIds.js +++ /dev/null @@ -1,6 +0,0 @@ -export const ENABLE_DEVICE_NOTIFICATION_NO_THANKS_BUTTON_ID = - "Don't Allow"; -export const ENABLE_DEVICE_NOTIFICATIONS_CONTAINER_ID = - 'Would Like to Send You Notifications'; -export const ENABLE_DEVICE_NOTIFICATIONS_YES_BUTTON_ID = 'Allow'; - diff --git a/yarn.lock b/yarn.lock index 87583d03a75..a98e130a51a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2299,17 +2299,6 @@ resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d" integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA== -"@firebase/analytics-compat@0.2.10": - version "0.2.10" - resolved "https://registry.yarnpkg.com/@firebase/analytics-compat/-/analytics-compat-0.2.10.tgz#c98005075c019eb8255764a5279f0ff86b36b863" - integrity sha512-ia68RcLQLLMFWrM10JfmFod7eJGwqr4/uyrtzHpTDnxGX/6gNCBTOuxdAbyWIqXI5XmcMQdz9hDijGKOHgDfPw== - dependencies: - "@firebase/analytics" "0.10.4" - "@firebase/analytics-types" "0.8.2" - "@firebase/component" "0.6.7" - "@firebase/util" "1.9.6" - tslib "^2.1.0" - "@firebase/analytics-compat@0.2.11": version "0.2.11" resolved "https://registry.yarnpkg.com/@firebase/analytics-compat/-/analytics-compat-0.2.11.tgz#82995b29805f306ad862773e2cd907ae8fb7b7e5" @@ -2326,17 +2315,6 @@ resolved "https://registry.yarnpkg.com/@firebase/analytics-types/-/analytics-types-0.8.2.tgz#947f85346e404332aac6c996d71fd4a89cd7f87a" integrity sha512-EnzNNLh+9/sJsimsA/FGqzakmrAUKLeJvjRHlg8df1f97NLUlFidk9600y0ZgWOp3CAxn6Hjtk+08tixlUOWyw== -"@firebase/analytics@0.10.4": - version "0.10.4" - resolved "https://registry.yarnpkg.com/@firebase/analytics/-/analytics-0.10.4.tgz#dc68a86774f9ee4f980708e824157617fd2b8ef7" - integrity sha512-OJEl/8Oye/k+vJ1zV/1L6eGpc1XzAj+WG2TPznJ7PszL7sOFLBXkL9IjHfOCGDGpXeO3btozy/cYUqv4zgNeHg== - dependencies: - "@firebase/component" "0.6.7" - "@firebase/installations" "0.6.7" - "@firebase/logger" "0.4.2" - "@firebase/util" "1.9.6" - tslib "^2.1.0" - "@firebase/analytics@0.10.5": version "0.10.5" resolved "https://registry.yarnpkg.com/@firebase/analytics/-/analytics-0.10.5.tgz#a455028952bdc25b9da2b0070ebb09ca487ee09f" @@ -2348,18 +2326,6 @@ "@firebase/util" "1.9.7" tslib "^2.1.0" -"@firebase/app-check-compat@0.3.11": - version "0.3.11" - resolved "https://registry.yarnpkg.com/@firebase/app-check-compat/-/app-check-compat-0.3.11.tgz#0a5d1c72c91ba239e4dabf6fd698b27f082030ca" - integrity sha512-t01zaH3RJpKEey0nGduz3Is+uSz7Sj4U5nwOV6lWb+86s5xtxpIvBJzu/lKxJfYyfZ29eJwpdjEgT1/lm4iQyA== - dependencies: - "@firebase/app-check" "0.8.4" - "@firebase/app-check-types" "0.5.2" - "@firebase/component" "0.6.7" - "@firebase/logger" "0.4.2" - "@firebase/util" "1.9.6" - tslib "^2.1.0" - "@firebase/app-check-compat@0.3.12": version "0.3.12" resolved "https://registry.yarnpkg.com/@firebase/app-check-compat/-/app-check-compat-0.3.12.tgz#34d826f72e058baf1aad11713fda337046fb863c" @@ -2382,16 +2348,6 @@ resolved "https://registry.yarnpkg.com/@firebase/app-check-types/-/app-check-types-0.5.2.tgz#1221bd09b471e11bb149252f16640a0a51043cbc" integrity sha512-FSOEzTzL5bLUbD2co3Zut46iyPWML6xc4x+78TeaXMSuJap5QObfb+rVvZJtla3asN4RwU7elaQaduP+HFizDA== -"@firebase/app-check@0.8.4": - version "0.8.4" - resolved "https://registry.yarnpkg.com/@firebase/app-check/-/app-check-0.8.4.tgz#1c965d34527d1b924fc7bd51789119b3f817bf94" - integrity sha512-2tjRDaxcM5G7BEpytiDcIl+NovV99q8yEqRMKDbn4J4i/XjjuThuB4S+4PkmTnZiCbdLXQiBhkVxNlUDcfog5Q== - dependencies: - "@firebase/component" "0.6.7" - "@firebase/logger" "0.4.2" - "@firebase/util" "1.9.6" - tslib "^2.1.0" - "@firebase/app-check@0.8.5": version "0.8.5" resolved "https://registry.yarnpkg.com/@firebase/app-check/-/app-check-0.8.5.tgz#e8b0a6d603592f6a04f2d429029f5adfe1a4d2ca" @@ -2402,17 +2358,6 @@ "@firebase/util" "1.9.7" tslib "^2.1.0" -"@firebase/app-compat@0.2.35": - version "0.2.35" - resolved "https://registry.yarnpkg.com/@firebase/app-compat/-/app-compat-0.2.35.tgz#ca918736e6b06bdd63eaed24ba213059ecd55f88" - integrity sha512-vgay/WRjeH0r97/Q6L6df2CMx7oyNFDsE5yPQ9oR1G+zx2eT0s8vNNh0WlKqQxUEWaOLRnXhQ8gy7uu0cBgTRg== - dependencies: - "@firebase/app" "0.10.5" - "@firebase/component" "0.6.7" - "@firebase/logger" "0.4.2" - "@firebase/util" "1.9.6" - tslib "^2.1.0" - "@firebase/app-compat@0.2.36": version "0.2.36" resolved "https://registry.yarnpkg.com/@firebase/app-compat/-/app-compat-0.2.36.tgz#46926ee9ba0d54fc5ec4695e62588b63e2f7584a" @@ -2429,17 +2374,6 @@ resolved "https://registry.yarnpkg.com/@firebase/app-types/-/app-types-0.9.2.tgz#8cbcceba784753a7c0066a4809bc22f93adee080" integrity sha512-oMEZ1TDlBz479lmABwWsWjzHwheQKiAgnuKxE0pz0IXCVx7/rtlkx1fQ6GfgK24WCrxDKMplZrT50Kh04iMbXQ== -"@firebase/app@0.10.5": - version "0.10.5" - resolved "https://registry.yarnpkg.com/@firebase/app/-/app-0.10.5.tgz#84d3c99b253366844335a411b568dd258800c794" - integrity sha512-iY/fNot+hWPk9sTX8aHMqlcX9ynRvpGkskWAdUZ2eQQdLo8d1hSFYcYNwPv0Q/frGMasw8udKWMcFOEpC9fG8g== - dependencies: - "@firebase/component" "0.6.7" - "@firebase/logger" "0.4.2" - "@firebase/util" "1.9.6" - idb "7.1.1" - tslib "^2.1.0" - "@firebase/app@0.10.6": version "0.10.6" resolved "https://registry.yarnpkg.com/@firebase/app/-/app-0.10.6.tgz#0f96a573c18d75723ddeedb45c02c5471d9de695" @@ -2463,18 +2397,6 @@ tslib "^2.1.0" undici "5.28.4" -"@firebase/auth-compat@0.5.9": - version "0.5.9" - resolved "https://registry.yarnpkg.com/@firebase/auth-compat/-/auth-compat-0.5.9.tgz#ab925dbe8baf0911fb4836c14403706132d751e8" - integrity sha512-RX8Zh/3zz2CsVbmYfgHkfUm4fAEPCl+KHVIImNygV5jTGDF6oKOhBIpf4Yigclyu8ESQKZ4elyN0MBYm9/7zGw== - dependencies: - "@firebase/auth" "1.7.4" - "@firebase/auth-types" "0.12.2" - "@firebase/component" "0.6.7" - "@firebase/util" "1.9.6" - tslib "^2.1.0" - undici "5.28.4" - "@firebase/auth-interop-types@0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@firebase/auth-interop-types/-/auth-interop-types-0.2.3.tgz#927f1f2139a680b55fef0bddbff2c982b08587e8" @@ -2485,17 +2407,6 @@ resolved "https://registry.yarnpkg.com/@firebase/auth-types/-/auth-types-0.12.2.tgz#f12d890585866e53b6ab18b16fa4d425c52eee6e" integrity sha512-qsEBaRMoGvHO10unlDJhaKSuPn4pyoTtlQuP1ghZfzB6rNQPuhp/N/DcFZxm9i4v0SogjCbf9reWupwIvfmH6w== -"@firebase/auth@1.7.4": - version "1.7.4" - resolved "https://registry.yarnpkg.com/@firebase/auth/-/auth-1.7.4.tgz#0dc8083314a61598c91cfe00cb96cf2cb3d74336" - integrity sha512-d2Fw17s5QesojwebrA903el20Li9/YGgkoOGJjagM4I1qAT36APa/FcZ+OX86KxbYKCtQKTMqraU8pxG7C2JWA== - dependencies: - "@firebase/component" "0.6.7" - "@firebase/logger" "0.4.2" - "@firebase/util" "1.9.6" - tslib "^2.1.0" - undici "5.28.4" - "@firebase/auth@1.7.5": version "1.7.5" resolved "https://registry.yarnpkg.com/@firebase/auth/-/auth-1.7.5.tgz#8135e0933e874231d7ebafc94f5796a19f5df39b" @@ -2507,14 +2418,6 @@ tslib "^2.1.0" undici "5.28.4" -"@firebase/component@0.6.7": - version "0.6.7" - resolved "https://registry.yarnpkg.com/@firebase/component/-/component-0.6.7.tgz#6fbffddb26833e1ed58bf296ad587cb330aee716" - integrity sha512-baH1AA5zxfaz4O8w0vDwETByrKTQqB5CDjRls79Sa4eAGAoERw4Tnung7XbMl3jbJ4B/dmmtsMrdki0KikwDYA== - dependencies: - "@firebase/util" "1.9.6" - tslib "^2.1.0" - "@firebase/component@0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@firebase/component/-/component-0.6.8.tgz#899b9318c0ce0586580e8cda7eaf61296f7fb43b" @@ -2523,18 +2426,6 @@ "@firebase/util" "1.9.7" tslib "^2.1.0" -"@firebase/database-compat@1.0.5": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@firebase/database-compat/-/database-compat-1.0.5.tgz#18c2314f169942ac315e46b68f86cbe64bafe063" - integrity sha512-NDSMaDjQ+TZEMDMmzJwlTL05kh1+0Y84C+kVMaOmNOzRGRM7VHi29I6YUhCetXH+/b1Wh4ZZRyp1CuWkd8s6hg== - dependencies: - "@firebase/component" "0.6.7" - "@firebase/database" "1.0.5" - "@firebase/database-types" "1.0.3" - "@firebase/logger" "0.4.2" - "@firebase/util" "1.9.6" - tslib "^2.1.0" - "@firebase/database-compat@1.0.6": version "1.0.6" resolved "https://registry.yarnpkg.com/@firebase/database-compat/-/database-compat-1.0.6.tgz#6a4966fe4a9d8bc2cb11ee98a1bb01ab954d7d66" @@ -2547,14 +2438,6 @@ "@firebase/util" "1.9.7" tslib "^2.1.0" -"@firebase/database-types@1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@firebase/database-types/-/database-types-1.0.3.tgz#1b764212dce88eca74b16da9d220cfea6814858e" - integrity sha512-39V/Riv2R3O/aUjYKh0xypj7NTNXNAK1bcgY5Kx+hdQPRS/aPTS8/5c0CGFYKgVuFbYlnlnhrCTYsh2uNhGwzA== - dependencies: - "@firebase/app-types" "0.9.2" - "@firebase/util" "1.9.6" - "@firebase/database-types@1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@firebase/database-types/-/database-types-1.0.4.tgz#dc507f7838ed29ac3235c68ebae5fd42a562e3e8" @@ -2563,19 +2446,6 @@ "@firebase/app-types" "0.9.2" "@firebase/util" "1.9.7" -"@firebase/database@1.0.5": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@firebase/database/-/database-1.0.5.tgz#09d7162b7dbc4533f17498ac6a76d5e757ab45be" - integrity sha512-cAfwBqMQuW6HbhwI3Cb/gDqZg7aR0OmaJ85WUxlnoYW2Tm4eR0hFl5FEijI3/gYPUiUcUPQvTkGV222VkT7KPw== - dependencies: - "@firebase/app-check-interop-types" "0.3.2" - "@firebase/auth-interop-types" "0.2.3" - "@firebase/component" "0.6.7" - "@firebase/logger" "0.4.2" - "@firebase/util" "1.9.6" - faye-websocket "0.11.4" - tslib "^2.1.0" - "@firebase/database@1.0.6": version "1.0.6" resolved "https://registry.yarnpkg.com/@firebase/database/-/database-1.0.6.tgz#cf0592b140e207e35c14efe6776fc92266ac408a" @@ -2589,17 +2459,6 @@ faye-websocket "0.11.4" tslib "^2.1.0" -"@firebase/firestore-compat@0.3.32": - version "0.3.32" - resolved "https://registry.yarnpkg.com/@firebase/firestore-compat/-/firestore-compat-0.3.32.tgz#1357ba5f80b83f33210d4fb49a1cd346cf95b291" - integrity sha512-at71mwK7a/mUXH0OgyY0+gUzedm/EUydDFYSFsBoO8DYowZ23Mgd6P4Rzq/Ll3zI/3xJN7LGe7Qp4iE/V/3Arg== - dependencies: - "@firebase/component" "0.6.7" - "@firebase/firestore" "4.6.3" - "@firebase/firestore-types" "3.0.2" - "@firebase/util" "1.9.6" - tslib "^2.1.0" - "@firebase/firestore-compat@0.3.33": version "0.3.33" resolved "https://registry.yarnpkg.com/@firebase/firestore-compat/-/firestore-compat-0.3.33.tgz#8e591bfafb574c695b09101b98c1a1057f55c60e" @@ -2616,20 +2475,6 @@ resolved "https://registry.yarnpkg.com/@firebase/firestore-types/-/firestore-types-3.0.2.tgz#75c301acc5fa33943eaaa9570b963c55398cad2a" integrity sha512-wp1A+t5rI2Qc/2q7r2ZpjUXkRVPtGMd6zCLsiWurjsQpqPgFin3AhNibKcIzoF2rnToNa/XYtyWXuifjOOwDgg== -"@firebase/firestore@4.6.3": - version "4.6.3" - resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-4.6.3.tgz#87ad38dfd0a0f16e79682177102ee1328d59af44" - integrity sha512-d/+N2iUsiJ/Dc7fApdpdmmTXzwuTCromsdA1lKwYfZtMIOd1fI881NSLwK2wV4I38wkLnvfKJUV6WpU1f3/ONg== - dependencies: - "@firebase/component" "0.6.7" - "@firebase/logger" "0.4.2" - "@firebase/util" "1.9.6" - "@firebase/webchannel-wrapper" "1.0.0" - "@grpc/grpc-js" "~1.9.0" - "@grpc/proto-loader" "^0.7.8" - tslib "^2.1.0" - undici "5.28.4" - "@firebase/firestore@4.6.4": version "4.6.4" resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-4.6.4.tgz#f53fcfc3ecfeb844f2147a43382d013d21e64968" @@ -2644,17 +2489,6 @@ tslib "^2.1.0" undici "5.28.4" -"@firebase/functions-compat@0.3.11": - version "0.3.11" - resolved "https://registry.yarnpkg.com/@firebase/functions-compat/-/functions-compat-0.3.11.tgz#9fdff8b174879b404501df7b8b519e5fb6d0b8ec" - integrity sha512-Qn+ts/M6Lj2/6i1cp5V5TRR+Hi9kyXyHbo+w9GguINJ87zxrCe6ulx3TI5AGQkoQa8YFHUhT3DMGmLFiJjWTSQ== - dependencies: - "@firebase/component" "0.6.7" - "@firebase/functions" "0.11.5" - "@firebase/functions-types" "0.6.2" - "@firebase/util" "1.9.6" - tslib "^2.1.0" - "@firebase/functions-compat@0.3.12": version "0.3.12" resolved "https://registry.yarnpkg.com/@firebase/functions-compat/-/functions-compat-0.3.12.tgz#aae387eb48466df1d031fc5bb755c657cfeb5994" @@ -2671,19 +2505,6 @@ resolved "https://registry.yarnpkg.com/@firebase/functions-types/-/functions-types-0.6.2.tgz#03b4ec9259d2f57548a3909d6a35ae35ad243552" integrity sha512-0KiJ9lZ28nS2iJJvimpY4nNccV21rkQyor5Iheu/nq8aKXJqtJdeSlZDspjPSBBiHRzo7/GMUttegnsEITqR+w== -"@firebase/functions@0.11.5": - version "0.11.5" - resolved "https://registry.yarnpkg.com/@firebase/functions/-/functions-0.11.5.tgz#e4187ae3ae262b0482114f7ad418600ca84f3459" - integrity sha512-qrHJ+l62mZiU5UZiVi84t/iLXZlhRuSvBQsa2qvNLgPsEWR7wdpWhRmVdB7AU8ndkSHJjGlMICqrVnz47sgU7Q== - dependencies: - "@firebase/app-check-interop-types" "0.3.2" - "@firebase/auth-interop-types" "0.2.3" - "@firebase/component" "0.6.7" - "@firebase/messaging-interop-types" "0.2.2" - "@firebase/util" "1.9.6" - tslib "^2.1.0" - undici "5.28.4" - "@firebase/functions@0.11.6": version "0.11.6" resolved "https://registry.yarnpkg.com/@firebase/functions/-/functions-0.11.6.tgz#607991a3a870051e6456d7ccb0217fac6305db89" @@ -2697,17 +2518,6 @@ tslib "^2.1.0" undici "5.28.4" -"@firebase/installations-compat@0.2.7": - version "0.2.7" - resolved "https://registry.yarnpkg.com/@firebase/installations-compat/-/installations-compat-0.2.7.tgz#c430f34bfcfc008c92ca32fd11d6c84ab5dd7888" - integrity sha512-RPcbD+3nqHbnhVjIOpWK2H5qzZ8pAAAScceiWph0VNTqpKyPQ5tDcp4V5fS0ELpfgsHYvroMLDKfeHxpfvm8cw== - dependencies: - "@firebase/component" "0.6.7" - "@firebase/installations" "0.6.7" - "@firebase/installations-types" "0.5.2" - "@firebase/util" "1.9.6" - tslib "^2.1.0" - "@firebase/installations-compat@0.2.8": version "0.2.8" resolved "https://registry.yarnpkg.com/@firebase/installations-compat/-/installations-compat-0.2.8.tgz#ebc908afe84db2754b19a62f7655608911e13819" @@ -2724,16 +2534,6 @@ resolved "https://registry.yarnpkg.com/@firebase/installations-types/-/installations-types-0.5.2.tgz#4d4949e0e83ced7f36cbee009355cd305a36e158" integrity sha512-que84TqGRZJpJKHBlF2pkvc1YcXrtEDOVGiDjovP/a3s6W4nlbohGXEsBJo0JCeeg/UG9A+DEZVDUV9GpklUzA== -"@firebase/installations@0.6.7": - version "0.6.7" - resolved "https://registry.yarnpkg.com/@firebase/installations/-/installations-0.6.7.tgz#4fc60ca86e838d7c45dfd1d4926d000060bd1079" - integrity sha512-i6iGoXRu5mX4rTsiMSSKrgh9pSEzD4hwBEzRh5kEhOTr8xN/wvQcCPZDSMVYKwM2XyCPBLVq0JzjyerwL0Rihg== - dependencies: - "@firebase/component" "0.6.7" - "@firebase/util" "1.9.6" - idb "7.1.1" - tslib "^2.1.0" - "@firebase/installations@0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@firebase/installations/-/installations-0.6.8.tgz#f9c9d493bce04b04ca28814e926ef3ed71f033d6" @@ -2761,16 +2561,6 @@ "@firebase/util" "1.9.7" tslib "^2.1.0" -"@firebase/messaging-compat@0.2.9": - version "0.2.9" - resolved "https://registry.yarnpkg.com/@firebase/messaging-compat/-/messaging-compat-0.2.9.tgz#a4cae54c9caf10a3a6c811152d5bd58f165337b7" - integrity sha512-5jN6wyhwPgBH02zOtmmoOeyfsmoD7ty48D1m0vVPsFg55RqN2Z3Q9gkZ5GmPklFPjTPLcxB1ObcHOZvThTkm7g== - dependencies: - "@firebase/component" "0.6.7" - "@firebase/messaging" "0.12.9" - "@firebase/util" "1.9.6" - tslib "^2.1.0" - "@firebase/messaging-interop-types@0.2.2": version "0.2.2" resolved "https://registry.yarnpkg.com/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.2.tgz#81042f7e9739733fa4571d17f6eb6869522754d0" @@ -2788,30 +2578,6 @@ idb "7.1.1" tslib "^2.1.0" -"@firebase/messaging@0.12.9": - version "0.12.9" - resolved "https://registry.yarnpkg.com/@firebase/messaging/-/messaging-0.12.9.tgz#c3cb7a26a3488161273839bf65237f8c485ba661" - integrity sha512-IH+JJmzbFGZXV3+TDyKdqqKPVfKRqBBg2BfYYOy7cm7J+SwV+uJMe8EnDKYeQLEQhtpwciPfJ3qQXJs2lbxDTw== - dependencies: - "@firebase/component" "0.6.7" - "@firebase/installations" "0.6.7" - "@firebase/messaging-interop-types" "0.2.2" - "@firebase/util" "1.9.6" - idb "7.1.1" - tslib "^2.1.0" - -"@firebase/performance-compat@0.2.7": - version "0.2.7" - resolved "https://registry.yarnpkg.com/@firebase/performance-compat/-/performance-compat-0.2.7.tgz#30e29934326888b164c67e5f3709c3a8e580a8d6" - integrity sha512-cb8ge/5iTstxfIGW+iiY+7l3FtN8gobNh9JSQNZgLC9xmcfBYWEs8IeEWMI6S8T+At0oHc3lv+b2kpRMUWr8zQ== - dependencies: - "@firebase/component" "0.6.7" - "@firebase/logger" "0.4.2" - "@firebase/performance" "0.6.7" - "@firebase/performance-types" "0.2.2" - "@firebase/util" "1.9.6" - tslib "^2.1.0" - "@firebase/performance-compat@0.2.8": version "0.2.8" resolved "https://registry.yarnpkg.com/@firebase/performance-compat/-/performance-compat-0.2.8.tgz#d97bab3fd0c147c7f796e9b8f78712bc0b83699c" @@ -2829,17 +2595,6 @@ resolved "https://registry.yarnpkg.com/@firebase/performance-types/-/performance-types-0.2.2.tgz#7b78cd2ab2310bac89a63348d93e67e01eb06dd7" integrity sha512-gVq0/lAClVH5STrIdKnHnCo2UcPLjJlDUoEB/tB4KM+hAeHUxWKnpT0nemUPvxZ5nbdY/pybeyMe8Cs29gEcHA== -"@firebase/performance@0.6.7": - version "0.6.7" - resolved "https://registry.yarnpkg.com/@firebase/performance/-/performance-0.6.7.tgz#7d6c4e5ec61df7369d87fb4a5c0af4e0cedee69b" - integrity sha512-d+Q4ltjdJZqjzcdms5i0UC9KLYX7vKGcygZ+7zHA/Xk+bAbMD2CPU0nWTnlNFWifZWIcXZ/2mAMvaGMW3lypUA== - dependencies: - "@firebase/component" "0.6.7" - "@firebase/installations" "0.6.7" - "@firebase/logger" "0.4.2" - "@firebase/util" "1.9.6" - tslib "^2.1.0" - "@firebase/performance@0.6.8": version "0.6.8" resolved "https://registry.yarnpkg.com/@firebase/performance/-/performance-0.6.8.tgz#668b0fc207389f7829fd3bfb6614fe819b7db124" @@ -2851,18 +2606,6 @@ "@firebase/util" "1.9.7" tslib "^2.1.0" -"@firebase/remote-config-compat@0.2.7": - version "0.2.7" - resolved "https://registry.yarnpkg.com/@firebase/remote-config-compat/-/remote-config-compat-0.2.7.tgz#8a7ac7658a7c9cc29a4ad5884bc224eaae950c38" - integrity sha512-Fq0oneQ4SluLnfr5/HfzRS1TZf1ANj1rWbCCW3+oC98An3nE+sCdp+FSuHsEVNwgMg4Tkwx9Oom2lkKeU+Vn+w== - dependencies: - "@firebase/component" "0.6.7" - "@firebase/logger" "0.4.2" - "@firebase/remote-config" "0.4.7" - "@firebase/remote-config-types" "0.3.2" - "@firebase/util" "1.9.6" - tslib "^2.1.0" - "@firebase/remote-config-compat@0.2.8": version "0.2.8" resolved "https://registry.yarnpkg.com/@firebase/remote-config-compat/-/remote-config-compat-0.2.8.tgz#a6df065c1fd0a943e84ee0e76acfc6c1bede42f9" @@ -2880,17 +2623,6 @@ resolved "https://registry.yarnpkg.com/@firebase/remote-config-types/-/remote-config-types-0.3.2.tgz#a5d1009c6fd08036c5cd4f28764e3cd694f966d5" integrity sha512-0BC4+Ud7y2aPTyhXJTMTFfrGGLqdYXrUB9sJVAB8NiqJswDTc4/2qrE/yfUbnQJhbSi6ZaTTBKyG3n1nplssaA== -"@firebase/remote-config@0.4.7": - version "0.4.7" - resolved "https://registry.yarnpkg.com/@firebase/remote-config/-/remote-config-0.4.7.tgz#1afd6f3089e3c66ed6909eb60d0eb1329d27c9ff" - integrity sha512-5oPNrPFLsbsjpq0lUEIXoDF2eJK7vAbyXe/DEuZQxnwJlfR7aQbtUlEkRgQWcicXpyDmAmDLo7q7lDbCYa6CpA== - dependencies: - "@firebase/component" "0.6.7" - "@firebase/installations" "0.6.7" - "@firebase/logger" "0.4.2" - "@firebase/util" "1.9.6" - tslib "^2.1.0" - "@firebase/remote-config@0.4.8": version "0.4.8" resolved "https://registry.yarnpkg.com/@firebase/remote-config/-/remote-config-0.4.8.tgz#b6a79acdf73554e0ee31c278162b85592fc8c1f3" @@ -2902,17 +2634,6 @@ "@firebase/util" "1.9.7" tslib "^2.1.0" -"@firebase/storage-compat@0.3.8": - version "0.3.8" - resolved "https://registry.yarnpkg.com/@firebase/storage-compat/-/storage-compat-0.3.8.tgz#0d6d66a683713953b2bd24494e83bddcbb562f3a" - integrity sha512-qDfY9kMb6Ch2hZb40sBjDQ8YPxbjGOxuT+gU1Z0iIVSSpSX0f4YpGJCypUXiA0T11n6InCXB+T/Dknh2yxVTkg== - dependencies: - "@firebase/component" "0.6.7" - "@firebase/storage" "0.12.5" - "@firebase/storage-types" "0.8.2" - "@firebase/util" "1.9.6" - tslib "^2.1.0" - "@firebase/storage-compat@0.3.9": version "0.3.9" resolved "https://registry.yarnpkg.com/@firebase/storage-compat/-/storage-compat-0.3.9.tgz#42496a7b5f7c384f0ea590d704934465102b4527" @@ -2929,16 +2650,6 @@ resolved "https://registry.yarnpkg.com/@firebase/storage-types/-/storage-types-0.8.2.tgz#edb321b8a3872a9f74e1f27de046f160021c8e1f" integrity sha512-0vWu99rdey0g53lA7IShoA2Lol1jfnPovzLDUBuon65K7uKG9G+L5uO05brD9pMw+l4HRFw23ah3GwTGpEav6g== -"@firebase/storage@0.12.5": - version "0.12.5" - resolved "https://registry.yarnpkg.com/@firebase/storage/-/storage-0.12.5.tgz#9277b4f838572ba78f017aa6207c6d7545400846" - integrity sha512-nGWBOGFNr10j0LA4NJ3/Yh3us/lb0Q1xSIKZ38N6FcS+vY54nqJ7k3zE3PENregHC8+8txRow++A568G3v8hOA== - dependencies: - "@firebase/component" "0.6.7" - "@firebase/util" "1.9.6" - tslib "^2.1.0" - undici "5.28.4" - "@firebase/storage@0.12.6": version "0.12.6" resolved "https://registry.yarnpkg.com/@firebase/storage/-/storage-0.12.6.tgz#49b2c77f10fd97da913a93e37c86cdff92a805eb" @@ -2949,13 +2660,6 @@ tslib "^2.1.0" undici "5.28.4" -"@firebase/util@1.9.6": - version "1.9.6" - resolved "https://registry.yarnpkg.com/@firebase/util/-/util-1.9.6.tgz#56dc34e20fcbc0dd07b11b800f95f5d0417cbfb4" - integrity sha512-IBr1MZbp4d5MjBCXL3TW1dK/PDXX4yOGbiwRNh1oAbE/+ci5Uuvy9KIrsFYY80as1I0iOaD5oOMA9Q8j4TJWcw== - dependencies: - tslib "^2.1.0" - "@firebase/util@1.9.7": version "1.9.7" resolved "https://registry.yarnpkg.com/@firebase/util/-/util-1.9.7.tgz#c03b0ae065b3bba22800da0bd5314ef030848038" @@ -2963,17 +2667,6 @@ dependencies: tslib "^2.1.0" -"@firebase/vertexai-preview@0.0.2": - version "0.0.2" - resolved "https://registry.yarnpkg.com/@firebase/vertexai-preview/-/vertexai-preview-0.0.2.tgz#a17454e4899bf4b3fa07322fb204659e7cfa5868" - integrity sha512-NOOL63kFQRq45ioi5P+hlqj/4LNmvn1URhGjQdvyV54c1Irvoq26aW861PRRLjrSMIeNeiLtCLD5pe+ediepAg== - dependencies: - "@firebase/app-check-interop-types" "0.3.2" - "@firebase/component" "0.6.7" - "@firebase/logger" "0.4.2" - "@firebase/util" "1.9.6" - tslib "^2.1.0" - "@firebase/vertexai-preview@0.0.3": version "0.0.3" resolved "https://registry.yarnpkg.com/@firebase/vertexai-preview/-/vertexai-preview-0.0.3.tgz#73dea839439ebdbb5ccd946f297ede5b57e6e7e9" @@ -2985,11 +2678,6 @@ "@firebase/util" "1.9.7" tslib "^2.1.0" -"@firebase/webchannel-wrapper@1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.0.tgz#a0e11b39fa3ef56ed5333bf321f581037aeda033" - integrity sha512-zuWxyfXNbsKbm96HhXzainONPFqRcoZblQ++e9cAIGUuHfl2cFSBzW01jtesqWG/lqaUyX3H8O1y9oWboGNQBA== - "@firebase/webchannel-wrapper@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.1.tgz#0b62c9f47f557a5b4adc073bb0a47542ce6af4c4" @@ -5893,10 +5581,10 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@notifee/react-native@^9.0.0": - version "9.0.2" - resolved "https://registry.yarnpkg.com/@notifee/react-native/-/react-native-9.0.2.tgz#28e2b11afa10b13717518d2fd6377b45a754bbba" - integrity sha512-fuStKItKNg+X8ke8qkFC/NjAFuI4Gvt0J8r8LbuXYHyPUj+YiauQSEyVKejQp+a98UyJrAJtKrgJ7XDru+vNiA== +"@notifee/react-native@^7.8.2": + version "7.8.2" + resolved "https://registry.yarnpkg.com/@notifee/react-native/-/react-native-7.8.2.tgz#72d3199ae830b4128ddaef3c1c2f11604759c9c4" + integrity sha512-VG4IkWJIlOKqXwa3aExC3WFCVCGCC9BA55Ivg0SMRfEs+ruvYy/zlLANcrVGiPtgkUEryXDhA8SXx9+JcO8oLA== "@npmcli/agent@^2.0.0": version "2.2.2" @@ -6939,18 +6627,18 @@ dependencies: invariant "^2.2.4" -"@react-native-firebase/app@^20.5.0": - version "20.5.0" - resolved "https://registry.yarnpkg.com/@react-native-firebase/app/-/app-20.5.0.tgz#9dc2fbc6eee7fb00eefc9f88e404411068befd29" - integrity sha512-zW6jc9R8Ikxxo5qXv25No6QxdWOx37D4U0ppKZQoY6CzwGUzqc4oQUU9MclbUcx+H5p+GEzvT1EIGV6JG7/FAg== +"@react-native-firebase/app@^20.1.0": + version "20.1.0" + resolved "https://registry.yarnpkg.com/@react-native-firebase/app/-/app-20.1.0.tgz#86b9371290f92d51821b7299eede95336949f214" + integrity sha512-FCcTtmfz/Bk2laOEKOiUrQUkAnzerkRml7d3kZzJSxaBWLFxpWJQnnXqGZmD8hNWio2QEauB8llUD71KiDk+sw== dependencies: - firebase "10.12.2" + opencollective-postinstall "^2.0.3" superstruct "^0.6.2" -"@react-native-firebase/messaging@^20.5.0": - version "20.5.0" - resolved "https://registry.yarnpkg.com/@react-native-firebase/messaging/-/messaging-20.5.0.tgz#90eeed62d1a069b4fd56746afcfb33ee2a61ba0f" - integrity sha512-S3M9zHJ3zKRH6PuxxjOrV9i0uRO3S5rGYaY3s64BTh4iLEG46UDgJ5uA+WsY+9IsJtSpN4HYDcvnwoxasiEUfw== +"@react-native-firebase/messaging@^20.1.0": + version "20.1.0" + resolved "https://registry.yarnpkg.com/@react-native-firebase/messaging/-/messaging-20.1.0.tgz#02026259c74d1725dfc5216158b05bc6655e7951" + integrity sha512-y9FtQ9dIQSyueuLeJghvfLYnay5BqPVgl9T94p+HtUlkxinOgNDjquQFtV/QlzVOyVpLrVPmknMohvBj/fvBzg== "@react-native-masked-view/masked-view@^0.3.1": version "0.3.1" @@ -18401,39 +18089,6 @@ findup-sync@^5.0.0: micromatch "^4.0.4" resolve-dir "^1.0.1" -firebase@10.12.2: - version "10.12.2" - resolved "https://registry.yarnpkg.com/firebase/-/firebase-10.12.2.tgz#9049286c5fafb6d686bb19ad93c7bb4a9e8756c0" - integrity sha512-ZxEdtSvP1I9su1yf32D8TIdgxtPgxwr6z3jYAR1TXS/t+fVfpoPc/N1/N2bxOco9mNjUoc+od34v5Fn4GeKs6Q== - dependencies: - "@firebase/analytics" "0.10.4" - "@firebase/analytics-compat" "0.2.10" - "@firebase/app" "0.10.5" - "@firebase/app-check" "0.8.4" - "@firebase/app-check-compat" "0.3.11" - "@firebase/app-compat" "0.2.35" - "@firebase/app-types" "0.9.2" - "@firebase/auth" "1.7.4" - "@firebase/auth-compat" "0.5.9" - "@firebase/database" "1.0.5" - "@firebase/database-compat" "1.0.5" - "@firebase/firestore" "4.6.3" - "@firebase/firestore-compat" "0.3.32" - "@firebase/functions" "0.11.5" - "@firebase/functions-compat" "0.3.11" - "@firebase/installations" "0.6.7" - "@firebase/installations-compat" "0.2.7" - "@firebase/messaging" "0.12.9" - "@firebase/messaging-compat" "0.2.9" - "@firebase/performance" "0.6.7" - "@firebase/performance-compat" "0.2.7" - "@firebase/remote-config" "0.4.7" - "@firebase/remote-config-compat" "0.2.7" - "@firebase/storage" "0.12.5" - "@firebase/storage-compat" "0.3.8" - "@firebase/util" "1.9.6" - "@firebase/vertexai-preview" "0.0.2" - firebase@^10.11.0: version "10.12.3" resolved "https://registry.yarnpkg.com/firebase/-/firebase-10.12.3.tgz#b94510728f603a15367b95e12a00b366700ba7f8" From d16e151819698a52f9ed63ff441d9d0971ecf805 Mon Sep 17 00:00:00 2001 From: metamaskbot Date: Tue, 24 Sep 2024 16:10:42 -0600 Subject: [PATCH 12/27] version bump 1437 RC 3 --- android/app/build.gradle | 2 +- bitrise.yml | 4 ++-- ios/MetaMask.xcodeproj/project.pbxproj | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index a8ee2bda369..389313e2eee 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -173,7 +173,7 @@ android { applicationId "io.metamask" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1436 + versionCode 1437 versionName "7.32.0" testBuildType System.getProperty('testBuildType', 'debug') missingDimensionStrategy 'react-native-camera', 'general' diff --git a/bitrise.yml b/bitrise.yml index 6f0c5be06ac..1950a352f5e 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -1500,13 +1500,13 @@ app: VERSION_NAME: 7.32.0 - opts: is_expand: false - VERSION_NUMBER: 1436 + VERSION_NUMBER: 1437 - opts: is_expand: false FLASK_VERSION_NAME: 7.32.0 - opts: is_expand: false - FLASK_VERSION_NUMBER: 1436 + FLASK_VERSION_NUMBER: 1437 - opts: is_expand: false ANDROID_APK_LINK: '' diff --git a/ios/MetaMask.xcodeproj/project.pbxproj b/ios/MetaMask.xcodeproj/project.pbxproj index 2bcb3d845c0..e9216dd3c56 100644 --- a/ios/MetaMask.xcodeproj/project.pbxproj +++ b/ios/MetaMask.xcodeproj/project.pbxproj @@ -1273,7 +1273,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1436; + CURRENT_PROJECT_VERSION = 1437; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1338,7 +1338,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1436; + CURRENT_PROJECT_VERSION = 1437; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1401,7 +1401,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1436; + CURRENT_PROJECT_VERSION = 1437; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1462,7 +1462,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1436; + CURRENT_PROJECT_VERSION = 1437; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1624,7 +1624,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1436; + CURRENT_PROJECT_VERSION = 1437; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1692,7 +1692,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1436; + CURRENT_PROJECT_VERSION = 1437; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; From 590736e098349ada3cde94b0c343ebc2d51b4e49 Mon Sep 17 00:00:00 2001 From: "runway-github[bot]" <73448015+runway-github[bot]@users.noreply.github.com> Date: Wed, 25 Sep 2024 10:22:12 +0200 Subject: [PATCH 13/27] chore(runway): cherry-pick fix: fix fixture builder network state (#11422) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fix: fix fixture builder network state (#11411) ## **Description** This PR addresses and fixes structural issues in the network state used by the E2E test fixture builder. Specifically, the duplicated keys and misaligned network configuration objects have been corrected to ensure the network state functions properly during end-to-end testing. ## **Related issues** Fixes: #11362 ## **Manual testing steps** 1. run locally the test `e2e/specs/networks/add-custom-rpc.spec.js` 2. Gnosis network should be added ## **Screenshots/Recordings** ### **Before** ### **After** https://github.com/user-attachments/assets/30534eb8-580a-4333-b8f9-7a5be9048b72 ## **Pre-merge author checklist** - [x] 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). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] 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. [34ffb57](https://github.com/MetaMask/metamask-mobile/commit/34ffb570c55629f0b26c64779d2268dd3952c5ff) Co-authored-by: Salim TOUBAL --- e2e/fixtures/fixture-builder.js | 36 +++++++++------------------------ 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/e2e/fixtures/fixture-builder.js b/e2e/fixtures/fixture-builder.js index 4f7840a9eac..471cb7f950f 100644 --- a/e2e/fixtures/fixture-builder.js +++ b/e2e/fixtures/fixture-builder.js @@ -144,38 +144,20 @@ class FixtureBuilder { NetworkController: { selectedNetworkClientId: 'mainnet', networksMetadata: { - goerli: { - EIPS: {}, - status: 'unknown', - }, - 'linea-goerli': { - EIPS: {}, - status: 'unknown', - }, - 'linea-sepolia': { - EIPS: {}, - status: 'unknown', - }, - 'linea-mainnet': { - EIPS: {}, - status: 'unknown', - }, mainnet: { - EIPS: {}, - status: 'unknown', + status: 'available', + EIPS: { + 1559: true, + }, }, - sepolia: { - EIPS: {}, - status: 'unknown', + networkId1: { + status: 'available', + EIPS: { + 1559: true, + }, }, }, networkConfigurations: { - mainnet: { - id: 'mainnet', - chainId: '0x1', - ticker: 'ETH', - rpcPrefs: {}, - }, networkId1: { rpcUrl: `http://localhost:${getGanachePort()}`, chainId: '1337', From af0b314efb5626c00eb04cb1ef1e8917372dcda6 Mon Sep 17 00:00:00 2001 From: "runway-github[bot]" <73448015+runway-github[bot]@users.noreply.github.com> Date: Wed, 25 Sep 2024 10:07:02 -0600 Subject: [PATCH 14/27] chore(runway): cherry-pick fix: Update steps of the methods that are no longer valid (#11423) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fix: Update steps of the methods that are no longer valid (#11367) ## **Description** Currently, e2e/specs/permission-systems/permission-system-revoking-multiple-accounts.spec.js is failing on main. Some of the methods used on some steps were changed in a previous PR. But they spec was not updated as expected, so the test failed because is unable to find those methods. ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** Regression Test run: https://app.bitrise.io/app/be69d4368ee7e86d/pipelines/d7aa537e-ac76-44dc-ab7d-b47b4ff995a8 iOS: ![image](https://github.com/user-attachments/assets/46120985-a3f6-4a3a-9782-9b996ac6d365) Android: ![image](https://github.com/user-attachments/assets/f9bd7103-de33-40de-a2a5-59d6eb28b752) ### **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. --------- Co-authored-by: Daniel Cross Co-authored-by: Curtis David [fb86c83](https://github.com/MetaMask/metamask-mobile/commit/fb86c8355dfc0269ed868d9a669882ae2f7254f3) Co-authored-by: SamuelSalas Co-authored-by: Daniel Cross Co-authored-by: Curtis David --- ...-system-revoking-multiple-accounts.spec.js | 14 ++++++++--- e2e/specs/swaps/swap-token-chart.spec.js | 25 +++++++++++-------- e2e/utils/Matchers.js | 9 ++++--- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/e2e/specs/permission-systems/permission-system-revoking-multiple-accounts.spec.js b/e2e/specs/permission-systems/permission-system-revoking-multiple-accounts.spec.js index c92f296ea36..685361d5729 100644 --- a/e2e/specs/permission-systems/permission-system-revoking-multiple-accounts.spec.js +++ b/e2e/specs/permission-systems/permission-system-revoking-multiple-accounts.spec.js @@ -6,12 +6,16 @@ import TabBarComponent from '../../pages/TabBarComponent'; import ToastModal from '../../pages/modals/ToastModal'; import ConnectedAccountsModal from '../../pages/modals/ConnectedAccountsModal'; import NetworkListModal from '../../pages/modals/NetworkListModal'; +import AddAccountModal from '../../pages/modals/AddAccountModal'; import { loginToApp } from '../../viewHelper'; import FixtureBuilder from '../../fixtures/fixture-builder'; import { withFixtures } from '../../fixtures/fixture-helper'; import Assertions from '../../utils/Assertions'; +import { Regression } from '../../tags'; -describe('Connecting to multiple dapps and revoking permission on one but staying connected to the other', () => { +const AccountTwoText = 'Account 2'; + +describe(Regression('Connecting to multiple dapps and revoking permission on one but staying connected to the other'), () => { beforeAll(async () => { jest.setTimeout(150000); await TestHelpers.reverseServerPort(); @@ -42,10 +46,12 @@ describe('Connecting to multiple dapps and revoking permission on one but stayin await Assertions.checkIfNotVisible(ToastModal.notificationTitle); await ConnectedAccountsModal.tapConnectMoreAccountsButton(); await AccountListView.tapAddAccountButton(); - await AccountListView.tapCreateAccountButton(); - await AccountListView.isAccount2VisibleAtIndex(0); + await AddAccountModal.tapCreateAccount(); + if (device.getPlatform() === 'android') { + await Assertions.checkIfTextIsDisplayed(AccountTwoText); + } await AccountListView.tapAccountIndex(0); - await AccountListView.connectAccountsButton(); + await AccountListView.tapConnectAccountsButton(); // should revoke accounts await Browser.tapNetworkAvatarButtonOnBrowser(); diff --git a/e2e/specs/swaps/swap-token-chart.spec.js b/e2e/specs/swaps/swap-token-chart.spec.js index 422ec021e37..d6d6bbbf0e9 100644 --- a/e2e/specs/swaps/swap-token-chart.spec.js +++ b/e2e/specs/swaps/swap-token-chart.spec.js @@ -22,6 +22,8 @@ import ActivitiesView from '../../pages/ActivitiesView'; import DetailsModal from '../../pages/modals/DetailsModal'; const fixtureServer = new FixtureServer(); +const sourceTokenSymbol = 'USDT'; +const destTokenSymbol = 'DAI'; describe(Regression('Swap from Token view'), () => { const swapOnboarded = true; // TODO: Set it to false once we show the onboarding page again. @@ -47,7 +49,7 @@ describe(Regression('Swap from Token view'), () => { jest.setTimeout(150000); }); - it('should complete a USDC to DAI swap from the token chart', async () => { + it('should complete a USDT to ETH swap from the token chart', async () => { await TabBarComponent.tapWallet(); await Assertions.checkIfVisible(WalletView.container); await WalletView.tapOnToken('Ethereum'); @@ -58,15 +60,15 @@ describe(Regression('Swap from Token view'), () => { await Assertions.checkIfVisible(QuoteView.getQuotes); await QuoteView.tapOnSelectSourceToken(); await QuoteView.tapSearchToken(); - await QuoteView.typeSearchToken('LINK'); + await QuoteView.typeSearchToken(sourceTokenSymbol); await TestHelpers.delay(1000); - await QuoteView.selectToken('LINK'); - await QuoteView.enterSwapAmount('5'); + await QuoteView.selectToken(sourceTokenSymbol); + await QuoteView.enterSwapAmount('10'); await QuoteView.tapOnSelectDestToken(); await QuoteView.tapSearchToken(); - await QuoteView.typeSearchToken('DAI'); + await QuoteView.typeSearchToken(destTokenSymbol); await TestHelpers.delay(1000); - await QuoteView.selectToken('DAI'); + await QuoteView.selectToken(destTokenSymbol); await QuoteView.tapOnGetQuotes(); await Assertions.checkIfVisible(SwapView.fetchingQuotes); await Assertions.checkIfVisible(SwapView.quoteSummary); @@ -75,7 +77,7 @@ describe(Regression('Swap from Token view'), () => { await SwapView.swipeToSwap(); try { await Assertions.checkIfVisible( - SwapView.swapCompleteLabel('LINK', 'DAI'), + SwapView.swapCompleteLabel(sourceTokenSymbol, destTokenSymbol), 100000, ); } catch (e) { @@ -84,22 +86,23 @@ describe(Regression('Swap from Token view'), () => { } await device.enableSynchronization(); await TestHelpers.delay(5000); + await TokenOverview.tapBackButton(); await TabBarComponent.tapActivity(); await Assertions.checkIfVisible(ActivitiesView.title); - await Assertions.checkIfVisible(ActivitiesView.swapActivity('LINK', 'DAI')); - await ActivitiesView.tapOnSwapActivity('LINK', 'DAI'); + await Assertions.checkIfVisible(ActivitiesView.swapActivity(sourceTokenSymbol, destTokenSymbol)); + await ActivitiesView.tapOnSwapActivity(sourceTokenSymbol, destTokenSymbol); try { await Assertions.checkIfVisible(DetailsModal.title); } catch (e) { - await ActivitiesView.tapOnSwapActivity('LINK', 'DAI'); + await ActivitiesView.tapOnSwapActivity(sourceTokenSymbol, destTokenSymbol); await Assertions.checkIfVisible(DetailsModal.title); } await Assertions.checkIfVisible(DetailsModal.title); await Assertions.checkIfElementToHaveText( DetailsModal.title, - DetailsModal.generateExpectedTitle('LINK', 'DAI'), + DetailsModal.generateExpectedTitle(sourceTokenSymbol, destTokenSymbol), ); await Assertions.checkIfVisible(DetailsModal.statusConfirmed); await DetailsModal.tapOnCloseIcon(); diff --git a/e2e/utils/Matchers.js b/e2e/utils/Matchers.js index cd9255de69e..50896f66702 100644 --- a/e2e/utils/Matchers.js +++ b/e2e/utils/Matchers.js @@ -9,7 +9,7 @@ class Matchers { * * @param {string} elementId - Match elements with the specified testID * @param {number} [index] - Index of the element (default: 0) - * @return {Promise | Promise} - Resolves to the located element + * @return {Promise} - Resolves to the located element */ static async getElementByID(elementId, index) { if (index) { @@ -49,7 +49,8 @@ class Matchers { * Get element by label. * * @param {string} label - Match elements with the specified accessibility label (iOS) or content description (Android) - * @return {Promise} - Resolves to the located element + * @param {number} index - Index of the element (default: 0) + * @return {Promise} - Resolves to the located element */ static async getElementByLabel(label, index = 0) { return element(by.label(label)).atIndex(index); @@ -96,7 +97,7 @@ class Matchers { * * @param {string} webviewID - The web ID of the inner element to locate within the webview * @param {string} innerID - The web ID of the browser webview - * @return {Promise} Resolves to the located element + * @return {Promise} Resolves to the located element */ static async getElementByWebID(webviewID, innerID) { const myWebView = this.getWebViewByID(webviewID); @@ -119,7 +120,7 @@ class Matchers { * Get element by XPath. * @param {string} webviewID - The web ID of the browser webview * @param {string} xpath - XPath expression to locate the element - * @return {Promise} - Resolves to the located element + * @return {Promise} - Resolves to the located element */ static async getElementByXPath(webviewID, xpath) { const myWebView = this.getWebViewByID(webviewID); From 95d041d992d998088f26ddf3d455a9cc675cdec2 Mon Sep 17 00:00:00 2001 From: Jongsun Suh Date: Thu, 26 Sep 2024 12:02:46 -0400 Subject: [PATCH 15/27] chore: cherry-pick fix: "chore(deps): Bump @metamask/base-controller from ^6.0.0 to ^7.0.0 (#11207)" (#11439) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** - Cherry-picks b6d21fd71f5064cee7ce3adab3ff5700d55a8110 into the `7.32.0` release candidate. - Fixes an ["Unused `@ts-expect-error` directive" error](https://github.com/MetaMask/metamask-mobile/actions/runs/11035415957/job/30651541194?pr=11439). - This does not affect runtime behavior as evidenced by all [unit tests](https://github.com/MetaMask/metamask-mobile/actions/runs/11035415957/job/30651915018?pr=11439) and [e2e tests](https://github.com/MetaMask/metamask-mobile/pull/11439#issuecomment-2374306306) passing before the fix commit is made. - Depending on the order of cherry-picking, the error in this commit may disappear and/or appear on a later commit. - This it because the error arises from interactions between commits that change `@metamask/base-controller` versions. - This is also why this error appears in this commit, but didn't in the original PR. - This is not a cause for concern, as long as the final commit results in the `@ts-expect-error` directive being removed. ## **Related issues** Fixes: ## **Manual testing steps** ## **Screenshots/Recordings** ## **Pre-merge author checklist** - [x] 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). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] 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. --------- Co-authored-by: Michele Esposito Co-authored-by: Michele Esposito <34438276+mikesposito@users.noreply.github.com> From ec3436bb0c3505ad6ea90555a79cc3a1b1c536ab Mon Sep 17 00:00:00 2001 From: Frank von Hoven <141057783+frankvonhoven@users.noreply.github.com> Date: Thu, 26 Sep 2024 11:46:22 -0500 Subject: [PATCH 16/27] chore: bump to 1439 (#11463) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Bump build to 1439 ## **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. --- android/app/build.gradle | 2 +- bitrise.yml | 4 ++-- ios/MetaMask.xcodeproj/project.pbxproj | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 389313e2eee..8279218ed1f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -173,7 +173,7 @@ android { applicationId "io.metamask" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1437 + versionCode 1439 versionName "7.32.0" testBuildType System.getProperty('testBuildType', 'debug') missingDimensionStrategy 'react-native-camera', 'general' diff --git a/bitrise.yml b/bitrise.yml index 1950a352f5e..a1426348545 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -1500,13 +1500,13 @@ app: VERSION_NAME: 7.32.0 - opts: is_expand: false - VERSION_NUMBER: 1437 + VERSION_NUMBER: 1439 - opts: is_expand: false FLASK_VERSION_NAME: 7.32.0 - opts: is_expand: false - FLASK_VERSION_NUMBER: 1437 + FLASK_VERSION_NUMBER: 1439 - opts: is_expand: false ANDROID_APK_LINK: '' diff --git a/ios/MetaMask.xcodeproj/project.pbxproj b/ios/MetaMask.xcodeproj/project.pbxproj index e9216dd3c56..37d7399d6b7 100644 --- a/ios/MetaMask.xcodeproj/project.pbxproj +++ b/ios/MetaMask.xcodeproj/project.pbxproj @@ -1273,7 +1273,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1437; + CURRENT_PROJECT_VERSION = 1439; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1338,7 +1338,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1437; + CURRENT_PROJECT_VERSION = 1439; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1401,7 +1401,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1437; + CURRENT_PROJECT_VERSION = 1439; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1462,7 +1462,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1437; + CURRENT_PROJECT_VERSION = 1439; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1624,7 +1624,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1437; + CURRENT_PROJECT_VERSION = 1439; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1692,7 +1692,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1437; + CURRENT_PROJECT_VERSION = 1439; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; From bb149a12a4501afa8ac0d3509e883af662a7f9b5 Mon Sep 17 00:00:00 2001 From: Frank von Hoven <141057783+frankvonhoven@users.noreply.github.com> Date: Tue, 1 Oct 2024 01:19:48 -0500 Subject: [PATCH 17/27] Cherry pick e5de779 to 7.32.0 (#11516) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** CP into 7.32.0 ## **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. --------- Co-authored-by: Cal-L --- .../Views/AccountConnect/AccountConnect.tsx | 11 +++----- app/components/Views/BrowserTab/index.js | 27 ++++++++----------- app/core/Engine.ts | 16 +++-------- app/util/linkCheck.test.ts | 5 ++-- app/util/linkCheck.ts | 10 +++---- app/util/test/initial-background-state.json | 1 + package.json | 2 +- yarn.lock | 24 +++++++++++++---- 8 files changed, 46 insertions(+), 50 deletions(-) diff --git a/app/components/Views/AccountConnect/AccountConnect.tsx b/app/components/Views/AccountConnect/AccountConnect.tsx index 1012decaec0..065a9787740 100644 --- a/app/components/Views/AccountConnect/AccountConnect.tsx +++ b/app/components/Views/AccountConnect/AccountConnect.tsx @@ -177,7 +177,7 @@ const AccountConnect = (props: AccountConnectProps) => { ? prefixUrlWithProtocol(hostname) : domainTitle; - const isAllowedUrl = useCallback((url: string) => { + const isAllowedOrigin = useCallback((origin: string) => { const { PhishingController } = Engine.context; // Update phishing configuration if it is out-of-date @@ -185,23 +185,20 @@ const AccountConnect = (props: AccountConnectProps) => { // down network requests. The configuration is updated for the next request. PhishingController.maybeUpdateState(); - const phishingControllerTestResult = PhishingController.test(url); + const phishingControllerTestResult = PhishingController.test(origin); return !phishingControllerTestResult.result; }, []); useEffect(() => { const url = dappUrl || channelIdOrHostname || ''; - - const cleanUrl = url.replace(/^https?:\/\//, ''); - - const isAllowed = isAllowedUrl(cleanUrl); + const isAllowed = isAllowedOrigin(url); if (!isAllowed) { setBlockedUrl(dappUrl); setShowPhishingModal(true); } - }, [isAllowedUrl, dappUrl, channelIdOrHostname]); + }, [isAllowedOrigin, dappUrl, channelIdOrHostname]); const faviconSource = useFavicon( inappBrowserOrigin || (!isChannelId ? channelIdOrHostname : ''), diff --git a/app/components/Views/BrowserTab/index.js b/app/components/Views/BrowserTab/index.js index f63a6f53f34..2ba823c511e 100644 --- a/app/components/Views/BrowserTab/index.js +++ b/app/components/Views/BrowserTab/index.js @@ -433,9 +433,9 @@ export const BrowserTab = (props) => { }; /** - * Check if a hostname is allowed + * Check if an origin is allowed */ - const isAllowedUrl = useCallback((hostname) => { + const isAllowedOrigin = useCallback((origin) => { const { PhishingController } = Engine.context; // Update phishing configuration if it is out-of-date @@ -443,14 +443,14 @@ export const BrowserTab = (props) => { // down network requests. The configuration is updated for the next request. PhishingController.maybeUpdateState(); - const phishingControllerTestResult = PhishingController.test(hostname); + const phishingControllerTestResult = PhishingController.test(origin); // Only assign the if the hostname is on the block list if (phishingControllerTestResult.result) blockListType.current = phishingControllerTestResult.name; return ( - (allowList.current && allowList.current.includes(hostname)) || + (allowList.current && allowList.current.includes(origin)) || !phishingControllerTestResult.result ); }, []); @@ -570,7 +570,7 @@ export const BrowserTab = (props) => { async (url, initialCall) => { setIsResolvedIpfsUrl(false); const prefixedUrl = prefixUrlWithProtocol(url); - const { hostname, query, pathname } = new URL(prefixedUrl); + const { hostname, query, pathname, origin } = new URL(prefixedUrl); let urlToGo = prefixedUrl; const isEnsUrl = isENSUrl(url); const { current } = webviewRef; @@ -592,7 +592,7 @@ export const BrowserTab = (props) => { } } - if (isAllowedUrl(hostname)) { + if (isAllowedOrigin(origin)) { if (initialCall || !firstUrlLoaded) { setInitialUrl(urlToGo); setFirstUrlLoaded(true); @@ -616,7 +616,7 @@ export const BrowserTab = (props) => { handleNotAllowedUrl(urlToGo); return null; }, - [firstUrlLoaded, handleIpfsContent, isAllowedUrl], + [firstUrlLoaded, handleIpfsContent, isAllowedOrigin], ); /** @@ -872,7 +872,7 @@ export const BrowserTab = (props) => { * Return `true` to continue loading the request and `false` to stop loading. */ const onShouldStartLoadWithRequest = ({ url }) => { - const { hostname } = new URL(url); + const { origin } = new URL(url); // Stops normal loading when it's ens, instead call go to be properly set up if (isENSUrl(url)) { @@ -881,7 +881,7 @@ export const BrowserTab = (props) => { } // Cancel loading the page if we detect its a phishing page - if (!isAllowedUrl(hostname)) { + if (!isAllowedOrigin(origin)) { handleNotAllowedUrl(url); return false; } @@ -1081,19 +1081,14 @@ export const BrowserTab = (props) => { */ const onLoadStart = async ({ nativeEvent }) => { // Use URL to produce real url. This should be the actual website that the user is viewing. - const { - origin, - pathname = '', - query = '', - hostname, - } = new URL(nativeEvent.url); + const { origin, pathname = '', query = '' } = new URL(nativeEvent.url); // Reset the previous bridges backgroundBridges.current.length && backgroundBridges.current.forEach((bridge) => bridge.onDisconnect()); // Cancel loading the page if we detect its a phishing page - if (!isAllowedUrl(hostname)) { + if (!isAllowedOrigin(origin)) { handleNotAllowedUrl(url); return false; } diff --git a/app/core/Engine.ts b/app/core/Engine.ts index 9ba99efe7f9..4218e4e19fa 100644 --- a/app/core/Engine.ts +++ b/app/core/Engine.ts @@ -57,6 +57,8 @@ import { } from '@metamask/network-controller'; import { PhishingController, + PhishingControllerActions, + PhishingControllerEvents, PhishingControllerState, } from '@metamask/phishing-controller'; import { @@ -237,18 +239,6 @@ const encryptor = new Encryptor({ let currentChainId: any; ///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) -// TODO remove these custom types when the PhishingController is to version >= 7.0.0 -interface MaybeUpdateState { - type: `${PhishingController['name']}:maybeUpdateState`; - handler: PhishingController['maybeUpdateState']; -} - -interface TestOrigin { - type: `${PhishingController['name']}:testOrigin`; - handler: PhishingController['test']; -} - -type PhishingControllerActions = MaybeUpdateState | TestOrigin; type AuthenticationControllerActions = AuthenticationController.AllowedActions; type UserStorageControllerActions = UserStorageController.AllowedActions; type NotificationsServicesControllerActions = @@ -263,6 +253,7 @@ type SnapsGlobalActions = type SnapsGlobalEvents = | SnapControllerEvents | SubjectMetadataControllerEvents + | PhishingControllerEvents | SnapsAllowedEvents; ///: END:ONLY_INCLUDE_IF @@ -716,7 +707,6 @@ class Engine { }); const phishingController = new PhishingController({ - // @ts-expect-error TODO: Resolve mismatch between base-controller versions. messenger: this.controllerMessenger.getRestricted({ name: 'PhishingController', allowedActions: [], diff --git a/app/util/linkCheck.test.ts b/app/util/linkCheck.test.ts index ffd18494b6d..2aae0f734d9 100644 --- a/app/util/linkCheck.test.ts +++ b/app/util/linkCheck.test.ts @@ -5,8 +5,8 @@ jest.mock('../core/Engine', () => ({ context: { PhishingController: { maybeUpdateState: jest.fn(), - test: jest.fn((url: string) => { - if (url === 'phishing.com') return { result: true }; + test: jest.fn((origin: string) => { + if (origin === 'http://phishing.com') return { result: true }; return { result: false }; }), }, @@ -15,6 +15,7 @@ jest.mock('../core/Engine', () => ({ describe('linkCheck', () => { it('should correctly check links for safety', () => { + expect(isLinkSafe('example.com')).toEqual(false); expect(isLinkSafe('htps://ww.example.com/')).toEqual(false); expect(isLinkSafe('https://ww.example.com/')).toEqual(true); expect(isLinkSafe('http://example com/page?id=123')).toEqual(false); diff --git a/app/util/linkCheck.ts b/app/util/linkCheck.ts index caaf4afc701..e6e6c431523 100644 --- a/app/util/linkCheck.ts +++ b/app/util/linkCheck.ts @@ -8,12 +8,12 @@ const DENYLISTED_DOMAINS = ['metamask.app.link']; const isAllowedProtocol = (protocol: string): boolean => ALLOWED_PROTOCOLS.includes(protocol); -const isAllowedHostname = (hostname: string): boolean => { +const isAllowedUrl = ({ hostname, origin }: Url): boolean => { const { PhishingController } = Engine.context as { PhishingController: PhishingControllerClass; }; PhishingController.maybeUpdateState(); - const phishingControllerTestResult = PhishingController.test(hostname); + const phishingControllerTestResult = PhishingController.test(origin); return !( phishingControllerTestResult.result || DENYLISTED_DOMAINS.includes(hostname) @@ -23,10 +23,8 @@ const isAllowedHostname = (hostname: string): boolean => { export const isLinkSafe = (link: string): boolean => { try { const url = new Url(link); - const { protocol, hostname, href } = url; - return ( - isUrl(href) && isAllowedProtocol(protocol) && isAllowedHostname(hostname) - ); + const { protocol, href } = url; + return isUrl(href) && isAllowedProtocol(protocol) && isAllowedUrl(url); } catch (err) { return false; } diff --git a/app/util/test/initial-background-state.json b/app/util/test/initial-background-state.json index cdca788d31c..db24fb07aba 100644 --- a/app/util/test/initial-background-state.json +++ b/app/util/test/initial-background-state.json @@ -67,6 +67,7 @@ "selectedNetworkClientId": "mainnet" }, "PhishingController": { + "c2DomainBlocklistLastFetched": 0, "phishingLists": [], "whitelist": [], "hotlistLastFetched": 0, diff --git a/package.json b/package.json index b82cfbb8c55..7f55139a3bb 100644 --- a/package.json +++ b/package.json @@ -171,7 +171,7 @@ "@metamask/network-controller": "^20.1.0", "@metamask/notification-services-controller": "^0.2.1", "@metamask/permission-controller": "^11.0.0", - "@metamask/phishing-controller": "^9.0.0", + "@metamask/phishing-controller": "^12.0.3", "@metamask/post-message-stream": "^8.0.0", "@metamask/ppom-validator": "0.32.0", "@metamask/preferences-controller": "^11.0.0", diff --git a/yarn.lock b/yarn.lock index a98e130a51a..cf80f715bce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4178,10 +4178,10 @@ eth-ens-namehash "^2.0.8" fast-deep-equal "^3.1.3" -"@metamask/controller-utils@^11.0.0", "@metamask/controller-utils@^11.0.2": - version "11.0.2" - resolved "https://registry.yarnpkg.com/@metamask/controller-utils/-/controller-utils-11.0.2.tgz#4da5ef150c8bb2d2ccec2f422afa839749aeb5f0" - integrity sha512-d9iqDwEIWhlDFJ1So70MfQEG95cWKSQ1MP0NZqo/ho5++/MI+/5k3yyxMY9uVMKBNBxvqlcYX1cvjyUhPDIx/w== +"@metamask/controller-utils@^11.0.0", "@metamask/controller-utils@^11.0.2", "@metamask/controller-utils@^11.3.0": + version "11.3.0" + resolved "https://registry.yarnpkg.com/@metamask/controller-utils/-/controller-utils-11.3.0.tgz#530fd22289f717b752b4a7b6e504e1f2911b30a4" + integrity sha512-5b+Jg9sKKESzvQcuipHC1D7KSh98MVIi7hXQUk7iX+YVMl4KoKDv94Bl+li8g+jCBshMOV9bRMRh25/hdEvTZQ== dependencies: "@ethereumjs/util" "^8.1.0" "@metamask/eth-query" "^4.0.0" @@ -4860,7 +4860,21 @@ fastest-levenshtein "^1.0.16" punycode "^2.1.1" -"@metamask/phishing-controller@^9.0.0", "@metamask/phishing-controller@^9.0.1": +"@metamask/phishing-controller@^12.0.3": + version "12.0.3" + resolved "https://registry.yarnpkg.com/@metamask/phishing-controller/-/phishing-controller-12.0.3.tgz#f1a5a2046e4c7a04613b4c41bc19771cf8235db7" + integrity sha512-CR1qN2FkMJp+MyNSXVTvrZY7MjCdkvsofW/kyv6oshPtLV6BGBWFyueS2UgjMNsmQDW/vMXUJMZfcMS6rs3S4w== + dependencies: + "@metamask/base-controller" "^7.0.1" + "@metamask/controller-utils" "^11.3.0" + "@noble/hashes" "^1.4.0" + "@types/punycode" "^2.1.0" + eth-phishing-detect "^1.2.0" + ethereum-cryptography "^2.1.2" + fastest-levenshtein "^1.0.16" + punycode "^2.1.1" + +"@metamask/phishing-controller@^9.0.1": version "9.0.2" resolved "https://registry.yarnpkg.com/@metamask/phishing-controller/-/phishing-controller-9.0.2.tgz#d140b6a8a05947c59b04bf161a0d9055e6b711b2" integrity sha512-eZRbym8o7isHbu741GVztxD2rzKvNQvB+XbmAq6qJD6eMKSSYKC9r/nDaxzaVz9qO93XCE8WnvSUmJx4m8RtaA== From 69bc63fc6a3821ef6c110a2d64e21819641fcedd Mon Sep 17 00:00:00 2001 From: Frank von Hoven <141057783+frankvonhoven@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:00:00 -0500 Subject: [PATCH 18/27] chore: bump build to 1441 (#11541) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** ## **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. --- android/app/build.gradle | 2 +- bitrise.yml | 4 ++-- ios/MetaMask.xcodeproj/project.pbxproj | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 8279218ed1f..c103d97dbf1 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -173,7 +173,7 @@ android { applicationId "io.metamask" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1439 + versionCode 1441 versionName "7.32.0" testBuildType System.getProperty('testBuildType', 'debug') missingDimensionStrategy 'react-native-camera', 'general' diff --git a/bitrise.yml b/bitrise.yml index a1426348545..25aaec99ee9 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -1500,13 +1500,13 @@ app: VERSION_NAME: 7.32.0 - opts: is_expand: false - VERSION_NUMBER: 1439 + VERSION_NUMBER: 1441 - opts: is_expand: false FLASK_VERSION_NAME: 7.32.0 - opts: is_expand: false - FLASK_VERSION_NUMBER: 1439 + FLASK_VERSION_NUMBER: 1441 - opts: is_expand: false ANDROID_APK_LINK: '' diff --git a/ios/MetaMask.xcodeproj/project.pbxproj b/ios/MetaMask.xcodeproj/project.pbxproj index 37d7399d6b7..18fcb36ea00 100644 --- a/ios/MetaMask.xcodeproj/project.pbxproj +++ b/ios/MetaMask.xcodeproj/project.pbxproj @@ -1273,7 +1273,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1439; + CURRENT_PROJECT_VERSION = 1441; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1338,7 +1338,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1439; + CURRENT_PROJECT_VERSION = 1441; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1401,7 +1401,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1439; + CURRENT_PROJECT_VERSION = 1441; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1462,7 +1462,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1439; + CURRENT_PROJECT_VERSION = 1441; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1624,7 +1624,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1439; + CURRENT_PROJECT_VERSION = 1441; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1692,7 +1692,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1439; + CURRENT_PROJECT_VERSION = 1441; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; From 769eeba1cddfb1b16c0e3d668ec4aaf32371a83e Mon Sep 17 00:00:00 2001 From: "runway-github[bot]" <73448015+runway-github[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 15:19:03 -0700 Subject: [PATCH 19/27] chore(runway): cherry-pick fix(11482): incorrect QR code error (#11531) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fix(11482): incorrect QR code error (#11518) ## **Description** When scanning a invalid QR code you're given a deeplink error instead of a QR code error. ## **Related issues** Fixes: [#11482 ](https://github.com/MetaMask/metamask-mobile/issues/11482) ## **Manual testing steps** 1. Goto the wallet homepage 2. Click on the QR code icon at the top right 3. Scan a incorrect QR code (a QR account that isn't associated with any address) 4. View [issue](https://github.com/MetaMask/metamask-mobile/issues/11482) to see recording if needed ## **Screenshots/Recordings** | Before | After | |:---:|:---:| |![before](https://github.com/user-attachments/assets/e811b492-e6f1-45e7-b8a6-229ac18203d6)|![after](https://github.com/user-attachments/assets/303d5aea-c186-47b8-9699-1629ab99d84d)| ### **Before** NA ### **After** NA ## **Pre-merge author checklist** - [x] 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). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] 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** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] 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. [f2a253c](https://github.com/MetaMask/metamask-mobile/commit/f2a253c153f67994c266a82052bfd6fea58dafa2) Co-authored-by: Vince Howard --- .../DeeplinkManager/ParseManager/parseDeeplink.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/core/DeeplinkManager/ParseManager/parseDeeplink.ts b/app/core/DeeplinkManager/ParseManager/parseDeeplink.ts index 4c799701503..7bca677e35e 100644 --- a/app/core/DeeplinkManager/ParseManager/parseDeeplink.ts +++ b/app/core/DeeplinkManager/ParseManager/parseDeeplink.ts @@ -10,6 +10,7 @@ import handleUniversalLink from './handleUniversalLink'; import connectWithWC from './connectWithWC'; import { Alert } from 'react-native'; import { strings } from '../../../../locales/i18n'; +import AppConstants from '../../../core/AppConstants'; function parseDeeplink({ deeplinkManager: instance, @@ -98,8 +99,14 @@ function parseDeeplink({ error as Error, 'DeepLinkManager:parse error parsing deeplink', ); - - Alert.alert(strings('deeplink.invalid'), `Invalid URL: ${url}`); + if (origin === AppConstants.DEEPLINKS.ORIGIN_QR_CODE) { + Alert.alert( + strings('qr_scanner.unrecognized_address_qr_code_title'), + strings('qr_scanner.unrecognized_address_qr_code_desc'), + ); + } else { + Alert.alert(strings('deeplink.invalid'), `Invalid URL: ${url}`); + } } return false; From e2090a8b65aebe0667af759b16bc9746a3ef2919 Mon Sep 17 00:00:00 2001 From: "runway-github[bot]" <73448015+runway-github[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 15:19:26 -0700 Subject: [PATCH 20/27] chore(runway): cherry-pick fix(11481): android system alert respects dark mode themes (#11555) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fix(11481): android system alert respects dark mode themes (#11552) ## **Description** In android our system alerts do not respect our dark mode themes as they should. KNOWN BUG: When switching from light mode to dark mode the changes to Android system alerts do not happen. The user is forced to close the app and reopen and the alerts will be in dark mode. However switching from dark to light mode is fine. Would love feedback on how to solve this issue I've created an issue for this bug [here](https://github.com/MetaMask/metamask-mobile/issues/11553) ## **Related issues** Fixes: [#11481](https://github.com/MetaMask/metamask-mobile/issues/11481) ## **Manual testing steps** 1. Make sure you're using an Android device, rebuild or reinstall a fresh app to make sure the changes are there. 2. Goto Android system settings and select light mode 3. Goto the MetaMask wallet home page 4. Click on the QR code on the top right 5. Scan an invalid QR code 6. See that the system alert is white background with black text 7. Go to the Android settings and change it to dark mode 8. Go back to the MetaMask app and scan the invalid QR code again, you should see a dark alert with white text. If not force close the app and reopen and the changes will take place ## **Screenshots/Recordings** NA ### **Before** | Dark Mode (broken) | Light Mode (not broken) |:---:|:---:| |![Screenshot_1727810220](https://github.com/user-attachments/assets/c0d3dd99-6939-4199-918f-d127b1bd6dd7)|![Screenshot_1727809386](https://github.com/user-attachments/assets/7a1e40fb-236d-4de4-811d-7c6d3155691e)|| ### **After** | Dark Mode | Light Mode | |:---:|:---:| |![darkmode](https://github.com/user-attachments/assets/543b0c4d-6e69-48c2-b965-2764378f6f03)]|![Screenshot_1727809386](https://github.com/user-attachments/assets/7a1e40fb-236d-4de4-811d-7c6d3155691e)| ## **Pre-merge author checklist** - [x] 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). - [x] 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 - [x] 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** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] 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. [95f9a91](https://github.com/MetaMask/metamask-mobile/commit/95f9a91a3e2c7a437cd5c3ffc6b429c9af4b9a73) Co-authored-by: Vince Howard --- android/app/src/main/res/values-night/colors.xml | 2 ++ android/app/src/main/res/values-night/styles.xml | 14 +++++++++++++- android/app/src/main/res/values/colors.xml | 2 ++ android/app/src/main/res/values/styles.xml | 12 ++++++++++++ android/app/src/qa/res/values-night/colors.xml | 2 ++ android/app/src/qa/res/values-night/styles.xml | 13 ++++++++++++- android/app/src/qa/res/values/colors.xml | 2 ++ android/app/src/qa/res/values/styles.xml | 11 +++++++++++ 8 files changed, 56 insertions(+), 2 deletions(-) diff --git a/android/app/src/main/res/values-night/colors.xml b/android/app/src/main/res/values-night/colors.xml index 4483fb4874b..5219ed36746 100644 --- a/android/app/src/main/res/values-night/colors.xml +++ b/android/app/src/main/res/values-night/colors.xml @@ -4,4 +4,6 @@ #FFFFFF #000000 #EBEBED + #000000 + #FFFFFF diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml index 6d59e168d49..effab25c978 100644 --- a/android/app/src/main/res/values-night/styles.xml +++ b/android/app/src/main/res/values-night/styles.xml @@ -1,7 +1,7 @@ - + + + + + diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml index 0dc4d5454dc..ac52d4d960b 100644 --- a/android/app/src/main/res/values/colors.xml +++ b/android/app/src/main/res/values/colors.xml @@ -4,4 +4,6 @@ #000000 #000000 #EBEBED + #000000 + #FFFFFF diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index db2cc884a80..c3fa11e5bde 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -21,4 +21,16 @@ @color/themeInverse + + + + + diff --git a/android/app/src/qa/res/values-night/colors.xml b/android/app/src/qa/res/values-night/colors.xml index 4483fb4874b..5219ed36746 100644 --- a/android/app/src/qa/res/values-night/colors.xml +++ b/android/app/src/qa/res/values-night/colors.xml @@ -4,4 +4,6 @@ #FFFFFF #000000 #EBEBED + #000000 + #FFFFFF diff --git a/android/app/src/qa/res/values-night/styles.xml b/android/app/src/qa/res/values-night/styles.xml index 6d59e168d49..eb80fa2c78f 100644 --- a/android/app/src/qa/res/values-night/styles.xml +++ b/android/app/src/qa/res/values-night/styles.xml @@ -1,7 +1,7 @@ - + + + + diff --git a/android/app/src/qa/res/values/colors.xml b/android/app/src/qa/res/values/colors.xml index 0dc4d5454dc..ac52d4d960b 100644 --- a/android/app/src/qa/res/values/colors.xml +++ b/android/app/src/qa/res/values/colors.xml @@ -4,4 +4,6 @@ #000000 #000000 #EBEBED + #000000 + #FFFFFF diff --git a/android/app/src/qa/res/values/styles.xml b/android/app/src/qa/res/values/styles.xml index db2cc884a80..b14c8bb69fa 100644 --- a/android/app/src/qa/res/values/styles.xml +++ b/android/app/src/qa/res/values/styles.xml @@ -21,4 +21,15 @@ @color/themeInverse + + + + From 7788094ff8928d0ae83b874cc8d5916e0c85b607 Mon Sep 17 00:00:00 2001 From: "runway-github[bot]" <73448015+runway-github[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 16:25:06 -0700 Subject: [PATCH 21/27] chore(runway): cherry-pick fix: Fix/use portfolio home page (#11561) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fix: Fix/use portfolio home page (#11554) ## **Description** Replaces the browser home page from `https://home.metamask.io` to `https://portfolio.metamask.io/explore?MetaMaskEntry=mobile` ## **Related issues** Fixes: Thread - https://consensys.slack.com/archives/C07PHNQ61SA/p1727810879864649 ## **Manual testing steps** 1. Install a new version of the app 2. Switch to the browser tab 3. Default home page should show portfolio explore page ## **Screenshots/Recordings** ### **Before** ### **After** https://github.com/user-attachments/assets/5211a097-baaf-4489-9cd4-b69871321f26 Old MM home still works image ## **Pre-merge author checklist** - [x] 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). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] 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** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] 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. [3084746](https://github.com/MetaMask/metamask-mobile/commit/3084746576cf803080f8c46a0dd265dd332db076) Co-authored-by: Cal Leung --- app/components/Views/BrowserTab/index.js | 7 ++-- app/core/AppConstants.ts | 5 ++- app/util/browser/index.test.ts | 12 +++---- e2e/fixtures/fixture-builder.js | 2 +- e2e/specs/browser/browser-tests.spec.js | 34 +++++++++---------- wdio/features/BrowserFlow/AddFavorite.feature | 4 +-- wdio/features/BrowserFlow/AddressBar.feature | 4 +-- wdio/features/BrowserFlow/InvalidURL.feature | 2 +- .../BrowserFlow/NavigationControls.feature | 2 +- wdio/features/BrowserFlow/OptionMenu.feature | 2 +- .../BrowserFlow/PhishingDetection.feature | 2 +- .../BrowserScreen/AddressBar.testIds.js | 2 +- wdio/step-definitions/browser-steps.js | 2 +- 13 files changed, 43 insertions(+), 37 deletions(-) diff --git a/app/components/Views/BrowserTab/index.js b/app/components/Views/BrowserTab/index.js index 2ba823c511e..501058acb1d 100644 --- a/app/components/Views/BrowserTab/index.js +++ b/app/components/Views/BrowserTab/index.js @@ -113,7 +113,8 @@ import handleWebViewFocus from '../../../util/browser/webViewFocus'; import { isTest } from '../../../util/test/utils.js'; import { EXTERNAL_LINK_TYPE } from '../../../constants/browser'; -const { HOMEPAGE_URL, NOTIFICATION_NAMES } = AppConstants; +const { HOMEPAGE_URL, NOTIFICATION_NAMES, OLD_HOMEPAGE_URL_HOST } = + AppConstants; const HOMEPAGE_HOST = new URL(HOMEPAGE_URL)?.hostname; const MM_MIXPANEL_TOKEN = process.env.MM_MIXPANEL_TOKEN; @@ -363,7 +364,9 @@ export const BrowserTab = (props) => { const currentPage = checkUrl || url.current; const prefixedUrl = prefixUrlWithProtocol(currentPage); const { host: currentHost } = getUrlObj(prefixedUrl); - return currentHost === HOMEPAGE_HOST; + return ( + currentHost === HOMEPAGE_HOST || currentHost === OLD_HOMEPAGE_URL_HOST + ); }, []); const notifyAllConnections = useCallback((payload, restricted = true) => { diff --git a/app/core/AppConstants.ts b/app/core/AppConstants.ts index 09629b44e22..e9a3ee64d44 100644 --- a/app/core/AppConstants.ts +++ b/app/core/AppConstants.ts @@ -40,7 +40,10 @@ export default { MM_UNIVERSAL_LINK_HOST: 'metamask.app.link', MM_DEEP_ITMS_APP_LINK: 'https://metamask.app.link/skAH3BaF99', SAI_ADDRESS: '0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359', - HOMEPAGE_URL: process.env.MM_HOMEPAGE || 'https://home.metamask.io/', + HOMEPAGE_URL: + process.env.MM_HOMEPAGE || + 'https://portfolio.metamask.io/explore?MetaMaskEntry=mobile/', + OLD_HOMEPAGE_URL_HOST: 'home.metamask.io', SHORT_HOMEPAGE_URL: 'MetaMask.io', ZERO_ADDRESS: '0x0000000000000000000000000000000000000000', USER_AGENT: Device.isAndroid() diff --git a/app/util/browser/index.test.ts b/app/util/browser/index.test.ts index ecc6ac7d95a..42bac963a0d 100644 --- a/app/util/browser/index.test.ts +++ b/app/util/browser/index.test.ts @@ -207,7 +207,7 @@ describe('Browser utils :: trustedProtocolToDeeplink', () => { expect(trustedProtocolToDeeplink.includes(protocol)).toBeTruthy(); }); it('should match metamask: protocol', () => { - const { protocol } = new URL('metamask://dapp/home.metamask.io'); + const { protocol } = new URL('metamask://dapp/portfolio.metamask.io'); expect(trustedProtocolToDeeplink.includes(protocol)).toBeTruthy(); }); @@ -217,27 +217,27 @@ describe('Browser utils :: trustedProtocolToDeeplink', () => { expect(trustedProtocolToDeeplink.includes(protocol)).toBeTruthy(); }); it('should match dapp: protocol', () => { - const { protocol } = new URL('dapp://home.metamask.io'); + const { protocol } = new URL('dapp://portfolio.metamask.io'); expect(trustedProtocolToDeeplink.includes(protocol)).toBeTruthy(); }); it('should not match eth: protocol', () => { - const { protocol } = new URL('eth://home.metamask.io'); + const { protocol } = new URL('eth://portfolio.metamask.io'); expect(trustedProtocolToDeeplink.includes(protocol)).toBeFalsy(); }); it('should not match tel: protocol', () => { - const { protocol } = new URL('tel://home.metamask.io'); + const { protocol } = new URL('tel://portfolio.metamask.io'); expect(trustedProtocolToDeeplink.includes(protocol)).toBeFalsy(); }); it('should not match mailto: protocol', () => { - const { protocol } = new URL('mailto://home.metamask.io'); + const { protocol } = new URL('mailto://portfolio.metamask.io'); expect(trustedProtocolToDeeplink.includes(protocol)).toBeFalsy(); }); it('should not match ldap: protocol', () => { - const { protocol } = new URL('ldap://home.metamask.io'); + const { protocol } = new URL('ldap://portfolio.metamask.io'); expect(trustedProtocolToDeeplink.includes(protocol)).toBeFalsy(); }); diff --git a/e2e/fixtures/fixture-builder.js b/e2e/fixtures/fixture-builder.js index 471cb7f950f..374b63e76fb 100644 --- a/e2e/fixtures/fixture-builder.js +++ b/e2e/fixtures/fixture-builder.js @@ -380,7 +380,7 @@ class FixtureBuilder { whitelist: [], tabs: [ { - url: 'https://home.metamask.io/', + url: 'https://portfolio.metamask.io/explore?MetaMaskEntry=mobile/', id: 1692550481062, }, ], diff --git a/e2e/specs/browser/browser-tests.spec.js b/e2e/specs/browser/browser-tests.spec.js index 07361e61440..65df03117b1 100644 --- a/e2e/specs/browser/browser-tests.spec.js +++ b/e2e/specs/browser/browser-tests.spec.js @@ -53,27 +53,27 @@ describe(SmokeCore('Browser Tests'), () => { await Browser.waitForBrowserPageToLoad(); }); - it('should add the test dapp to favorites', async () => { - // Check that we are still on the browser screen + // it('should add the test dapp to favorites', async () => { + // // Check that we are still on the browser screen - // Tap on options - await Browser.tapOptionsButton(); - await Browser.tapAddToFavoritesButton(); + // // Tap on options + // await Browser.tapOptionsButton(); + // await Browser.tapAddToFavoritesButton(); - await Assertions.checkIfVisible(AddBookmarkView.container); + // await Assertions.checkIfVisible(AddBookmarkView.container); - await AddBookmarkView.tapAddBookmarksButton(); - await Assertions.checkIfNotVisible(AddBookmarkView.container); - }); + // await AddBookmarkView.tapAddBookmarksButton(); + // await Assertions.checkIfNotVisible(AddBookmarkView.container); + // }); - it('should tap on the test dapp in favorites on the home page', async () => { - await Browser.tapHomeButton(); - // Wait for page to load - await TestHelpers.delay(3000); - await Browser.tapDappInFavorites(); - await Assertions.checkIfTextIsDisplayed('metamask.github.io'); - // } - }); + // it('should tap on the test dapp in favorites on the home page', async () => { + // await Browser.tapHomeButton(); + // // Wait for page to load + // await TestHelpers.delay(3000); + // await Browser.tapDappInFavorites(); + // await Assertions.checkIfTextIsDisplayed('metamask.github.io'); + // // } + // }); it('should test invalid URL', async () => { await TestHelpers.delay(2000); diff --git a/wdio/features/BrowserFlow/AddFavorite.feature b/wdio/features/BrowserFlow/AddFavorite.feature index 60bfd4c7329..106cc46e446 100644 --- a/wdio/features/BrowserFlow/AddFavorite.feature +++ b/wdio/features/BrowserFlow/AddFavorite.feature @@ -4,7 +4,7 @@ Feature: Browser Add Favorite Scenario: Adding browser Favorites - Add, click and delete favorites. Display favorites in the Favorites tab of home.metamask.io + Add, click and delete favorites. Display favorites in the Favorites tab of portfolio.metamask.io Given the app displayed the splash animation And I have imported my wallet @@ -22,7 +22,7 @@ Feature: Browser Add Favorite And Url field is pre populated with "https://app.uniswap.org/" When I tap on "Cancel" on the Add Favorite Screen Then the "https://app.uniswap.org/" is displayed in the browser tab - And the favorite is not added on the home "https://home.metamask.io" page + And the favorite is not added on the home "https://portfolio.metamask.io/explore?MetaMaskEntry=mobile" page When I navigate to "https://uniswap.exchange" And I tap on browser control menu icon on the bottom right of the browser view And I tap the "Add to Favorites" option on the Option Menu diff --git a/wdio/features/BrowserFlow/AddressBar.feature b/wdio/features/BrowserFlow/AddressBar.feature index 0c896b35592..f8beb0f624a 100644 --- a/wdio/features/BrowserFlow/AddressBar.feature +++ b/wdio/features/BrowserFlow/AddressBar.feature @@ -13,12 +13,12 @@ Feature: Browser Address Bar And I have 1 browser tab displayed When I tap on address bar Then browser address view is displayed - And the "https://home.metamask.io/" url is displayed in address field + And the "https://portfolio.metamask.io/explore?MetaMaskEntry=mobile/" url is displayed in address field When I tap on "clear icon" in address field Then address field is cleared When I tap on "Cancel button" in address field Then browser address bar input view is no longer displayed - And the browser view is on the "https://home.metamask.io/" website + And the browser view is on the "https://portfolio.metamask.io/explore?MetaMaskEntry=mobile/" website When I tap on address bar And I navigate to "reddit.com" Then the browser view is on the "https://www.reddit.com/" website diff --git a/wdio/features/BrowserFlow/InvalidURL.feature b/wdio/features/BrowserFlow/InvalidURL.feature index 23e27aaed84..2079b53c069 100644 --- a/wdio/features/BrowserFlow/InvalidURL.feature +++ b/wdio/features/BrowserFlow/InvalidURL.feature @@ -13,4 +13,4 @@ Feature: Browser Invalid URL Then I should see "Something went wrong" error title And I should see "We couldn't load that page" error message When I tap on the Return button from the error page - Then the browser view is on the "https://home.metamask.io/" website + Then the browser view is on the "https://portfolio.metamask.io/explore?MetaMaskEntry=mobile/" website diff --git a/wdio/features/BrowserFlow/NavigationControls.feature b/wdio/features/BrowserFlow/NavigationControls.feature index 0f60d66bdb2..370145f8472 100644 --- a/wdio/features/BrowserFlow/NavigationControls.feature +++ b/wdio/features/BrowserFlow/NavigationControls.feature @@ -16,7 +16,7 @@ Feature: Browser Control Options When I navigate to "reddit.com" Then the browser view is on the "https://www.reddit.com/" website And I tap on the back arrow control button - Then the browser view is on the "https://home.metamask.io/" website + Then the browser view is on the "https://portfolio.metamask.io/explore?MetaMaskEntry=mobile/" website When I tap on browser tab button with count 1 Then multi browser tab view is displayed When I tap on "Add" button on multi browser tab view diff --git a/wdio/features/BrowserFlow/OptionMenu.feature b/wdio/features/BrowserFlow/OptionMenu.feature index 2da183e7b0d..4e11df6debd 100644 --- a/wdio/features/BrowserFlow/OptionMenu.feature +++ b/wdio/features/BrowserFlow/OptionMenu.feature @@ -41,4 +41,4 @@ Feature: Browser Options Menu And "New tab" option item is displayed in browser options menu When I tap the "New Tab" option on the Option Menu Then new browser tab is added - #And the browser view is on the "https://home.metamask.io/" website + #And the browser view is on the "https://portfolio.metamask.io/explore?MetaMaskEntry=mobile/" website diff --git a/wdio/features/BrowserFlow/PhishingDetection.feature b/wdio/features/BrowserFlow/PhishingDetection.feature index 9bea18e0f3d..43d03a4ec0b 100644 --- a/wdio/features/BrowserFlow/PhishingDetection.feature +++ b/wdio/features/BrowserFlow/PhishingDetection.feature @@ -14,4 +14,4 @@ Feature: Browser Phishing Detection When I navigate to "http://www.empowr.com/FanFeed/Home.aspx" Then I should see a warning screen with Ethereum Phishing Detection title When I tap the Back button on Phishing Detection page - And the browser view is on the "https://home.metamask.io/" website + And the browser view is on the "https://portfolio.metamask.io/explore?MetaMaskEntry=mobile/" website diff --git a/wdio/screen-objects/testIDs/BrowserScreen/AddressBar.testIds.js b/wdio/screen-objects/testIDs/BrowserScreen/AddressBar.testIds.js index 771cc75cee1..9786dcb5645 100644 --- a/wdio/screen-objects/testIDs/BrowserScreen/AddressBar.testIds.js +++ b/wdio/screen-objects/testIDs/BrowserScreen/AddressBar.testIds.js @@ -4,6 +4,6 @@ export const CANCEL_BUTTON_ON_BROWSER_ID = 'cancel-url-button'; export const UNISWAP_SUGGESTION = 'https://uniswap.exchange/'; -export const HOME_SUGGESTION = 'https://home.metamask.io/'; +export const HOME_SUGGESTION = 'https://portfolio.metamask.io/explore?MetaMaskEntry=mobile/'; export const URL_CLEAR_ICON = 'url-clear-icon'; diff --git a/wdio/step-definitions/browser-steps.js b/wdio/step-definitions/browser-steps.js index 12b11d316c1..8d6f55473bf 100644 --- a/wdio/step-definitions/browser-steps.js +++ b/wdio/step-definitions/browser-steps.js @@ -22,7 +22,7 @@ const TEST_DAPP = 'https://metamask.github.io/test-dapp/'; Given(/^I am on Home MetaMask website$/, async () => { await ExternalWebsitesScreen.isHomeFavoriteButtonDisplayed(); await BrowserScreen.tapUrlBar(); - await AddressBarScreen.isUrlValueContains('https://home.metamask.io/'); + await AddressBarScreen.isUrlValueContains('https://portfolio.metamask.io/explore?MetaMaskEntry=mobile/'); await AddressBarScreen.tapUrlCancelButton(); }); From 2c06427ce302f3b79f6eba012bdbb1bd105e3b18 Mon Sep 17 00:00:00 2001 From: Cal Leung Date: Tue, 1 Oct 2024 16:27:12 -0700 Subject: [PATCH 22/27] chore: Chore/7.32.0 bump to 1442 (#11560) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Bump 7.32.0 to 1442 for RC 5 ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [x] 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). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] 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** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] 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. --- android/app/build.gradle | 2 +- bitrise.yml | 4 ++-- ios/MetaMask.xcodeproj/project.pbxproj | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index c103d97dbf1..7a1834bed36 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -173,7 +173,7 @@ android { applicationId "io.metamask" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1441 + versionCode 1442 versionName "7.32.0" testBuildType System.getProperty('testBuildType', 'debug') missingDimensionStrategy 'react-native-camera', 'general' diff --git a/bitrise.yml b/bitrise.yml index 25aaec99ee9..01efb1ad891 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -1500,13 +1500,13 @@ app: VERSION_NAME: 7.32.0 - opts: is_expand: false - VERSION_NUMBER: 1441 + VERSION_NUMBER: 1442 - opts: is_expand: false FLASK_VERSION_NAME: 7.32.0 - opts: is_expand: false - FLASK_VERSION_NUMBER: 1441 + FLASK_VERSION_NUMBER: 1442 - opts: is_expand: false ANDROID_APK_LINK: '' diff --git a/ios/MetaMask.xcodeproj/project.pbxproj b/ios/MetaMask.xcodeproj/project.pbxproj index 18fcb36ea00..7c8be017686 100644 --- a/ios/MetaMask.xcodeproj/project.pbxproj +++ b/ios/MetaMask.xcodeproj/project.pbxproj @@ -1273,7 +1273,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1441; + CURRENT_PROJECT_VERSION = 1442; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1338,7 +1338,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1441; + CURRENT_PROJECT_VERSION = 1442; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1401,7 +1401,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1441; + CURRENT_PROJECT_VERSION = 1442; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1462,7 +1462,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1441; + CURRENT_PROJECT_VERSION = 1442; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1624,7 +1624,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1441; + CURRENT_PROJECT_VERSION = 1442; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1692,7 +1692,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1441; + CURRENT_PROJECT_VERSION = 1442; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; From e3047977890403484d8fc5edeb11f1c8ad00a5b6 Mon Sep 17 00:00:00 2001 From: Salim TOUBAL Date: Wed, 2 Oct 2024 21:29:58 +0200 Subject: [PATCH 23/27] chore: cherry pick #11524 (#11571) This PR cherry-picks #11524 --- .../NetworkSelector/NetworkSelector.test.tsx | 68 +++++++++++++++++++ .../Views/NetworkSelector/NetworkSelector.tsx | 15 ++-- .../NetworkSelector.test.tsx.snap | 8 +-- 3 files changed, 83 insertions(+), 8 deletions(-) diff --git a/app/components/Views/NetworkSelector/NetworkSelector.test.tsx b/app/components/Views/NetworkSelector/NetworkSelector.test.tsx index 9fe6db9eb2e..9e53784fbb2 100644 --- a/app/components/Views/NetworkSelector/NetworkSelector.test.tsx +++ b/app/components/Views/NetworkSelector/NetworkSelector.test.tsx @@ -207,6 +207,7 @@ describe('Network Selector', () => { expect(testNetworksSwitch.props.value).toBeTruthy(); expect(testNetworksSwitch.props.disabled).toBeTruthy(); }); + it('changes to non infura network when another network cell is pressed', async () => { const { getByText } = renderComponent(initialState); const gnosisCell = getByText('Gnosis Chain'); @@ -238,6 +239,7 @@ describe('Network Selector', () => { expect(mockEngine.context.NetworkController.setActiveNetwork).toBeCalled(); }); + it('renders correctly with no network configurations', async () => { (isNetworkUiRedesignEnabled as jest.Mock).mockImplementation(() => true); const stateWithNoNetworkConfigurations = { @@ -287,4 +289,70 @@ describe('Network Selector', () => { fireEvent.press(rpcOption); }); }); + + // Add this test for selecting between two Polygon networks + it('should select only one Polygon network when two networks with different RPC URLs exist', async () => { + jest.clearAllMocks(); // Clears mock data, ensuring that no mock has been called + jest.resetAllMocks(); // Resets mock implementation and mock instances + + const customState = { + ...initialState, + engine: { + backgroundState: { + ...initialState.engine.backgroundState, + NetworkController: { + networkConfigurations: { + polygonNetwork1: { + chainId: '0x89', // Polygon Mainnet + nickname: 'Polygon Mainnet 1', + rpcUrl: 'https://polygon-mainnet-1.rpc', + ticker: 'POL', + }, + polygonNetwork2: { + chainId: '0x89', // Polygon Mainnet (same chainId, different RPC URL) + nickname: 'Polygon Mainnet 2', + rpcUrl: 'https://polygon-mainnet-2.rpc', + ticker: 'POL', + }, + }, + }, + }, + }, + }; + + ( + Engine.context.NetworkController.getNetworkClientById as jest.Mock + ).mockReturnValue({ + configuration: { + chainId: '0x89', // Polygon Mainnet + nickname: 'Polygon Mainnet 1', + rpcUrl: 'https://polygon-mainnet-1.rpc', + ticker: 'POL', + type: 'custom', + }, + }); + + const { getByText, queryByTestId } = renderComponent(customState); + + // Ensure both networks are rendered + const polygonNetwork1 = getByText('Polygon Mainnet 1'); + const polygonNetwork2 = getByText('Polygon Mainnet 2'); + expect(polygonNetwork1).toBeTruthy(); + expect(polygonNetwork2).toBeTruthy(); + + // Select the first network + fireEvent.press(polygonNetwork1); + + // Wait for the selection to be applied + await waitFor(() => { + const polygonNetwork1Selected = queryByTestId( + 'Polygon Mainnet 1-selected', + ); + expect(polygonNetwork1Selected).toBeTruthy(); + }); + + // Assert that the second network is NOT selected + const polygonNetwork2Selected = queryByTestId('Polygon Mainnet 2-selected'); + expect(polygonNetwork2Selected).toBeNull(); // Not selected + }); }); diff --git a/app/components/Views/NetworkSelector/NetworkSelector.tsx b/app/components/Views/NetworkSelector/NetworkSelector.tsx index 61414175187..eb18992d684 100644 --- a/app/components/Views/NetworkSelector/NetworkSelector.tsx +++ b/app/components/Views/NetworkSelector/NetworkSelector.tsx @@ -466,7 +466,7 @@ const NetworkSelector = () => { imageSource: images['LINEA-MAINNET'], size: avatarSize, }} - isSelected={chainId === selectedChainId} + isSelected={chainId === selectedChainId && !providerConfig.rpcUrl} onPress={() => onNetworkChange(LINEA_MAINNET)} /> ); @@ -520,7 +520,8 @@ const NetworkSelector = () => { return ( { imageSource: image, size: avatarSize, }} - isSelected={Boolean(chainId === selectedChainId && selectedRpcUrl)} + isSelected={Boolean( + chainId === selectedChainId && selectedRpcUrl === rpcUrl, + )} onPress={() => onSetRpcTarget(rpcUrl)} style={styles.networkCell} - /> + > + {Boolean( + chainId === selectedChainId && selectedRpcUrl === rpcUrl, + ) && } + ); }, ); diff --git a/app/components/Views/NetworkSelector/__snapshots__/NetworkSelector.test.tsx.snap b/app/components/Views/NetworkSelector/__snapshots__/NetworkSelector.test.tsx.snap index d8ceabfffba..b477c66e1b6 100644 --- a/app/components/Views/NetworkSelector/__snapshots__/NetworkSelector.test.tsx.snap +++ b/app/components/Views/NetworkSelector/__snapshots__/NetworkSelector.test.tsx.snap @@ -694,7 +694,7 @@ exports[`Network Selector renders correctly 1`] = ` "position": "relative", } } - testID="cellselect" + testID="network-cell-Avalanche Mainnet C-Chain" > Date: Wed, 2 Oct 2024 16:25:18 -0500 Subject: [PATCH 24/27] chore(runway): cherry-pick fix: Fix invalid browser url crash (#11583) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fix: Fix invalid browser url crash (#11581) ## **Description** This PR fixes the crash in 7.32.0 for invalid URL in browser. The global `URL` didn't catch invalid urls gracefully, so we are using the lib `url-parser` ## **Related issues** Fixes: #11479 ## **Manual testing steps** 1. Go to browser 2. Enter `httttps://app.uniswap.org` 3. Website shows invalid url scheme 4. App doesn't crash ## **Screenshots/Recordings** ### **Before** ### **After** https://github.com/user-attachments/assets/2980c632-ed4b-48a4-976c-b1da54c590ce ## **Pre-merge author checklist** - [x] 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). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] 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** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] 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. [e80a1e8](https://github.com/MetaMask/metamask-mobile/commit/e80a1e83a835492eb732f6b037f8702247e7e714) Co-authored-by: Cal Leung --- app/components/UI/AccountRightButton/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/components/UI/AccountRightButton/index.tsx b/app/components/UI/AccountRightButton/index.tsx index 84351a1b056..4b21c8a6702 100644 --- a/app/components/UI/AccountRightButton/index.tsx +++ b/app/components/UI/AccountRightButton/index.tsx @@ -29,6 +29,7 @@ import { MetaMetricsEvents } from '../../../core/Analytics'; import { AccountOverviewSelectorsIDs } from '../../../../e2e/selectors/AccountOverview.selectors'; import { useMetrics } from '../../../components/hooks/useMetrics'; import { useNetworkInfo } from '../../../selectors/selectedNetworkController'; +import UrlParser from 'url-parse'; const styles = StyleSheet.create({ leftButton: { @@ -138,7 +139,7 @@ const AccountRightButton = ({ const currentUrl = route.params?.url; let hostname; if (currentUrl) { - hostname = new URL(currentUrl).hostname; + hostname = new UrlParser(currentUrl)?.hostname; } const { networkName, networkImageSource } = useNetworkInfo(hostname); From e2b7b2b3bfc59032c0148003eeb205ab430bc721 Mon Sep 17 00:00:00 2001 From: Frank von Hoven <141057783+frankvonhoven@users.noreply.github.com> Date: Wed, 2 Oct 2024 16:51:46 -0500 Subject: [PATCH 25/27] chore: bump to 1443 (#11584) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Bump build to 1443 ## **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. --- android/app/build.gradle | 2 +- bitrise.yml | 4 ++-- ios/MetaMask.xcodeproj/project.pbxproj | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 7a1834bed36..e8b0f214c2f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -173,7 +173,7 @@ android { applicationId "io.metamask" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1442 + versionCode 1443 versionName "7.32.0" testBuildType System.getProperty('testBuildType', 'debug') missingDimensionStrategy 'react-native-camera', 'general' diff --git a/bitrise.yml b/bitrise.yml index 01efb1ad891..56ae7b46b39 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -1500,13 +1500,13 @@ app: VERSION_NAME: 7.32.0 - opts: is_expand: false - VERSION_NUMBER: 1442 + VERSION_NUMBER: 1443 - opts: is_expand: false FLASK_VERSION_NAME: 7.32.0 - opts: is_expand: false - FLASK_VERSION_NUMBER: 1442 + FLASK_VERSION_NUMBER: 1443 - opts: is_expand: false ANDROID_APK_LINK: '' diff --git a/ios/MetaMask.xcodeproj/project.pbxproj b/ios/MetaMask.xcodeproj/project.pbxproj index 7c8be017686..b60012d357d 100644 --- a/ios/MetaMask.xcodeproj/project.pbxproj +++ b/ios/MetaMask.xcodeproj/project.pbxproj @@ -1273,7 +1273,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1442; + CURRENT_PROJECT_VERSION = 1443; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1338,7 +1338,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1442; + CURRENT_PROJECT_VERSION = 1443; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1401,7 +1401,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1442; + CURRENT_PROJECT_VERSION = 1443; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1462,7 +1462,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1442; + CURRENT_PROJECT_VERSION = 1443; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1624,7 +1624,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1442; + CURRENT_PROJECT_VERSION = 1443; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1692,7 +1692,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1442; + CURRENT_PROJECT_VERSION = 1443; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; From bd4d3b4daf7aa842a2775710214a2b26ae8f4c2f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 3 Oct 2024 08:14:03 -0700 Subject: [PATCH 26/27] chore: cherry-pick #11325 (#11593) This PR cherry-picks #11325 Co-authored-by: abretonc7s <107169956+abretonc7s@users.noreply.github.com> Co-authored-by: Christopher Ferreira <104831203+christopherferreira9@users.noreply.github.com> --- .../SDK/SDKSessionModal/SDKSessionModal.tsx | 6 - .../SDK/SDKSessionsManager/SDKSessionItem.tsx | 3 - app/core/BackgroundBridge/BackgroundBridge.js | 24 +- .../handleMetaMaskDeeplink.test.ts | 75 ++++- .../ParseManager/handleMetaMaskDeeplink.ts | 15 +- .../ParseManager/handleUniversalLink.test.ts | 84 +++++- .../ParseManager/handleUniversalLink.ts | 13 +- app/core/SDKConnect/Connection/Connection.ts | 6 +- .../ConnectionManagement/connectToChannel.ts | 204 ++++++++------ .../ConnectionManagement/reconnect.test.ts | 2 +- .../ConnectionManagement/reconnect.ts | 257 +++++++++--------- .../ConnectionManagement/reconnectAll.test.ts | 22 +- .../ConnectionManagement/reconnectAll.ts | 17 +- .../ConnectionManagement/removeChannel.ts | 2 +- .../InitializationManagement/postInit.test.ts | 6 - .../InitializationManagement/postInit.ts | 32 ++- .../StateManagement/updateSDKLoadingState.ts | 6 +- .../handlers/checkPermissions.test.ts | 58 ++-- .../SDKConnect/handlers/checkPermissions.ts | 59 +--- .../handlers/handleConnectionMessage.test.ts | 41 ++- .../handlers/handleConnectionMessage.ts | 9 +- .../handlers/handleDeeplink.test.ts | 26 +- .../SDKConnect/handlers/handleDeeplink.ts | 73 +++-- 23 files changed, 626 insertions(+), 414 deletions(-) diff --git a/app/components/Views/SDK/SDKSessionModal/SDKSessionModal.tsx b/app/components/Views/SDK/SDKSessionModal/SDKSessionModal.tsx index d0030024d40..93a29c0be67 100644 --- a/app/components/Views/SDK/SDKSessionModal/SDKSessionModal.tsx +++ b/app/components/Views/SDK/SDKSessionModal/SDKSessionModal.tsx @@ -111,12 +111,6 @@ const SDKSessionModal = ({ route }: SDKSEssionMoodalProps) => { [accounts, permittedAccountsAddresses], ); - DevLogger.log(`permittedAccountsAddresses`, permittedAccountsAddresses); - DevLogger.log( - `permittedAccounts state`, - JSON.stringify(permittedAccountsList, null, 2), - ); - useEffect(() => { if (channelId) { const origin = channelId; diff --git a/app/components/Views/SDK/SDKSessionsManager/SDKSessionItem.tsx b/app/components/Views/SDK/SDKSessionsManager/SDKSessionItem.tsx index 57b7d10a625..dcc8c114129 100644 --- a/app/components/Views/SDK/SDKSessionsManager/SDKSessionItem.tsx +++ b/app/components/Views/SDK/SDKSessionsManager/SDKSessionItem.tsx @@ -70,9 +70,6 @@ export const SDKSessionItem = ({ string[] >([]); - DevLogger.log( - `Rendering SDKSessionItem connected=${connection.connected} ${connection.id}`, - ); useEffect(() => { let _sessionName = connection.id; diff --git a/app/core/BackgroundBridge/BackgroundBridge.js b/app/core/BackgroundBridge/BackgroundBridge.js index 9b7b4f63554..4a5b5e23bc5 100644 --- a/app/core/BackgroundBridge/BackgroundBridge.js +++ b/app/core/BackgroundBridge/BackgroundBridge.js @@ -272,7 +272,7 @@ export class BackgroundBridge extends EventEmitter { try { let approvedAccounts = []; DevLogger.log( - `notifySelectedAddressChanged: ${selectedAddress} wc=${this.isWalletConnect} url=${this.url}`, + `notifySelectedAddressChanged: ${selectedAddress} channelId=${this.channelId} wc=${this.isWalletConnect} url=${this.url}`, ); if (this.isWalletConnect) { approvedAccounts = await getPermittedAccounts(this.url); @@ -294,15 +294,21 @@ export class BackgroundBridge extends EventEmitter { (addr) => addr.toLowerCase() !== selectedAddress.toLowerCase(), ), ]; + + DevLogger.log( + `notifySelectedAddressChanged url: ${this.url} hostname: ${this.hostname}: ${selectedAddress}`, + approvedAccounts, + ); + this.sendNotification({ + method: NOTIFICATION_NAMES.accountsChanged, + params: approvedAccounts, + }); + } else { + DevLogger.log( + `notifySelectedAddressChanged: selectedAddress ${selectedAddress} not found in approvedAccounts`, + approvedAccounts, + ); } - DevLogger.log( - `notifySelectedAddressChanged url: ${this.url} hostname: ${this.hostname}: ${selectedAddress}`, - approvedAccounts, - ); - this.sendNotification({ - method: NOTIFICATION_NAMES.accountsChanged, - params: approvedAccounts, - }); } catch (err) { console.error(`notifySelectedAddressChanged: ${err}`); } diff --git a/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.test.ts b/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.test.ts index 9ffede5aee6..b7eed8fd925 100644 --- a/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.test.ts +++ b/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.test.ts @@ -7,6 +7,9 @@ import DeeplinkManager from '../DeeplinkManager'; import extractURLParams from './extractURLParams'; import handleMetaMaskDeeplink from './handleMetaMaskDeeplink'; import handleDeeplink from '../../SDKConnect/handlers/handleDeeplink'; +import Device from '../../../util/device'; +import { Platform } from 'react-native'; +import Routes from '../../../constants/navigation/Routes'; jest.mock('../../../core/AppConstants'); jest.mock('../../../core/SDKConnect/handlers/handleDeeplink'); @@ -30,6 +33,7 @@ describe('handleMetaMaskProtocol', () => { const mockWC2ManagerConnect = jest.fn(); const mockGetApprovedHosts = jest.fn(); const mockBindAndroidSDK = jest.fn(); + const mockNavigate = jest.fn(); const mockHandleDeeplink = handleDeeplink as jest.Mock; const mockSDKConnectGetInstance = SDKConnect.getInstance as jest.Mock; @@ -44,6 +48,7 @@ describe('handleMetaMaskProtocol', () => { const handled = jest.fn(); + let url = ''; let params = { @@ -70,6 +75,11 @@ describe('handleMetaMaskProtocol', () => { reconnect: mockReconnect, getApprovedHosts: mockGetApprovedHosts, bindAndroidSDK: mockBindAndroidSDK, + state: { + navigation: { + navigate: mockNavigate, + }, + } })); mockWC2ManagerGetInstance.mockResolvedValue({ @@ -259,21 +269,73 @@ describe('handleMetaMaskProtocol', () => { url = `${PREFIXES.METAMASK}${ACTIONS.CONNECT}`; }); - it('should call Minimizer.goBack when params.redirect is truthy', () => { - params.redirect = 'ABC'; + it('should call Minimizer.goBack if params.redirect is truthy on android', () => { + params.redirect = 'true'; + // Mock Device.isIos() to return true + jest.spyOn(Device, 'isIos').mockReturnValue(false); + + // Set Platform.Version to '16' to ensure it's less than 17 + Object.defineProperty(Platform, 'Version', { get: () => '16' }); handleMetaMaskDeeplink({ instance, handled, params, + origin: AppConstants.DEEPLINKS.ORIGIN_DEEPLINK, + wcURL, url, - origin, + }); + + expect(handled).toHaveBeenCalled(); + expect(Minimizer.goBack).toHaveBeenCalled(); + }); + + it('should call Minimizer.goBack if params.redirect is truthy on ios <17', () => { + params.redirect = 'true'; + // Mock Device.isIos() to return true + jest.spyOn(Device, 'isIos').mockReturnValue(true); + + // Set Platform.Version to '16' to ensure it's less than 17 + Object.defineProperty(Platform, 'Version', { get: () => '16' }); + + handleMetaMaskDeeplink({ + instance, + handled, + params, + origin: AppConstants.DEEPLINKS.ORIGIN_DEEPLINK, wcURL, + url, }); + expect(handled).toHaveBeenCalled(); expect(Minimizer.goBack).toHaveBeenCalled(); }); + it('should displays RETURN_TO_DAPP_MODAL if params.redirect is truthy on ios >17', () => { + params.redirect = 'true'; + // Mock Device.isIos() to return true + jest.spyOn(Device, 'isIos').mockReturnValue(true); + + // Set Platform.Version to '16' to ensure it's less than 17 + Object.defineProperty(Platform, 'Version', { get: () => '17' }); + + handleMetaMaskDeeplink({ + instance, + handled, + params, + origin: AppConstants.DEEPLINKS.ORIGIN_DEEPLINK, + wcURL, + url, + }); + + expect(handled).toHaveBeenCalled(); + expect(mockNavigate).toHaveBeenCalledWith(Routes.MODAL.ROOT_MODAL_FLOW, { + screen: Routes.SHEET.RETURN_TO_DAPP_MODAL, + }); + expect(Minimizer.goBack).not.toHaveBeenCalled(); + }); + + it('should call handleDeeplink when channel exists and params.redirect is falsy', () => { origin = AppConstants.DEEPLINKS.ORIGIN_DEEPLINK; params.channelId = 'ABC'; @@ -296,6 +358,8 @@ describe('handleMetaMaskProtocol', () => { context: 'deeplink_scheme', otherPublicKey: params.pubkey, protocolVersion: 1, + originatorInfo: undefined, + rpc: undefined, sdkConnect: { getConnections: mockGetConnections, connectToChannel: mockConnectToChannel, @@ -303,6 +367,11 @@ describe('handleMetaMaskProtocol', () => { reconnect: mockReconnect, getApprovedHosts: mockGetApprovedHosts, bindAndroidSDK: mockBindAndroidSDK, + state: { + navigation: { + navigate: mockNavigate, + }, + }, }, }); }); diff --git a/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.ts b/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.ts index ee5e4a75a6c..6cb1fbabfee 100644 --- a/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.ts +++ b/app/core/DeeplinkManager/ParseManager/handleMetaMaskDeeplink.ts @@ -9,7 +9,10 @@ import WC2Manager from '../../WalletConnect/WalletConnectV2'; import DeeplinkManager from '../DeeplinkManager'; import extractURLParams from './extractURLParams'; import parseOriginatorInfo from '../parseOriginatorInfo'; - +import { Platform } from 'react-native'; +import Device from '../../../util/device'; +import Routes from '../../../constants/navigation/Routes'; +import AppConstants from '../../AppConstants'; export function handleMetaMaskDeeplink({ instance, handled, @@ -39,8 +42,14 @@ export function handleMetaMaskDeeplink({ } if (url.startsWith(`${PREFIXES.METAMASK}${ACTIONS.CONNECT}`)) { - if (params.redirect) { - Minimizer.goBack(); + if (params.redirect && origin === AppConstants.DEEPLINKS.ORIGIN_DEEPLINK) { + if (Device.isIos() && parseInt(Platform.Version as string) >= 17) { + SDKConnect.getInstance().state.navigation?.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { + screen: Routes.SHEET.RETURN_TO_DAPP_MODAL, + }); + } else { + Minimizer.goBack(); + } } else if (params.channelId) { // differentiate between deeplink callback and socket connection if (params.comm === 'deeplinking') { diff --git a/app/core/DeeplinkManager/ParseManager/handleUniversalLink.test.ts b/app/core/DeeplinkManager/ParseManager/handleUniversalLink.test.ts index f8cfd62b75c..e0764f00e59 100644 --- a/app/core/DeeplinkManager/ParseManager/handleUniversalLink.test.ts +++ b/app/core/DeeplinkManager/ParseManager/handleUniversalLink.test.ts @@ -1,4 +1,6 @@ +import { Platform } from 'react-native'; import { ACTIONS } from '../../../constants/deeplinks'; +import Device from '../../../util/device'; import AppConstants from '../../AppConstants'; import { Minimizer } from '../../NativeModules'; import SDKConnect from '../../SDKConnect/SDKConnect'; @@ -8,6 +10,7 @@ import WC2Manager from '../../WalletConnect/WalletConnectV2'; import DeeplinkManager from '../DeeplinkManager'; import extractURLParams from './extractURLParams'; import handleUniversalLink from './handleUniversalLink'; +import Routes from '../../../constants/navigation/Routes'; jest.mock('../../../core/SDKConnect/handlers/handleDeeplink'); jest.mock('../../../core/AppConstants'); @@ -105,7 +108,7 @@ describe('handleUniversalLinks', () => { urlObj, params, browserCallBack: mockBrowserCallBack, - origin, + origin: AppConstants.DEEPLINKS.ORIGIN_DEEPLINK, wcURL, url, }); @@ -119,8 +122,13 @@ describe('handleUniversalLinks', () => { }); describe('ACTIONS.CONNECT', () => { - it('should call Minimizer.goBack if params.redirect is truthy', () => { - params.redirect = 'ABC'; + it('should call Minimizer.goBack if params.redirect is truthy on android', () => { + params.redirect = 'true'; + // Mock Device.isIos() to return true + jest.spyOn(Device, 'isIos').mockReturnValue(false); + + // Set Platform.Version to '16' to ensure it's less than 17 + Object.defineProperty(Platform, 'Version', { get: () => '16' }); urlObj = { hostname: AppConstants.MM_UNIVERSAL_LINK_HOST, @@ -133,7 +141,35 @@ describe('handleUniversalLinks', () => { urlObj, params, browserCallBack: mockBrowserCallBack, - origin, + origin: AppConstants.DEEPLINKS.ORIGIN_DEEPLINK, + wcURL, + url, + }); + + expect(handled).toHaveBeenCalled(); + expect(Minimizer.goBack).toHaveBeenCalled(); + }); + + it('should call Minimizer.goBack if params.redirect is truthy on ios <17', () => { + params.redirect = 'true'; + // Mock Device.isIos() to return true + jest.spyOn(Device, 'isIos').mockReturnValue(false); + + // Set Platform.Version to '16' to ensure it's less than 17 + Object.defineProperty(Platform, 'Version', { get: () => '16' }); + + urlObj = { + hostname: AppConstants.MM_UNIVERSAL_LINK_HOST, + pathname: `/${ACTIONS.CONNECT}/additional/path`, + } as ReturnType['urlObj']; + + handleUniversalLink({ + instance, + handled, + urlObj, + params, + browserCallBack: mockBrowserCallBack, + origin: AppConstants.DEEPLINKS.ORIGIN_DEEPLINK, wcURL, url, }); @@ -142,6 +178,46 @@ describe('handleUniversalLinks', () => { expect(Minimizer.goBack).toHaveBeenCalled(); }); + it('should displays RETURN_TO_DAPP_MODAL if params.redirect is truthy on ios >17', () => { + params.redirect = 'true'; + // Mock Device.isIos() to return true + jest.spyOn(Device, 'isIos').mockReturnValue(true); + + // Set Platform.Version to '17' to ensure it's greater than 17 + Object.defineProperty(Platform, 'Version', { get: () => '17' }); + + const mockNavigate = jest.fn(); + mockSDKConnectGetInstance.mockImplementation(() => ({ + state: { + navigation: { + navigate: mockNavigate, + }, + }, + })); + + urlObj = { + hostname: AppConstants.MM_UNIVERSAL_LINK_HOST, + pathname: `/${ACTIONS.CONNECT}/additional/path`, + } as ReturnType['urlObj']; + + handleUniversalLink({ + instance, + handled, + urlObj, + params, + browserCallBack: mockBrowserCallBack, + origin: AppConstants.DEEPLINKS.ORIGIN_DEEPLINK, + wcURL, + url, + }); + + expect(handled).toHaveBeenCalled(); + expect(mockNavigate).toHaveBeenCalledWith(Routes.MODAL.ROOT_MODAL_FLOW, { + screen: Routes.SHEET.RETURN_TO_DAPP_MODAL, + }); + expect(Minimizer.goBack).not.toHaveBeenCalled(); + }); + it('should NOT call Minimizer.goBack if params.redirect is falsy', () => { params.redirect = ''; diff --git a/app/core/DeeplinkManager/ParseManager/handleUniversalLink.ts b/app/core/DeeplinkManager/ParseManager/handleUniversalLink.ts index 187b33254e2..b447dd64f37 100644 --- a/app/core/DeeplinkManager/ParseManager/handleUniversalLink.ts +++ b/app/core/DeeplinkManager/ParseManager/handleUniversalLink.ts @@ -10,6 +10,9 @@ import DeeplinkManager from '../DeeplinkManager'; import extractURLParams from './extractURLParams'; import { OriginatorInfo } from '@metamask/sdk-communication-layer'; import parseOriginatorInfo from '../parseOriginatorInfo'; +import Device from '../../../util/device'; +import { Platform } from 'react-native'; +import Routes from '../../../constants/navigation/Routes'; function handleUniversalLink({ instance, @@ -53,8 +56,14 @@ function handleUniversalLink({ } if (action === ACTIONS.CONNECT) { - if (params.redirect) { - Minimizer.goBack(); + if (params.redirect && origin === AppConstants.DEEPLINKS.ORIGIN_DEEPLINK) { + if (Device.isIos() && parseInt(Platform.Version as string) >= 17) { + SDKConnect.getInstance().state.navigation?.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { + screen: Routes.SHEET.RETURN_TO_DAPP_MODAL, + }); + } else { + Minimizer.goBack(); + } } else if (params.channelId) { const protocolVersion = parseInt(params.v ?? '1', 10); diff --git a/app/core/SDKConnect/Connection/Connection.ts b/app/core/SDKConnect/Connection/Connection.ts index e53430b60a1..5c8e0a5e775 100644 --- a/app/core/SDKConnect/Connection/Connection.ts +++ b/app/core/SDKConnect/Connection/Connection.ts @@ -49,8 +49,8 @@ export interface ConnectionProps { lastAuthorized?: number; // timestamp of last received activity } -// eslint-disable-next-line -const { version } = require('../../../../package.json'); +import packageJSON from '../../../../package.json'; +const { version: walletVersion } = packageJSON; export class Connection extends EventEmitter2 { channelId; @@ -207,7 +207,7 @@ export class Connection extends EventEmitter2 { transports: ['websocket'], walletInfo: { type: 'MetaMask Mobile', - version, + version: walletVersion, }, ecies: { debug: true, diff --git a/app/core/SDKConnect/ConnectionManagement/connectToChannel.ts b/app/core/SDKConnect/ConnectionManagement/connectToChannel.ts index 3076f4e3ca3..ffc3fb6c0db 100644 --- a/app/core/SDKConnect/ConnectionManagement/connectToChannel.ts +++ b/app/core/SDKConnect/ConnectionManagement/connectToChannel.ts @@ -1,4 +1,4 @@ -import { MessageType } from '@metamask/sdk-communication-layer'; +import { MessageType, SendAnalytics, TrackingEvents } from '@metamask/sdk-communication-layer'; import { Platform } from 'react-native'; import { resetConnections } from '../../../../app/actions/sdk'; import { store } from '../../../../app/store'; @@ -17,6 +17,9 @@ import { wait, waitForCondition } from '../utils/wait.util'; import Logger from '../../../util/Logger'; import AppConstants from '../../AppConstants'; +import packageJSON from '../../../../package.json'; +const { version: walletVersion } = packageJSON; + async function connectToChannel({ id, trigger, @@ -68,106 +71,123 @@ async function connectToChannel({ } instance.state.connecting[id] = true; - instance.state.connections[id] = { - id, - otherPublicKey, - origin, - initialConnection, - validUntil, - originatorInfo, - lastAuthorized: initialConnection ? 0 : instance.state.approvedHosts[id], - }; - DevLogger.log( - `SDKConnect connections[${id}]`, - instance.state.connections[id], - ); + try { + instance.state.connections[id] = { + id, + otherPublicKey, + origin, + initialConnection, + validUntil, + originatorInfo, + lastAuthorized: initialConnection ? 0 : instance.state.approvedHosts[id], + }; - const connected = new Connection({ - ...instance.state.connections[id], - socketServerUrl: instance.state.socketServerUrl, - protocolVersion, - initialConnection, - trigger, - rpcQueueManager: instance.state.rpcqueueManager, - originatorInfo, - navigation: instance.state.navigation, - updateOriginatorInfos: instance.updateOriginatorInfos.bind(instance), - approveHost: instance._approveHost.bind(instance), - disapprove: instance.disapproveChannel.bind(instance), - getApprovedHosts: instance.getApprovedHosts.bind(instance), - revalidate: instance.revalidateChannel.bind(instance), - isApproved: instance.isApproved.bind(instance), - onTerminate: ({ - channelId, - sendTerminate, - }: { - channelId: string; - sendTerminate?: boolean; - }) => { - instance.removeChannel({ channelId, sendTerminate }); - }, - }); - - // Update state with local privateKey info, stored for relayPersistence - const privateKey = connected.remote.getKeyInfo()?.ecies.private; - instance.state.connections[id].privateKey = privateKey; - instance.state.connections[id].protocolVersion = protocolVersion ?? 1; - instance.state.connections[id].originatorInfo = originatorInfo; - instance.state.connected[id] = connected; - - let authorized = false; - DevLogger.log( - `SDKConnect::connectToChannel - originatorInfo`, - originatorInfo, - ); - // Check permissions first - if (originatorInfo) { - // Only check permissions if we have originatorInfo with the deeplink (not available in protocol V1) DevLogger.log( - `SDKConnect::connectToChannel checkPermissions`, - originatorInfo, + `SDKConnect connections[${id}]`, + instance.state.connections[id], ); - try { - await checkPermissions({ - connection: connected, - engine: Engine, - }); - authorized = true; - } catch (error) { - // TODO: send rejected event + const connected = new Connection({ + ...instance.state.connections[id], + socketServerUrl: instance.state.socketServerUrl, + protocolVersion, + initialConnection, + trigger, + rpcQueueManager: instance.state.rpcqueueManager, + originatorInfo, + navigation: instance.state.navigation, + updateOriginatorInfos: instance.updateOriginatorInfos.bind(instance), + approveHost: instance._approveHost.bind(instance), + disapprove: instance.disapproveChannel.bind(instance), + getApprovedHosts: instance.getApprovedHosts.bind(instance), + revalidate: instance.revalidateChannel.bind(instance), + isApproved: instance.isApproved.bind(instance), + onTerminate: ({ + channelId, + sendTerminate, + }: { + channelId: string; + sendTerminate?: boolean; + }) => { + instance.removeChannel({ channelId, sendTerminate }); + }, + }); - // first needs to connect without key exchange to send the event - await instance.state.connected[id].remote.reject({channelId: id}); + // Update state with local privateKey info, stored for relayPersistence + const privateKey = connected.remote.getKeyInfo()?.ecies.private; + instance.state.connections[id].privateKey = privateKey; + instance.state.connections[id].protocolVersion = protocolVersion ?? 1; + instance.state.connections[id].originatorInfo = originatorInfo; + instance.state.connected[id] = connected; - instance.removeChannel({ channelId: id, sendTerminate: true }); - // cleanup connection - await wait(100); // Add delay for connect modal to be fully closed - await instance.updateSDKLoadingState({ channelId: id, loading: false }); - // Check for iOS 17 and above to use a custom modal, as Minimizer.goBack() is incompatible with these versions - if (Device.isIos() && parseInt(Platform.Version as string) >= 17) { - connected.navigation?.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { - screen: Routes.SHEET.RETURN_TO_DAPP_MODAL, + let authorized = false; + DevLogger.log( + `SDKConnect::connectToChannel - originatorInfo`, + originatorInfo, + ); + // Check permissions first + if (originatorInfo) { + // Only check permissions if we have originatorInfo with the deeplink (not available in protocol V1) + DevLogger.log( + `SDKConnect::connectToChannel checkPermissions`, + originatorInfo, + ); + + try { + // We cannot request permissions if the user is on the login screen or the account connect screen otherwise it will kill other permissions requests. + const skipRoutes = [Routes.LOCK_SCREEN, Routes.ONBOARDING.LOGIN, Routes.SHEET.ACCOUNT_CONNECT]; + // Wait for login screen to be closed + await waitForCondition({ + fn: () => { + const currentRouteName = connected.navigation?.getCurrentRoute()?.name; + DevLogger.log(`connectToChannel:: currentRouteName=${currentRouteName}`); + return !!currentRouteName && !skipRoutes.includes(currentRouteName); + }, + context: 'connectToChannel', + }); + + const res = await checkPermissions({ + connection: connected, + engine: Engine, }); - } else { - DevLogger.log(`[handleSendMessage] goBack()`); - await Minimizer.goBack(); + DevLogger.log(`SDKConnect::connectToChannel - checkPermissions - authorized`, res); + authorized = true; + } catch (error) { + DevLogger.log(`SDKConnect::connectToChannel - checkPermissions - error`, error); + // first needs to connect without key exchange to send the event + await instance.state.connected[id].remote.reject({channelId: id}); + // Send rejection event without awaiting + SendAnalytics({id, event: TrackingEvents.REJECTED, ...originatorInfo}, instance.state.socketServerUrl).catch((err: Error) => { + Logger.error(err, 'SendAnalytics failed'); + }); + + instance.removeChannel({ channelId: id, sendTerminate: true }); + // cleanup connection + await wait(100); // Add delay for connect modal to be fully closed + await instance.updateSDKLoadingState({ channelId: id, loading: false }); + // Check for iOS 17 and above to use a custom modal, as Minimizer.goBack() is incompatible with these versions + if (Device.isIos() && parseInt(Platform.Version as string) >= 17) { + connected.navigation?.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { + screen: Routes.SHEET.RETURN_TO_DAPP_MODAL, + }); + } else { + DevLogger.log(`[handleSendMessage] goBack()`); + await Minimizer.goBack(); + } + return; } - return; } - } - try { - // Make sure to watch event before you connect - instance.watchConnection(instance.state.connected[id]); - store.dispatch(resetConnections(instance.state.connections)); + + // SDK PROTOCOL pre 0.28.0 + DevLogger.log(`SDKConnect::connectToChannel - before connect`, instance.state.connected[id]); // Initialize connection - await instance.state.connected[id].connect({ + await connected.connect({ withKeyExchange: true, authorized, }); - instance.state.connecting[id] = false; + DevLogger.log(`SDKConnect::connectToChannel - connected - state after connect`, instance.state); DevLogger.log( `SDKConnect::connectToChannel - connected - authorized=${authorized} initialConnection=${initialConnection}`, @@ -175,18 +195,23 @@ async function connectToChannel({ if (authorized) { connected.remote.state.relayPersistence = true; } + // Make sure to watch event before you connect + instance.watchConnection(instance.state.connected[id]); + store.dispatch(resetConnections(instance.state.connections)); if (authorized && initialConnection) { const accounts = await getPermittedAccounts(id); const currentChainId = selectChainId(store.getState()); - + connected.remote.state.channelId = id; const data = { accounts, chainId: currentChainId, + walletVersion, + deeplinkProtocol: true, walletKey: instance.state.connected[id].remote.getKeyInfo()?.ecies.public, }; - DevLogger.log(`send account / chainId to dapp`, data); + DevLogger.log(`Sending WALLET_INIT message to dapp`, data); // Directly send the account / chainId to the dapp await connected.remote.sendMessage({ type: MessageType.WALLET_INIT, @@ -226,6 +251,9 @@ async function connectToChannel({ } } catch (error) { Logger.error(error as Error, 'Failed to connect to channel'); + } finally { + DevLogger.log(`SDKConnect::connectToChannel - finally - state.connecting[${id}]=${instance.state.connecting[id]}`); + instance.state.connecting[id] = false; } } diff --git a/app/core/SDKConnect/ConnectionManagement/reconnect.test.ts b/app/core/SDKConnect/ConnectionManagement/reconnect.test.ts index b888f29e45a..8c0e49ca55c 100644 --- a/app/core/SDKConnect/ConnectionManagement/reconnect.test.ts +++ b/app/core/SDKConnect/ConnectionManagement/reconnect.test.ts @@ -230,7 +230,7 @@ describe('reconnect', () => { context: 'test-context', }); - expect(mockInstance.state.connecting['test-channel-id']).toEqual(true); + expect(mockInstance.state.connecting['test-channel-id']).toEqual(false); }); }); }); diff --git a/app/core/SDKConnect/ConnectionManagement/reconnect.ts b/app/core/SDKConnect/ConnectionManagement/reconnect.ts index 3c4cb5f618d..bf4b0a21cd0 100644 --- a/app/core/SDKConnect/ConnectionManagement/reconnect.ts +++ b/app/core/SDKConnect/ConnectionManagement/reconnect.ts @@ -21,155 +21,146 @@ async function reconnect({ initialConnection: boolean; instance: SDKConnect; }) { - const existingConnection: Connection | undefined = - instance.state.connected[channelId]; - - // Check if already connected - if (existingConnection?.remote.isReady()) { - DevLogger.log(`SDKConnect::reconnect[${context}] - already ready - ignore`); - if (trigger) { - instance.state.connected[channelId].setTrigger('deeplink'); - } - instance.updateSDKLoadingState({ channelId, loading: false }); + try { + const existingConnection: Connection | undefined = + instance.state.connected[channelId]; + // Check if already connected + if (existingConnection?.remote.isReady()) { + DevLogger.log(`SDKConnect::reconnect[${context}] - already ready - ignore`); + if (trigger) { + instance.state.connected[channelId].setTrigger(trigger); + } + instance.updateSDKLoadingState({ channelId, loading: false }); - return; - } + return; + } - if (instance.state.paused && updateKey) { - instance.state.connections[channelId].otherPublicKey = otherPublicKey; - const currentOtherPublicKey = - instance.state.connections[channelId].otherPublicKey; - if (currentOtherPublicKey !== otherPublicKey) { - console.warn( - `SDKConnect::reconnect[${context}] existing=${ - existingConnection !== undefined - } - update otherPublicKey - ${currentOtherPublicKey} --> ${otherPublicKey}`, - ); - if (existingConnection) { - existingConnection.remote.setOtherPublicKey(otherPublicKey); + if (instance.state.paused && updateKey) { + instance.state.connections[channelId].otherPublicKey = otherPublicKey; + const currentOtherPublicKey = + instance.state.connections[channelId].otherPublicKey; + if (currentOtherPublicKey !== otherPublicKey) { + console.warn( + `SDKConnect::reconnect[${context}] existing=${ + existingConnection !== undefined + } - update otherPublicKey - ${currentOtherPublicKey} --> ${otherPublicKey}`, + ); + if (existingConnection) { + existingConnection.remote.setOtherPublicKey(otherPublicKey); + } + } else { + DevLogger.log(`SDKConnect::reconnect[${context}] - same otherPublicKey`); } - } else { - DevLogger.log(`SDKConnect::reconnect[${context}] - same otherPublicKey`); } - } - // Update initial connection state - instance.state.connections[channelId].initialConnection = initialConnection; - - const wasPaused = existingConnection?.remote.isPaused(); - // Make sure the connection has resumed from pause before reconnecting. - await waitForCondition({ - fn: () => !instance.state.paused, - context: 'reconnect_from_pause', - }); - if (wasPaused) { - DevLogger.log(`SDKConnect::reconnect[${context}] - not paused anymore`); - } - const connecting = instance.state.connecting[channelId] === true; - const socketConnected = existingConnection?.remote.isConnected() ?? false; - - DevLogger.log( - `SDKConnect::reconnect[${context}][${trigger}] - channel=${channelId} paused=${ - instance.state.paused - } connecting=${connecting} socketConnected=${socketConnected} existingConnection=${ - existingConnection !== undefined - }`, - otherPublicKey, - ); - - let interruptReason = ''; - - if (connecting && trigger !== 'deeplink') { - // Prioritize deeplinks -- interrup other connection attempts. - interruptReason = 'already connecting'; - } else if (connecting && trigger === 'deeplink') { - // Keep comment for future reference in case android issue re-surface - // special case on android where the socket was not updated - // if (Platform.OS === 'android') { - // interruptReason = 'already connecting'; - // } else { - // console.warn(`Priotity to deeplink - overwrite previous connection`); - // instance.removeChannel(channelId, true); - // } - - // issue can happen during dev because bundle takes too long to load via metro. - // should not happen but keeping it for reference / debug purpose. - console.warn(`BUNDLE WARNING: Already connecting --- Priotity to deeplink`); - // instance.removeChannel({ channelId, sendTerminate: true }); - } - if (!instance.state.connections[channelId]) { - interruptReason = 'no connection'; - } + // Update initial connection state + instance.state.connections[channelId].initialConnection = initialConnection; + + const wasPaused = existingConnection?.remote.isPaused(); + // Make sure the connection has resumed from pause before reconnecting. + await waitForCondition({ + fn: () => !instance.state.paused, + context: 'reconnect_from_pause', + }); + if (wasPaused) { + DevLogger.log(`SDKConnect::reconnect[${context}] - not paused anymore`); + } + const connecting = instance.state.connecting[channelId] === true; + const socketConnected = existingConnection?.remote.isConnected() ?? false; + - if (interruptReason) { DevLogger.log( - `SDKConnect::reconnect - interrupting reason=${interruptReason}`, + `SDKConnect::reconnect[${context}][${trigger}] - channel=${channelId} paused=${ + instance.state.paused + } connecting=${connecting} socketConnected=${socketConnected} existingConnection=${ + existingConnection !== undefined + }`, + otherPublicKey, ); - return; - } - if (existingConnection) { - const connected = existingConnection?.remote.isConnected(); - const ready = existingConnection?.isReady; - if (trigger) { - instance.state.connected[channelId].setTrigger(trigger); - DevLogger.log( - `SDKConnect::reconnect - connected=${connected} -- trigger updated to '${trigger}'`, - ); - instance.updateSDKLoadingState({ channelId, loading: false }); + let interruptReason = ''; + + if (connecting && trigger !== 'deeplink') { + // Prioritize deeplinks -- interrup other connection attempts. + interruptReason = 'already connecting'; + } else if (!instance.state.connections[channelId]) { + interruptReason = 'no connection'; } - if (ready) { + if (interruptReason) { DevLogger.log( - `SDKConnect::reconnect - already connected [ready=${ready}] -- ignoring`, + `SDKConnect::reconnect - interrupting reason=${interruptReason}`, ); - instance.updateSDKLoadingState({ channelId, loading: false }); return; - } else if (connected) { - // disconnect socket before reconnecting to avoid room being full - DevLogger.log( - `SDKConnect::reconnect - disconnecting socket before reconnecting`, - ); - existingConnection.remote.disconnect(); } - } - DevLogger.log( - `SDKConnect::reconnect - starting reconnection channel=${channelId}`, - ); - - const connection = instance.state.connections[channelId]; - DevLogger.log(`SDKConnect::reconnect - connection`, connection); - instance.state.connecting[channelId] = true; - instance.state.connected[channelId] = new Connection({ - ...connection, - socketServerUrl: instance.state.socketServerUrl, - otherPublicKey, - reconnect: true, - trigger, - initialConnection, - rpcQueueManager: instance.state.rpcqueueManager, - navigation: instance.state.navigation, - approveHost: instance._approveHost.bind(instance), - disapprove: instance.disapproveChannel.bind(instance), - getApprovedHosts: instance.getApprovedHosts.bind(instance), - revalidate: instance.revalidateChannel.bind(instance), - isApproved: instance.isApproved.bind(instance), - updateOriginatorInfos: instance.updateOriginatorInfos.bind(instance), - // eslint-disable-next-line @typescript-eslint/no-shadow - onTerminate: ({ channelId }) => { - instance.removeChannel({ channelId }); - }, - }); - instance.state.connected[channelId].connect({ - withKeyExchange: true, - authorized: connection.originatorInfo !== undefined, - }); - - instance.watchConnection(instance.state.connected[channelId]); - const afterConnected = - instance.state.connected[channelId].remote.isConnected() ?? false; - instance.state.connecting[channelId] = !afterConnected; // If not connected, it means it's connecting. + if (existingConnection) { + const connected = existingConnection?.remote.isConnected(); + const ready = existingConnection?.isReady; + if (trigger) { + instance.state.connected[channelId].setTrigger(trigger); + DevLogger.log( + `SDKConnect::reconnect - connected=${connected} -- trigger updated to '${trigger}'`, + ); + instance.updateSDKLoadingState({ channelId, loading: false }); + } + + if (ready) { + DevLogger.log( + `SDKConnect::reconnect - already connected [ready=${ready}] -- ignoring`, + ); + instance.updateSDKLoadingState({ channelId, loading: false }); + return; + } else if (connected) { + // disconnect socket before reconnecting to avoid room being full + DevLogger.log( + `SDKConnect::reconnect - disconnecting socket before reconnecting`, + ); + existingConnection.remote.disconnect(); + } + } + + DevLogger.log( + `SDKConnect::reconnect - starting reconnection channel=${channelId}`, + ); + + const connection = instance.state.connections[channelId]; + DevLogger.log(`SDKConnect::reconnect - connection`, connection); + instance.state.connecting[channelId] = true; + instance.state.connected[channelId] = new Connection({ + ...connection, + socketServerUrl: instance.state.socketServerUrl, + otherPublicKey, + reconnect: true, + trigger, + initialConnection, + rpcQueueManager: instance.state.rpcqueueManager, + navigation: instance.state.navigation, + approveHost: instance._approveHost.bind(instance), + disapprove: instance.disapproveChannel.bind(instance), + getApprovedHosts: instance.getApprovedHosts.bind(instance), + revalidate: instance.revalidateChannel.bind(instance), + isApproved: instance.isApproved.bind(instance), + updateOriginatorInfos: instance.updateOriginatorInfos.bind(instance), + // eslint-disable-next-line @typescript-eslint/no-shadow + onTerminate: ({ channelId }) => { + instance.removeChannel({ channelId }); + }, + }); + instance.state.connected[channelId].connect({ + withKeyExchange: true, + authorized: connection.originatorInfo !== undefined, + }); + + instance.watchConnection(instance.state.connected[channelId]); + const afterConnected = + instance.state.connected[channelId].remote.isConnected() ?? false; + instance.state.connecting[channelId] = !afterConnected; // If not connected, it means it's connecting. + } catch (error) { + DevLogger.log(`SDKConnect::reconnect[${context}] - error`, error); + } finally { + instance.state.connecting[channelId] = false; + } } export default reconnect; diff --git a/app/core/SDKConnect/ConnectionManagement/reconnectAll.test.ts b/app/core/SDKConnect/ConnectionManagement/reconnectAll.test.ts index 1045da3b501..bdac8482a9b 100644 --- a/app/core/SDKConnect/ConnectionManagement/reconnectAll.test.ts +++ b/app/core/SDKConnect/ConnectionManagement/reconnectAll.test.ts @@ -19,7 +19,9 @@ describe('reconnectAll', () => { paused: false, reconnected: false, connections: {}, + connecting: {} }, + getConnected: jest.fn().mockReturnValue({}), reconnect: mockReconnect, emit: jest.fn(), } as unknown as SDKConnect; @@ -34,13 +36,15 @@ describe('reconnectAll', () => { }); describe('Reconnecting process', () => { - it('should reconnect to each channel in the connections list', () => { + it('should reconnect to each channel in the connections list with relayPersistence', () => { const mockChannelId = 'mockChannelId'; const mockOtherPublicKey = 'mockOtherPublicKey'; mockInstance.state.connections[mockChannelId] = { otherPublicKey: mockOtherPublicKey, origin: 'qr-code', + relayPersistence: true, + protocolVersion: '1.0', } as unknown as SDKConnect['state']['connections'][string]; reconnectAll(mockInstance); @@ -51,8 +55,24 @@ describe('reconnectAll', () => { initialConnection: false, trigger: 'reconnect', context: 'reconnectAll', + protocolVersion: '1.0', }); }); + + it('should reconnect to channels without relayPersistence', () => { + const mockChannelId = 'mockChannelId'; + const mockOtherPublicKey = 'mockOtherPublicKey'; + + mockInstance.state.connections[mockChannelId] = { + otherPublicKey: mockOtherPublicKey, + origin: 'qr-code', + relayPersistence: false, + } as unknown as SDKConnect['state']['connections'][string]; + + reconnectAll(mockInstance); + + expect(mockReconnect).toHaveBeenCalled(); + }); }); it('should set the reconnected state to true after reconnecting', () => { diff --git a/app/core/SDKConnect/ConnectionManagement/reconnectAll.ts b/app/core/SDKConnect/ConnectionManagement/reconnectAll.ts index 84053beb6fc..bdc2e1c2f46 100644 --- a/app/core/SDKConnect/ConnectionManagement/reconnectAll.ts +++ b/app/core/SDKConnect/ConnectionManagement/reconnectAll.ts @@ -1,4 +1,3 @@ -import AppConstants from '../../../../app/core/AppConstants'; import Logger from '../../../util/Logger'; import SDKConnect from '../SDKConnect'; import DevLogger from '../utils/DevLogger'; @@ -16,15 +15,15 @@ async function reconnectAll(instance: SDKConnect) { const channelIds = Object.keys(instance.state.connections); channelIds.forEach((channelId) => { - // Only reconnects to type 'qrcode' connections. const connection = instance.state.connections[channelId]; - DevLogger.log( - `SDKConnect::reconnectAll - reconnecting to ${channelId} origin=${connection.origin} relayPersistence=${connection.relayPersistence} protocolVersion=${connection.protocolVersion}`, - ); + const connecting = instance.state.connecting[channelId]; + const connected = instance.getConnected()?.[channelId]?.remote.isConnected() ?? false; if ( - connection.origin === AppConstants.DEEPLINKS.ORIGIN_QR_CODE || - connection.relayPersistence + !connecting && !connected ) { + DevLogger.log( + `SDKConnect::reconnectAll - reconnecting to ${channelId} origin=${connection.origin} relayPersistence=${connection.relayPersistence} protocolVersion=${connection.protocolVersion}`, + ); instance .reconnect({ channelId, @@ -40,6 +39,10 @@ async function reconnectAll(instance: SDKConnect) { `SDKConnect::reconnectAll error reconnecting to ${channelId}`, ); }); + } else { + DevLogger.log( + `SDKConnect::reconnectAll -- SKIP connected / connecting -- ${channelId} connected=${connected} connecting=${connecting} origin=${connection.origin} relayPersistence=${connection.relayPersistence} protocolVersion=${connection.protocolVersion}`, + ); } }); instance.state.reconnected = true; diff --git a/app/core/SDKConnect/ConnectionManagement/removeChannel.ts b/app/core/SDKConnect/ConnectionManagement/removeChannel.ts index 1943e3fe17e..b9c35801920 100644 --- a/app/core/SDKConnect/ConnectionManagement/removeChannel.ts +++ b/app/core/SDKConnect/ConnectionManagement/removeChannel.ts @@ -37,7 +37,7 @@ async function removeChannel({ if (instance.state.connected[channelId]) { try { const terminated = await instance.state.connected[channelId].removeConnection({ - terminate: sendTerminate ?? false, + terminate: true, context: 'SDKConnect::removeChannel', }); if(!terminated) { diff --git a/app/core/SDKConnect/InitializationManagement/postInit.test.ts b/app/core/SDKConnect/InitializationManagement/postInit.test.ts index 45e7d054f83..fb35886e777 100644 --- a/app/core/SDKConnect/InitializationManagement/postInit.test.ts +++ b/app/core/SDKConnect/InitializationManagement/postInit.test.ts @@ -130,12 +130,6 @@ describe('postInit', () => { ); }); - it('should wait before reconnecting all', async () => { - await postInit(mockInstance); - - expect(wait).toHaveBeenCalledWith(3000); - }); - it('should reconnect all sessions', async () => { await postInit(mockInstance); diff --git a/app/core/SDKConnect/InitializationManagement/postInit.ts b/app/core/SDKConnect/InitializationManagement/postInit.ts index e0539b6988c..0288bde754c 100644 --- a/app/core/SDKConnect/InitializationManagement/postInit.ts +++ b/app/core/SDKConnect/InitializationManagement/postInit.ts @@ -1,19 +1,26 @@ import { KeyringController } from '@metamask/keyring-controller'; -import Engine from '../../../core/Engine'; import { AppState } from 'react-native'; +import Routes from '../../../constants/navigation/Routes'; +import Engine from '../../../core/Engine'; import SDKConnect from '../SDKConnect'; import DevLogger from '../utils/DevLogger'; import { - wait, waitForCondition, - waitForKeychainUnlocked, + waitForKeychainUnlocked } from '../utils/wait.util'; +import { isE2E } from '../../../util/test/utils'; async function postInit(instance: SDKConnect, callback?: () => void) { if (!instance.state._initialized) { throw new Error(`SDKConnect::postInit() - not initialized`); } + if(isE2E) { + DevLogger.log(`SDKConnect::postInit() - SKIP -- E2E`); + instance.state._postInitialized = true; + return; + } + if (instance.state._postInitializing) { DevLogger.log( `SDKConnect::postInit() -- already doing post init -- wait for completion`, @@ -42,13 +49,28 @@ async function postInit(instance: SDKConnect, callback?: () => void) { ); await waitForKeychainUnlocked({ keyringController, context: 'init' }); + + let currentRouteName = instance.state.navigation?.getCurrentRoute()?.name; + DevLogger.log(`SDKConnect::postInit() - currentRouteName=${currentRouteName}`); + + const waitRoutes = [Routes.LOCK_SCREEN, Routes.ONBOARDING.LOGIN]; + await waitForCondition({ + fn: () => { + currentRouteName = instance.state.navigation?.getCurrentRoute()?.name; + return !waitRoutes.includes(currentRouteName ?? ''); + }, + context: 'post_init', + waitTime: 1000, + }); + DevLogger.log(`SDKConnect::postInit() - currentRouteName=${currentRouteName} - start reconnectAll`); + + // Also wait for user to be logged in (outside of login screen) instance.state.appStateListener = AppState.addEventListener( 'change', instance._handleAppState.bind(instance), ); - // Add delay to pioritize reconnecting from deeplink because it contains the updated connection info (channel dapp public key) - await wait(3000); + DevLogger.log(`SDKConnect::postInit() - keychain unlocked -- wait for reconnectAll`); await instance.reconnectAll(); instance.state._postInitialized = true; diff --git a/app/core/SDKConnect/StateManagement/updateSDKLoadingState.ts b/app/core/SDKConnect/StateManagement/updateSDKLoadingState.ts index 10547144138..b53cd9e8ff0 100644 --- a/app/core/SDKConnect/StateManagement/updateSDKLoadingState.ts +++ b/app/core/SDKConnect/StateManagement/updateSDKLoadingState.ts @@ -21,7 +21,11 @@ async function updateSDKLoadingState({ } const currentRouteName = instance.state.navigation?.getCurrentRoute?.()?.name; - if (currentRouteName === Routes.LOCK_SCREEN) { + DevLogger.log( + `updateSDKLoadingState:: currentRouteName=${currentRouteName} loading=${loading}`, + ); + const skipRoutes = [Routes.LOCK_SCREEN, Routes.ONBOARDING.LOGIN, Routes.SHEET.ACCOUNT_CONNECT]; + if (currentRouteName && skipRoutes.includes(currentRouteName)) { // Skip on lock screen return; } diff --git a/app/core/SDKConnect/handlers/checkPermissions.test.ts b/app/core/SDKConnect/handlers/checkPermissions.test.ts index 3b22fbd9343..ea1e7111c81 100644 --- a/app/core/SDKConnect/handlers/checkPermissions.test.ts +++ b/app/core/SDKConnect/handlers/checkPermissions.test.ts @@ -44,7 +44,6 @@ describe('checkPermissions', () => { // TODO: Replace "any" with type // eslint-disable-next-line @typescript-eslint/no-explicit-any } as unknown as PermissionController; - const HOUR_IN_MS = 3600000; const currentTime = Date.now(); const mockGetPermittedAccounts = getPermittedAccounts as jest.MockedFunction< @@ -107,56 +106,33 @@ describe('checkPermissions', () => { jest.useRealTimers(); }); - it('should return true if the connection is already approved and a selected address exists', async () => { - mockIsApproved.mockReturnValue(true); - preferencesController.state.selectedAddress = '0x123'; + it('should return true if permitted accounts exist', async () => { + mockGetPermittedAccounts.mockResolvedValue(['0x123']); const result = await checkPermissions({ connection, engine }); expect(result).toBe(true); }); - it('should handle approval if the connection is not initially approved', async () => { - mockIsApproved.mockReturnValue(false); - - await checkPermissions({ connection, engine }); - expect(requestPermissions).toHaveBeenCalled(); - }); - - it('should return true if the connection is not initially approved and a deeplink origin exists', async () => { - mockIsApproved.mockReturnValue(false); - connection.initialConnection = false; + it('should return false if no permitted accounts exist and no approval promise', async () => { + mockGetPermittedAccounts.mockResolvedValue([]); + permissionController.getPermission = jest.fn().mockReturnValue(null); const result = await checkPermissions({ connection, engine }); - expect(result).toBe(true); + expect(result).toBe(false); }); - it('should revalidate the connection if the channel was active recently', async () => { - const lastAuthorized = currentTime - HOUR_IN_MS / 2; - mockIsApproved.mockReturnValue(false); + it('should request permissions if no eth_accounts permission exists', async () => { + mockGetPermittedAccounts.mockResolvedValue([]); + permissionController.getPermission = jest.fn().mockReturnValue(null); + requestPermissions.mockResolvedValue({}); + mockGetPermittedAccounts.mockResolvedValueOnce([]).mockResolvedValueOnce(['0x123']); - const result = await checkPermissions({ - connection, - engine, - lastAuthorized, - }); + const result = await checkPermissions({ connection, engine }); + expect(requestPermissions).toHaveBeenCalledWith( + { origin: connection.channelId }, + { eth_accounts: {} }, + { preserveExistingPermissions: false } + ); expect(result).toBe(true); }); - - // TODO: re-enable once we properly mock the waiting event - // it('should handle when approvalPromise already exists', async () => { - // mockIsApproved.mockReturnValue(false); - // connection.approvalPromise = Promise.resolve(); - - // const result = await checkPermissions({ connection, engine }); - // expect(result).toBe(true); - // }); - - it('should revalidate connection if not an initial connection and a deeplink origin exists', async () => { - connection.initialConnection = false; - - await checkPermissions({ connection, engine }); - expect(connection.revalidate).toHaveBeenCalledWith({ - channelId: connection.channelId, - }); - }); }); diff --git a/app/core/SDKConnect/handlers/checkPermissions.ts b/app/core/SDKConnect/handlers/checkPermissions.ts index 09a0be24ec5..0b59edfa02a 100644 --- a/app/core/SDKConnect/handlers/checkPermissions.ts +++ b/app/core/SDKConnect/handlers/checkPermissions.ts @@ -1,18 +1,16 @@ import { KeyringController } from '@metamask/keyring-controller'; import { PermissionController } from '@metamask/permission-controller'; import { CommunicationLayerMessage } from '@metamask/sdk-communication-layer'; -import AppConstants from '../../AppConstants'; +import Routes from '../../../constants/navigation/Routes'; import Engine from '../../Engine'; import { getPermittedAccounts } from '../../Permissions'; import { Connection } from '../Connection'; -import { HOUR_IN_MS } from '../SDKConnectConstants'; import DevLogger from '../utils/DevLogger'; import { wait, waitForCondition, waitForKeychainUnlocked, } from '../utils/wait.util'; -import Routes from '../../../constants/navigation/Routes'; // TODO: should be more generic and be used in wallet connect and android service as well export const checkPermissions = async ({ @@ -27,18 +25,12 @@ export const checkPermissions = async ({ lastAuthorized?: number; }) => { try { - const OTPExpirationDuration = - Number(process.env.OTP_EXPIRATION_DURATION_IN_MS) || HOUR_IN_MS; - // close poientially open loading modal connection.setLoading(false); const currentRouteName = connection.navigation?.getCurrentRoute()?.name; - const channelWasActiveRecently = - !!lastAuthorized && Date.now() - lastAuthorized < OTPExpirationDuration; - DevLogger.log( - `checkPermissions initialConnection=${connection.initialConnection} method=${message?.method} lastAuthorized=${lastAuthorized} OTPExpirationDuration ${OTPExpirationDuration} channelWasActiveRecently ${channelWasActiveRecently}`, + `checkPermissions initialConnection=${connection.initialConnection} method=${message?.method} lastAuthorized=${lastAuthorized}`, connection.originatorInfo, ); @@ -57,39 +49,14 @@ export const checkPermissions = async ({ } ).PermissionController; - // only ask approval if needed - const approved = connection.isApproved({ - channelId: connection.channelId, - context: 'checkPermission', - }); - - DevLogger.log( - `checkPermissions approved=${approved} approvalPromise=${ - connection.approvalPromise !== undefined ? 'exists' : 'undefined' - }`, - ); - - if (approved) { - return true; - } + // Make sure to wait for user to be on main pages before requesting permissions or request can get cancelled. + const pendingRoutes = [Routes.LOCK_SCREEN, Routes.ONBOARDING.LOGIN]; - DevLogger.log( - `checkPermissions channelWasActiveRecently=${channelWasActiveRecently} OTPExpirationDuration=${OTPExpirationDuration}`, - ); - - if (channelWasActiveRecently) { - return true; - } - - DevLogger.log( - `checkPermissions keychain unlocked -- route=${currentRouteName}`, - ); - //FIXME Issue with KeyringController not being ready that thinks keychain is unlocked while the current route is still loading. - if (currentRouteName === Routes.LOCK_SCREEN) { + if (currentRouteName && pendingRoutes.includes(currentRouteName)) { await waitForCondition({ - fn: () => { + fn: (): boolean => { const activeRoute = connection.navigation?.getCurrentRoute()?.name; - return activeRoute !== Routes.LOCK_SCREEN; + return Boolean(activeRoute && !pendingRoutes.includes(activeRoute)); }, waitTime: 1000, context: 'checkPermissions', @@ -121,13 +88,6 @@ export const checkPermissions = async ({ return allowed; } - if ( - !connection.initialConnection && - AppConstants.DEEPLINKS.ORIGIN_DEEPLINK - ) { - connection.revalidate({ channelId: connection.channelId }); - } - const accountPermission = permissionsController.getPermission( connection.channelId, 'eth_accounts', @@ -151,10 +111,9 @@ export const checkPermissions = async ({ } const res = await connection.approvalPromise; + const accounts = await getPermittedAccounts(connection.channelId); DevLogger.log(`checkPermissions approvalPromise completed`, res); - // Clear previous permissions if already approved. - connection.revalidate({ channelId: connection.channelId }); - return true; + return accounts.length > 0; } catch (err) { console.warn(`checkPermissions error`, err); connection.approvalPromise = undefined; diff --git a/app/core/SDKConnect/handlers/handleConnectionMessage.test.ts b/app/core/SDKConnect/handlers/handleConnectionMessage.test.ts index c7a0a3eb3fc..a6b9a54e78e 100644 --- a/app/core/SDKConnect/handlers/handleConnectionMessage.test.ts +++ b/app/core/SDKConnect/handlers/handleConnectionMessage.test.ts @@ -70,7 +70,6 @@ describe('handleConnectionMessage', () => { const mockSetLoading = jest.fn(); const mockOnTerminate = jest.fn(); const mockSendAuthorized = jest.fn(); - const mockRpcQueueManagerAdd = jest.fn(); const mockBackgroundBridgeOnMessage = jest.fn(); let connection = {} as unknown as Connection; @@ -80,6 +79,9 @@ describe('handleConnectionMessage', () => { params: [], } as unknown as CommunicationLayerMessage; + const mockGetId = jest.fn(); + const mockAdd = jest.fn(); + beforeEach(() => { jest.clearAllMocks(); @@ -89,7 +91,8 @@ describe('handleConnectionMessage', () => { onTerminate: mockOnTerminate, sendAuthorized: mockSendAuthorized, rpcQueueManager: { - add: mockRpcQueueManagerAdd, + getId: mockGetId, + add: mockAdd, }, remote: { hasRelayPersistence: () => false, @@ -268,22 +271,17 @@ describe('handleConnectionMessage', () => { describe('RPC Queue Manager interactions', () => { beforeEach(() => { mockCheckPermissions.mockResolvedValueOnce(true); + mockGetId.mockReturnValue(null); // Simulate that the message hasn't been processed }); describe('When handleCustomRpcCalls return processedRpc', () => { let processedRpc: { method: string; id: string; - // TODO: Replace "any" with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - params: any[]; + params: unknown[]; jsonrpc: string; - } = { - method: '', - id: '', - params: [], - jsonrpc: '', }; + beforeEach(() => { processedRpc = { method: 'eth_requestAccounts', @@ -294,27 +292,27 @@ describe('handleConnectionMessage', () => { mockHandleCustomRpcCalls.mockResolvedValueOnce(processedRpc); }); + it('should add processed RPC to the RPC queue', async () => { await handleConnectionMessage({ message, engine: Engine, connection }); - expect(mockRpcQueueManagerAdd).toHaveBeenCalledTimes(1); - expect(mockRpcQueueManagerAdd).toHaveBeenCalledWith({ - id: processedRpc?.id, - method: processedRpc?.method, + expect(mockAdd).toHaveBeenCalledTimes(1); + expect(mockAdd).toHaveBeenCalledWith({ + id: processedRpc.id, + method: processedRpc.method, }); }); }); describe('When handleCustomRpcCalls do NOT return processedRpc', () => { beforeEach(() => { - // @ts-ignore mockHandleCustomRpcCalls.mockResolvedValueOnce(undefined); }); - it('should add processed RPC to the RPC queue', async () => { + it('should not add processed RPC to the RPC queue', async () => { await handleConnectionMessage({ message, engine: Engine, connection }); - expect(mockRpcQueueManagerAdd).toHaveBeenCalledTimes(0); + expect(mockAdd).not.toHaveBeenCalled(); }); }); }); @@ -323,15 +321,8 @@ describe('handleConnectionMessage', () => { let processedRpc: { method: string; id: string; - // TODO: Replace "any" with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - params: any[]; + params: unknown[]; jsonrpc: string; - } = { - method: '', - id: '', - params: [], - jsonrpc: '', }; beforeEach(() => { processedRpc = { diff --git a/app/core/SDKConnect/handlers/handleConnectionMessage.ts b/app/core/SDKConnect/handlers/handleConnectionMessage.ts index 9493c5b6e6f..9e397d792b2 100644 --- a/app/core/SDKConnect/handlers/handleConnectionMessage.ts +++ b/app/core/SDKConnect/handlers/handleConnectionMessage.ts @@ -48,6 +48,13 @@ export const handleConnectionMessage = async ({ engine: typeof Engine; connection: Connection; }) => { + // Check if message has already been processed + const rpcQueueManager = connection.rpcQueueManager; + if (message.id && rpcQueueManager.getId(message.id)) { + DevLogger.log(`Connection::onMessage rpcId=${message.id} already processed`); + return; + } + // TODO should probably handle this in a separate EventType.TERMINATE event. // handle termination message if (message.type === MessageType.TERMINATE) { @@ -203,7 +210,7 @@ export const handleConnectionMessage = async ({ fn: async () => { const accounts = await getPermittedAccounts(connection.channelId); DevLogger.log( - `handleDeeplink::waitForAsyncCondition accounts`, + `handleConnectionMessage::waitForAsyncCondition channelId=${connection.channelId} accounts`, accounts, ); return accounts.length > 0; diff --git a/app/core/SDKConnect/handlers/handleDeeplink.test.ts b/app/core/SDKConnect/handlers/handleDeeplink.test.ts index e18920314d9..e20d55701cd 100644 --- a/app/core/SDKConnect/handlers/handleDeeplink.test.ts +++ b/app/core/SDKConnect/handlers/handleDeeplink.test.ts @@ -1,6 +1,6 @@ import Logger from '../../../util/Logger'; import AppConstants from '../../AppConstants'; -import SDKConnect from '../SDKConnect'; +import SDKConnect, { SDKConnectState } from '../SDKConnect'; import { waitForCondition } from '../utils/wait.util'; import handleDeeplink from './handleDeeplink'; import handleConnectionMessage from './handleConnectionMessage'; @@ -132,6 +132,17 @@ describe('handleDeeplink', () => { mockHasInitialized.mockReturnValue(true); mockGetConnections.mockReturnValue({ [channelId]: {} }); + // Mock the getConnected method to return a connection that's not connected + mockGetConnected.mockReturnValue({ + [channelId]: { + remote: { + isConnected: jest.fn().mockReturnValue(false), + }, + }, + }); + + sdkConnect.state = { connecting: {} } as unknown as SDKConnectState; + await handleDeeplink({ sdkConnect, channelId, @@ -203,6 +214,19 @@ describe('handleDeeplink', () => { mockHasInitialized.mockReturnValue(true); mockGetConnections.mockReturnValue({ [channelId]: {} }); + sdkConnect.state = { connecting: {} } as unknown as SDKConnectState; + + // Mock a connected channel + mockGetConnected.mockReturnValue({ + [channelId]: { + remote: { + isConnected: jest.fn().mockReturnValue(true), + decrypt: mockDecrypt, + getRPCMethodTracker: jest.fn().mockReturnValue({}), + }, + }, + }); + await handleDeeplink({ sdkConnect, channelId, diff --git a/app/core/SDKConnect/handlers/handleDeeplink.ts b/app/core/SDKConnect/handlers/handleDeeplink.ts index 6e5bd16342f..5535712e669 100644 --- a/app/core/SDKConnect/handlers/handleDeeplink.ts +++ b/app/core/SDKConnect/handlers/handleDeeplink.ts @@ -1,15 +1,16 @@ +import { KeyringController } from '@metamask/keyring-controller'; import { CommunicationLayerMessage, OriginatorInfo, } from '@metamask/sdk-communication-layer'; +import { Platform } from 'react-native'; import Logger from '../../../util/Logger'; import AppConstants from '../../AppConstants'; import Engine from '../../Engine'; import SDKConnect from '../SDKConnect'; import DevLogger from '../utils/DevLogger'; -import { waitForCondition } from '../utils/wait.util'; +import { waitForCondition, waitForKeychainUnlocked } from '../utils/wait.util'; import handleConnectionMessage from './handleConnectionMessage'; -import { Platform } from 'react-native'; const QRCODE_PARAM_PATTERN = '&t=q'; @@ -49,6 +50,17 @@ const handleDeeplink = async ({ } DevLogger.log(`handleDeeplink:: origin=${origin} url=${url}`); + + // Wait for keychain to be unlocked before handling rpc calls. + const keyringController = ( + Engine.context as { KeyringController: KeyringController } + ).KeyringController; + + await waitForKeychainUnlocked({ + keyringController, + context: 'connection::on_message', + }); + // Detect if origin matches qrcode param // SDKs should all add the type of intended use in the qrcode so it can be used correctly when scaning with the camera // does url contains t=d (deelink) or t=q (qrcode) @@ -72,24 +84,40 @@ const handleDeeplink = async ({ try { if (channelExists) { - if (origin === AppConstants.DEEPLINKS.ORIGIN_DEEPLINK) { - // Automatically re-approve hosts. - sdkConnect.revalidateChannel({ - channelId, - }); + // is it already connected? + let connected = sdkConnect.getConnected()[channelId]?.remote.isConnected() ?? false; + if(!connected) { + if(sdkConnect.state.connecting[channelId] === true) { + // skip reconnect if connecting + DevLogger.log(`handleDeeplink:: channel=${channelId} is connecting --- SKIP reconnect`, sdkConnect.state.connecting); + // wait for connection to be established + await waitForCondition({ + fn: () => { + DevLogger.log(`handleDeeplink:: channel=${channelId} is connecting --- wait for connection to be established`, sdkConnect.state.connecting); + return sdkConnect.state.connecting[channelId] === false; + }, + context: 'handleDeeplink', + waitTime: 1000, + }); + } else { + DevLogger.log(`handleDeeplink:: channel=${channelId} reconnecting`); + await sdkConnect.reconnect({ + channelId, + otherPublicKey, + context, + protocolVersion, + initialConnection: false, + trigger: 'deeplink', + updateKey: true, + }); + } + } else { + DevLogger.log(`handleDeeplink:: channel=${channelId} is already connected`); } - await sdkConnect.reconnect({ - channelId, - otherPublicKey, - context, - protocolVersion, - initialConnection: false, - trigger: 'deeplink', - updateKey: true, - }); + connected = sdkConnect.getConnected()[channelId]?.remote.isConnected() ?? false; DevLogger.log( - `handleDeeplink:: channel=${channelId} reconnected -- handle rcp`, + `handleDeeplink:: channel=${channelId} reconnected=${connected} -- handle rcp`, ); // If msg contains rpc calls, handle them if (rpc) { @@ -110,14 +138,19 @@ const handleDeeplink = async ({ const message = JSON.parse(clearRPC) as CommunicationLayerMessage; DevLogger.log(`handleDeeplink:: message`, message); + // Check if already received via websocket + const rpcMethodTracker = connection.remote.getRPCMethodTracker(); + if (rpcMethodTracker?.[message.id ?? '']) { + // Already received via websocket + DevLogger.log(`handleDeeplink:: rpcId=${message.id} already received via websocket`); + return; + } + await handleConnectionMessage({ message, connection, engine: Engine, }); - } else { - // network call to connect to channel - sdkConnect.updateSDKLoadingState({ channelId, loading: true }); } } else { const trigger = From e2385e8e37dc699eddeafdabf362f54cb88d2af6 Mon Sep 17 00:00:00 2001 From: Frank von Hoven <141057783+frankvonhoven@users.noreply.github.com> Date: Thu, 3 Oct 2024 10:20:33 -0500 Subject: [PATCH 27/27] chore: bump to 1444 (#11604) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Bump version to 1444 ## **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. --- android/app/build.gradle | 2 +- bitrise.yml | 4 ++-- ios/MetaMask.xcodeproj/project.pbxproj | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index e8b0f214c2f..7ff5e6cd321 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -173,7 +173,7 @@ android { applicationId "io.metamask" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1443 + versionCode 1444 versionName "7.32.0" testBuildType System.getProperty('testBuildType', 'debug') missingDimensionStrategy 'react-native-camera', 'general' diff --git a/bitrise.yml b/bitrise.yml index 56ae7b46b39..afacd62ddf8 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -1500,13 +1500,13 @@ app: VERSION_NAME: 7.32.0 - opts: is_expand: false - VERSION_NUMBER: 1443 + VERSION_NUMBER: 1444 - opts: is_expand: false FLASK_VERSION_NAME: 7.32.0 - opts: is_expand: false - FLASK_VERSION_NUMBER: 1443 + FLASK_VERSION_NUMBER: 1444 - opts: is_expand: false ANDROID_APK_LINK: '' diff --git a/ios/MetaMask.xcodeproj/project.pbxproj b/ios/MetaMask.xcodeproj/project.pbxproj index b60012d357d..ebf4b615148 100644 --- a/ios/MetaMask.xcodeproj/project.pbxproj +++ b/ios/MetaMask.xcodeproj/project.pbxproj @@ -1273,7 +1273,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1443; + CURRENT_PROJECT_VERSION = 1444; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1338,7 +1338,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1443; + CURRENT_PROJECT_VERSION = 1444; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1401,7 +1401,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1443; + CURRENT_PROJECT_VERSION = 1444; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1462,7 +1462,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1443; + CURRENT_PROJECT_VERSION = 1444; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG; @@ -1624,7 +1624,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMaskDebug.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1443; + CURRENT_PROJECT_VERSION = 1444; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 48XVW22RCG; @@ -1692,7 +1692,7 @@ CODE_SIGN_ENTITLEMENTS = MetaMask/MetaMask.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1443; + CURRENT_PROJECT_VERSION = 1444; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 48XVW22RCG; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 48XVW22RCG;