Skip to content

Commit 10ea293

Browse files
authored
feat: auto create solana account after ondboarding or srp import cp-12.17.0 (#32038)
<!-- 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** This PR makes Solana accounts are added automatically after onboarding or importing SRP. ## **Related issues** Fixes: ## **Manual testing steps** 1. Onboard or import SRP 2. Solana account should be added automatically with default name ## **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** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/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-extension/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.
1 parent db9369e commit 10ea293

File tree

3 files changed

+118
-7
lines changed

3 files changed

+118
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
### Added
99
- Changes in account modal to switch to smart account type ([#31899](https://github.com/MetaMask/metamask-extension/pull/31899))
1010
- Support for Solana Devnet ([#31702](https://github.com/MetaMask/metamask-extension/pull/31702))
11+
- [Beta] Create Solana account automatically on wallet creation or SRP import [#32038](https://github.com/MetaMask/metamask-extension/pull/32038)
1112

1213
## [12.15.2]
1314
### Added

app/scripts/metamask-controller.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ import { BRIDGE_API_BASE_URL } from '../../shared/constants/bridge';
254254
import { BridgeStatusAction } from '../../shared/types/bridge-status';
255255
///: BEGIN:ONLY_INCLUDE_IF(solana)
256256
import { addDiscoveredSolanaAccounts } from '../../shared/lib/accounts';
257+
import { SOLANA_WALLET_SNAP_ID } from '../../shared/lib/accounts/solana-wallet-snap';
257258
///: END:ONLY_INCLUDE_IF
258259
import {
259260
///: BEGIN:ONLY_INCLUDE_IF(build-mmi)
@@ -1264,6 +1265,9 @@ export default class MetamaskController extends EventEmitter {
12641265
if (!prevCompletedOnboarding && currCompletedOnboarding) {
12651266
const { address } = this.accountsController.getSelectedAccount();
12661267

1268+
///: BEGIN:ONLY_INCLUDE_IF(solana)
1269+
await this._addSolanaAccount();
1270+
///: END:ONLY_INCLUDE_IF
12671271
await this._addAccountsWithBalance();
12681272

12691273
this.postOnboardingInitialization();
@@ -4726,6 +4730,9 @@ export default class MetamaskController extends EventEmitter {
47264730
this.accountsController.getAccountByAddress(newAccountAddress);
47274731
this.accountsController.setSelectedAccount(account.id);
47284732

4733+
///: BEGIN:ONLY_INCLUDE_IF(solana)
4734+
await this._addSolanaAccount(id);
4735+
///: END:ONLY_INCLUDE_IF
47294736
await this._addAccountsWithBalance(id);
47304737

47314738
return newAccountAddress;
@@ -4808,6 +4815,9 @@ export default class MetamaskController extends EventEmitter {
48084815
);
48094816

48104817
if (completedOnboarding) {
4818+
///: BEGIN:ONLY_INCLUDE_IF(solana)
4819+
await this._addSolanaAccount();
4820+
///: END:ONLY_INCLUDE_IF
48114821
await this._addAccountsWithBalance();
48124822

48134823
// This must be set as soon as possible to communicate to the
@@ -4910,6 +4920,40 @@ export default class MetamaskController extends EventEmitter {
49104920
}
49114921
}
49124922

4923+
/**
4924+
* Adds Solana account to the keyring.
4925+
*
4926+
* @param {string} keyringId - The ID of the keyring to add the account to.
4927+
*/
4928+
///: BEGIN:ONLY_INCLUDE_IF(solana)
4929+
async _addSolanaAccount(keyringId) {
4930+
const snapId = SOLANA_WALLET_SNAP_ID;
4931+
let entropySource = keyringId;
4932+
if (!entropySource) {
4933+
// Get the entropy source from the first HD keyring
4934+
const id = await this.keyringController.withKeyring(
4935+
{ type: KeyringTypes.hd },
4936+
async ({ metadata }) => {
4937+
return metadata.id;
4938+
},
4939+
);
4940+
entropySource = id;
4941+
}
4942+
4943+
const keyring = await this.getSnapKeyring();
4944+
4945+
return await keyring.createAccount(
4946+
snapId,
4947+
{ entropySource },
4948+
{
4949+
displayConfirmation: false,
4950+
displayAccountNameSuggestion: false,
4951+
setSelectedAccount: false,
4952+
},
4953+
);
4954+
}
4955+
///: END:ONLY_INCLUDE_IF
4956+
49134957
/**
49144958
* Encodes a BIP-39 mnemonic as the indices of words in the English BIP-39 wordlist.
49154959
*

app/scripts/metamask-controller.test.js

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,40 @@ describe('MetaMaskController', () => {
780780
allDetectedTokens: { '0x1': { [TEST_ADDRESS_2]: [{}] } },
781781
});
782782

783+
const mockSnapKeyring = {
784+
createAccount: jest
785+
.fn()
786+
.mockResolvedValue({ address: 'mockedAddress' }),
787+
};
788+
789+
const originalGetKeyringsByType =
790+
metamaskController.keyringController.getKeyringsByType;
791+
let snapKeyringCallCount = 0;
792+
jest
793+
.spyOn(metamaskController.keyringController, 'getKeyringsByType')
794+
.mockImplementation((type) => {
795+
if (type === 'Snap Keyring') {
796+
snapKeyringCallCount += 1;
797+
798+
if (snapKeyringCallCount === 1) {
799+
// First call - use original implementation to let controller initialize snap keyring
800+
return originalGetKeyringsByType.call(
801+
metamaskController.keyringController,
802+
type,
803+
);
804+
}
805+
// Second call and beyond - return mock
806+
console.log('returning mocked snap keyring!');
807+
return [mockSnapKeyring];
808+
}
809+
810+
// For other types, always use original implementation
811+
return originalGetKeyringsByType.call(
812+
metamaskController.keyringController,
813+
type,
814+
);
815+
});
816+
783817
await metamaskController.createNewVaultAndRestore(
784818
'foobar1337',
785819
TEST_SEED,
@@ -3912,6 +3946,39 @@ describe('MetaMaskController', () => {
39123946
it('generates a new hd keyring instance with a mnemonic', async () => {
39133947
const password = 'what-what-what';
39143948
jest.spyOn(metamaskController, 'getBalance').mockResolvedValue('0x0');
3949+
const mockSnapKeyring = {
3950+
createAccount: jest
3951+
.fn()
3952+
.mockResolvedValue({ address: 'mockedAddress' }),
3953+
};
3954+
3955+
const originalGetKeyringsByType =
3956+
metamaskController.keyringController.getKeyringsByType;
3957+
let snapKeyringCallCount = 0;
3958+
jest
3959+
.spyOn(metamaskController.keyringController, 'getKeyringsByType')
3960+
.mockImplementation((type) => {
3961+
if (type === 'Snap Keyring') {
3962+
snapKeyringCallCount += 1;
3963+
3964+
if (snapKeyringCallCount === 1) {
3965+
// First call - use original implementation to let controller initialize snap keyring
3966+
return originalGetKeyringsByType.call(
3967+
metamaskController.keyringController,
3968+
type,
3969+
);
3970+
}
3971+
// Second call and beyond - return mock
3972+
console.log('returning mocked snap keyring!');
3973+
return [mockSnapKeyring];
3974+
}
3975+
3976+
// For other types, always use original implementation
3977+
return originalGetKeyringsByType.call(
3978+
metamaskController.keyringController,
3979+
type,
3980+
);
3981+
});
39153982

39163983
await metamaskController.createNewVaultAndRestore(password, TEST_SEED);
39173984

@@ -3971,10 +4038,9 @@ describe('MetaMaskController', () => {
39714038
.mockImplementation(mockDiscoverAccounts);
39724039

39734040
const mockCreateAccount = jest.fn().mockResolvedValue(undefined);
3974-
const mockSnapKeyring = { createAccount: mockCreateAccount };
39754041
jest
39764042
.spyOn(metamaskController, 'getSnapKeyring')
3977-
.mockResolvedValue(mockSnapKeyring);
4043+
.mockResolvedValue({ createAccount: mockCreateAccount });
39784044

39794045
await metamaskController.createNewVaultAndRestore(password, TEST_SEED);
39804046
await metamaskController.importMnemonicToVault(TEST_SEED_ALT);
@@ -4000,14 +4066,14 @@ describe('MetaMaskController', () => {
40004066
expect(mockDiscoverAccounts.mock.calls[2][2]).toBe(2);
40014067

40024068
// Assert that createAccount was called correctly for each discovered account
4003-
expect(mockCreateAccount).toHaveBeenCalledTimes(2);
4069+
expect(mockCreateAccount).toHaveBeenCalledTimes(3);
40044070

40054071
// All calls should use the solana snap ID
40064072
expect(mockCreateAccount.mock.calls[0][0]).toStrictEqual(
40074073
expect.stringContaining('solana-wallet'),
40084074
);
4009-
// First call should use derivation path on index 0
4010-
expect(mockCreateAccount.mock.calls[0][1]).toStrictEqual({
4075+
// Second call should use derivation path on index 0
4076+
expect(mockCreateAccount.mock.calls[1][1]).toStrictEqual({
40114077
derivationPath: "m/44'/501'/0'/0'",
40124078
entropySource: expect.any(String),
40134079
});
@@ -4018,8 +4084,8 @@ describe('MetaMaskController', () => {
40184084
setSelectedAccount: false,
40194085
});
40204086

4021-
// Second call should use derivation path on index 1
4022-
expect(mockCreateAccount.mock.calls[1][1]).toStrictEqual({
4087+
// Third call should use derivation path on index 1
4088+
expect(mockCreateAccount.mock.calls[2][1]).toStrictEqual({
40234089
derivationPath: "m/44'/501'/1'/0'",
40244090
entropySource: expect.any(String),
40254091
});

0 commit comments

Comments
 (0)