-
-
Notifications
You must be signed in to change notification settings - Fork 261
refactor(multichain-account-service): Improved performance across package classes and improved error messages #6654
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…to wallets and groups
…tead of getAccountByAddress which iterates through the whole of internal accounts in the AccountsController
| accountsList, | ||
| ); | ||
| // we cast here because we know that the accounts are BIP-44 compatible | ||
| return internalAccounts as Bip44Account<KeyringAccount>[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although the getAccounts's return type is (InternalAccount | undefined)[], we're sure to get back all the accounts we want since the accounts list will never be stale.
…accountAdded and accountRemoved handling, it is dead code
| MultichainAccountWallet<Bip44Account<KeyringAccount>> | ||
| >; | ||
|
|
||
| readonly #accountIdToContext: Map< |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Decided to get rid of this mapping since it was only being used for handling the accountRemoved and accountAdded events, removing this gets rid of a large loop in the init function as well. If there's a particular need for this data at the client level, we can always add this back in.
…handle createNewVaultAndKeychain and createNewVaultAndRestore code paths
…s, remove redundant state assignment, use assert to ensure wallet existence after creation
…ble with new changes
packages/multichain-account-service/src/providers/BaseBip44AccountProvider.ts
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Wrapper Data Disconnect: Init vs. Get
AccountProviderWrapper inherits from BaseBip44AccountProvider, creating its own accounts Set. When init() is called on a wrapper instance, it populates the wrapper's inherited accounts Set. However, getAccounts() delegates to the wrapped provider's implementation, which uses the wrapped provider's separate accounts Set. This creates a disconnect where initialization populates the wrapper's Set while retrieval reads from the provider's empty Set, causing getAccounts() to return incorrect results.
packages/multichain-account-service/src/providers/AccountProviderWrapper.ts#L11-L25
core/packages/multichain-account-service/src/providers/AccountProviderWrapper.ts
Lines 11 to 25 in 602ce8f
| */ | |
| export class AccountProviderWrapper extends BaseBip44AccountProvider { | |
| private isEnabled: boolean = true; | |
| private readonly provider: BaseBip44AccountProvider; | |
| constructor( | |
| messenger: MultichainAccountServiceMessenger, | |
| provider: BaseBip44AccountProvider, | |
| ) { | |
| super(messenger); | |
| this.provider = provider; | |
| } | |
| override getName(): string { |
| #getPrimaryEntropySourceId(): EntropySourceId { | ||
| const { keyrings } = this.#messenger.call('KeyringController:getState'); | ||
| const primaryKeyring = keyrings.find( | ||
| (keyring) => keyring.type === 'HD Key Tree', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should re-use the enum from the keyring-controller
| (keyring) => keyring.type === 'HD Key Tree', | |
| (keyring) => keyring.type === KeyringTypes.hd, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TS seems to complain about this, that's why I used the string instead.
packages/multichain-account-service/src/providers/BaseBip44AccountProvider.ts
Outdated
Show resolved
Hide resolved
packages/multichain-account-service/src/providers/BaseBip44AccountProvider.ts
Show resolved
Hide resolved
| init(accounts: Account['id'][]): void { | ||
| for (const account of accounts) { | ||
| this.accountsList.add(account); | ||
| this.accounts.add(account); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WDYT about adding a small comment saying we assume the caller is always sending valid/compatible accounts for this provider?
Here, nothing prevents me from doing the wrong accounts (which could mess up the entire logic).
So either we assert(this.isAccountCompatible(account), 'Incompatible account') here (should be a pretty cheap call I think)
OR we assume the caller uses the right accounts right away.
If we go without the assert I'd go add this comment/note in the jsdoc directly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add the comment! However the init call is piped from the service's filtered accounts so I don't think it would ever be an issue. Do we see the provider classes being used outside of the context of the service?
packages/multichain-account-service/src/MultichainAccountWallet.ts
Outdated
Show resolved
Hide resolved
packages/multichain-account-service/src/MultichainAccountWallet.ts
Outdated
Show resolved
Hide resolved
packages/multichain-account-service/src/MultichainAccountWallet.ts
Outdated
Show resolved
Hide resolved
packages/multichain-account-service/src/MultichainAccountWallet.ts
Outdated
Show resolved
Hide resolved
| return allAccounts; | ||
| } | ||
|
|
||
| /** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Disabled provider account retrieval throws exception
When retrieving accounts through getAccounts() or getAccount(), if a provider in the group's internal maps becomes disabled (via AccountProviderWrapper.setEnabled(false)), calling these methods will throw an error because the disabled wrapper's getAccount method throws "Provider is disabled". This could occur between the time a provider is disabled and the group's alignAccounts() method is invoked to clean up. The code should handle the exception gracefully or remove the disabled provider's accounts immediately upon disable.
Additional Locations (1)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think is an issue, the methods wouldn't overlap cc: @ccharly
| }); | ||
| const accountIds = accounts.map((account) => account.id); | ||
| return accountIds; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: alignAccounts loses existing accounts for each provider
The alignAccounts method returns only newly created accounts, but the interface documentation and usage in MultichainAccountGroup.alignAccounts expect it to return both existing and newly created account IDs. After alignment, the group rebuilds its state using only these returned IDs, causing existing accounts for each provider to be lost. When alignAccounts is called, all previously existing accounts should remain in the group's internal maps alongside any newly created ones.
Additional Locations (1)
|
@metamaskbot publish-preview |
|
Preview builds have been published. See these instructions for more information about preview builds. Expand for full list of packages and versions. |
| this.#providerToAccounts.set(provider, accounts); | ||
| for (const accountId of accounts) { | ||
| this.#accountToProvider.set(accountId, provider); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Redundant state updates in alignAccounts method
The alignAccounts method updates internal state twice. Lines 272-273 clear #providerToAccounts and #accountToProvider, then lines 294-297 populate these maps directly within the try block. However, line 336 calls this.update(groupState) which invokes #setState to repopulate the same maps. This redundant update is unnecessary since #setState already handles clearing and repopulating the maps for all providers. The direct map updates in lines 294-297 should be removed to avoid potential inconsistencies and maintain a single source of truth for state updates.
|
|
||
| for (const accountId of accountIds) { | ||
| this.#accountToProvider.set(accountId, provider); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Partial state update clears unrelated provider account mappings
The #setState method iterates through all providers and unconditionally calls #clearAccountToProviderState for each, but only repopulates the mapping if the provider has data in the passed groupState. When group.update() is called with partial state (e.g., only one provider's accounts from the background creation at line 285), accounts from other providers are cleared from #accountToProvider but remain in #providerToAccounts. This creates an inconsistent state where getAccount(id), getAccountIds(), and hasAccounts() return incorrect results for accounts whose providers weren't in the update, while getAccounts() may still work. This particularly affects the background mode of #createNonEvmAccounts where each provider calls update with only its own accounts.
Additional Locations (1)
## **Description** This PR implements new `@metamask/native-utils` package for C++ cryptography instead of JS implementation. That provides significant performance improvements all across the app. Most visible improvements are during app startup and SRP imports, but for example `keccak256` helps also in many other places. This PR should also have really nice synergy with MetaMask/core#6654 that could shave of another tens of percent for login times. For now I am patching `@metamask/key-tree`, once MetaMask/key-tree#223 is done, patch could be removed, but it may take a while. ### Performance Optimization Results Device: Pixel 4a 5G Tested on SRP with 200+ accounts. | Metric | Before Optimization* | After Optimization | Improvement | % Faster | | :--- | :--- | :--- | :--- | :--- | | **App Loaded to Login screen** | 7s 333ms | **4s 750ms** | ⚡️ 2.58s faster | **35.2%** | | **Dashboard Loaded** | 14s 0ms | **6s 333ms** | ⚡️ 7.67s faster | **54.8%** | | **App is Responsive (60 FPS)** | 18s 783ms | **12s 166ms** | ⚡️ 6.62s faster | **35.2%** | | **SRP Import (Discovery)** | 276s 616ms | **203s 450ms** | ⚡️ 73.17s faster | **26.5%** | _* Before optimalization version has `@metamask/native-utils` completely removed (including secp256k1 that was merged before)._ There should be around 200 - 300ms improvement in account creation time but I am not including this in result because I did many measurements but spread was too big to conclude any results from it. Another big improvement for acccount creation should be MetaMask/core#6654 ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: ## **Related issues** Fixes: ## **Manual testing steps** 1. All accounts are discovered 2. Balances for tokens and total balance is correct 3. Correct receive addresses are generated ## **Screenshots/Recordings** ### Startup + Login https://github.com/user-attachments/assets/24c8ca90-5475-4fa8-9062-30f6fa5133b2 ### SRP Import Observe also FPS counter, as you can see optimized version is maintaining higher FPS (around ~20) compared to non-optimized (around ~10). That should be enough to make app usable even on very slow device during running accounts discovery. To improve FPS even more we need to optimize rerenders and some selectors. In order to reduce discovery total time even more it would require different strategies of discovery, for example instead doing account detail requests one by one, until you find empty one, you could for example request them in batches of 3 which should improve total time significantly. https://github.com/user-attachments/assets/9f6c9825-5d97-415e-903c-8f4327273a2d ## **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. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Replaces JS crypto with native implementations via @metamask/native-utils, adds runtime perf shims, patches dependencies, updates iOS pods/Jest config, and optimizes account sorting. > > - **Performance/crypto integration** > - Add `shimPerf.js` to monkey‑patch `@noble/*` and `js-sha3` with native `getPublicKey`, `hmacSha512`, and `keccak256` from `@metamask/native-utils`. > - Update `shim.js` to load the new perf shim. > - **Library patches** > - Patch `@ethereumjs/util` to export `pubToAddress` from `@metamask/native-utils`. > - Patch `@metamask/key-tree` Ed25519 to use `nativeUtils.getPublicKeyEd25519`. > - **Encryption** > - Replace `Buffer.from(..., 'utf-8')` with `TextEncoder().encode(...)` in `quick-crypto.ts` for key derivation and encryption inputs. > - **Selectors** > - Optimize `selectInternalAccounts` sorting by precomputing address indices to avoid repeated `toFormattedAddress` calls. > - **Testing** > - Add Jest mock `app/__mocks__/@metamask/native-utils.js` and map it in `jest.config.js`; expand `transformIgnorePatterns` for native modules. > - **iOS/Pods** > - Bump `NativeUtils` pod to `0.8.0`. > - **Dependencies** > - Upgrade `@metamask/native-utils` to `^0.8.0`; add `js-sha3@0.9.3`, `@noble/hashes@1.8.0`. > - Add Yarn patches/resolutions for `@ethereumjs/util@9.1.0` and `@metamask/key-tree@10.1.1`; update `yarn.lock`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 7ff47ad. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
## **Description** This PR is from an external contributor. Initial review and context [here](#23270) Credit @Nodonisko This PR implements new `@metamask/native-utils` package for C++ cryptography instead of JS implementation. That provides significant performance improvements all across the app. Most visible improvements are during app startup and SRP imports, but for example `keccak256` helps also in many other places. This PR should also have really nice synergy with MetaMask/core#6654 that could shave of another tens of percent for login times. For now I am patching `@metamask/key-tree`, once MetaMask/key-tree#223 is done, patch could be removed, but it may take a while. ### Performance Optimization Results Device: Pixel 4a 5G Tested on SRP with 200+ accounts. | Metric | Before Optimization* | After Optimization | Improvement | % Faster | | :--- | :--- | :--- | :--- | :--- | | **App Loaded to Login screen** | 7s 333ms | **4s 750ms** | ⚡️ 2.58s faster | **35.2%** | | **Dashboard Loaded** | 14s 0ms | **6s 333ms** | ⚡️ 7.67s faster | **54.8%** | | **App is Responsive (60 FPS)** | 18s 783ms | **12s 166ms** | ⚡️ 6.62s faster | **35.2%** | | **SRP Import (Discovery)** | 276s 616ms | **203s 450ms** | ⚡️ 73.17s faster | **26.5%** | _* Before optimalization version has `@metamask/native-utils` completely removed (including secp256k1 that was merged before)._ There should be around 200 - 300ms improvement in account creation time but I am not including this in result because I did many measurements but spread was too big to conclude any results from it. Another big improvement for acccount creation should be MetaMask/core#6654 ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: null ## **Related issues** Fixes: ## **Manual testing steps** 1. All accounts are discovered 2. Balances for tokens and total balance is correct 3. Correct receive addresses are generated ## **Screenshots/Recordings** ### Startup + Login https://github.com/user-attachments/assets/24c8ca90-5475-4fa8-9062-30f6fa5133b2 ### SRP Import Observe also FPS counter, as you can see optimized version is maintaining higher FPS (around ~20) compared to non-optimized (around ~10). That should be enough to make app usable even on very slow device during running accounts discovery. To improve FPS even more we need to optimize rerenders and some selectors. In order to reduce discovery total time even more it would require different strategies of discovery, for example instead doing account detail requests one by one, until you find empty one, you could for example request them in batches of 3 which should improve total time significantly. https://github.com/user-attachments/assets/9f6c9825-5d97-415e-903c-8f4327273a2d ## **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. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Replaces JS crypto with native implementations via @metamask/native-utils, adds perf shims, patches key libs, and updates tests/config to support it. > > - **Crypto performance**: > - Add `shimPerf` to monkey-patch `@noble` and `js-sha3` (`secp256k1.getPublicKey`, `hmacSha512`, `keccak256`) to use native C++ via `@metamask/native-utils`. > - Update `shim.js` to load `shimPerf`. > - Switch `quick-crypto` string encoding to `TextEncoder` for key derivation/encryption. > - **Library patches**: > - Patch `@ethereumjs/util` to export `pubToAddress` from `@metamask/native-utils`. > - Patch `@metamask/key-tree` `ed25519` to use `native-utils.getPublicKeyEd25519`. > - **Selectors**: > - Optimize `selectInternalAccounts` sorting to pre-compute indices and reduce `toFormattedAddress` calls. > - **Testing/config**: > - Add Jest mock `app/__mocks__/@metamask/native-utils.js` and map in `jest.config.js`; extend `transformIgnorePatterns`. > - **Dependencies/Pods/Lockfiles**: > - Bump `@metamask/native-utils` to `^0.8.0` (iOS `NativeUtils` pod 0.8.0). > - Add `js-sha3@0.9.3`, pin `@noble/hashes@1.8.0`, add Yarn patches/locks for `@ethereumjs/util@9.1.0` and `@metamask/key-tree@10.1.1`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 95728ed. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [ ] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --------- Co-authored-by: Daniel Suchý <suchydan@gmail.com>
## **Description** This PR is from an external contributor. Initial review and context [here](#23270) Credit @Nodonisko This PR implements new `@metamask/native-utils` package for C++ cryptography instead of JS implementation. That provides significant performance improvements all across the app. Most visible improvements are during app startup and SRP imports, but for example `keccak256` helps also in many other places. This PR should also have really nice synergy with MetaMask/core#6654 that could shave of another tens of percent for login times. For now I am patching `@metamask/key-tree`, once MetaMask/key-tree#223 is done, patch could be removed, but it may take a while. ### Performance Optimization Results Device: Pixel 4a 5G Tested on SRP with 200+ accounts. | Metric | Before Optimization* | After Optimization | Improvement | % Faster | | :--- | :--- | :--- | :--- | :--- | | **App Loaded to Login screen** | 7s 333ms | **4s 750ms** | ⚡️ 2.58s faster | **35.2%** | | **Dashboard Loaded** | 14s 0ms | **6s 333ms** | ⚡️ 7.67s faster | **54.8%** | | **App is Responsive (60 FPS)** | 18s 783ms | **12s 166ms** | ⚡️ 6.62s faster | **35.2%** | | **SRP Import (Discovery)** | 276s 616ms | **203s 450ms** | ⚡️ 73.17s faster | **26.5%** | _* Before optimalization version has `@metamask/native-utils` completely removed (including secp256k1 that was merged before)._ There should be around 200 - 300ms improvement in account creation time but I am not including this in result because I did many measurements but spread was too big to conclude any results from it. Another big improvement for acccount creation should be MetaMask/core#6654 ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: null ## **Related issues** Fixes: ## **Manual testing steps** 1. All accounts are discovered 2. Balances for tokens and total balance is correct 3. Correct receive addresses are generated ## **Screenshots/Recordings** ### Startup + Login https://github.com/user-attachments/assets/24c8ca90-5475-4fa8-9062-30f6fa5133b2 ### SRP Import Observe also FPS counter, as you can see optimized version is maintaining higher FPS (around ~20) compared to non-optimized (around ~10). That should be enough to make app usable even on very slow device during running accounts discovery. To improve FPS even more we need to optimize rerenders and some selectors. In order to reduce discovery total time even more it would require different strategies of discovery, for example instead doing account detail requests one by one, until you find empty one, you could for example request them in batches of 3 which should improve total time significantly. https://github.com/user-attachments/assets/9f6c9825-5d97-415e-903c-8f4327273a2d ## **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. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Replaces JS crypto with native implementations via @metamask/native-utils, adds perf shims, patches key libs, and updates tests/config to support it. > > - **Crypto performance**: > - Add `shimPerf` to monkey-patch `@noble` and `js-sha3` (`secp256k1.getPublicKey`, `hmacSha512`, `keccak256`) to use native C++ via `@metamask/native-utils`. > - Update `shim.js` to load `shimPerf`. > - Switch `quick-crypto` string encoding to `TextEncoder` for key derivation/encryption. > - **Library patches**: > - Patch `@ethereumjs/util` to export `pubToAddress` from `@metamask/native-utils`. > - Patch `@metamask/key-tree` `ed25519` to use `native-utils.getPublicKeyEd25519`. > - **Selectors**: > - Optimize `selectInternalAccounts` sorting to pre-compute indices and reduce `toFormattedAddress` calls. > - **Testing/config**: > - Add Jest mock `app/__mocks__/@metamask/native-utils.js` and map in `jest.config.js`; extend `transformIgnorePatterns`. > - **Dependencies/Pods/Lockfiles**: > - Bump `@metamask/native-utils` to `^0.8.0` (iOS `NativeUtils` pod 0.8.0). > - Add `js-sha3@0.9.3`, pin `@noble/hashes@1.8.0`, add Yarn patches/locks for `@ethereumjs/util@9.1.0` and `@metamask/key-tree@10.1.1`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 95728ed. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [ ] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --------- Co-authored-by: Daniel Suchý <suchydan@gmail.com>
Explanation
MultichainAccountServicecreateMultichainAccountWallet) that handles import/restore/new vault.ServiceStateindex in one pass and passes state slices to wallets/groups (cuts repeated controller scans/calls).initpath and removed deadaccountIdToContextmapping.MultichainAccountWalletinitnow consumes a pre-sliced wallet state (entropySource → groups → providerName → ids) instead of querying providers.MultichainAccountGroupinitregisters account IDs per provider and fills reverse maps; callsprovider.addAccounts(ids)to keep providers in sync.getAccountIds()for direct access to underlying IDs.BaseBip44AccountProvideraddAccounts(ids: string[]), enabling providers to track their own account ID lists.getAccounts()paths rely on known IDs (plural lookups) rather than scanning the full controller list.EvmAccountProvidergetAccount(s)) for create/discover (removesPerformance Analysis
When fully aligned$g = n / p$ .$g = max(f(p))$ , where $f(p)$ is the number of accounts associated with a provider.
When accounts are not fully aligned then
Consider two scenarios:
General formulas
For Scenario 2, the formulas are as follows:
Before this refactor, the number of loops can be represented$n * p * (1 + w + g)$ , which with $p = 4$ , becomes $n^2 + 4n(1 + w)$ .
Before this refactor, the number of controller calls can be represented as$1 + w + g$ , which with $p = 4$ , becomes $1 + w + n/4$ .
After this refactor, the number of loops can be represented by$n * p$ , which with $p = 4$ , becomes $4n$ .
After this refactor, the number of calls is just$1$ .
For Scenario 1, the formulas are entirely dependent on the breakdown of the number of accounts each provider has amongst the$n$ accounts, let's consider a scenario where Solana has $n/2$ , Ethereum has $n/8$ , Bitcoin has $n/4$ and Tron has $n/8$ , the formulas would be as follows:
Before this refactor, the number of loops in the alignment process can be represented as$(p * g) + (n * e)$ , which with $p=4$ and $g = n/2$ , becomes $2n + 3n^2/8$ . Therefore the number of loops for initialization + alignment in this scenario with $p = 4$ and $g = n/2$ , becomes $(19/8)n^2 + (4w + 6)n$ .
Before this refactor, the number of controller calls in the alignment process can be represented as$e$ , which becomes $3n/8$ . Therefore the number of controller calls for initialization + alignment in this scenario with $p = 4$ , becomes $1 + w + 5n/8$ .
After this refactor, the number of loops in the alignment process can be represented as$p * g$ , which becomes $2n$ . Therefore, the number of loops for initialization + alignment in this scenario with $p = 4$ and $g = n/2$ , becomes $6n$ .
After this refactor, the number of controller calls in the alignment process can be represented as$e$ which becomes $3n/8$ . Therefore, the number of controller calls for initialization + alignment in this scenario with $p = 4$ and $g = n/2$ , becomes $1 + 3n/8$ .
In short, previous
initperformance for loops and controller calls was quadratic and linear, respectively. After, it is linear and constant.Performance Charts
Below are charts that show performance (loops and controller calls)$n = 0$ -> $n = 256$ for Scenario 1 and 2 with $w = 2$ , respectively:
References
N/A
Checklist
Note
Refactors multichain account service to state-driven initialization, unifies wallet creation (import/create/restore), and shifts providers to ID-based account tracking with improved alignment and error reporting.
ServiceStatefromAccountsController, initializes wallets/groups from state; removes sync/account-context logic; adds unifiedcreateMultichainAccountWallet(supports import/create/restore via KeyringController actions); registers new actions; improves error reporting.init(walletState); creates/updates groups from provider ID slices; group creation awaits EVM, runs others in background; discovery constructs groups then aligns; emits status and group lifecycle events.init/update; tracks provider→IDs and reverse map; addsgetAccountIds();alignAccounts()uses provideralignAccounts, handles disabled providers, aggregates warnings, emits updated event.init),getAccountsviaAccountsController:getAccounts,getAccountby ID, addsalignAccountshelper.init, addsisDisabled()and disabled-awarealignAccounts/guards.getUUIDFromAddressOfNormalAccountand fetches viaAccountsController:getAccount.AccountsController:getAccounts,KeyringController:createNewVaultAndKeychain/Restore); removes unused error utils; updates tests accordingly.Written by Cursor Bugbot for commit 0a0db20. This will update automatically on new commits. Configure here.