From 15f4fc23cbe14f4b85888ea79aa6b8a49b50b3ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20=C5=81ucka?= <5708018+PatrykLucka@users.noreply.github.com> Date: Wed, 16 Apr 2025 13:41:59 +0200 Subject: [PATCH 1/3] feat: auto create solana account after ondboarding or srp import --- app/scripts/metamask-controller.js | 45 ++++++++++++++ app/scripts/metamask-controller.test.js | 80 ++++++++++++++++++++++--- 2 files changed, 118 insertions(+), 7 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 3f2e10204b13..e3dde34e2be5 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -254,6 +254,7 @@ import { BRIDGE_API_BASE_URL } from '../../shared/constants/bridge'; import { BridgeStatusAction } from '../../shared/types/bridge-status'; ///: BEGIN:ONLY_INCLUDE_IF(solana) import { addDiscoveredSolanaAccounts } from '../../shared/lib/accounts'; +import { SOLANA_WALLET_SNAP_ID } from '../../shared/lib/accounts/solana-wallet-snap'; ///: END:ONLY_INCLUDE_IF import { ///: BEGIN:ONLY_INCLUDE_IF(build-mmi) @@ -1264,6 +1265,9 @@ export default class MetamaskController extends EventEmitter { if (!prevCompletedOnboarding && currCompletedOnboarding) { const { address } = this.accountsController.getSelectedAccount(); + ///: BEGIN:ONLY_INCLUDE_IF(solana) + await this._addSolanaAccount(); + ///: END:ONLY_INCLUDE_IF await this._addAccountsWithBalance(); this.postOnboardingInitialization(); @@ -4726,6 +4730,9 @@ export default class MetamaskController extends EventEmitter { this.accountsController.getAccountByAddress(newAccountAddress); this.accountsController.setSelectedAccount(account.id); + ///: BEGIN:ONLY_INCLUDE_IF(solana) + await this._addSolanaAccount(id); + ///: END:ONLY_INCLUDE_IF await this._addAccountsWithBalance(id); return newAccountAddress; @@ -4808,6 +4815,9 @@ export default class MetamaskController extends EventEmitter { ); if (completedOnboarding) { + ///: BEGIN:ONLY_INCLUDE_IF(solana) + await this._addSolanaAccount(); + ///: END:ONLY_INCLUDE_IF await this._addAccountsWithBalance(); // This must be set as soon as possible to communicate to the @@ -4910,6 +4920,41 @@ export default class MetamaskController extends EventEmitter { } } + /** + * Adds Solana account to the keyring. + * + * @param {string} keyringId - The ID of the keyring to add the account to. + */ + ///: BEGIN:ONLY_INCLUDE_IF(solana) + async _addSolanaAccount(keyringId) { + const snapId = SOLANA_WALLET_SNAP_ID; + let entropySource = keyringId; + if (!entropySource) { + // Get the entropy source from the first HD keyring + const id = await this.keyringController.withKeyring( + { type: KeyringTypes.hd }, + async ({ metadata }) => { + return metadata.id; + }, + ); + entropySource = id; + } + + const keyring = await this.getSnapKeyring(); + console.log('keyring: ', keyring); + + return await keyring.createAccount( + snapId, + { entropySource }, + { + displayConfirmation: false, + displayAccountNameSuggestion: false, + setSelectedAccount: false, + }, + ); + } + ///: END:ONLY_INCLUDE_IF + /** * Encodes a BIP-39 mnemonic as the indices of words in the English BIP-39 wordlist. * diff --git a/app/scripts/metamask-controller.test.js b/app/scripts/metamask-controller.test.js index ba542709ea7e..4e4d14715a96 100644 --- a/app/scripts/metamask-controller.test.js +++ b/app/scripts/metamask-controller.test.js @@ -780,6 +780,40 @@ describe('MetaMaskController', () => { allDetectedTokens: { '0x1': { [TEST_ADDRESS_2]: [{}] } }, }); + const mockSnapKeyring = { + createAccount: jest + .fn() + .mockResolvedValue({ address: 'mockedAddress' }), + }; + + const originalGetKeyringsByType = + metamaskController.keyringController.getKeyringsByType; + let snapKeyringCallCount = 0; + jest + .spyOn(metamaskController.keyringController, 'getKeyringsByType') + .mockImplementation((type) => { + if (type === 'Snap Keyring') { + snapKeyringCallCount += 1; + + if (snapKeyringCallCount === 1) { + // First call - use original implementation to let controller initialize snap keyring + return originalGetKeyringsByType.call( + metamaskController.keyringController, + type, + ); + } + // Second call and beyond - return mock + console.log('returning mocked snap keyring!'); + return [mockSnapKeyring]; + } + + // For other types, always use original implementation + return originalGetKeyringsByType.call( + metamaskController.keyringController, + type, + ); + }); + await metamaskController.createNewVaultAndRestore( 'foobar1337', TEST_SEED, @@ -3912,6 +3946,39 @@ describe('MetaMaskController', () => { it('generates a new hd keyring instance with a mnemonic', async () => { const password = 'what-what-what'; jest.spyOn(metamaskController, 'getBalance').mockResolvedValue('0x0'); + const mockSnapKeyring = { + createAccount: jest + .fn() + .mockResolvedValue({ address: 'mockedAddress' }), + }; + + const originalGetKeyringsByType = + metamaskController.keyringController.getKeyringsByType; + let snapKeyringCallCount = 0; + jest + .spyOn(metamaskController.keyringController, 'getKeyringsByType') + .mockImplementation((type) => { + if (type === 'Snap Keyring') { + snapKeyringCallCount += 1; + + if (snapKeyringCallCount === 1) { + // First call - use original implementation to let controller initialize snap keyring + return originalGetKeyringsByType.call( + metamaskController.keyringController, + type, + ); + } + // Second call and beyond - return mock + console.log('returning mocked snap keyring!'); + return [mockSnapKeyring]; + } + + // For other types, always use original implementation + return originalGetKeyringsByType.call( + metamaskController.keyringController, + type, + ); + }); await metamaskController.createNewVaultAndRestore(password, TEST_SEED); @@ -3971,10 +4038,9 @@ describe('MetaMaskController', () => { .mockImplementation(mockDiscoverAccounts); const mockCreateAccount = jest.fn().mockResolvedValue(undefined); - const mockSnapKeyring = { createAccount: mockCreateAccount }; jest .spyOn(metamaskController, 'getSnapKeyring') - .mockResolvedValue(mockSnapKeyring); + .mockResolvedValue({ createAccount: mockCreateAccount }); await metamaskController.createNewVaultAndRestore(password, TEST_SEED); await metamaskController.importMnemonicToVault(TEST_SEED_ALT); @@ -4000,14 +4066,14 @@ describe('MetaMaskController', () => { expect(mockDiscoverAccounts.mock.calls[2][2]).toBe(2); // Assert that createAccount was called correctly for each discovered account - expect(mockCreateAccount).toHaveBeenCalledTimes(2); + expect(mockCreateAccount).toHaveBeenCalledTimes(3); // All calls should use the solana snap ID expect(mockCreateAccount.mock.calls[0][0]).toStrictEqual( expect.stringContaining('solana-wallet'), ); - // First call should use derivation path on index 0 - expect(mockCreateAccount.mock.calls[0][1]).toStrictEqual({ + // Second call should use derivation path on index 0 + expect(mockCreateAccount.mock.calls[1][1]).toStrictEqual({ derivationPath: "m/44'/501'/0'/0'", entropySource: expect.any(String), }); @@ -4018,8 +4084,8 @@ describe('MetaMaskController', () => { setSelectedAccount: false, }); - // Second call should use derivation path on index 1 - expect(mockCreateAccount.mock.calls[1][1]).toStrictEqual({ + // Third call should use derivation path on index 1 + expect(mockCreateAccount.mock.calls[2][1]).toStrictEqual({ derivationPath: "m/44'/501'/1'/0'", entropySource: expect.any(String), }); From 4870254377e7fe3a506028906fb20dad8a7bef09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20=C5=81ucka?= <5708018+PatrykLucka@users.noreply.github.com> Date: Wed, 16 Apr 2025 16:35:05 +0200 Subject: [PATCH 2/3] chore: remove console log --- app/scripts/metamask-controller.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index e3dde34e2be5..9283fd68c95d 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -4941,7 +4941,6 @@ export default class MetamaskController extends EventEmitter { } const keyring = await this.getSnapKeyring(); - console.log('keyring: ', keyring); return await keyring.createAccount( snapId, From 8f31c3855e3f89aebbe3ee55815349264a893bfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20=C5=81ucka?= <5708018+PatrykLucka@users.noreply.github.com> Date: Thu, 17 Apr 2025 14:34:53 +0200 Subject: [PATCH 3/3] chore: update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1fa439b8101..4eba725b73ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added - Support for Solana Devnet ([#31702](https://github.com/MetaMask/metamask-extension/pull/31702)) +- [Beta] Create Solana account automatically on wallet creation or SRP import [#32038](https://github.com/MetaMask/metamask-extension/pull/32038) ## [12.15.2] ### Added