From 267b1072918920f58d7e16425e58cdf95cb139da Mon Sep 17 00:00:00 2001 From: Nico MASSART Date: Mon, 14 Oct 2024 11:02:37 +0200 Subject: [PATCH 01/11] fix: Android: Splash screen always showing behind other screens (#11760) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** ### Cause of the issue The splash screen has been reworked recently for performances and replaced by an Android window background. But this is visible quickly between screen changes as nav stack has been reorganised. ### Fix - remove the transparent background on MainFlow stack navigator to prevent the window background to be visible behind it ## **Related issues** Fixes #11697 ## **Manual testing steps** ```gherkin Feature: Navigate between screens without splash screen visible Scenario: go to token detail screen Given wallet is ready And some tokens are available in the list (load from a test address) When user touches the token title or icon Then detail screen opens And no splash screen fox is visible even very quicly. ``` >[!NOTE] > The issue was on Android but do not hesitate to also test iOS to make sure nothing is broken. ## **Screenshots/Recordings** ### **Before** See recording in #11697 ### **After** [android.webm](https://github.com/user-attachments/assets/98585c9a-942d-406a-ae87-6d440e5f9ec3) ## **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. --- app/components/Nav/Main/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/components/Nav/Main/index.js b/app/components/Nav/Main/index.js index 0d0a523f547..32a35e27754 100644 --- a/app/components/Nav/Main/index.js +++ b/app/components/Nav/Main/index.js @@ -492,7 +492,6 @@ const MainFlow = () => ( mode={'modal'} screenOptions={{ headerShown: false, - cardStyle: { backgroundColor: importedColors.transparent }, }} > From 6a2dd749a6ba733dc0fdfeaa4e4df9d5e9db377b Mon Sep 17 00:00:00 2001 From: OGPoyraz Date: Mon, 14 Oct 2024 11:35:10 +0200 Subject: [PATCH 02/11] fix: Add `preferContractSymbol` to Name components (#11771) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR adds `preferContractSymbol` property to name components which used in simulations. ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/3494 ## **Manual testing steps** 1. Uniswap in in-app browser 2. Try swap from / to `DAI` 3. In simulations instead of `Dai stablecoin` see `DAI` ## **Screenshots/Recordings** ### **Before** Screenshot 2024-10-14 at 11 16 25 ### **After** Screenshot 2024-10-14 at 11 10 29 ## **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/components/UI/Name/Name.tsx | 14 ++++++++++++-- app/components/UI/Name/Name.types.ts | 3 +++ .../UI/SimulationDetails/AssetPill/AssetPill.tsx | 3 ++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/components/UI/Name/Name.tsx b/app/components/UI/Name/Name.tsx index 1f338a8b0a7..5e44d36fe8f 100644 --- a/app/components/UI/Name/Name.tsx +++ b/app/components/UI/Name/Name.tsx @@ -47,11 +47,21 @@ const UnknownEthereumAddress: React.FC<{ address: string }> = ({ address }) => { ); }; -const Name: React.FC = ({ type, value }) => { +const Name: React.FC = ({ + chainId, + preferContractSymbol, + type, + value, +}) => { if (type !== NameType.EthereumAddress) { throw new Error('Unsupported NameType: ' + type); } - const displayName = useDisplayName(type, value); + const displayName = useDisplayName( + type, + value, + chainId, + preferContractSymbol, + ); const { styles } = useStyles(styleSheet, { displayNameVariant: displayName.variant, }); diff --git a/app/components/UI/Name/Name.types.ts b/app/components/UI/Name/Name.types.ts index 007a5077b06..45f7f241b6f 100644 --- a/app/components/UI/Name/Name.types.ts +++ b/app/components/UI/Name/Name.types.ts @@ -1,4 +1,5 @@ import { ViewProps } from 'react-native'; +import { Hex } from '@metamask/utils'; /** * The name types supported by the NameController. @@ -11,6 +12,8 @@ export enum NameType { } export interface NameProperties extends ViewProps { + chainId?: Hex; + preferContractSymbol?: boolean; type: NameType; value: string; } diff --git a/app/components/UI/SimulationDetails/AssetPill/AssetPill.tsx b/app/components/UI/SimulationDetails/AssetPill/AssetPill.tsx index 96edaf2b2a3..534c0d58c50 100644 --- a/app/components/UI/SimulationDetails/AssetPill/AssetPill.tsx +++ b/app/components/UI/SimulationDetails/AssetPill/AssetPill.tsx @@ -63,9 +63,10 @@ const AssetPill: React.FC = ({ asset }) => { ) : ( )} From 79f0c636397ea892d1b38c08f6ebe1feda306770 Mon Sep 17 00:00:00 2001 From: legobeat <109787230+legobeat@users.noreply.github.com> Date: Mon, 14 Oct 2024 09:38:43 +0000 Subject: [PATCH 03/11] chore(devDeps): remove unused react-native-cli (#11751) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This replaces legacy `react-native-cli` with the cli already provided by `react-native`. ## **Related issues** ## **Manual testing steps** - Run `yarn watch:clean` - Run `yarn start:android` and/or `yarn start:ios` ## **Screenshots/Recordings** ### **Before** n/a ### **After** n/a ## **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. --- package.json | 1 - yarn.lock | 145 +++++++++------------------------------------------ 2 files changed, 26 insertions(+), 120 deletions(-) diff --git a/package.json b/package.json index 8ad00c34adb..5fc6a3457f5 100644 --- a/package.json +++ b/package.json @@ -476,7 +476,6 @@ "prettier": "^2.2.1", "prettier-plugin-gherkin": "^1.1.1", "react-dom": "18.2.0", - "react-native-cli": "2.0.1", "react-native-flipper": "^0.263.0", "react-native-launch-arguments": "^4.0.1", "react-native-performance": "^5.1.2", diff --git a/yarn.lock b/yarn.lock index ddce029d470..4d029ce93c5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12995,11 +12995,6 @@ async-mutex@^0.5.0: dependencies: tslib "^2.4.0" -async@0.2.x, async@~0.2.9: - version "0.2.10" - resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" - integrity sha1-trvgsGdLnXGXCMo43owjfLUmw9E= - async@^1.4.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" @@ -14051,7 +14046,7 @@ cbor-sync@^1.0.4: resolved "https://registry.yarnpkg.com/cbor-sync/-/cbor-sync-1.0.4.tgz#5a11a1ab75c2a14d1af1b237fd84aa8c1593662f" integrity sha512-GWlXN4wiz0vdWWXBU71Dvc1q3aBo0HytqwAZnXF1wOwjqNnDWA1vZ1gDMFLlqohak31VQzmhiYfiCX5QSSfagA== -chalk@^1.1.1, chalk@^1.1.3: +chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= @@ -14563,11 +14558,6 @@ colorette@^2.0.10, colorette@^2.0.14, colorette@^2.0.20: resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== -colors@0.6.x: - version "0.6.2" - resolved "https://registry.yarnpkg.com/colors/-/colors-0.6.2.tgz#2423fe6678ac0c5dae8852e5d0e5be08c997abcc" - integrity sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w= - colors@^1.1.2, colors@^1.3.3: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" @@ -15270,7 +15260,7 @@ csv-writer@^1.6.0: resolved "https://registry.yarnpkg.com/csv-writer/-/csv-writer-1.6.0.tgz#d0cea44b6b4d7d3baa2ecc6f3f7209233514bcf9" integrity sha512-NOx7YDFWEsM/fTRAJjRpPp8t+MKRVvniAg9wQlUKx20MFrPs73WLJhFf5iteqrxNYnsy924K3Iroh3yNHeYd2g== -cycle@1.0.x, cycle@^1.0.3: +cycle@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2" integrity sha1-IegLK+hYD5i0aPN5QwZisEbDStI= @@ -15518,7 +15508,19 @@ dedent@^1.0.0: resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.3.tgz#99aee19eb9bae55a67327717b6e848d0bf777e5a" integrity sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ== -deep-equal@*, deep-equal@^2.0.5: +deep-equal@^1.0.0, deep-equal@^1.0.1, deep-equal@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" + integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g== + dependencies: + is-arguments "^1.0.4" + is-date-object "^1.0.1" + is-regex "^1.0.4" + object-is "^1.0.1" + object-keys "^1.1.1" + regexp.prototype.flags "^1.2.0" + +deep-equal@^2.0.5: version "2.2.2" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.2.2.tgz#9b2635da569a13ba8e1cc159c2f744071b115daa" integrity sha512-xjVyBf0w5vH0I42jdAZzOKVldmPgSulmiyPRywoyq7HXC9qdgo17kxJE+rdnif5Tz6+pIrpJI8dCpMNLIGkUiA== @@ -15542,18 +15544,6 @@ deep-equal@*, deep-equal@^2.0.5: which-collection "^1.0.1" which-typed-array "^1.1.9" -deep-equal@^1.0.0, deep-equal@^1.0.1, deep-equal@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" - integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g== - dependencies: - is-arguments "^1.0.4" - is-date-object "^1.0.1" - is-regex "^1.0.4" - object-is "^1.0.1" - object-keys "^1.1.1" - regexp.prototype.flags "^1.2.0" - deep-equal@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" @@ -17925,11 +17915,6 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= -eyes@0.1.x: - version "0.1.8" - resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" - integrity sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A= - fake-merkle-patricia-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/fake-merkle-patricia-tree/-/fake-merkle-patricia-tree-1.0.1.tgz#4b8c3acfb520afadf9860b1f14cd8ce3402cddd3" @@ -19685,11 +19670,6 @@ i18n-js@3.0.11: resolved "https://registry.yarnpkg.com/i18n-js/-/i18n-js-3.0.11.tgz#f9e96bdb641c5b9d6be12759d7c422089987ef02" integrity sha512-v7dG3kYJTQTyox3NqDabPDE/ZotWntyMI9kh4cYi+XlCSnsIR+KBTS2opPyObL8WndnklcLzbNU92FP/mLge3Q== -i@0.3.x: - version "0.3.7" - resolved "https://registry.yarnpkg.com/i/-/i-0.3.7.tgz#2a7437a923d59c14b17243dc63a549af24d85799" - integrity sha512-FYz4wlXgkQwIPqhzC5TdNMLSE5+GS1IIDJZY/1ZiEPCT2S3COUVZeT5OW4BmW4r5LHLQuOosSwsvnroG9GR59Q== - iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -20459,7 +20439,7 @@ isomorphic-ws@^4.0.1: resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== -isstream@0.1.x, isstream@~0.1.2: +isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= @@ -22993,7 +22973,7 @@ mkdirp@0.5.1: dependencies: minimist "0.0.8" -mkdirp@0.x.x, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.6, mkdirp@~0.5.1: +mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.6, mkdirp@~0.5.1: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== @@ -23226,7 +23206,7 @@ multiple-cucumber-html-reporter@^3.0.1: open "^8.4.2" uuid "^9.0.0" -mute-stream@0.0.8, mute-stream@~0.0.4: +mute-stream@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== @@ -23289,11 +23269,6 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -ncp@0.4.x: - version "0.4.2" - resolved "https://registry.yarnpkg.com/ncp/-/ncp-0.4.2.tgz#abcc6cbd3ec2ed2a729ff6e7c1fa8f01784a8574" - integrity sha1-q8xsvT7C7Spyn/bnwfqPAXhKhXQ= - ncp@2.0.0, ncp@^2.0.0, ncp@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3" @@ -24599,16 +24574,6 @@ pkg-types@^1.0.3: mlly "^1.2.0" pathe "^1.1.0" -pkginfo@0.3.x: - version "0.3.1" - resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.3.1.tgz#5b29f6a81f70717142e09e765bbeab97b4f81e21" - integrity sha1-Wyn2qB9wcXFC4J52W76rl7T4HiE= - -pkginfo@0.x.x: - version "0.4.1" - resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.4.1.tgz#b5418ef0439de5425fc4995042dced14fb2a84ff" - integrity sha1-tUGO8EOd5UJfxJlQQtztFPsqhP8= - please-upgrade-node@^3.1.1, please-upgrade-node@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" @@ -24966,17 +24931,6 @@ promise@^8.3.0: dependencies: asap "~2.0.6" -prompt@^0.2.14: - version "0.2.14" - resolved "https://registry.yarnpkg.com/prompt/-/prompt-0.2.14.tgz#57754f64f543fd7b0845707c818ece618f05ffdc" - integrity sha1-V3VPZPVD/XsIRXB8gY7OYY8F/9w= - dependencies: - pkginfo "0.x.x" - read "1.0.x" - revalidator "0.1.x" - utile "0.2.x" - winston "0.8.x" - prompts@^2.0.1, prompts@^2.4.0: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" @@ -25641,16 +25595,6 @@ react-native-camera@^3.36.0: dependencies: prop-types "^15.6.2" -react-native-cli@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/react-native-cli/-/react-native-cli-2.0.1.tgz#f2cd3c7aa1b83828cdfba630e2dfd817df766d54" - integrity sha1-8s08eqG4OCjN+6Yw4t/YF992bVQ= - dependencies: - chalk "^1.1.1" - minimist "^1.2.0" - prompt "^0.2.14" - semver "^5.0.3" - react-native-confetti-cannon@^1.5.0: version "1.5.2" resolved "https://registry.yarnpkg.com/react-native-confetti-cannon/-/react-native-confetti-cannon-1.5.2.tgz#ca1a05edd2a64b080ea8b6238c49277908e1f123" @@ -26428,13 +26372,6 @@ read-tls-client-hello@^1.0.0: dependencies: "@types/node" "*" -read@1.0.x: - version "1.0.7" - resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" - integrity sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ= - dependencies: - mute-stream "~0.0.4" - "readable-stream@2 || 3", readable-stream@3.6.2, readable-stream@^3.0.2, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0, readable-stream@^3.6.2: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" @@ -26971,11 +26908,6 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -revalidator@0.1.x: - version "0.1.8" - resolved "https://registry.yarnpkg.com/revalidator/-/revalidator-0.1.8.tgz#fece61bfa0c1b52a206bd6b18198184bdd523a3b" - integrity sha1-/s5hv6DBtSoga9axgZgYS91SOjs= - rfc4648@^1.0.0: version "1.5.3" resolved "https://registry.yarnpkg.com/rfc4648/-/rfc4648-1.5.3.tgz#e62b81736c10361ca614efe618a566e93d0b41c0" @@ -27001,13 +26933,6 @@ rgb2hex@^0.1.0: resolved "https://registry.yarnpkg.com/rgb2hex/-/rgb2hex-0.1.10.tgz#4fdd432665273e2d5900434940ceba0a04c8a8a8" integrity sha512-vKz+kzolWbL3rke/xeTE2+6vHmZnNxGyDnaVW4OckntAIcc7DcZzWkQSfxMDwqHS8vhgySnIFyBUH7lIk6PxvQ== -rimraf@2.x.x, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.3: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -27015,6 +26940,13 @@ rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: dependencies: glob "^7.1.3" +rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.3: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + rimraf@~2.4.0: version "2.4.5" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.4.5.tgz#ee710ce5d93a8fdb856fb5ea8ff0e2d75934b2da" @@ -27300,7 +27232,7 @@ semver-compare@^1.0.0: resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= -"semver@2 || 3 || 4 || 5", semver@^5.0.1, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.5.0, semver@^5.6.0, semver@^5.7.1, semver@^5.7.2, semver@~2.3.1: +"semver@2 || 3 || 4 || 5", semver@^5.0.1, semver@^5.1.0, semver@^5.3.0, semver@^5.5.0, semver@^5.6.0, semver@^5.7.1, semver@^5.7.2, semver@~2.3.1: version "5.7.2" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== @@ -29496,18 +29428,6 @@ utila@~0.4: resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" integrity sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA== -utile@0.2.x: - version "0.2.1" - resolved "https://registry.yarnpkg.com/utile/-/utile-0.2.1.tgz#930c88e99098d6220834c356cbd9a770522d90d7" - integrity sha1-kwyI6ZCY1iIINMNWy9mncFItkNc= - dependencies: - async "~0.2.9" - deep-equal "*" - i "0.3.x" - mkdirp "0.x.x" - ncp "0.4.x" - rimraf "2.x.x" - utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" @@ -30098,19 +30018,6 @@ winston-transport@^4.5.0: readable-stream "^3.6.0" triple-beam "^1.3.0" -winston@0.8.x: - version "0.8.3" - resolved "https://registry.yarnpkg.com/winston/-/winston-0.8.3.tgz#64b6abf4cd01adcaefd5009393b1d8e8bec19db0" - integrity sha1-ZLar9M0Brcrv1QCTk7HY6L7BnbA= - dependencies: - async "0.2.x" - colors "0.6.x" - cycle "1.0.x" - eyes "0.1.x" - isstream "0.1.x" - pkginfo "0.3.x" - stack-trace "0.0.x" - winston@3.x: version "3.10.0" resolved "https://registry.yarnpkg.com/winston/-/winston-3.10.0.tgz#d033cb7bd3ced026fed13bf9d92c55b903116803" From 094c578fd3fee437fcd41038ac7f58bba0dbb638 Mon Sep 17 00:00:00 2001 From: abretonc7s <107169956+abretonc7s@users.noreply.github.com> Date: Mon, 14 Oct 2024 17:45:03 +0800 Subject: [PATCH 04/11] feat: add utm field to app_open event (#11651) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Add utm fields to the APP_OPEN event. ## **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** - [ ] 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/core/AppStateEventListener.test.ts | 58 ++++++-------- app/core/AppStateEventListener.ts | 17 ++-- .../ParseManager/extractURLParams.test.ts | 4 + .../ParseManager/extractURLParams.ts | 2 + app/core/processAttribution.test.tsx | 77 ++++++++++++++++--- app/core/processAttribution.tsx | 50 ++++++++++-- 6 files changed, 151 insertions(+), 57 deletions(-) diff --git a/app/core/AppStateEventListener.test.ts b/app/core/AppStateEventListener.test.ts index 3cf93c94aac..34f7c6ad07a 100644 --- a/app/core/AppStateEventListener.test.ts +++ b/app/core/AppStateEventListener.test.ts @@ -3,7 +3,7 @@ import { store } from '../store'; import Logger from '../util/Logger'; import { MetaMetrics, MetaMetricsEvents } from './Analytics'; import { AppStateEventListener } from './AppStateEventListener'; -import extractURLParams from './DeeplinkManager/ParseManager/extractURLParams'; +import { processAttribution } from './processAttribution'; jest.mock('react-native', () => ({ AppState: { @@ -27,12 +27,14 @@ jest.mock('../store', () => ({ }, })); -jest.mock('./DeeplinkManager/ParseManager/extractURLParams', () => jest.fn()); - jest.mock('../util/Logger', () => ({ error: jest.fn(), })); +jest.mock('./processAttribution', () => ({ + processAttribution: jest.fn(), +})); + describe('AppStateEventListener', () => { let appStateManager: AppStateEventListener; let mockAppStateListener: (state: AppStateStatus) => void; @@ -66,11 +68,15 @@ describe('AppStateEventListener', () => { 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' } }); + it('tracks event when app becomes active and attribution data is available', () => { + const mockAttribution = { + attributionId: 'test123', + utm: 'test_utm', + utm_source: 'source', + utm_medium: 'medium', + utm_campaign: 'campaign', + }; + (processAttribution as jest.Mock).mockReturnValue(mockAttribution); appStateManager.setCurrentDeeplink('metamask://connect?attributionId=test123'); mockAppStateListener('active'); @@ -78,51 +84,31 @@ describe('AppStateEventListener', () => { expect(mockTrackEvent).toHaveBeenCalledWith( MetaMetricsEvents.APP_OPENED, - { attributionId: 'test123' }, + { attributionId: 'test123', utm_source: 'source', utm_medium: 'medium', utm_campaign: 'campaign' }, true ); }); - it('does not track event when data collection is disabled', () => { - (store.getState as jest.Mock).mockReturnValue({ - security: { dataCollectionForMarketing: false }, - }); + it('does not track event when processAttribution returns undefined', () => { + (processAttribution as jest.Mock).mockReturnValue(undefined); 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 - ); + expect(mockTrackEvent).not.toHaveBeenCalled(); }); it('handles errors gracefully', () => { - (store.getState as jest.Mock).mockImplementation(() => { - throw new Error('Test error'); + const testError = new Error('Test error'); + (processAttribution as jest.Mock).mockImplementation(() => { + throw testError; }); mockAppStateListener('active'); jest.advanceTimersByTime(2000); expect(Logger.error).toHaveBeenCalledWith( - expect.any(Error), + testError, 'AppStateManager: Error processing app state change' ); expect(mockTrackEvent).not.toHaveBeenCalled(); diff --git a/app/core/AppStateEventListener.ts b/app/core/AppStateEventListener.ts index 46752626e2a..8a9046462f6 100644 --- a/app/core/AppStateEventListener.ts +++ b/app/core/AppStateEventListener.ts @@ -51,13 +51,16 @@ export class AppStateEventListener { } 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 - ); + const attribution = processAttribution({ currentDeeplink: this.currentDeeplink, store: this.store }); + if(attribution) { + const { attributionId, utm, ...utmParams } = attribution; + DevLogger.log(`AppStateManager:: processAppStateChange:: sending event 'APP_OPENED' attributionId=${attribution.attributionId} utm=${attribution.utm}`, utmParams); + MetaMetrics.getInstance().trackEvent( + MetaMetricsEvents.APP_OPENED, + { attributionId, ...utmParams }, + true + ); + } } catch (error) { Logger.error(error as Error, 'AppStateManager: Error processing app state change'); } diff --git a/app/core/DeeplinkManager/ParseManager/extractURLParams.test.ts b/app/core/DeeplinkManager/ParseManager/extractURLParams.test.ts index 50b942a1366..423f78628a4 100644 --- a/app/core/DeeplinkManager/ParseManager/extractURLParams.test.ts +++ b/app/core/DeeplinkManager/ParseManager/extractURLParams.test.ts @@ -43,6 +43,7 @@ describe('extractURLParams', () => { comm: 'test', v: '2', attributionId: '', + utm: '', }; mockUrlParser.mockImplementation( @@ -83,6 +84,7 @@ describe('extractURLParams', () => { pubkey: '', v: '', attributionId: '', + utm: '', }); }); @@ -116,6 +118,7 @@ describe('extractURLParams', () => { pubkey: '', v: '', attributionId: '', + utm: '', }); expect(alertSpy).toHaveBeenCalledWith( @@ -137,6 +140,7 @@ describe('extractURLParams', () => { sdkVersion: '', pubkey: 'xyz', attributionId: '', + utm: '', }; mockUrlParser.mockImplementation( diff --git a/app/core/DeeplinkManager/ParseManager/extractURLParams.ts b/app/core/DeeplinkManager/ParseManager/extractURLParams.ts index 723ce7148b7..4d992b6c3e4 100644 --- a/app/core/DeeplinkManager/ParseManager/extractURLParams.ts +++ b/app/core/DeeplinkManager/ParseManager/extractURLParams.ts @@ -20,6 +20,7 @@ export interface DeeplinkUrlParams { originatorInfo?: string; request?: string; attributionId?: string; + utm?: string; account?: string; // This is the format => "address@chainId" } @@ -41,6 +42,7 @@ function extractURLParams(url: string) { channelId: '', comm: '', attributionId: '', + utm: '', }; DevLogger.log(`extractParams:: urlObj`, urlObj); diff --git a/app/core/processAttribution.test.tsx b/app/core/processAttribution.test.tsx index fa4a196a2e2..79c29c2b908 100644 --- a/app/core/processAttribution.test.tsx +++ b/app/core/processAttribution.test.tsx @@ -1,6 +1,7 @@ import { store } from '../store'; import extractURLParams from './DeeplinkManager/ParseManager/extractURLParams'; import { processAttribution } from './processAttribution'; +import Logger from '../util/Logger'; jest.mock('../store', () => ({ store: { @@ -9,22 +10,42 @@ jest.mock('../store', () => ({ })); jest.mock('./DeeplinkManager/ParseManager/extractURLParams', () => jest.fn()); +jest.mock('../util/Logger', () => ({ + error: jest.fn(), +})); describe('processAttribution', () => { beforeEach(() => { jest.clearAllMocks(); }); - it('returns attributionId when marketing is enabled and deeplink is provided', () => { + it('returns attribution data 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' }, + params: { + attributionId: 'test123', + utm: JSON.stringify({ + source: 'twitter', + medium: 'social', + campaign: 'cmp-57731027-afbf09/', + term: null, + content: null + }) + }, }); - const result = processAttribution({ currentDeeplink: 'metamask://connect?attributionId=test123', store }); - expect(result).toBe('test123'); + const result = processAttribution({ currentDeeplink: 'metamask://connect?attributionId=test123&utm=...', store }); + expect(result).toEqual({ + attributionId: 'test123', + utm: expect.any(String), + utm_source: 'twitter', + utm_medium: 'social', + utm_campaign: 'cmp-57731027-afbf09/', + utm_term: null, + utm_content: null + }); }); it('returns undefined when marketing is disabled', () => { @@ -32,7 +53,7 @@ describe('processAttribution', () => { security: { dataCollectionForMarketing: false }, }); - const result = processAttribution({ currentDeeplink: 'metamask://connect?attributionId=test123', store }); + const result = processAttribution({ currentDeeplink: 'metamask://connect?attributionId=test123&utm=...', store }); expect(result).toBeUndefined(); }); @@ -45,15 +66,53 @@ describe('processAttribution', () => { expect(result).toBeUndefined(); }); - it('returns undefined when attributionId is not present in params', () => { + it('returns partial data when some UTM params are missing', () => { (store.getState as jest.Mock).mockReturnValue({ security: { dataCollectionForMarketing: true }, }); (extractURLParams as jest.Mock).mockReturnValue({ - params: {}, + params: { + attributionId: 'test123', + utm: JSON.stringify({ + source: 'twitter', + medium: 'social' + }) + }, }); - const result = processAttribution({ currentDeeplink: 'metamask://connect', store }); - expect(result).toBeUndefined(); + const result = processAttribution({ currentDeeplink: 'metamask://connect?attributionId=test123&utm=...', store }); + expect(result).toEqual({ + attributionId: 'test123', + utm: expect.any(String), + utm_source: 'twitter', + utm_medium: 'social', + utm_campaign: undefined, + utm_term: undefined, + utm_content: undefined + }); + }); + + it('handles JSON parsing errors gracefully', () => { + (store.getState as jest.Mock).mockReturnValue({ + security: { dataCollectionForMarketing: true }, + }); + (extractURLParams as jest.Mock).mockReturnValue({ + params: { + attributionId: 'test123', + utm: 'invalid-json' + }, + }); + + const result = processAttribution({ currentDeeplink: 'metamask://connect?attributionId=test123&utm=invalid-json', store }); + expect(result).toEqual({ + attributionId: 'test123', + utm: 'invalid-json', + utm_source: undefined, + utm_medium: undefined, + utm_campaign: undefined, + utm_term: undefined, + utm_content: undefined + }); + expect(Logger.error).toHaveBeenCalledWith(expect.any(Error), expect.any(Error)); }); }); diff --git a/app/core/processAttribution.tsx b/app/core/processAttribution.tsx index f3a604e3cf5..d49518bdaaa 100644 --- a/app/core/processAttribution.tsx +++ b/app/core/processAttribution.tsx @@ -1,6 +1,8 @@ import extractURLParams from './DeeplinkManager/ParseManager/extractURLParams'; import { RootState } from '../reducers'; import { Store } from 'redux'; +import Logger from '../util/Logger'; +import DevLogger from './SDKConnect/utils/DevLogger'; interface ProcessAttributionParams { currentDeeplink: string | null; @@ -8,13 +10,51 @@ interface ProcessAttributionParams { store: Store; } -export function processAttribution({ currentDeeplink, store }: ProcessAttributionParams): string | undefined { - const state = store.getState(); - const isMarketingEnabled = state.security.dataCollectionForMarketing; +interface AttributionResult { + attributionId?: string; + utm?: string; + utm_source?: string; + utm_medium?: string; + utm_campaign?: string; + utm_term?: string; + utm_content?: string; +} + +export function processAttribution({ currentDeeplink, store }: ProcessAttributionParams): AttributionResult | undefined { + const { security } = store.getState(); + if (!security.dataCollectionForMarketing) { + return undefined; + } - if (isMarketingEnabled && currentDeeplink) { + if (currentDeeplink) { const { params } = extractURLParams(currentDeeplink); - return params.attributionId || undefined; // Force undefined to be returned as extractUrlParams default to empty string on error. + const attributionId = params.attributionId || undefined; + const utm = params.utm || undefined; + let utm_source, utm_medium, utm_campaign, utm_term, utm_content; + + if (utm) { + try { + const utmParams = JSON.parse(utm); + DevLogger.log('processAttribution:: UTM params', utmParams); + utm_source = utmParams.source; + utm_medium = utmParams.medium; + utm_campaign = utmParams.campaign; + utm_term = utmParams.term; + utm_content = utmParams.content; + } catch (error) { + Logger.error(new Error('Error parsing UTM params'), error); + } + } + + return { + attributionId, + utm, + utm_source, + utm_medium, + utm_campaign, + utm_term, + utm_content + }; } return undefined; From 7863f59e44124ecf6b58264386eda548c5071745 Mon Sep 17 00:00:00 2001 From: sahar-fehri Date: Mon, 14 Oct 2024 15:25:57 +0200 Subject: [PATCH 05/11] chore: upgrade assets controllers v32.0.0 (#11687) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** PR to upgrade assets-controllers to v32 ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** https://github.com/user-attachments/assets/0bc5e970-f069-4838-89c5-ca4dfdc71826 https://github.com/user-attachments/assets/7b6d9987-a57b-48d8-9e6c-97cf882120e2 ## **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/core/Engine.ts | 5 - package.json | 2 +- ...amask+preferences-controller+13.0.3.patch} | 8 +- ...@metamask+assets-controllers+32.0.0.patch} | 511 +++++++----------- yarn.lock | 162 +----- 5 files changed, 227 insertions(+), 461 deletions(-) rename patches/{@metamask+assets-controllers++@metamask+preferences-controller+12.0.0.patch => @metamask+assets-controllers++@metamask+preferences-controller+13.0.3.patch} (54%) rename patches/{@metamask+assets-controllers+31.0.0.patch => @metamask+assets-controllers+32.0.0.patch} (77%) diff --git a/app/core/Engine.ts b/app/core/Engine.ts index 48cc6279e84..0c19aa58502 100644 --- a/app/core/Engine.ts +++ b/app/core/Engine.ts @@ -569,7 +569,6 @@ class Engine { chainId: networkController.getNetworkClientById( networkController?.state.selectedNetworkClientId, ).configuration.chainId, - // @ts-expect-error TODO: Resolve bump the assets controller version. getNetworkClientById: networkController.getNetworkClientById.bind(networkController), }); @@ -654,7 +653,6 @@ class Engine { networkController?.state.selectedNetworkClientId, ).configuration.chainId, selectedAddress: preferencesController.state.selectedAddress, - // @ts-expect-error TODO: Resolve provider type mismatch provider: networkController.getProviderAndBlockTracker().provider, state: initialState.TokensController, // @ts-expect-error TODO: Resolve mismatch between base-controller versions. @@ -953,7 +951,6 @@ class Engine { networkController?.state.selectedNetworkClientId, ).configuration.chainId, ), - // @ts-expect-error TODO: Resolve mismatch between base-controller versions. getNetworkClientById: networkController.getNetworkClientById.bind(networkController), }); @@ -1529,7 +1526,6 @@ class Engine { selectedAddress: preferencesController.state.selectedAddress, tokenPricesService: codefiTokenApiV2, interval: 30 * 60 * 1000, - // @ts-expect-error TODO: Resolve mismatch between base-controller versions. getNetworkClientById: networkController.getNetworkClientById.bind(networkController), }), @@ -1780,7 +1776,6 @@ class Engine { } provider.sendAsync = provider.sendAsync.bind(provider); AccountTrackerController.configure({ provider }); - // @ts-expect-error TODO: Resolve mismatch between base-controller versions. AssetsContractController.configure({ provider }); SwapsController.configure({ diff --git a/package.json b/package.json index 5fc6a3457f5..97c3759e780 100644 --- a/package.json +++ b/package.json @@ -145,7 +145,7 @@ "@metamask/accounts-controller": "^18.2.1", "@metamask/address-book-controller": "^6.0.1", "@metamask/approval-controller": "^7.0.1", - "@metamask/assets-controllers": "^31.0.0", + "@metamask/assets-controllers": "^32.0.0", "@metamask/base-controller": "^7.0.1", "@metamask/composable-controller": "^3.0.0", "@metamask/contract-metadata": "^2.1.0", diff --git a/patches/@metamask+assets-controllers++@metamask+preferences-controller+12.0.0.patch b/patches/@metamask+assets-controllers++@metamask+preferences-controller+13.0.3.patch similarity index 54% rename from patches/@metamask+assets-controllers++@metamask+preferences-controller+12.0.0.patch rename to patches/@metamask+assets-controllers++@metamask+preferences-controller+13.0.3.patch index 00f4da63438..c45e6f5109a 100644 --- a/patches/@metamask+assets-controllers++@metamask+preferences-controller+12.0.0.patch +++ b/patches/@metamask+assets-controllers++@metamask+preferences-controller+13.0.3.patch @@ -1,7 +1,7 @@ -diff --git a/node_modules/@metamask/assets-controllers/node_modules/@metamask/preferences-controller/dist/types/PreferencesController.d.ts b/node_modules/@metamask/assets-controllers/node_modules/@metamask/preferences-controller/dist/types/PreferencesController.d.ts -index ddf6eb4..e8dac6d 100644 ---- a/node_modules/@metamask/assets-controllers/node_modules/@metamask/preferences-controller/dist/types/PreferencesController.d.ts -+++ b/node_modules/@metamask/assets-controllers/node_modules/@metamask/preferences-controller/dist/types/PreferencesController.d.ts +diff --git a/node_modules/@metamask/assets-controllers/node_modules/@metamask/preferences-controller/dist/PreferencesController.d.cts b/node_modules/@metamask/assets-controllers/node_modules/@metamask/preferences-controller/dist/PreferencesController.d.cts +index 04a9d6f..391652d 100644 +--- a/node_modules/@metamask/assets-controllers/node_modules/@metamask/preferences-controller/dist/PreferencesController.d.cts ++++ b/node_modules/@metamask/assets-controllers/node_modules/@metamask/preferences-controller/dist/PreferencesController.d.cts @@ -65,7 +65,7 @@ export type PreferencesState = { /** * Controls whether the OpenSea API is used diff --git a/patches/@metamask+assets-controllers+31.0.0.patch b/patches/@metamask+assets-controllers+32.0.0.patch similarity index 77% rename from patches/@metamask+assets-controllers+31.0.0.patch rename to patches/@metamask+assets-controllers+32.0.0.patch index 7de2ffecb6a..875d616b53e 100644 --- a/patches/@metamask+assets-controllers+31.0.0.patch +++ b/patches/@metamask+assets-controllers+32.0.0.patch @@ -1,10 +1,10 @@ diff --git a/node_modules/@metamask/assets-controllers/dist/chunk-4AC3X2U5.js b/node_modules/@metamask/assets-controllers/dist/chunk-4AC3X2U5.js -index bb55790..b235cbf 100644 +index bb55790..c7e7f99 100644 --- a/node_modules/@metamask/assets-controllers/dist/chunk-4AC3X2U5.js +++ b/node_modules/@metamask/assets-controllers/dist/chunk-4AC3X2U5.js -@@ -292,6 +292,18 @@ var TokensController = class extends _basecontroller.BaseController { - releaseLock(); - } +@@ -187,6 +187,18 @@ var TokensController = class extends _basecontroller.BaseController { + } + ); } + /** + * THIS FUNCTIONS IS CURRENTLY PATCHED AND STILL NEEDS TO BE IMPLEMENTED ON THE CORE REPO @@ -19,9 +19,9 @@ index bb55790..b235cbf 100644 + }); + } /** - * Add a batch of tokens. + * Adds a token to the stored token list. * -@@ -605,9 +617,14 @@ _selectedAddress = new WeakMap(); +@@ -605,9 +617,13 @@ _selectedAddress = new WeakMap(); _provider = new WeakMap(); _abortController = new WeakMap(); _onNetworkDidChange = new WeakSet(); @@ -29,7 +29,6 @@ index bb55790..b235cbf 100644 +onNetworkDidChange_fn = function({ selectedNetworkClientId }) { const { allTokens, allIgnoredTokens, allDetectedTokens } = this.state; - const { chainId } = providerConfig; -+ // This wont be needed in v32 + const selectedNetworkClient = this.messagingSystem.call( + 'NetworkController:getNetworkClientById', + selectedNetworkClientId, @@ -39,22 +38,10 @@ index bb55790..b235cbf 100644 _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _abortController, new AbortController()); _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _chainId, chainId); diff --git a/node_modules/@metamask/assets-controllers/dist/chunk-CGLUTXI7.js b/node_modules/@metamask/assets-controllers/dist/chunk-CGLUTXI7.js -index 7cc44fa..a7663b4 100644 +index 7cc44fa..7a1de65 100644 --- a/node_modules/@metamask/assets-controllers/dist/chunk-CGLUTXI7.js +++ b/node_modules/@metamask/assets-controllers/dist/chunk-CGLUTXI7.js -@@ -17,7 +17,10 @@ var _basecontroller = require('@metamask/base-controller'); - - - -- -+/** -+ * Changes regarding displayNftMedia, TokenURI and error nft metadata property are not on the core repo and needed to be refactor to be removed from the patch -+ * updateNftMetadata changes will be introduced on latest versions of changes of assets controllers, v^30 or next -+ */ - - - -@@ -44,7 +47,7 @@ var getDefaultNftControllerState = () => ({ +@@ -44,7 +44,7 @@ var getDefaultNftControllerState = () => ({ allNfts: {}, ignoredNfts: [] }); @@ -63,7 +50,7 @@ index 7cc44fa..a7663b4 100644 var NftController = class extends _basecontroller.BaseController { /** * Creates an NftController instance. -@@ -53,7 +56,7 @@ var NftController = class extends _basecontroller.BaseController { +@@ -53,7 +53,7 @@ var NftController = class extends _basecontroller.BaseController { * @param options.chainId - The chain ID of the current network. * @param options.selectedAddress - The currently selected address. * @param options.ipfsGateway - The configured IPFS gateway. @@ -72,7 +59,7 @@ index 7cc44fa..a7663b4 100644 * @param options.useIpfsSubdomains - Controls whether IPFS subdomains are used. * @param options.isIpfsGatewayEnabled - Controls whether IPFS is enabled or not. * @param options.getERC721AssetName - Gets the name of the asset at the given address. -@@ -71,7 +74,7 @@ var NftController = class extends _basecontroller.BaseController { +@@ -71,7 +71,7 @@ var NftController = class extends _basecontroller.BaseController { chainId: initialChainId, selectedAddress = "", ipfsGateway = _controllerutils.IPFS_DEFAULT_GATEWAY_URL, @@ -81,7 +68,7 @@ index 7cc44fa..a7663b4 100644 useIpfsSubdomains = true, isIpfsGatewayEnabled = true, getERC721AssetName, -@@ -104,7 +107,7 @@ var NftController = class extends _basecontroller.BaseController { +@@ -104,7 +104,7 @@ var NftController = class extends _basecontroller.BaseController { * @param preferencesState - The new state of the preference controller. * @param preferencesState.selectedAddress - The current selected address. * @param preferencesState.ipfsGateway - The configured IPFS gateway. @@ -90,7 +77,7 @@ index 7cc44fa..a7663b4 100644 * @param preferencesState.isIpfsGatewayEnabled - Controls whether IPFS is enabled or not. */ _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _onPreferencesControllerStateChange); -@@ -233,7 +236,7 @@ var NftController = class extends _basecontroller.BaseController { +@@ -233,7 +233,7 @@ var NftController = class extends _basecontroller.BaseController { _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _selectedAddress, void 0); _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _chainId, void 0); _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _ipfsGateway, void 0); @@ -99,7 +86,7 @@ index 7cc44fa..a7663b4 100644 _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _useIpfsSubdomains, void 0); _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _isIpfsGatewayEnabled, void 0); _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _getERC721AssetName, void 0); -@@ -246,7 +249,7 @@ var NftController = class extends _basecontroller.BaseController { +@@ -246,7 +246,7 @@ var NftController = class extends _basecontroller.BaseController { _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _selectedAddress, selectedAddress); _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _chainId, initialChainId); _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _ipfsGateway, ipfsGateway); @@ -108,7 +95,7 @@ index 7cc44fa..a7663b4 100644 _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _useIpfsSubdomains, useIpfsSubdomains); _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _isIpfsGatewayEnabled, isIpfsGatewayEnabled); _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _getERC721AssetName, getERC721AssetName); -@@ -268,6 +271,17 @@ var NftController = class extends _basecontroller.BaseController { +@@ -268,6 +268,17 @@ var NftController = class extends _basecontroller.BaseController { getNftApi() { return `${_controllerutils.NFT_API_BASE_URL}/tokens`; } @@ -126,87 +113,7 @@ index 7cc44fa..a7663b4 100644 /** * Adds a new suggestedAsset to state. Parameters will be validated according to * asset type being watched. A `:pending` hub event will be emitted once added. -@@ -430,43 +444,48 @@ var NftController = class extends _basecontroller.BaseController { - userAddress = _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _selectedAddress), - networkClientId - }) { -- const chainId = _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getCorrectChainId, getCorrectChainId_fn).call(this, { networkClientId }); -- const nftsWithChecksumAdr = nfts.map((nft) => { -- return { -- ...nft, -- address: _controllerutils.toChecksumHexAddress.call(void 0, nft.address) -- }; -- }); -- const nftMetadataResults = await Promise.all( -- nftsWithChecksumAdr.map(async (nft) => { -- const resMetadata = await _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getNftInformation, getNftInformation_fn).call(this, nft.address, nft.tokenId, networkClientId); -+ const releaseLock = await _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _mutex).acquire(); -+ try{ -+ const chainId = _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getCorrectChainId, getCorrectChainId_fn).call(this, { networkClientId }); -+ const nftsWithChecksumAdr = nfts.map((nft) => { - return { -- nft, -- newMetadata: resMetadata -+ ...nft, -+ address: _controllerutils.toChecksumHexAddress.call(void 0, nft.address) - }; -- }) -- ); -- const nftsWithDifferentMetadata = []; -- const { allNfts } = this.state; -- const stateNfts = allNfts[userAddress]?.[chainId] || []; -- nftMetadataResults.forEach((singleNft) => { -- const existingEntry = stateNfts.find( -- (nft) => nft.address.toLowerCase() === singleNft.nft.address.toLowerCase() && nft.tokenId === singleNft.nft.tokenId -+ }); -+ const nftMetadataResults = await Promise.all( -+ nftsWithChecksumAdr.map(async (nft) => { -+ const resMetadata = await _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getNftInformation, getNftInformation_fn).call(this, nft.address, nft.tokenId, networkClientId); -+ return { -+ nft, -+ newMetadata: resMetadata -+ }; -+ }) - ); -- if (existingEntry) { -- const differentMetadata = _chunkNEXY7SE2js.compareNftMetadata.call(void 0, -- singleNft.newMetadata, -- existingEntry -+ const nftsWithDifferentMetadata = []; -+ const { allNfts } = this.state; -+ const stateNfts = allNfts[userAddress]?.[chainId] || []; -+ nftMetadataResults.forEach((singleNft) => { -+ const existingEntry = stateNfts.find( -+ (nft) => nft.address.toLowerCase() === singleNft.nft.address.toLowerCase() && nft.tokenId === singleNft.nft.tokenId - ); -- if (differentMetadata) { -- nftsWithDifferentMetadata.push(singleNft); -+ if (existingEntry) { -+ const differentMetadata = _chunkNEXY7SE2js.compareNftMetadata.call(void 0, -+ singleNft.newMetadata, -+ existingEntry -+ ); -+ if (differentMetadata) { -+ nftsWithDifferentMetadata.push(singleNft); -+ } - } -+ }); -+ if (nftsWithDifferentMetadata.length !== 0) { -+ nftsWithDifferentMetadata.forEach( -+ (elm) => this.updateNft(elm.nft, elm.newMetadata, userAddress, chainId) -+ ); - } -- }); -- if (nftsWithDifferentMetadata.length !== 0) { -- nftsWithDifferentMetadata.forEach( -- (elm) => this.updateNft(elm.nft, elm.newMetadata, userAddress, chainId) -- ); -+ } finally { -+ releaseLock(); - } - } - /** -@@ -771,7 +790,7 @@ _mutex = new WeakMap(); +@@ -771,7 +782,7 @@ _mutex = new WeakMap(); _selectedAddress = new WeakMap(); _chainId = new WeakMap(); _ipfsGateway = new WeakMap(); @@ -215,7 +122,7 @@ index 7cc44fa..a7663b4 100644 _useIpfsSubdomains = new WeakMap(); _isIpfsGatewayEnabled = new WeakMap(); _getERC721AssetName = new WeakMap(); -@@ -797,14 +816,14 @@ _onPreferencesControllerStateChange = new WeakSet(); +@@ -797,14 +808,14 @@ _onPreferencesControllerStateChange = new WeakSet(); onPreferencesControllerStateChange_fn = async function({ selectedAddress, ipfsGateway, @@ -233,7 +140,15 @@ index 7cc44fa..a7663b4 100644 if (needsUpdateNftMetadata) { const nfts = this.state.allNfts[selectedAddress]?.[_chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _chainId)] ?? []; const nftsToUpdate = nfts.filter( -@@ -850,12 +869,25 @@ getNftInformationFromApi_fn = async function(contractAddress, tokenId) { +@@ -818,6 +829,7 @@ onPreferencesControllerStateChange_fn = async function({ + } + } + }; ++ + _updateNestedNftState = new WeakSet(); + updateNestedNftState_fn = function(newCollection, baseStateKey, { userAddress, chainId }) { + this.update((state) => { +@@ -850,12 +862,25 @@ getNftInformationFromApi_fn = async function(contractAddress, tokenId) { } } }); @@ -242,9 +157,9 @@ index 7cc44fa..a7663b4 100644 + id: `${nftInformation?.tokens[0]?.token?.collection?.id}` + }).toString(); + const collectionInformation = await _controllerutils.fetchWithErrorHandling.call(void 0, { -+ url: `${_controllerutils.NFT_API_BASE_URL}/collections?${getCollectionParams}`, -+ options: { -+ headers: { ++ url: `${_controllerutils.NFT_API_BASE_URL}/collections?${getCollectionParams}`, ++ options: { ++ headers: { + Version: '1' + } + } @@ -260,52 +175,25 @@ index 7cc44fa..a7663b4 100644 }; } const { -@@ -887,7 +919,16 @@ getNftInformationFromApi_fn = async function(contractAddress, tokenId) { +@@ -887,7 +912,16 @@ getNftInformationFromApi_fn = async function(contractAddress, tokenId) { }, rarityRank && { rarityRank }, rarity && { rarity }, - collection && { collection } + (collection || collectionInformation) && { -+ collection: { -+ ...collection || {}, -+ creator: collection?.creator || collectionInformation?.collections[0].creator, -+ openseaVerificationStatus: collectionInformation?.collections[0].openseaVerificationStatus, -+ contractDeployedAt: collectionInformation?.collections[0].contractDeployedAt, -+ ownerCount: collectionInformation?.collections[0].ownerCount, -+ topBid: collectionInformation?.collections[0].topBid ++ collection: { ++ ...collection || {}, ++ creator: collection?.creator || collectionInformation?.collections[0].creator, ++ openseaVerificationStatus: collectionInformation?.collections[0].openseaVerificationStatus, ++ contractDeployedAt: collectionInformation?.collections[0].contractDeployedAt, ++ ownerCount: collectionInformation?.collections[0].ownerCount, ++ topBid: collectionInformation?.collections[0].topBid ++ } + } -+ } ); return nftMetadata; }; -@@ -896,6 +937,17 @@ getNftInformationFromTokenURI_fn = async function(contractAddress, tokenId, netw - const result = await _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getNftURIAndStandard, getNftURIAndStandard_fn).call(this, contractAddress, tokenId, networkClientId); - let tokenURI = result[0]; - const standard = result[1]; -+ if (!_chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _displayNftMedia) && !_chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _isIpfsGatewayEnabled)) { -+ return { -+ image: null, -+ name: null, -+ description: null, -+ standard: standard || null, -+ favorite: false, -+ tokenURI, -+ }; -+ } -+ - const hasIpfsTokenURI = tokenURI.startsWith("ipfs://"); - if (hasIpfsTokenURI && !_chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _isIpfsGatewayEnabled)) { - return { -@@ -907,7 +959,7 @@ getNftInformationFromTokenURI_fn = async function(contractAddress, tokenId, netw - tokenURI: tokenURI ?? null - }; - } -- const isDisplayNFTMediaToggleEnabled = _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _openSeaEnabled); -+ const isDisplayNFTMediaToggleEnabled = _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _displayNftMedia); - if (!hasIpfsTokenURI && !isDisplayNFTMediaToggleEnabled) { - return { - image: null, -@@ -915,7 +967,8 @@ getNftInformationFromTokenURI_fn = async function(contractAddress, tokenId, netw +@@ -904,10 +938,11 @@ getNftInformationFromTokenURI_fn = async function(contractAddress, tokenId, netw description: null, standard: standard || null, favorite: false, @@ -314,8 +202,12 @@ index 7cc44fa..a7663b4 100644 + error: 'URI import error', }; } - if (hasIpfsTokenURI) { -@@ -925,6 +978,16 @@ getNftInformationFromTokenURI_fn = async function(contractAddress, tokenId, netw +- const isDisplayNFTMediaToggleEnabled = _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _openSeaEnabled); ++ const isDisplayNFTMediaToggleEnabled = _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _displayNftMedia); + if (!hasIpfsTokenURI && !isDisplayNFTMediaToggleEnabled) { + return { + image: null, +@@ -925,6 +960,16 @@ getNftInformationFromTokenURI_fn = async function(contractAddress, tokenId, netw _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _useIpfsSubdomains) ); } @@ -332,7 +224,7 @@ index 7cc44fa..a7663b4 100644 try { const object = await _controllerutils.handleFetch.call(void 0, tokenURI); const image = Object.prototype.hasOwnProperty.call(object, "image") ? "image" : ( -@@ -946,7 +1009,8 @@ getNftInformationFromTokenURI_fn = async function(contractAddress, tokenId, netw +@@ -946,7 +991,8 @@ getNftInformationFromTokenURI_fn = async function(contractAddress, tokenId, netw description: null, standard: standard || null, favorite: false, @@ -342,7 +234,7 @@ index 7cc44fa..a7663b4 100644 }; } }; -@@ -977,15 +1041,26 @@ getNftInformation_fn = async function(contractAddress, tokenId, networkClientId) +@@ -977,15 +1023,27 @@ getNftInformation_fn = async function(contractAddress, tokenId, networkClientId) _controllerutils.safelyExecute.call(void 0, () => _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getNftInformationFromTokenURI, getNftInformationFromTokenURI_fn).call(this, contractAddress, tokenId, networkClientId) ), @@ -351,16 +243,17 @@ index 7cc44fa..a7663b4 100644 () => _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getNftInformationFromApi, getNftInformationFromApi_fn).call(this, contractAddress, tokenId) ) : void 0 ]); ++ + if (blockchainMetadata?.error && nftApiMetadata?.error) { -+ return { -+ image: null, -+ name: null, -+ description: null, -+ standard: blockchainMetadata.standard ?? null, -+ favorite: false, -+ tokenURI: blockchainMetadata.tokenURI ?? null, -+ error: 'Both import failed', -+ }; ++ return { ++ image: null, ++ name: null, ++ description: null, ++ standard: blockchainMetadata.standard ?? null, ++ favorite: false, ++ tokenURI: blockchainMetadata.tokenURI ?? null, ++ error: 'Both import failed', ++ }; + } return { ...nftApiMetadata, @@ -371,7 +264,7 @@ index 7cc44fa..a7663b4 100644 standard: blockchainMetadata?.standard ?? nftApiMetadata?.standard ?? null, tokenURI: blockchainMetadata?.tokenURI ?? null }; -@@ -1048,7 +1123,8 @@ addIndividualNft_fn = async function(tokenAddress, tokenId, nftMetadata, nftCont +@@ -1048,7 +1106,8 @@ addIndividualNft_fn = async function(tokenAddress, tokenId, nftMetadata, nftCont nftMetadata, existingEntry ); @@ -381,7 +274,7 @@ index 7cc44fa..a7663b4 100644 return; } const indexToUpdate = nfts.findIndex( -@@ -1080,7 +1156,8 @@ addIndividualNft_fn = async function(tokenAddress, tokenId, nftMetadata, nftCont +@@ -1080,7 +1139,8 @@ addIndividualNft_fn = async function(tokenAddress, tokenId, nftMetadata, nftCont symbol: nftContract.symbol, tokenId: tokenId.toString(), standard: nftMetadata.standard, @@ -392,7 +285,7 @@ index 7cc44fa..a7663b4 100644 } } finally { diff --git a/node_modules/@metamask/assets-controllers/dist/chunk-ELSMS5S7.js b/node_modules/@metamask/assets-controllers/dist/chunk-ELSMS5S7.js -index 45254ad..cd5f3a1 100644 +index 45254ad..f3c6204 100644 --- a/node_modules/@metamask/assets-controllers/dist/chunk-ELSMS5S7.js +++ b/node_modules/@metamask/assets-controllers/dist/chunk-ELSMS5S7.js @@ -87,6 +87,7 @@ var CurrencyRateController = class extends _pollingcontroller.StaticIntervalPoll @@ -426,7 +319,7 @@ index 45254ad..cd5f3a1 100644 - currentCurrency - }; - }); -+ if (shouldUpdateState) { ++ if(shouldUpdateState) { + this.update(() => { + return { + currencyRates: { @@ -439,13 +332,13 @@ index 45254ad..cd5f3a1 100644 + }, + currentCurrency + }; -+ }) ++ }); + } } finally { releaseLock(); } diff --git a/node_modules/@metamask/assets-controllers/dist/chunk-GU53EI7A.js b/node_modules/@metamask/assets-controllers/dist/chunk-GU53EI7A.js -index 33b048f..8815b95 100644 +index 33b048f..5867375 100644 --- a/node_modules/@metamask/assets-controllers/dist/chunk-GU53EI7A.js +++ b/node_modules/@metamask/assets-controllers/dist/chunk-GU53EI7A.js @@ -61,7 +61,7 @@ var AccountTrackerController = class extends _pollingcontroller.StaticIntervalPo @@ -457,36 +350,34 @@ index 33b048f..8815b95 100644 const accountsForChain = { ...accountsByChainId[chainId] }; for (const address of accountsToUpdate) { const balance = await this.getBalanceFromChain(address, ethQuery); -@@ -80,9 +80,11 @@ var AccountTrackerController = class extends _pollingcontroller.StaticIntervalPo +@@ -80,9 +80,8 @@ var AccountTrackerController = class extends _pollingcontroller.StaticIntervalPo [chainId]: accountsForChain } }); - } catch (err) { + } finally { -+ /** -+ * This change is not present on the core repo -+ */ releaseLock(); - throw err; } }; this.defaultConfig = { diff --git a/node_modules/@metamask/assets-controllers/dist/chunk-HDI4L2DD.js b/node_modules/@metamask/assets-controllers/dist/chunk-HDI4L2DD.js -index 76e3362..f733c85 100644 +index 76e3362..e11991d 100644 --- a/node_modules/@metamask/assets-controllers/dist/chunk-HDI4L2DD.js +++ b/node_modules/@metamask/assets-controllers/dist/chunk-HDI4L2DD.js -@@ -165,7 +165,9 @@ var TokenDetectionController = class extends _pollingcontroller.StaticIntervalPo +@@ -165,7 +165,10 @@ var TokenDetectionController = class extends _pollingcontroller.StaticIntervalPo if (!this.isActive) { return; } - const addressAgainstWhichToDetect = selectedAddress ?? _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _selectedAddress); ++ //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; -@@ -224,12 +226,10 @@ registerEventListeners_fn = function() { +@@ -224,12 +227,10 @@ registerEventListeners_fn = function() { ); this.messagingSystem.subscribe( "PreferencesController:stateChange", @@ -502,7 +393,7 @@ index 76e3362..f733c85 100644 selectedAddress: _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _selectedAddress) }); diff --git a/node_modules/@metamask/assets-controllers/dist/chunk-IBK6AXPP.js b/node_modules/@metamask/assets-controllers/dist/chunk-IBK6AXPP.js -index f7509a1..52bc67e 100644 +index f7509a1..4573718 100644 --- a/node_modules/@metamask/assets-controllers/dist/chunk-IBK6AXPP.js +++ b/node_modules/@metamask/assets-controllers/dist/chunk-IBK6AXPP.js @@ -19,7 +19,7 @@ function getDefaultTokenBalancesState() { @@ -526,24 +417,23 @@ index f7509a1..52bc67e 100644 this.messagingSystem.subscribe( "TokensController:stateChange", ({ tokens: newTokens, detectedTokens }) => { -@@ -79,6 +81,16 @@ var TokenBalancesController = class extends _basecontroller.BaseController { +@@ -79,6 +81,15 @@ var TokenBalancesController = class extends _basecontroller.BaseController { disable() { _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _disabled, true); } -+ -+/** ++ /** + * THIS FUNCTIONS IS CURRENTLY PATCHED AND STILL NEEDS TO BE IMPLEMENTED ON THE CORE REPO + * Resets to the default state + */ + reset() { -+ this.update((state) => { ++ this.update((state) => { + state.contractBalances = {}; -+ }); -+ } ++ }); ++ } /** * Starts a new polling interval. * -@@ -100,27 +112,34 @@ var TokenBalancesController = class extends _basecontroller.BaseController { +@@ -100,27 +111,34 @@ var TokenBalancesController = class extends _basecontroller.BaseController { * Updates balances for all tokens. */ async updateBalances() { @@ -567,20 +457,20 @@ index f7509a1..52bc67e 100644 - } - } + const balancePromises = _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _tokens).map((token) => { -+ const { address } = token; ++ const { address } = token; + return _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _getERC20BalanceOf).call(this, address, selectedAddress).then((balance) => { -+ newContractBalances[address] = _controllerutils.toHex.call(void 0, balance); -+ token = { -+ ...token, -+ hasBalanceError: false -+ }; ++ newContractBalances[address] = _controllerutils.toHex.call(void 0, balance); ++ token = { ++ ...token, ++ hasBalanceError: false ++ }; + }).catch((error) => { -+ newContractBalances[address] = _controllerutils.toHex.call(void 0, 0); -+ token = { -+ ...token, -+ hasBalanceError: true -+ }; -+ }); ++ newContractBalances[address] = _controllerutils.toHex.call(void 0, 0); ++ token = { ++ ...token, ++ hasBalanceError: true ++ }; ++ }); + }); + await Promise.all(balancePromises); this.update((state) => { @@ -590,7 +480,7 @@ index f7509a1..52bc67e 100644 } }; _handle = new WeakMap(); -@@ -128,6 +147,7 @@ _getERC20BalanceOf = new WeakMap(); +@@ -128,6 +146,7 @@ _getERC20BalanceOf = new WeakMap(); _interval = new WeakMap(); _tokens = new WeakMap(); _disabled = new WeakMap(); @@ -621,38 +511,36 @@ index 44804c8..911a6e6 100644 this.clearingTokenListData(); } else { diff --git a/node_modules/@metamask/assets-controllers/dist/chunk-QFDTOEYR.js b/node_modules/@metamask/assets-controllers/dist/chunk-QFDTOEYR.js -index 5335fa5..ae37683 100644 +index 5335fa5..0854306 100644 --- a/node_modules/@metamask/assets-controllers/dist/chunk-QFDTOEYR.js +++ b/node_modules/@metamask/assets-controllers/dist/chunk-QFDTOEYR.js -@@ -7,6 +7,8 @@ var _chunkZ4BLTVTBjs = require('./chunk-Z4BLTVTB.js'); - +@@ -8,12 +8,14 @@ var _chunkZ4BLTVTBjs = require('./chunk-Z4BLTVTB.js'); // src/NftDetectionController.ts + +- +var utils_1 = require('@metamask/utils'); +var _chunkR4HATJKUjs = require('./chunk-NEXY7SE2.js'); -@@ -14,6 +16,7 @@ var _chunkZ4BLTVTBjs = require('./chunk-Z4BLTVTB.js'); - var _controllerutils = require('@metamask/controller-utils'); +var supportedNftDetectionNetworks = [_controllerutils.ChainId.mainnet]; var _pollingcontroller = require('@metamask/polling-controller'); var DEFAULT_INTERVAL = 18e4; var controllerName = "NftDetectionController"; -@@ -24,7 +27,9 @@ var BlockaidResultType = /* @__PURE__ */ ((BlockaidResultType2) => { +@@ -24,7 +26,8 @@ var BlockaidResultType = /* @__PURE__ */ ((BlockaidResultType2) => { BlockaidResultType2["Malicious"] = "Malicious"; return BlockaidResultType2; })(BlockaidResultType || {}); -var _intervalId, _interval, _disabled, _addNft, _getNftState, _stopPolling, stopPolling_fn, _startPolling, startPolling_fn, _onPreferencesControllerStateChange, onPreferencesControllerStateChange_fn, _getOwnerNftApi, getOwnerNftApi_fn, _getOwnerNfts, getOwnerNfts_fn; -+// This patch wont be needed in v35 +var MAX_GET_COLLECTION_BATCH_SIZE = 20; +var _intervalId, _interval, _disabled, _addNft, _getNftState, _stopPolling, stopPolling_fn, _startPolling, startPolling_fn, _onPreferencesControllerStateChange, onPreferencesControllerStateChange_fn, _getOwnerNftApi, getOwnerNftApi_fn, _getOwnerNfts, getOwnerNfts_fn, _inProcessNftFetchingUpdates; var NftDetectionController = class extends _pollingcontroller.StaticIntervalPollingController { /** * The controller options -@@ -68,8 +73,10 @@ var NftDetectionController = class extends _pollingcontroller.StaticIntervalPoll +@@ -68,8 +71,10 @@ var NftDetectionController = class extends _pollingcontroller.StaticIntervalPoll _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _disabled, void 0); _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _addNft, void 0); _chunkZ4BLTVTBjs.__privateAdd.call(void 0, this, _getNftState, void 0); @@ -663,19 +551,16 @@ index 5335fa5..ae37683 100644 _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _getNftState, getNftState); _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _addNft, addNft); this.messagingSystem.subscribe( -@@ -126,62 +133,154 @@ var NftDetectionController = class extends _pollingcontroller.StaticIntervalPoll +@@ -126,62 +131,152 @@ var NftDetectionController = class extends _pollingcontroller.StaticIntervalPoll */ async detectNfts(options) { const userAddress = options?.userAddress ?? this.messagingSystem.call("PreferencesController:getState").selectedAddress; - if (!this.isMainnet() || _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _disabled)) { -+ const { selectedNetworkClientId } = this.messagingSystem.call( -+ "NetworkController:getState" -+ ); -+ const { -+ configuration: { chainId } -+ } = this.messagingSystem.call( -+ "NetworkController:getNetworkClientById", -+ selectedNetworkClientId ++ ++ const { selectedNetworkClientId } = this.messagingSystem.call("NetworkController:getState"); ++ const { configuration: { chainId }} = this.messagingSystem.call( ++ "NetworkController:getNetworkClientById", ++ selectedNetworkClientId + ); + if (!supportedNftDetectionNetworks.includes(chainId) || _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _disabled)) { return; @@ -727,19 +612,18 @@ index 5335fa5..ae37683 100644 + const updateKey = `${chainId}:${userAddress}`; + if (updateKey in _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _inProcessNftFetchingUpdates)) { + await _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _inProcessNftFetchingUpdates)[updateKey]; -+ return; ++ return; + } + const { -+ promise: inProgressUpdate, -+ resolve: updateSucceeded, -+ reject: updateFailed -+ } = utils_1.createDeferredPromise.call(void 0, { suppressUnhandledRejection: true }); ++ promise: inProgressUpdate, ++ resolve: updateSucceeded, ++ reject: updateFailed ++ } = utils_1.createDeferredPromise.call(void 0, { suppressUnhandledRejection: true }); + _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _inProcessNftFetchingUpdates)[updateKey] = inProgressUpdate; + let next; + let apiNfts = []; + let resultNftApi; -+ -+ try { ++ try{ + do { + resultNftApi = await _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getOwnerNfts, getOwnerNfts_fn).call(this, userAddress, chainId, next); + apiNfts = resultNftApi.tokens.filter( @@ -752,7 +636,7 @@ index 5335fa5..ae37683 100644 - networkClientId: options?.networkClientId + const collections = apiNfts.reduce((acc, currValue) => { + if (!acc.includes(currValue.token.contract) && currValue.token.contract === currValue?.token?.collection?.id) { -+ acc.push(currValue.token.contract); ++ acc.push(currValue.token.contract); + } + return acc; + }, []); @@ -766,108 +650,110 @@ index 5335fa5..ae37683 100644 + ); + params.append("chainId", "1"); + const collectionResponseForBatch = await _controllerutils.fetchWithErrorHandling.call(void 0, -+ { -+ url: `${_controllerutils.NFT_API_BASE_URL}/collections?${params.toString()}`, -+ options: { ++ { ++ url: `${_controllerutils.NFT_API_BASE_URL}/collections?${params.toString()}`, ++ options: { + headers: { -+ Version: '1' ++ Version: '1' + } -+ }, -+ timeout: 15000 -+ } -+ ); -+ return { ++ }, ++ timeout: 15000 ++ } ++ ); ++ return { + ...allResponses, + ...collectionResponseForBatch -+ }; -+ }, ++ }; ++ }, + initialResult: {} + }); ++ + if (collectionResponse.collections?.length) { + apiNfts.forEach((singleNFT) => { -+ const found = collectionResponse.collections.find( ++ const found = collectionResponse.collections.find( + (elm) => elm.id?.toLowerCase() === singleNFT.token.contract.toLowerCase() -+ ); -+ if (found) { -+ singleNFT.token = { -+ ...singleNFT.token, -+ collection: { -+ ...singleNFT.token.collection ?? {}, -+ creator: found?.creator, -+ openseaVerificationStatus: found?.openseaVerificationStatus, -+ contractDeployedAt: found.contractDeployedAt, -+ ownerCount: found.ownerCount, -+ topBid: found.topBid -+ } -+ }; -+ } ++ ); ++ if (found) { ++ singleNFT.token = { ++ ...singleNFT.token, ++ collection: { ++ ...singleNFT.token.collection ?? {}, ++ creator: found?.creator, ++ openseaVerificationStatus: found?.openseaVerificationStatus, ++ contractDeployedAt: found.contractDeployedAt, ++ ownerCount: found.ownerCount, ++ topBid: found.topBid ++ } ++ }; ++ } + }); + } + } ++ + const addNftPromises = apiNfts.map(async (nft) => { -+ const { ++ const { + tokenId, + contract, + kind, + image: imageUrl, -+ imageSmall: imageThumbnailUrl, -+ metadata: { imageOriginal: imageOriginalUrl } = {}, -+ name, -+ description, -+ attributes, -+ topBid, -+ lastSale, -+ rarityRank, -+ rarityScore, -+ collection -+ } = nft.token; -+ let ignored; -+ const { ignoredNfts } = _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _getNftState).call(this); -+ if (ignoredNfts.length) { ++ imageSmall: imageThumbnailUrl, ++ metadata: { imageOriginal: imageOriginalUrl } = {}, ++ name, ++ description, ++ attributes, ++ topBid, ++ lastSale, ++ rarityRank, ++ rarityScore, ++ collection ++ } = nft.token; ++ let ignored; ++ const { ignoredNfts } = _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _getNftState).call(this); ++ if (ignoredNfts.length) { + ignored = ignoredNfts.find((c) => { + return c.address === _controllerutils.toChecksumHexAddress.call(void 0, contract) && c.tokenId === tokenId; + }); -+ } -+ if (!ignored) { -+ const nftMetadata = Object.assign( -+ {}, -+ { name }, -+ description && { description }, -+ imageUrl && { image: imageUrl }, -+ imageThumbnailUrl && { imageThumbnail: imageThumbnailUrl }, -+ imageOriginalUrl && { imageOriginal: imageOriginalUrl }, -+ kind && { standard: kind.toUpperCase() }, -+ lastSale && { lastSale }, -+ attributes && { attributes }, -+ topBid && { topBid }, -+ rarityRank && { rarityRank }, -+ rarityScore && { rarityScore }, -+ collection && { collection } -+ ); -+ await _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _addNft).call(this, contract, tokenId, { -+ nftMetadata, -+ userAddress, -+ source: "detected" /* Detected */, -+ networkClientId: options?.networkClientId -+ }); -+ } ++ } ++ if (!ignored) { ++ const nftMetadata = Object.assign( ++ {}, ++ { name }, ++ description && { description }, ++ imageUrl && { image: imageUrl }, ++ imageThumbnailUrl && { imageThumbnail: imageThumbnailUrl }, ++ imageOriginalUrl && { imageOriginal: imageOriginalUrl }, ++ kind && { standard: kind.toUpperCase() }, ++ lastSale && { lastSale }, ++ attributes && { attributes }, ++ topBid && { topBid }, ++ rarityRank && { rarityRank }, ++ rarityScore && { rarityScore }, ++ collection && { collection } ++ ); ++ await _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _addNft).call(this, contract, tokenId, { ++ nftMetadata, ++ userAddress, ++ source: "detected" /* Detected */, ++ networkClientId: options?.networkClientId ++ }); ++ } }); - } - }); - await Promise.all(addNftPromises); + await Promise.all(addNftPromises); -+ } while (next = resultNftApi.continuation); ++ } while(next = resultNftApi.continuation) + updateSucceeded(); -+ } catch (error) { ++ }catch(error){ + updateFailed(error); + throw error; -+ } finally { ++ }finally{ + delete _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _inProcessNftFetchingUpdates)[updateKey]; + } } }; _intervalId = new WeakMap(); -@@ -190,6 +289,7 @@ _disabled = new WeakMap(); +@@ -190,6 +285,7 @@ _disabled = new WeakMap(); _addNft = new WeakMap(); _getNftState = new WeakMap(); _stopPolling = new WeakSet(); @@ -875,7 +761,7 @@ index 5335fa5..ae37683 100644 stopPolling_fn = function() { if (_chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _intervalId)) { clearInterval(_chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _intervalId)); -@@ -207,41 +307,26 @@ _onPreferencesControllerStateChange = new WeakSet(); +@@ -207,41 +303,26 @@ _onPreferencesControllerStateChange = new WeakSet(); onPreferencesControllerStateChange_fn = function({ useNftDetection }) { if (!useNftDetection !== _chunkZ4BLTVTBjs.__privateGet.call(void 0, this, _disabled)) { _chunkZ4BLTVTBjs.__privateSet.call(void 0, this, _disabled, !useNftDetection); @@ -909,6 +795,13 @@ index 5335fa5..ae37683 100644 - }); - if (!nftApiResponse) { - return nfts; +- } +- const newNfts = nftApiResponse.tokens?.filter( +- (elm) => elm.token.isSpam === false && (elm.blockaidResult?.result_type ? elm.blockaidResult?.result_type === "Benign" /* Benign */ : true) +- ) ?? []; +- nfts = [...nfts, ...newNfts]; +- } while (next = nftApiResponse.continuation); +- return nfts; +getOwnerNfts_fn = async function(address, chainId, cursor) { + const convertedChainId = _controllerutils.convertHexToDecimal.call(void 0, chainId).toString(); + const url = _chunkZ4BLTVTBjs.__privateMethod.call(void 0, this, _getOwnerNftApi, getOwnerNftApi_fn).call(this, { @@ -917,15 +810,9 @@ index 5335fa5..ae37683 100644 + next: cursor + }); + const nftApiResponse = await _controllerutils.handleFetch.call(void 0, url, { -+ headers: { -+ Version: '1' - } -- const newNfts = nftApiResponse.tokens?.filter( -- (elm) => elm.token.isSpam === false && (elm.blockaidResult?.result_type ? elm.blockaidResult?.result_type === "Benign" /* Benign */ : true) -- ) ?? []; -- nfts = [...nfts, ...newNfts]; -- } while (next = nftApiResponse.continuation); -- return nfts; ++ headers: { ++ Version: '1' ++ } + }); + return nftApiResponse; }; @@ -949,14 +836,11 @@ index 6f461a4..a3573af 100644 } }); diff --git a/node_modules/@metamask/assets-controllers/dist/chunk-UEDNQBJN.js b/node_modules/@metamask/assets-controllers/dist/chunk-UEDNQBJN.js -index 80cecfb..f625ec2 100644 +index 80cecfb..e19a2e9 100644 --- a/node_modules/@metamask/assets-controllers/dist/chunk-UEDNQBJN.js +++ b/node_modules/@metamask/assets-controllers/dist/chunk-UEDNQBJN.js -@@ -378,9 +378,10 @@ fetchAndMapExchangeRatesForSupportedNativeCurrency_fn = async function({ - } - return Object.entries(contractNativeInformations).reduce( +@@ -380,7 +380,7 @@ fetchAndMapExchangeRatesForSupportedNativeCurrency_fn = async function({ (obj, [tokenAddress, token]) => { -+ // This wont be needed in v33 obj = { ...obj, - [tokenAddress.toLowerCase()]: { ...token } @@ -964,7 +848,7 @@ index 80cecfb..f625ec2 100644 }; return obj; }, -@@ -416,7 +417,7 @@ fetchAndMapExchangeRatesForUnsupportedNativeCurrency_fn = async function({ +@@ -416,7 +416,7 @@ fetchAndMapExchangeRatesForUnsupportedNativeCurrency_fn = async function({ ...acc, [tokenAddress]: { ...token, @@ -974,10 +858,10 @@ index 80cecfb..f625ec2 100644 }; return acc; diff --git a/node_modules/@metamask/assets-controllers/dist/chunk-Z6TBQQE5.js b/node_modules/@metamask/assets-controllers/dist/chunk-Z6TBQQE5.js -index 2f1b66f..8436bd9 100644 +index 2f1b66f..f4acd79 100644 --- a/node_modules/@metamask/assets-controllers/dist/chunk-Z6TBQQE5.js +++ b/node_modules/@metamask/assets-controllers/dist/chunk-Z6TBQQE5.js -@@ -295,13 +295,11 @@ var CodefiTokenPricesServiceV2 = class { +@@ -295,13 +295,12 @@ var CodefiTokenPricesServiceV2 = class { (obj, tokenAddress) => { const lowercasedTokenAddress = tokenAddress.toLowerCase(); const marketData = addressCryptoDataMap[lowercasedTokenAddress]; @@ -986,6 +870,7 @@ index 2f1b66f..8436bd9 100644 return obj; } - const { price } = marketData; ++ const token = { tokenAddress, - value: price, @@ -1089,7 +974,7 @@ index 758a85e..a4a4b72 100644 bps?: number; recipient?: string; diff --git a/node_modules/@metamask/assets-controllers/dist/types/TokenBalancesController.d.ts b/node_modules/@metamask/assets-controllers/dist/types/TokenBalancesController.d.ts -index 52bb3ac..b291078 100644 +index 52bb3ac..d4d5c0a 100644 --- a/node_modules/@metamask/assets-controllers/dist/types/TokenBalancesController.d.ts +++ b/node_modules/@metamask/assets-controllers/dist/types/TokenBalancesController.d.ts @@ -79,6 +79,11 @@ export declare class TokenBalancesController extends BaseController Date: Mon, 14 Oct 2024 11:17:52 -0400 Subject: [PATCH 06/11] docs: update e2e best practices link in readme (#11757) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This pull request updates the outdated end-to-end best practices link in the testing section of the README, pointing it to the latest contributor docs for accurate reference. ## **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. --- docs/readme/testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/readme/testing.md b/docs/readme/testing.md index 546686c39e0..c79373ddc92 100644 --- a/docs/readme/testing.md +++ b/docs/readme/testing.md @@ -357,4 +357,4 @@ Our CI/CD process is automated through various Bitrise pipelines, each designed ### Best Practices -For more guidelines and best practices, refer to our [Best Practices Document](https://github.com/MetaMask/contributor-docs/blob/main/docs/e2e-testing.md). +For more guidelines and best practices, refer to our [Best Practices Document](https://github.com/MetaMask/contributor-docs/blob/main/docs/testing/e2e-testing.md). From 7bc75e900a5245a0ebd0431c0bf10487886ea179 Mon Sep 17 00:00:00 2001 From: Jonathan Ferreira <44679989+Jonathansoufer@users.noreply.github.com> Date: Mon, 14 Oct 2024 17:46:57 +0100 Subject: [PATCH 07/11] fix: refactor notifications unit tests (#11431) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR addresses all comments from [this PR](https://github.com/MetaMask/metamask-mobile/pull/11250) related to unit tests best practices. ## **Related issues** Fixes: https://github.com/MetaMask/metamask-mobile/pull/11250 ## **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. --------- Co-authored-by: Nico MASSART --- .../__snapshots__/index.test.tsx.snap | 2 +- .../NotificationsSettings/index.test.tsx | 150 +------------ .../Settings/NotificationsSettings/index.tsx | 42 +--- .../useToggleNotifications.test.tsx | 125 +++++++++++ .../useToggleNotifications.ts | 70 +++++++ .../notifications/androidChannels.test.ts | 10 +- app/util/notifications/hooks/index.test.ts | 198 +++--------------- app/util/notifications/hooks/index.ts | 4 +- 8 files changed, 244 insertions(+), 357 deletions(-) create mode 100644 app/components/Views/Settings/NotificationsSettings/useToggleNotifications.test.tsx create mode 100644 app/components/Views/Settings/NotificationsSettings/useToggleNotifications.ts diff --git a/app/components/Views/Settings/NotificationsSettings/__snapshots__/index.test.tsx.snap b/app/components/Views/Settings/NotificationsSettings/__snapshots__/index.test.tsx.snap index 1cde78a543d..1859f635317 100644 --- a/app/components/Views/Settings/NotificationsSettings/__snapshots__/index.test.tsx.snap +++ b/app/components/Views/Settings/NotificationsSettings/__snapshots__/index.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`NotificationsSettings should render correctly 1`] = ` +exports[`NotificationsSettings render matches snapshot 1`] = ` { mockGetState = jest.fn(); @@ -57,130 +50,10 @@ 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', () => { + it('render matches snapshot', () => { mockGetState.mockImplementation(() => ({ notifications: {}, })); @@ -199,21 +72,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 34e9da18047..e046b203c2e 100644 --- a/app/components/Views/Settings/NotificationsSettings/index.tsx +++ b/app/components/Views/Settings/NotificationsSettings/index.tsx @@ -31,7 +31,6 @@ import { selectIsProfileSyncingEnabled, } from '../../../../selectors/notifications'; -import NotificationsService from '../../../../util/notifications/services/NotificationService'; import Routes from '../../../../constants/navigation/Routes'; import ButtonIcon, { @@ -57,6 +56,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 { useToggleNotifications } from './useToggleNotifications'; interface MainNotificationSettingsProps extends Props { toggleNotificationsEnabled: () => void; @@ -109,6 +109,7 @@ const NotificationsSettings = ({ navigation, route }: Props) => { const { accounts } = useAccounts(); const { trackEvent } = useMetrics(); const theme = useTheme(); + const isMetamaskNotificationsEnabled = useSelector( selectIsMetamaskNotificationsEnabled, ); @@ -177,42 +178,15 @@ const NotificationsSettings = ({ navigation, route }: Props) => { * it will request the push notifications permission and enable the notifications * if the permission is granted. */ - const toggleNotificationsEnabled = useCallback(async () => { - if (!basicFunctionalityEnabled) { - navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { - screen: Routes.SHEET.BASIC_FUNCTIONALITY, - params: { - caller: Routes.SETTINGS.NOTIFICATIONS, - }, - }); - } else if (isMetamaskNotificationsEnabled) { - disableNotifications(); - setUiNotificationStatus(false); - } else { - const { permission } = await NotificationsService.getAllPermissions(false); - if (permission !== 'authorized') { - return; - } - enableNotifications(); - setUiNotificationStatus(true); - } - trackEvent(MetaMetricsEvents.NOTIFICATIONS_SETTINGS_UPDATED, { - settings_type: 'notifications', - old_value: isMetamaskNotificationsEnabled, - new_value: !isMetamaskNotificationsEnabled, - was_profile_syncing_on: isMetamaskNotificationsEnabled - ? true - : isProfileSyncingEnabled, - }); - }, [ + const { toggleNotificationsEnabled } = useToggleNotifications({ + navigation, basicFunctionalityEnabled, - disableNotifications, - enableNotifications, isMetamaskNotificationsEnabled, - navigation, - trackEvent, isProfileSyncingEnabled, - ]); + disableNotifications, + enableNotifications, + setUiNotificationStatus, + }); const toggleCustomNotificationsEnabled = useCallback(async () => { setPlatformAnnouncementsState(!platformAnnouncementsState); diff --git a/app/components/Views/Settings/NotificationsSettings/useToggleNotifications.test.tsx b/app/components/Views/Settings/NotificationsSettings/useToggleNotifications.test.tsx new file mode 100644 index 00000000000..54c7addc69b --- /dev/null +++ b/app/components/Views/Settings/NotificationsSettings/useToggleNotifications.test.tsx @@ -0,0 +1,125 @@ +import { renderHook, act } from '@testing-library/react-hooks'; +import { useToggleNotifications } from './useToggleNotifications'; +import NotificationsService from '../../../../util/notifications/services/NotificationService'; +import Routes from '../../../../constants/navigation/Routes'; +import { NavigationProp, ParamListBase } from '@react-navigation/native'; + +jest.mock( + '../../../../util/notifications/services/NotificationService', + () => ({ + getAllPermissions: jest.fn(), + }), +); + +const mockNavigation = { + navigate: jest.fn(), +} as unknown as NavigationProp; + +const mockDisableNotifications = jest.fn(); +const mockEnableNotifications = jest.fn(); +const mockSetUiNotificationStatus = jest.fn(); + +describe('useToggleNotifications', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('navigates to basic functionality screen if basic functionality is disabled', async () => { + const { result } = renderHook(() => + useToggleNotifications({ + navigation: mockNavigation, + basicFunctionalityEnabled: false, + isMetamaskNotificationsEnabled: false, + isProfileSyncingEnabled: false, + disableNotifications: mockDisableNotifications, + enableNotifications: mockEnableNotifications, + setUiNotificationStatus: mockSetUiNotificationStatus, + }), + ); + + await act(async () => { + await result.current.toggleNotificationsEnabled(); + }); + + expect(mockNavigation.navigate).toHaveBeenCalledWith( + Routes.MODAL.ROOT_MODAL_FLOW, + { + screen: Routes.SHEET.BASIC_FUNCTIONALITY, + params: { + caller: Routes.SETTINGS.NOTIFICATIONS, + }, + }, + ); + }); + + it('switches notifications OFF if notifications previously enabled', async () => { + const { result } = renderHook(() => + useToggleNotifications({ + navigation: mockNavigation, + basicFunctionalityEnabled: true, + isMetamaskNotificationsEnabled: true, + isProfileSyncingEnabled: false, + disableNotifications: mockDisableNotifications, + enableNotifications: mockEnableNotifications, + setUiNotificationStatus: mockSetUiNotificationStatus, + }), + ); + + await act(async () => { + await result.current.toggleNotificationsEnabled(); + }); + + expect(mockDisableNotifications).toHaveBeenCalled(); + expect(mockSetUiNotificationStatus).toHaveBeenCalledWith(false); + }); + + it('switches notifications ON if notifications previously disabled and permission is authorized', async () => { + (NotificationsService.getAllPermissions as jest.Mock).mockResolvedValue({ + permission: 'authorized', + }); + + const { result } = renderHook(() => + useToggleNotifications({ + navigation: mockNavigation, + basicFunctionalityEnabled: true, + isMetamaskNotificationsEnabled: false, + isProfileSyncingEnabled: false, + disableNotifications: mockDisableNotifications, + enableNotifications: mockEnableNotifications, + setUiNotificationStatus: mockSetUiNotificationStatus, + }), + ); + + await act(async () => { + await result.current.toggleNotificationsEnabled(); + }); + + expect(mockEnableNotifications).toHaveBeenCalled(); + expect(mockSetUiNotificationStatus).toHaveBeenCalledWith(true); + }); + + it('switches notifications OFF if device permission is not authorized', async () => { + (NotificationsService.getAllPermissions as jest.Mock).mockResolvedValue({ + permission: 'denied', + }); + + const { result } = renderHook(() => + useToggleNotifications({ + navigation: mockNavigation, + basicFunctionalityEnabled: true, + isMetamaskNotificationsEnabled: false, + isProfileSyncingEnabled: false, + disableNotifications: mockDisableNotifications, + enableNotifications: mockEnableNotifications, + setUiNotificationStatus: mockSetUiNotificationStatus, + }), + ); + + await act(async () => { + await result.current.toggleNotificationsEnabled(); + }); + + expect(mockEnableNotifications).not.toHaveBeenCalled(); + expect(mockSetUiNotificationStatus).not.toHaveBeenCalled(); + }); +}); diff --git a/app/components/Views/Settings/NotificationsSettings/useToggleNotifications.ts b/app/components/Views/Settings/NotificationsSettings/useToggleNotifications.ts new file mode 100644 index 00000000000..f5fff6ee5b6 --- /dev/null +++ b/app/components/Views/Settings/NotificationsSettings/useToggleNotifications.ts @@ -0,0 +1,70 @@ +import { useCallback } from 'react'; +import { NavigationProp, ParamListBase } from '@react-navigation/native'; +import Routes from '../../../../constants/navigation/Routes'; +import NotificationsService from '../../../../util/notifications/services/NotificationService'; +import { MetaMetricsEvents } from '../../../../core/Analytics/MetaMetrics.events'; +import { useMetrics } from '../../../hooks/useMetrics'; + +interface Props { + navigation: NavigationProp; + basicFunctionalityEnabled: boolean; + isMetamaskNotificationsEnabled: boolean; + isProfileSyncingEnabled: boolean | null; + disableNotifications: () => Promise; + enableNotifications: () => Promise; + setUiNotificationStatus: (status: boolean) => void; +} + +export function useToggleNotifications({ + navigation, + basicFunctionalityEnabled, + isMetamaskNotificationsEnabled, + isProfileSyncingEnabled, + disableNotifications, + enableNotifications, + setUiNotificationStatus, +}: Props) { + const { trackEvent } = useMetrics(); + const toggleNotificationsEnabled = useCallback(async () => { + if (!basicFunctionalityEnabled) { + navigation.navigate(Routes.MODAL.ROOT_MODAL_FLOW, { + screen: Routes.SHEET.BASIC_FUNCTIONALITY, + params: { + caller: Routes.SETTINGS.NOTIFICATIONS, + }, + }); + } else if (isMetamaskNotificationsEnabled) { + disableNotifications(); + setUiNotificationStatus(false); + } else { + const { permission } = await NotificationsService.getAllPermissions( + false, + ); + if (permission !== 'authorized') { + return; + } + + enableNotifications(); + setUiNotificationStatus(true); + } + trackEvent(MetaMetricsEvents.NOTIFICATIONS_SETTINGS_UPDATED, { + settings_type: 'notifications', + old_value: isMetamaskNotificationsEnabled, + new_value: !isMetamaskNotificationsEnabled, + was_profile_syncing_on: isMetamaskNotificationsEnabled + ? true + : isProfileSyncingEnabled, + }); + }, [ + basicFunctionalityEnabled, + isMetamaskNotificationsEnabled, + trackEvent, + isProfileSyncingEnabled, + navigation, + disableNotifications, + setUiNotificationStatus, + enableNotifications, + ]); + + return { toggleNotificationsEnabled }; +} diff --git a/app/util/notifications/androidChannels.test.ts b/app/util/notifications/androidChannels.test.ts index c0cb6a45412..debc8448ab3 100644 --- a/app/util/notifications/androidChannels.test.ts +++ b/app/util/notifications/androidChannels.test.ts @@ -6,11 +6,11 @@ import { } from './androidChannels'; describe('notificationChannels', () => { - it('should have two channels', () => { + it('contains two channels', () => { expect(notificationChannels).toHaveLength(2); }); - it('should have the correct properties for the first channel', () => { + it('first channel has DEFAULT_NOTIFICATION_CHANNEL_ID', () => { const firstChannel: MetaMaskAndroidChannel = notificationChannels[0]; expect(firstChannel).toEqual({ id: ChannelId.DEFAULT_NOTIFICATION_CHANNEL_ID, @@ -23,7 +23,7 @@ describe('notificationChannels', () => { }); }); - it('should have the correct properties for the second channel', () => { + it('second channel should have the correct properties for DEFAULT_NOTIFICATION_CHANNEL_ID', () => { const secondChannel: MetaMaskAndroidChannel = notificationChannels[1]; expect(secondChannel).toEqual({ id: ChannelId.ANNOUNCEMENT_NOTIFICATION_CHANNEL_ID, @@ -36,13 +36,13 @@ describe('notificationChannels', () => { }); }); - it('should have unique titles for each channel', () => { + it('channels have unique titles', () => { 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', () => { + it('channels have unique subtitles ', () => { const subtitles = notificationChannels.map((channel) => channel.subtitle); const uniqueSubtitles = new Set(subtitles); expect(uniqueSubtitles.size).toBe(subtitles.length); diff --git a/app/util/notifications/hooks/index.test.ts b/app/util/notifications/hooks/index.test.ts index bbe94a97dc3..ff4b9fd77aa 100644 --- a/app/util/notifications/hooks/index.test.ts +++ b/app/util/notifications/hooks/index.test.ts @@ -1,19 +1,13 @@ -import { renderHook, act } from '@testing-library/react-hooks'; -import notifee, { - EventType, - Event as NotifeeEvent, -} from '@notifee/react-native'; - +import { act, renderHook } from '@testing-library/react-hooks'; +// eslint-disable-next-line import/no-namespace +import * as constants from '../constants'; 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'; +import NotificationsService from '../services/NotificationService'; -jest.mock('../../../util/device'); -jest.mock('../../../core/NotificationManager', () => ({ - setTransactionToView: jest.fn(), -})); jest.mock('@notifee/react-native', () => ({ setBadgeCount: jest.fn(), decrementBadgeCount: jest.fn(), @@ -29,12 +23,9 @@ jest.mock('@notifee/react-native', () => ({ }, })); -jest.mock('../../../core/NotificationManager', () => ({ - setTransactionToView: jest.fn(), -})); - -jest.mock('../../../util/device', () => ({ - isAndroid: jest.fn(), +jest.mock('../constants', () => ({ + ...jest.requireActual('../constants'), + isNotificationsFeatureEnabled: jest.fn(), })); const mockNavigate = jest.fn(); @@ -43,10 +34,10 @@ const mockNavigation = { } as unknown as NavigationProp; const notification = { - id: 1, + id: '123', type: TRIGGER_TYPES.ERC1155_RECEIVED, data: { - id: 1, + id: '123', trigger_id: '1', chain_id: 1, block_number: 1, @@ -62,22 +53,22 @@ const notification = { }, } as unknown as Notification; -const mockNotificationEvent = (event: NotifeeEvent) => ({ - type: event.type, - detail: { - notification, - }, -}); - +jest.mock('../services/NotificationService', () => ({ + onForegroundEvent: jest.fn(), + onBackgroundEvent: jest.fn(), + handleNotificationEvent: jest.fn(), +})); describe('useNotificationHandler', () => { beforeEach(() => { jest.clearAllMocks(); }); - it('should navigate to NOTIFICATIONS.DETAILS if notification is pressed', async () => { + it('navigates to NOTIFICATIONS.DETAILS when notification is pressed', async () => { const { result } = renderHook(() => useNotificationHandler(mockNavigation)); - await result.current.handlePressedNotification(notification); + await act(async () => { + result.current.handlePressedNotification(notification); + }); expect(mockNavigation.navigate).toHaveBeenCalledWith( Routes.NOTIFICATIONS.DETAILS, @@ -87,163 +78,32 @@ describe('useNotificationHandler', () => { ); }); - it('should handle notifications correctly', async () => { - const { waitFor } = renderHook(() => - useNotificationHandler(mockNavigation), - ); + it('does not navigates when notification is null', async () => { - await act(async () => { - notifee.onForegroundEvent(() => - mockNotificationEvent({ - type: EventType.PRESS, - detail: { - notification: { - body: 'notificationTest', - data: { - action: 'tx', - id: '123', - }, - }, - }, - }), - ); - await waitFor(() => { - expect(notifee.onForegroundEvent).toHaveBeenCalled(); - }); - }); - }); - it('should do nothing if the EventType is DISMISSED', async () => { - const { waitFor } = renderHook(() => + const { result } = renderHook(() => useNotificationHandler(mockNavigation), ); await act(async () => { - notifee.onForegroundEvent(() => - mockNotificationEvent({ - type: EventType.DISMISSED, - detail: { - notification: { - body: 'notificationTest', - data: { - action: 'tx', - id: '123', - }, - }, - }, - }), - ); - - await waitFor(() => { - expect(notifee.onForegroundEvent).toHaveBeenCalled(); - }); + result.current.handlePressedNotification(); }); - }); - - it('should do nothing if data.action is not tx', async () => { - const { waitFor } = renderHook(() => - useNotificationHandler(mockNavigation), - ); - await act(async () => { - notifee.onForegroundEvent(() => - mockNotificationEvent({ - type: EventType.DELIVERED, - detail: { - notification: { - body: 'notificationTest', - data: { - action: 'no-tx', - id: '123', - }, - }, - }, - }), - ); - - await waitFor(() => { - expect(notifee.onForegroundEvent).toHaveBeenCalled(); - }); - - expect(mockNavigate).not.toHaveBeenCalled(); - }); + expect(mockNavigation.navigate).not.toHaveBeenCalled(); }); - it('handleOpenedNotification should do nothing if notification is null', async () => { - const { waitFor } = renderHook(() => - useNotificationHandler(mockNavigation), - ); - - await act(async () => { - notifee.onForegroundEvent(() => - mockNotificationEvent({ - type: EventType.DELIVERED, - detail: { - notification: undefined, - }, - }), - ); - await waitFor(() => { - expect(notifee.onForegroundEvent).toHaveBeenCalled(); - }); - - expect(mockNavigate).not.toHaveBeenCalled(); - }); - }); + it('does nothing if the isNotificationsFeatureEnabled is false', async () => { + jest.spyOn(constants, 'isNotificationsFeatureEnabled').mockReturnValue(false); - it('should navigate to the transaction view when the notification action is "tx"', async () => { - const { waitFor } = renderHook(() => - useNotificationHandler(mockNavigation), - ); + const { result } = renderHook(() => useNotificationHandler(mockNavigation)); await act(async () => { - notifee.onForegroundEvent(() => - mockNotificationEvent({ - type: EventType.DELIVERED, - detail: { - notification: { - body: 'notificationTest', - data: { - action: 'tx', - id: '123', - }, - }, - }, - }), - ); - await waitFor(() => { - expect(notifee.onForegroundEvent).toHaveBeenCalled(); - }); + result.current.handlePressedNotification(notification); }); - }, 10000); - it('should process notification on Android', async () => { - jest.doMock('react-native/Libraries/Utilities/Platform', () => ({ - OS: 'android', - })); + expect(NotificationsService.onForegroundEvent).not.toHaveBeenCalled(); + expect(NotificationsService.onBackgroundEvent).not.toHaveBeenCalled(); - const { waitFor } = renderHook(() => - useNotificationHandler(mockNavigation), - ); - - await act(async () => { - notifee.onForegroundEvent(() => - mockNotificationEvent({ - type: EventType.PRESS, - detail: { - notification: { - body: 'notificationTest', - data: { - action: 'tx', - id: '123', - }, - }, - }, - }), - ); - await waitFor(() => { - expect(notifee.onForegroundEvent).toHaveBeenCalled(); - }); - }); + jest.restoreAllMocks(); }); }); diff --git a/app/util/notifications/hooks/index.ts b/app/util/notifications/hooks/index.ts index e91fe593eaf..3f6bb98c9a3 100644 --- a/app/util/notifications/hooks/index.ts +++ b/app/util/notifications/hooks/index.ts @@ -4,9 +4,11 @@ import NotificationsService from '../../../util/notifications/services/Notificat import Routes from '../../../constants/navigation/Routes'; import { isNotificationsFeatureEnabled, - TRIGGER_TYPES, } from '../../../util/notifications'; import { Notification } from '../../../util/notifications/types'; +import { + TRIGGER_TYPES, +} from '../../../util/notifications/constants'; import { Linking } from 'react-native'; const useNotificationHandler = (navigation: NavigationProp) => { From 4f8f66c2e2e74b787876e88c51edb351b671a25e Mon Sep 17 00:00:00 2001 From: Pedro Pablo Aste Kompen Date: Mon, 14 Oct 2024 13:30:20 -0600 Subject: [PATCH 08/11] chore(ramp): upgrade sdk to 1.28.5 (#11788) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** - update on-ramp-sdk to version 1.28.5, which removes a unused dependency ## **Related issues** Fixes: [dependency issue](https://github.com/MetaMask/metamask-mobile/actions/runs/11332800511/job/31515686025?pr=11787) ## **Manual testing steps** - go to buy crypto - request a quote - quote should be provided ## **Screenshots/Recordings** N/A ### **Before** N/A ### **After** N/A ## **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. --- package.json | 2 +- yarn.lock | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 97c3759e780..c65145f8a8a 100644 --- a/package.json +++ b/package.json @@ -136,7 +136,7 @@ "send": "0.19.0" }, "dependencies": { - "@consensys/on-ramp-sdk": "1.28.3", + "@consensys/on-ramp-sdk": "1.28.5", "@ethersproject/abi": "^5.7.0", "@keystonehq/bc-ur-registry-eth": "^0.19.1", "@keystonehq/metamask-airgapped-keyring": "^0.13.1", diff --git a/yarn.lock b/yarn.lock index 9524b5f1a13..057a20745bb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1288,16 +1288,15 @@ resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.6.0.tgz#ec6cd237440700bc23ca23087f513c75508958b0" integrity sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA== -"@consensys/on-ramp-sdk@1.28.3": - version "1.28.3" - resolved "https://registry.yarnpkg.com/@consensys/on-ramp-sdk/-/on-ramp-sdk-1.28.3.tgz#5c7c9293f6ee83e1a681a6a76f982e801d3e75ec" - integrity sha512-QwWBFFqP3NMOhcJmcfmFd6fpEU3iXI/tYdWXM8u679/mY1/2rnRKUc3RnuWGFgs3qESZ5sUG2Xc7cp5OrmnDgA== +"@consensys/on-ramp-sdk@1.28.5": + version "1.28.5" + resolved "https://registry.yarnpkg.com/@consensys/on-ramp-sdk/-/on-ramp-sdk-1.28.5.tgz#b9ff6c2b0f46abef30bd32a720f34d963aa76491" + integrity sha512-tBZ2ZsEz+du/vHF4NChTTvcdYddoJCtNZDZj5lry8/TeNxctnYiZGx2tHtw2GJHfYa9Uuux2hwtdhubcR//ZeA== dependencies: async "^3.2.3" axios "^0.28.0" axios-retry "^3.1.2" crypto-js "^4.2.0" - jsonpath-plus "^7.2.0" reflect-metadata "^0.1.13" uuid "^9.0.0" From d5c3dc0560e2a061360e392d61a56936a1170f22 Mon Sep 17 00:00:00 2001 From: Curtis David Date: Mon, 14 Oct 2024 15:44:14 -0400 Subject: [PATCH 09/11] ci: Enable Detox E2E in Release mode (#11710) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR enhances our end-to-end testing setup by enabling Detox to run on release builds. This change ensures that our tests are running in a production-like environment, which will help uncover issues that may not be visible in debug builds. There is a noticeable performance difference while building the app in release mode. The apps in release mode take ~12 minutes to build compared to debug mode, where it takes ~17 minutes to build. See release mode build times Screenshot 2024-10-10 at 5 20 27 PM See debug mode build times: Screenshot 2024-10-10 at 5 07 08 PM Key Changes: - Updated build scripts to differentiate between release scripts and debug scripts - Updated the Detox configuration to build the app in release mode while running on CI. - Updated NFT tests to remove redundant steps - The Importing NFTs e2e tests were crashing only in release mode on iOS. The assets team will investigate and address in a follow up PR ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** Smoke runs: - https://app.bitrise.io/app/be69d4368ee7e86d/pipelines/af0893da-2466-4a32-b6f4-4d0056f7be81 - https://app.bitrise.io/app/be69d4368ee7e86d/pipelines/f3ef024d-c0fe-4b58-a601-bd66c0023de0 Regression runs: - https://app.bitrise.io/app/be69d4368ee7e86d/pipelines/c1214e8a-86a2-4e20-972b-6247af5bac8a - https://app.bitrise.io/app/be69d4368ee7e86d/pipelines/53db98a1-bcfe-4dc4-91b3-20491d1171fd ## **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 --- .depcheckrc.yml | 1 - .detoxrc.js | 28 +++------- android/app/build.gradle | 2 +- bitrise.yml | 53 ++++++++++++++----- e2e/specs/assets/nft-detection-modal.spec.js | 17 +----- ios/Podfile | 2 +- ios/Podfile.lock | 9 +--- package.json | 19 +++---- ...ative-community+datetimepicker+7.7.0.patch | 13 ----- scripts/build.sh | 25 +++++---- yarn.lock | 7 --- 11 files changed, 73 insertions(+), 103 deletions(-) delete mode 100644 patches/@react-native-community+datetimepicker+7.7.0.patch diff --git a/.depcheckrc.yml b/.depcheckrc.yml index c90705901b4..f51f487fd9b 100644 --- a/.depcheckrc.yml +++ b/.depcheckrc.yml @@ -2,7 +2,6 @@ ignores: - '@metamask/oss-attribution-generator' - 'webpack-cli' - - '@react-native-community/datetimepicker' - '@react-native-community/slider' - 'patch-package' - '@lavamoat/allow-scripts' diff --git a/.detoxrc.js b/.detoxrc.js index d637d3c937c..ad953511261 100644 --- a/.detoxrc.js +++ b/.detoxrc.js @@ -26,7 +26,7 @@ module.exports = { configurations: { 'ios.sim.apiSpecs': { device: 'ios.simulator', - app: 'ios.debug', + app: 'ios.qa', testRunner: { args: { "$0": "node e2e/api-specs/run-api-spec-tests.js", @@ -41,10 +41,9 @@ module.exports = { device: 'ios.simulator', app: 'ios.release', }, - // because e2e run on debug mode in bitrise - 'android.emu.bitrise.debug': { - device: 'android.bitrise.emulator', - app: 'android.bitrise.debug', + 'ios.sim.qa': { + device: 'ios.simulator', + app: 'ios.qa', }, 'android.emu.debug': { @@ -86,32 +85,21 @@ module.exports = { binaryPath: 'ios/build/Build/Products/Debug-iphonesimulator/MetaMask.app', build: 'yarn start:ios:e2e', }, - 'ios.release': { + 'ios.qa': { type: 'ios.app', binaryPath: - 'ios/build/Build/Products/Release-iphonesimulator/MetaMask.app', - build: "METAMASK_BUILD_TYPE='main' METAMASK_ENVIRONMENT='production' yarn build:ios:release:e2e", - }, - 'android.bitrise.debug': { - type: 'android.apk', - binaryPath: 'android/app/build/outputs/apk/prod/debug/app-prod-debug.apk', - build: 'yarn start:android:e2e', + 'ios/build/Build/Products/Release-iphonesimulator/MetaMask-QA.app', + build: "METAMASK_BUILD_TYPE='main' METAMASK_ENVIRONMENT='qa' yarn build:ios:qa", }, 'android.debug': { type: 'android.apk', binaryPath: 'android/app/build/outputs/apk/prod/debug/app-prod-debug.apk', build: 'yarn start:android:e2e', }, - 'android.release': { - type: 'android.apk', - binaryPath: - 'android/app/build/outputs/apk/prod/release/app-prod-release.apk', - build: "METAMASK_BUILD_TYPE='main' METAMASK_ENVIRONMENT='production' yarn build:android:release:e2e", - }, 'android.qa': { type: 'android.apk', binaryPath: 'android/app/build/outputs/apk/qa/release/app-qa-release.apk', - build: "METAMASK_BUILD_TYPE='main' METAMASK_ENVIRONMENT='qa' yarn build:android:qa:e2e", + build: "METAMASK_BUILD_TYPE='main' METAMASK_ENVIRONMENT='qa' yarn build:android:qa", }, }, }; diff --git a/android/app/build.gradle b/android/app/build.gradle index 7978222f4a7..80ffad8fa28 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -229,7 +229,7 @@ android { release { manifestPlaceholders.isDebug = false minifyEnabled enableProguardInReleaseBuilds - proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" + proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro", "${rootProject.projectDir}/../node_modules/detox/android/detox/proguard-rules-app.pro", "${rootProject.projectDir}/../node_modules/detox/android/detox/proguard-rules.pro" } } diff --git a/bitrise.yml b/bitrise.yml index 4df0c76e2c4..40c4e621f3b 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -135,7 +135,7 @@ stages: - run_ios_api_specs: {} - run_tag_smoke_accounts_ios: {} - run_tag_smoke_accounts_android: {} - - run_tag_smoke_assets_ios: {} + # - run_tag_smoke_assets_ios: {} - run_tag_smoke_assets_android: {} - run_tag_smoke_confirmations_ios: {} - run_tag_smoke_confirmations_android: {} @@ -145,8 +145,8 @@ stages: - run_tag_smoke_core_android: {} build_regression_e2e_ios_android_stage: workflows: - - ios_build_regression_tests: {} - - android_build_regression_tests: {} + - ios_e2e_build: {} + - android_e2e_build: {} run_regression_e2e_ios_android_stage: workflows: - ios_run_regression_tests: {} @@ -267,8 +267,29 @@ workflows: source "${HOME}/.nvm/nvm.sh" echo 'source "${HOME}/.nvm/nvm.sh"' | tee -a ${HOME}/.{bashrc,profile} - nvm install ${NODE_VERSION} + # Retry logic for Node installation + MAX_ATTEMPTS=3 + ATTEMPT=1 + until [ $ATTEMPT -gt $MAX_ATTEMPTS ] + do + echo "Attempt $ATTEMPT to install Node.js" + nvm install ${NODE_VERSION} + INSTALL_STATUS=$? # Capture the exit status of the nvm install command + if [ $INSTALL_STATUS -eq 0 ]; then + echo "Node.js installation successful!" + break + else + echo "Node.js installation failed with exit code $INSTALL_STATUS" + ATTEMPT=$((ATTEMPT+1)) + echo "Node.js installation failed, retrying in 5 seconds..." + sleep 5 + fi + done + if [ $ATTEMPT -gt $MAX_ATTEMPTS ]; then + echo "Node.js installation failed after $MAX_ATTEMPTS attempts." + exit 1 + fi envman add --key PATH --value $PATH node --version @@ -586,6 +607,10 @@ workflows: inputs: - ndk_version: $NDK_VERSION - gradlew_path: $PROJECT_LOCATION/gradlew + - file-downloader@1: + inputs: + - source: $BITRISEIO_ANDROID_QA_KEYSTORE_URL + - destination: android/keystores/internalRelease.keystore - script@1: title: Install CCache & symlink inputs: @@ -620,7 +645,7 @@ workflows: node -v export METAMASK_ENVIRONMENT='local' export METAMASK_BUILD_TYPE='main' - IGNORE_BOXLOGS_DEVELOPMENT="true" FORCE_BUNDLING=true yarn test:e2e:android:bitrise:build + IGNORE_BOXLOGS_DEVELOPMENT="true" yarn test:e2e:android:build:qa-release - save-gradle-cache@1: {} - save-cache@1: title: Save CCache @@ -700,7 +725,7 @@ workflows: fi export METAMASK_ENVIRONMENT='local' export METAMASK_BUILD_TYPE='main' - IGNORE_BOXLOGS_DEVELOPMENT="true" FORCE_BUNDLING=true yarn test:e2e:android:bitrise:run "$TEST_SUITE_FOLDER" --testNamePattern="$TEST_SUITE" + IGNORE_BOXLOGS_DEVELOPMENT="true" yarn test:e2e:android:run:qa-release "$TEST_SUITE_FOLDER" --testNamePattern="$TEST_SUITE" - custom-test-results-export@1: title: Export test results is_always_run: true @@ -802,6 +827,8 @@ workflows: - deploy_path: $BITRISE_HTML_REPORT_DIR title: Deploy test report files ios_e2e_build: + envs: + - NO_FLIPPER: '1' before_run: - install_applesimutils - code_setup @@ -833,7 +860,7 @@ workflows: inputs: - content: |- #!/usr/bin/env bash - HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew install ccache + brew install ccache with HOMEBREW_NO_DEPENDENTS_CHECK=1 ln -s $(which ccache) /usr/local/bin/gcc ln -s $(which ccache) /usr/local/bin/g++ ln -s $(which ccache) /usr/local/bin/cc @@ -867,7 +894,7 @@ workflows: node -v export METAMASK_ENVIRONMENT='local' export METAMASK_BUILD_TYPE='main' - IGNORE_BOXLOGS_DEVELOPMENT="true" FORCE_BUNDLING=true yarn test:e2e:ios:debug:build + IGNORE_BOXLOGS_DEVELOPMENT="true" yarn test:e2e:ios:build:qa-release - save-cocoapods-cache@1: {} - save-cache@1: title: Save CCache @@ -888,6 +915,8 @@ workflows: - key: node_modules-{{ .OS }}-{{ .Arch }}-{{ getenv "BRANCH_COMMIT_HASH" }} - paths: node_modules ios_e2e_test: + envs: + - NO_FLIPPER: '1' before_run: - setup - install_applesimutils @@ -923,7 +952,7 @@ workflows: - set-xcode-build-number@1: inputs: - build_short_version_string: $VERSION_NAME - - plist_path: $PROJECT_LOCATION_IOS/MetaMask/Info.plist + - plist_path: $PROJECT_LOCATION_IOS/MetaMask/MetaMask-QA-Info.plist - script: inputs: - content: |- @@ -954,7 +983,7 @@ workflows: node -v export METAMASK_ENVIRONMENT='local' export METAMASK_BUILD_TYPE='main' - IGNORE_BOXLOGS_DEVELOPMENT="true" FORCE_BUNDLING=true yarn test:e2e:ios:debug:run "$TEST_SUITE_FOLDER" --testNamePattern="$TEST_SUITE" + IGNORE_BOXLOGS_DEVELOPMENT="true" yarn test:e2e:ios:run:qa-release "$TEST_SUITE_FOLDER" --testNamePattern="$TEST_SUITE" - custom-test-results-export@1: is_always_run: true is_skippable: false @@ -1234,8 +1263,6 @@ workflows: inputs: - ipa_path: $BITRISE_APP_STORE_IPA_PATH build_ios_release: - envs: - - NO_FLIPPER: '1' before_run: - code_setup after_run: @@ -1275,8 +1302,6 @@ workflows: - deploy_path: sourcemaps/ios/index.js.map title: Deploy Source Map build_ios_qa: - envs: - - NO_FLIPPER: '1' before_run: - code_setup after_run: diff --git a/e2e/specs/assets/nft-detection-modal.spec.js b/e2e/specs/assets/nft-detection-modal.spec.js index b0b4b4e41ce..798b9267e3a 100644 --- a/e2e/specs/assets/nft-detection-modal.spec.js +++ b/e2e/specs/assets/nft-detection-modal.spec.js @@ -10,12 +10,10 @@ import TestHelpers from '../../helpers'; import Assertions from '../../utils/Assertions'; import NftDetectionModal from '../../pages/modals/NftDetectionModal'; import { SmokeAssets } from '../../tags'; -import NetworkListModal from '../../pages/modals/NetworkListModal'; -import NetworkEducationModal from '../../pages/modals/NetworkEducationModal'; + import { NftDetectionModalSelectorsText } from '../../selectors/Modals/NftDetectionModal.selectors'; describe(SmokeAssets('NFT Detection Modal'), () => { - const ETHEREUM = 'Ethereum Main Network'; beforeAll(async () => { jest.setTimeout(170000); await TestHelpers.reverseServerPort(); @@ -25,7 +23,6 @@ describe(SmokeAssets('NFT Detection Modal'), () => { await withFixtures( { fixture: new FixtureBuilder() - .withGanacheNetwork() .withPreferencesController({ useNftDetection: false, }) @@ -35,12 +32,6 @@ describe(SmokeAssets('NFT Detection Modal'), () => { }, async () => { await loginToApp(); - - // Switch to Mainnet - await WalletView.tapNetworksButtonOnNavBar(); - await NetworkListModal.changeNetworkTo(ETHEREUM); - await NetworkEducationModal.tapGotItButton(); - await Assertions.checkIfVisible(NftDetectionModal.container); // fix flaky test: toast should desapear to get access to cancel button @@ -65,7 +56,6 @@ describe(SmokeAssets('NFT Detection Modal'), () => { await withFixtures( { fixture: new FixtureBuilder() - .withGanacheNetwork() .withPreferencesController({ useNftDetection: false, }) @@ -76,11 +66,6 @@ describe(SmokeAssets('NFT Detection Modal'), () => { async () => { await loginToApp(); - // Switch to Mainnet - await WalletView.tapNetworksButtonOnNavBar(); - await NetworkListModal.changeNetworkTo(ETHEREUM); - await NetworkEducationModal.tapGotItButton(); - await Assertions.checkIfVisible(NftDetectionModal.container); await NftDetectionModal.tapAllowButton(); // Check that we are on the wallet screen diff --git a/ios/Podfile b/ios/Podfile index 759ce5f2e02..56b8c3f9164 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -98,7 +98,7 @@ def common_target_logic pod 'GzipSwift' # Pod for fixing react-native-quick-crypto issue: https://github.com/margelo/react-native-quick-crypto/issues/244 - pod "OpenSSL-Universal", "= 1.1.1100" + pod 'OpenSSL-Universal', :modular_headers => true, :configurations => ['Release'] end target 'MetaMask' do diff --git a/ios/Podfile.lock b/ios/Podfile.lock index b8151245f04..24d83fa9fef 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -702,8 +702,6 @@ PODS: - React-Core - RNCPicker (2.2.1): - React-Core - - RNDateTimePicker (7.7.0): - - React-Core - RNDefaultPreference (1.4.3): - React - RNDeviceInfo (9.0.2): @@ -825,6 +823,7 @@ DEPENDENCIES: - GzipSwift - lottie-ios (from `../node_modules/lottie-ios`) - lottie-react-native (from `../node_modules/lottie-react-native`) + - OpenSSL-Universal - OpenSSL-Universal (= 1.1.1100) - Permission-BluetoothPeripheral (from `../node_modules/react-native-permissions/ios/BluetoothPeripheral`) - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) @@ -897,7 +896,6 @@ DEPENDENCIES: - "RNCClipboard (from `../node_modules/@react-native-clipboard/clipboard`)" - "RNCMaskedView (from `../node_modules/@react-native-masked-view/masked-view`)" - "RNCPicker (from `../node_modules/@react-native-picker/picker`)" - - "RNDateTimePicker (from `../node_modules/@react-native-community/datetimepicker`)" - RNDefaultPreference (from `../node_modules/react-native-default-preference`) - RNDeviceInfo (from `../node_modules/react-native-device-info`) - "RNFBApp (from `../node_modules/@react-native-firebase/app`)" @@ -1115,8 +1113,6 @@ EXTERNAL SOURCES: :path: "../node_modules/@react-native-masked-view/masked-view" RNCPicker: :path: "../node_modules/@react-native-picker/picker" - RNDateTimePicker: - :path: "../node_modules/@react-native-community/datetimepicker" RNDefaultPreference: :path: "../node_modules/react-native-default-preference" RNDeviceInfo: @@ -1274,7 +1270,6 @@ SPEC CHECKSUMS: RNCClipboard: ddd4d291537f1667209c9c405aaa4307297e252e RNCMaskedView: 090213d32d8b3bb83a4dcb7d12c18f0152591906 RNCPicker: cb57c823d5ce8d2d0b5dfb45ad97b737260dc59e - RNDateTimePicker: 4f3c4dbd4f908be32ec8c93f086e8924bd4a2e07 RNDefaultPreference: 2f8d6d54230edbd78708ada8d63bb275e5a8415b RNDeviceInfo: 1e3f62b9ec32f7754fac60bd06b8f8a27124e7f0 RNFBApp: 5f87753a8d8b37d229adf85cd0ff37709ffdf008 @@ -1302,6 +1297,6 @@ SPEC CHECKSUMS: Yoga: 6f5ab94cd8b1ecd04b6e973d0bc583ede2a598cc YogaKit: f782866e155069a2cca2517aafea43200b01fd5a -PODFILE CHECKSUM: 876298d4a106643492005466f7a314cd08711f4d +PODFILE CHECKSUM: e0bcc4eb12d48746028cd4f4161a292fa9ddc627 COCOAPODS: 1.15.2 diff --git a/package.json b/package.json index c65145f8a8a..1b811292afe 100644 --- a/package.json +++ b/package.json @@ -32,8 +32,6 @@ "start:android:flask": "export METAMASK_BUILD_TYPE='flask' && ./scripts/build.sh android flaskDebug", "build:announce": "node ./scripts/metamask-bot-build-announce-bitrise.js", "build:android:release": "./scripts/build.sh android release", - "build:android:release:e2e": "./scripts/build.sh android releaseE2E", - "build:android:qa:e2e": "./scripts/build.sh android QAE2E", "build:android:checksum": "./scripts/checksum.sh", "build:android:checksum:qa": "./scripts/checksum.sh QA", "build:android:checksum:flask": "export METAMASK_BUILD_TYPE='flask' && ./scripts/checksum.sh flask", @@ -44,28 +42,26 @@ "build:android:pre-release:bundle:flask": "export METAMASK_BUILD_TYPE='flask' && GENERATE_BUNDLE=true ./scripts/build.sh android flask --pre", "build:ios:release": "./scripts/build.sh ios release", "build:ios:pre-flask": "export METAMASK_BUILD_TYPE='flask' && ./scripts/build.sh ios flask --pre", - "build:ios:release:e2e": "./scripts/build.sh ios releaseE2E", "build:ios:pre-release": "./scripts/build.sh ios release --pre", - "build:ios:qa": "./scripts/build.sh ios QA", "build:ios:pre-qa": "./scripts/build.sh ios QA --pre", + "build:android:qa": "NO_FLIPPER='1' ./scripts/build.sh android QA", + "build:ios:qa": "NO_FLIPPER='1' ./scripts/build.sh ios QA", "build:attribution": "./scripts/generate-attributions.sh", "release:android": "./scripts/build.sh android release && open android/app/build/outputs/apk/release/", "release:ios": "./scripts/build.sh ios release", "release:android:qa": "./scripts/build.sh android QA && open android/app/build/outputs/apk/release/", - "test": "yarn test:unit && yarn test:e2e", + "test": "yarn test:unit", "test:unit": "jest ./app/ ./locales/", "test:unit:update": "time jest -u ./app/", "test:api-specs": "detox reset-lock-file && detox test -c ios.sim.apiSpecs", - "test:e2e": "yarn test:e2e:ios && yarn test:e2e:android", - "test:e2e:ios": "detox build -c ios.sim.release && detox test -c ios.sim.release", + "test:e2e:ios:build:qa-release": "IS_TEST='true' detox build -c ios.sim.qa", + "test:e2e:ios:run:qa-release": "IS_TEST='true' detox test -c ios.sim.qa", + "test:e2e:android:build:qa-release": "NO_FLIPPER='1' IS_TEST='true' detox build -c android.emu.release.qa", + "test:e2e:android:run:qa-release": "NO_FLIPPER='1' IS_TEST='true' detox test -c android.emu.release.qa --headless --record-logs all", "test:e2e:ios:debug:build": "IS_TEST='true' detox build -c ios.sim.debug", "test:e2e:ios:debug:run": "IS_TEST='true' detox reset-lock-file && detox test -c ios.sim.debug", "test:e2e:android:debug:build": "IS_TEST='true' detox build -c android.emu.debug", - "test:e2e:android:bitrise:build": "IS_TEST='true' detox build -c android.emu.bitrise.debug", "test:e2e:android:debug:run": "IS_TEST='true' detox test -c android.emu.debug", - "test:e2e:android:bitrise:run": "IS_TEST='true' detox reset-lock-file && detox test -c android.emu.bitrise.debug --headless", - "test:e2e:android": "detox build -c android.emu.release && detox test -c android.emu.release --record-videos failing", - "test:e2e:android:qa": "detox build -c android.emu.release.qa && detox test -c android.emu.release.qa --record-videos failing", "test:wdio:ios": "yarn wdio ./wdio/config/ios.config.debug.js", "test:wdio:ios:browserstack:local": "yarn wdio ./wdio/config/ios.config.browserstack.local.js", "test:wdio:android": "yarn wdio ./wdio/config/android.config.debug.js", @@ -197,7 +193,6 @@ "@react-native-clipboard/clipboard": "1.8.4", "@react-native-community/blur": "^4.4.0", "@react-native-community/checkbox": "^0.5.17", - "@react-native-community/datetimepicker": "^7.5.0", "@react-native-community/netinfo": "^9.5.0", "@react-native-community/slider": "^4.4.3", "@react-native-cookies/cookies": "^6.2.1", diff --git a/patches/@react-native-community+datetimepicker+7.7.0.patch b/patches/@react-native-community+datetimepicker+7.7.0.patch deleted file mode 100644 index 1ebdc150937..00000000000 --- a/patches/@react-native-community+datetimepicker+7.7.0.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/node_modules/@react-native-community/datetimepicker/ios/RNDateTimePickerShadowView.m b/node_modules/@react-native-community/datetimepicker/ios/RNDateTimePickerShadowView.m -index 4ff3362..c139440 100644 ---- a/node_modules/@react-native-community/datetimepicker/ios/RNDateTimePickerShadowView.m -+++ b/node_modules/@react-native-community/datetimepicker/ios/RNDateTimePickerShadowView.m -@@ -41,7 +41,7 @@ - (void)setTimeZoneName:(NSString *)timeZoneName { - YGNodeMarkDirty(self.yogaNode); - } - --static YGSize RNDateTimePickerShadowViewMeasure(YGNodeConstRef node, float width, YGMeasureMode widthMode, float height, YGMeasureMode heightMode) -+static YGSize RNDateTimePickerShadowViewMeasure(YGNodeRef node, float width, YGMeasureMode widthMode, float height, YGMeasureMode heightMode) - { - RNDateTimePickerShadowView *shadowPickerView = (__bridge RNDateTimePickerShadowView *)YGNodeGetContext(node); - diff --git a/scripts/build.sh b/scripts/build.sh index f32e593e54e..1623dc30ca1 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -361,16 +361,16 @@ buildIosReleaseE2E(){ } buildIosQA(){ + echo "Start iOS QA build..." + remapEnvVariableQA prebuild_ios - echo "Start QA build..." - # Replace release.xcconfig with ENV vars if [ "$PRE_RELEASE" = true ] ; then echo "Setting up env vars..."; - echo "$IOS_ENV" + echo "$IOS_ENV" echo "$IOS_ENV" | tr "|" "\n" > $IOS_ENV_FILE echo "Build started..." brew install watchman @@ -380,22 +380,25 @@ buildIosQA(){ if [ ! -f "ios/release.xcconfig" ] ; then echo "$IOS_ENV" | tr "|" "\n" > ios/release.xcconfig fi - ./node_modules/.bin/react-native run-ios --scheme MetaMask-QA--configuration Release --simulator "iPhone 13 Pro" + cd ios && xcodebuild -workspace MetaMask.xcworkspace -scheme MetaMask-QA -configuration Release -sdk iphonesimulator -derivedDataPath build + # ./node_modules/.bin/react-native run-ios --scheme MetaMask-QA- -configuration Release --simulator "iPhone 13 Pro" fi } buildAndroidQA(){ + echo "Start Android QA build..." + remapEnvVariableQA - if [ "$PRE_RELEASE" = false ] ; then - adb uninstall io.metamask.qa - fi + # if [ "$PRE_RELEASE" = false ] ; then + # adb uninstall io.metamask.qa + # fi prebuild_android # Generate APK - cd android && ./gradlew assembleQaRelease --no-daemon --max-workers 2 + cd android && ./gradlew assembleQaRelease app:assembleQaReleaseAndroidTest -PminSdkVersion=26 -DtestBuildType=release # GENERATE BUNDLE if [ "$GENERATE_BUNDLE" = true ] ; then @@ -407,9 +410,9 @@ buildAndroidQA(){ yarn build:android:checksum:qa fi - if [ "$PRE_RELEASE" = false ] ; then - adb install app/build/outputs/apk/qa/release/app-qa-release.apk - fi + # if [ "$PRE_RELEASE" = false ] ; then + # adb install app/build/outputs/apk/qa/release/app-qa-release.apk + # fi } buildAndroidRelease(){ diff --git a/yarn.lock b/yarn.lock index 057a20745bb..4d2b39ab497 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6689,13 +6689,6 @@ prompts "^2.4.0" semver "^7.5.2" -"@react-native-community/datetimepicker@^7.5.0": - version "7.7.0" - resolved "https://registry.yarnpkg.com/@react-native-community/datetimepicker/-/datetimepicker-7.7.0.tgz#0d0162b0434c7b35883f8c5af846f35e23d045ec" - integrity sha512-nYzZy4DQLRFUzKJShWzRleCaebmCJfZ1lIcFmZgMXJoiVuGJNw3OIGHSWmHhPETh3OhP1RO3to882d7WmDIyrA== - dependencies: - invariant "^2.2.4" - "@react-native-community/netinfo@^9.5.0": version "9.5.0" resolved "https://registry.yarnpkg.com/@react-native-community/netinfo/-/netinfo-9.5.0.tgz#93663bbb105feb8f729b8f0271ee06ffc009f024" From 6db794f7dc4799918dc83c97211db25d5a6542e2 Mon Sep 17 00:00:00 2001 From: Nico MASSART Date: Mon, 14 Oct 2024 22:34:03 +0200 Subject: [PATCH 10/11] fix: non deterministic date in test (#11787) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Fixes date that was generating an ever changing duration in the snapshot. - replace it with a relative date to the moment the test is running to make the duration always one day: the notification renders as if it was sent yesterday, always. - fix duration to one day (yesterday) as we have to fix one and it's not a worse value than any other. - rename test case to match guideline while I was on it... ## **Related issues** Fixes #11785 ## **Manual testing steps** N/A ## **Screenshots/Recordings** N/A ### **Before** N/A ### **After** N/A ## **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: sethkfman <10342624+sethkfman@users.noreply.github.com> --- .../UI/Notification/NotificationMenuItem/Content.test.tsx | 5 +++-- .../NotificationMenuItem/__snapshots__/Content.test.tsx.snap | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/components/UI/Notification/NotificationMenuItem/Content.test.tsx b/app/components/UI/Notification/NotificationMenuItem/Content.test.tsx index 1a65102a1ad..c227b206ec5 100644 --- a/app/components/UI/Notification/NotificationMenuItem/Content.test.tsx +++ b/app/components/UI/Notification/NotificationMenuItem/Content.test.tsx @@ -5,14 +5,15 @@ import NotificationContent from './Content'; describe('NotificationContent', () => { const title = 'Welcome to the new Test!'; - const createdAt = '2024-04-26T16:35:03.147606Z'; + const yesterday = new Date().setDate(new Date().getDate() - 1); + const createdAt = new Date(yesterday).toISOString(); // Relative date: one day before current date const description = { start: 'We are excited to announce the launch of our brand new website and app!', end: 'Ethereum', }; - it('renders correctly', () => { + it('render matches snapshot', () => { const { toJSON } = renderWithProvider( - 6 months ago + Yesterday Date: Tue, 15 Oct 2024 11:41:00 +0100 Subject: [PATCH 11/11] fix: support for batch of signature requests (#11729) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR addresses the issue of multiple signature popups being triggered sequentially on Mobile, which could lead to potential spam or overwhelm the user. Currently, multiple signature requests appear one after the other, even if the user accepts or rejects them. To align with the existing behavior for transactions, this PR removes signatures from the rate-limiting exclusions, ensuring that only one signature request is triggered at a time, preventing spam-like behavior until a proper queuing system is introduced on Mobile. ## **Related issues** Fixes: https://github.com/MetaMask/metamask-mobile/issues/8771 ## **Manual testing steps** 1. go to the test dapp 2. connect mm 3. trigger signature x10 batch 4. See you need to accept/reject 1 time ## **Screenshots/Recordings** [sign one.webm](https://github.com/user-attachments/assets/972f8643-0e29-44a8-9ad9-1587e2bdf5d3) Example of the current behaviour of batch 10 transactions: [transaction one.webm](https://github.com/user-attachments/assets/df6c1f92-3d09-4b5c-848b-07db56b9aede) ### **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. --- app/core/Engine.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/app/core/Engine.ts b/app/core/Engine.ts index 0c19aa58502..95199c1b9eb 100644 --- a/app/core/Engine.ts +++ b/app/core/Engine.ts @@ -232,7 +232,7 @@ import { selectSwapsChainFeatureFlags } from '../reducers/swaps'; import { SmartTransactionStatuses } from '@metamask/smart-transactions-controller/dist/types'; import { submitSmartTransactionHook } from '../util/smart-transactions/smart-publish-hook'; import { zeroAddress } from 'ethereumjs-util'; -import { toChecksumHexAddress } from '@metamask/controller-utils'; +import { ApprovalType, toChecksumHexAddress } from '@metamask/controller-utils'; import { ExtendedControllerMessenger } from './ExtendedControllerMessenger'; import EthQuery from '@metamask/eth-query'; import DomainProxyMap from '../lib/DomainProxyMap/DomainProxyMap'; @@ -514,11 +514,8 @@ class Engine { }), showApprovalRequest: () => undefined, typesExcludedFromRateLimiting: [ - // TODO: Replace with ApprovalType enum from @metamask/controller-utils when breaking change is fixed - 'personal_sign', - 'eth_signTypedData', - 'transaction', - 'wallet_watchAsset', + ApprovalType.Transaction, + ApprovalType.WatchAsset ], });