Skip to content

feat: create solana account and call discoverAccounts during creation and import of hd keyring #14775

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

Open
wants to merge 60 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
eb0709f
refactor: AddNewAccount
montelaidev Apr 11, 2025
b599527
fix: update import and selector id name
montelaidev Apr 14, 2025
14e3bb0
feat: add multisrp to beta
montelaidev Apr 14, 2025
dfd941d
fix: add account labels
montelaidev Apr 14, 2025
efd4abf
fix: add account redirect
montelaidev Apr 14, 2025
c317d38
fix: test
montelaidev Apr 14, 2025
e512541
fix: capitalize account
montelaidev Apr 14, 2025
5216f27
feat: add test for client creation
montelaidev Apr 14, 2025
913c579
fix: remove unused.
montelaidev Apr 14, 2025
dd0a2b2
Merge remote-tracking branch 'origin/main' into fix/multichain-accoun…
montelaidev Apr 15, 2025
29afd66
chore: update changelog
montelaidev Apr 15, 2025
f4e0aef
fix: changelog
montelaidev Apr 15, 2025
ed1bafc
fix: remove unused
montelaidev Apr 15, 2025
dc514f5
fix: deps in callback
montelaidev Apr 16, 2025
2401ec5
fix: solana account creation with multi srp
montelaidev Apr 16, 2025
a5ff1eb
fix: changelog
montelaidev Apr 16, 2025
66f6b6a
fix: tests
montelaidev Apr 16, 2025
bfd7809
chore: remove unused
montelaidev Apr 16, 2025
329896e
refactor: client to MultichainWalletSnapFactory
montelaidev Apr 17, 2025
88eb4b2
Merge remote-tracking branch 'origin/main' into fix/mmmultisrp-129
montelaidev Apr 18, 2025
06757af
Merge remote-tracking branch 'origin/main' into fix/mmmultisrp-129
montelaidev Apr 22, 2025
7a916ec
fix: disable button while loading
montelaidev Apr 22, 2025
fa9a816
fix: test
montelaidev Apr 22, 2025
9885aa5
fix: test
montelaidev Apr 22, 2025
6e7e39a
fix: update changelog
montelaidev Apr 22, 2025
5da6ffa
feat: add tests to MultichainWalletSnapClient
montelaidev Apr 22, 2025
e9236b2
fix: use scopes from keyringapi
montelaidev Apr 22, 2025
070a8c8
fix: use scope from keyring api
montelaidev Apr 22, 2025
ea0a926
feat: add discover after vault creation
montelaidev Apr 22, 2025
cf6e1e1
revert: test
montelaidev Apr 22, 2025
88c2d01
fix: remove logs
montelaidev Apr 22, 2025
803c66e
feat: add discover account after import seed during onboarding
montelaidev Apr 23, 2025
c7cecb4
Merge remote-tracking branch 'origin/main' into feat/mmmultisrp-165
montelaidev Apr 23, 2025
ad930e0
fix: tests
montelaidev Apr 23, 2025
cf9c223
fix: lint
montelaidev Apr 23, 2025
b878dee
fix: remove log
montelaidev Apr 23, 2025
af87fa2
Merge remote-tracking branch 'origin/main' into feat/mmmultisrp-165
montelaidev Apr 25, 2025
54a32db
fix: import srp test
montelaidev Apr 25, 2025
93141f8
fix: discover logic
montelaidev Apr 25, 2025
bd9edaf
refactor: naming logic to another file
montelaidev Apr 28, 2025
1f52a52
Merge remote-tracking branch 'origin/main' into feat/mmmultisrp-165
montelaidev Apr 28, 2025
f658d84
fix: fence
montelaidev Apr 28, 2025
3b77958
Merge remote-tracking branch 'origin/main' into feat/mmmultisrp-165
montelaidev Apr 28, 2025
866c006
fix: fence
montelaidev Apr 29, 2025
8f536f1
fix: test
montelaidev Apr 29, 2025
2997d46
Merge branch 'main' into feat/mmmultisrp-165
montelaidev Apr 29, 2025
5660363
Merge branch 'main' into feat/mmmultisrp-165
montelaidev Apr 29, 2025
b991788
Merge remote-tracking branch 'origin/main' into feat/mmmultisrp-165
montelaidev Apr 29, 2025
de9e998
fix: remove unneeded logger mock and mock getNextAvailableAccountName
montelaidev Apr 29, 2025
d1fade9
fix: clean up tests
montelaidev Apr 30, 2025
34ca63f
Merge remote-tracking branch 'origin/main' into feat/mmmultisrp-165
montelaidev Apr 30, 2025
0f88b78
fix: missing import
montelaidev Apr 30, 2025
537680f
Merge remote-tracking branch 'origin/main' into feat/mmmultisrp-165
montelaidev Apr 30, 2025
3d1dc7b
Merge remote-tracking branch 'origin/main' into feat/mmmultisrp-165
montelaidev May 7, 2025
6d60039
fix: mock
montelaidev May 7, 2025
fae58c7
Merge remote-tracking branch 'origin/main' into feat/mmmultisrp-165
montelaidev May 7, 2025
946185e
fix: add try catch to discover accounts
montelaidev May 7, 2025
e1517ba
fix: lint
montelaidev May 7, 2025
420f5e7
Merge remote-tracking branch 'origin/main' into feat/mmmultisrp-165
montelaidev May 7, 2025
486947f
Merge remote-tracking branch 'origin/main' into feat/mmmultisrp-165
montelaidev May 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions app/actions/multiSrp/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import {
} from './';
import { wordlist } from '@metamask/scure-bip39/dist/wordlists/english';

jest.mock('../../util/Logger');

const mockSetSelectedAddress = jest.fn();
const mockAddNewKeyring = jest.fn();
const mockGetKeyringsByType = jest.fn();
const mockGetAccounts = jest.fn();
const mockAddAccounts = jest.fn();
const mockSetAccountLabel = jest.fn();
const mockControllerMessenger = jest.fn();
const mockAddDiscoveredAccounts = jest.fn();

const hdKeyring = {
getAccounts: () => {
Expand All @@ -28,6 +28,17 @@ const hdKeyring = {
},
};

const mockSnapClient = {
addDiscoveredAccounts: mockAddDiscoveredAccounts,
};

jest.mock('../../core/SnapKeyring/MultichainWalletSnapClient', () => ({
...jest.requireActual('../../core/SnapKeyring/MultichainWalletSnapClient'),
MultichainWalletSnapFactory: {
createClient: () => mockSnapClient,
},
}));

jest.mock('../../core/Engine', () => ({
context: {
KeyringController: {
Expand All @@ -37,10 +48,14 @@ jest.mock('../../core/Engine', () => ({
withKeyring: (_selector: unknown, operation: (args: unknown) => void) =>
operation({ keyring: hdKeyring, metadata: { id: '1234' } }),
},
AccountsController: {
getNextAvailableAccountName: jest.fn().mockReturnValue('Snap Account 1'),
},
},
setSelectedAddress: (address: string) => mockSetSelectedAddress(address),
setAccountLabel: (address: string, label: string) =>
mockSetAccountLabel(address, label),
controllerMessenger: mockControllerMessenger,
}));

jest.mocked(Engine);
Expand All @@ -66,6 +81,7 @@ describe('MultiSRP Actions', () => {
numberOfAccounts: 1,
});
expect(mockSetSelectedAddress).toHaveBeenCalledWith(testAddress);
expect(mockAddDiscoveredAccounts).toHaveBeenCalled();
});

it('throws error if SRP already imported', async () => {
Expand Down
19 changes: 18 additions & 1 deletion app/actions/multiSrp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,16 @@ import { wordlist } from '@metamask/scure-bip39/dist/wordlists/english';
import ExtendedKeyringTypes from '../../constants/keyringTypes';
import Engine from '../../core/Engine';
import { KeyringSelector } from '@metamask/keyring-controller';
import { endPerformanceTrace, startPerformanceTrace } from '../../core/redux/slices/performance';
///: BEGIN:ONLY_INCLUDE_IF(beta)
import {
MultichainWalletSnapFactory,
WalletClientType,
} from '../../core/SnapKeyring/MultichainWalletSnapClient';
///: END:ONLY_INCLUDE_IF
import {
endPerformanceTrace,
startPerformanceTrace,
} from '../../core/redux/slices/performance';
import { PerformanceEventNames } from '../../core/redux/slices/performance/constants';
import { store } from '../../store';

Expand Down Expand Up @@ -55,6 +64,14 @@ export async function importNewSecretRecoveryPhrase(mnemonic: string) {
async ({ keyring }) => keyring.getAccounts(),
);

///: BEGIN:ONLY_INCLUDE_IF(beta)
const multichainClient = MultichainWalletSnapFactory.createClient(
WalletClientType.Solana,
);

await multichainClient.addDiscoveredAccounts(newKeyring.id);
///: END:ONLY_INCLUDE_IF

return Engine.setSelectedAddress(newAccountAddress);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,15 @@ jest.mock('@metamask/keyring-snap-client', () => ({
})),
}));

jest.mock('../../../core/SnapKeyring/SolanaWalletSnap', () => ({
SolanaWalletSnapSender: jest.fn(),
const mockSnapClient = {
createAccount: jest.fn(),
};

jest.mock('../../../core/SnapKeyring/MultichainWalletSnapClient', () => ({
...jest.requireActual('../../../core/SnapKeyring/MultichainWalletSnapClient'),
MultichainWalletSnapFactory: {
createClient: jest.fn().mockImplementation(() => mockSnapClient),
},
}));

const mockNavigate = jest.fn();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ import {
import createStyles from './SolanaNewFeatureContent.styles';
import StorageWrapper from '../../../store/storage-wrapper';
import { SOLANA_FEATURE_MODAL_SHOWN } from '../../../constants/storage';
import { WalletClientType } from '../../../core/SnapKeyring/MultichainWalletSnapClient';
import Engine from '../../../core/Engine';
import { SOLANA_NEW_FEATURE_CONTENT_LEARN_MORE } from '../../../constants/urls';
import Routes from '../../../constants/navigation/Routes';
import { useNavigation } from '@react-navigation/native';
import { WalletClientType } from '../../../core/SnapKeyring/MultichainWalletSnapClient';

const SolanaNewFeatureContent = () => {
const [isVisible, setIsVisible] = useState(false);
Expand Down
4 changes: 4 additions & 0 deletions app/components/Views/AddNewAccount/AddNewAccount.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ const mockMultichainWalletSnapClient = {

jest.mock('../../../core/SnapKeyring/MultichainWalletSnapClient', () => ({
...jest.requireActual('../../../core/SnapKeyring/MultichainWalletSnapClient'),
WalletClientType: {
Bitcoin: 'bitcoin',
Solana: 'solana',
},
MultichainWalletSnapFactory: {
createClient: jest
.fn()
Expand Down
49 changes: 2 additions & 47 deletions app/components/Views/AddNewAccount/AddNewAccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import Icon, {
IconName,
} from '../../../component-library/components/Icons/Icon';
import { strings } from '../../../../locales/i18n';
import Engine from '../../../core/Engine';

// Internal dependencies
import { AddNewAccountProps } from './AddNewAccount.types';
Expand All @@ -36,19 +35,18 @@ import Button, {
} from '../../../component-library/components/Buttons/Button';
import SRPList from '../../UI/SRPList';
import Logger from '../../../util/Logger';
import { KeyringTypes } from '@metamask/keyring-controller';
import { getHdKeyringOfSelectedAccountOrPrimaryKeyring } from '../../../selectors/multisrp';
import {
MultichainWalletSnapFactory,
WalletClientType,
} from '../../../core/SnapKeyring/MultichainWalletSnapClient';
import { MultichainNetwork } from '@metamask/multichain-transactions-controller';
import BottomSheet, {
BottomSheetRef,
} from '../../../component-library/components/BottomSheets/BottomSheet';
import { useNavigation } from '@react-navigation/native';
import Routes from '../../../constants/navigation/Routes';
import { selectInternalAccounts } from '../../../selectors/accountsController';
import { getMultichainAccountName } from '../../../core/SnapKeyring/utils/getMultichainAccountName';

const AddNewAccount = ({ route }: AddNewAccountProps) => {
const { navigate } = useNavigation();
Expand Down Expand Up @@ -117,50 +115,7 @@ const AddNewAccount = ({ route }: AddNewAccountProps) => {
}, [clientType, scope, accountName, keyringId, navigate]);

useEffect(() => {
const nextAvailableAccountName =
Engine.context.AccountsController.getNextAvailableAccountName(
clientType ? KeyringTypes.snap : KeyringTypes.hd,
);
const accountNumber = nextAvailableAccountName.split(' ').pop();

let accountNameToUse = nextAvailableAccountName;
switch (clientType) {
case WalletClientType.Bitcoin: {
if (scope === MultichainNetwork.BitcoinTestnet) {
accountNameToUse = `${strings(
'accounts.labels.bitcoin_testnet_account_name',
)} ${accountNumber}`;
break;
}
accountNameToUse = `${strings(
'accounts.labels.bitcoin_account_name',
)} ${accountNumber}`;
break;
}
case WalletClientType.Solana: {
switch (scope) {
case MultichainNetwork.SolanaDevnet:
accountNameToUse = `${strings(
'accounts.labels.solana_devnet_account_name',
)} ${accountNumber}`;
break;
case MultichainNetwork.SolanaTestnet:
accountNameToUse = `${strings(
'accounts.labels.solana_testnet_account_name',
)} ${accountNumber}`;
break;
default:
accountNameToUse = `${strings(
'accounts.labels.solana_account_name',
)} ${accountNumber}`;
break;
}
break;
}
default:
break;
}
setAccountName(accountNameToUse);
setAccountName(getMultichainAccountName(scope, clientType));
}, [clientType, scope]);

const hdKeyringIndex = useMemo(
Expand Down
47 changes: 46 additions & 1 deletion app/core/Authentication/Authentication.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import AUTHENTICATION_TYPE from '../../constants/userProperties';
import * as Keychain from 'react-native-keychain';
import SecureKeychain from '../SecureKeychain';
import ReduxService, { ReduxStore } from '../redux';

const storage: Record<string, unknown> = {};

jest.mock('../../store/storage-wrapper', () => ({
Expand All @@ -30,6 +29,19 @@ jest.mock('../../store/storage-wrapper', () => ({
}),
}));

const mockSnapClient = {
addDiscoveredAccounts: jest.fn(),
};

jest.mock('../SnapKeyring/MultichainWalletSnapClient', () => ({
MultichainWalletSnapFactory: {
createClient: () => mockSnapClient,
},
WalletClientType: {
Solana: 'solana',
},
}));

describe('Authentication', () => {
afterEach(() => {
StorageWrapper.clearAll();
Expand Down Expand Up @@ -170,4 +182,37 @@ describe('Authentication', () => {
await Authentication.storePassword('1234', AUTHENTICATION_TYPE.UNKNOWN);
expect(methodCalled).toBeTruthy();
});

describe('Multichain - discoverAccounts', () => {
it('calls discoverAccounts after vault creation in newWalletAndKeychain', async () => {
jest.spyOn(ReduxService, 'store', 'get').mockReturnValue({
dispatch: jest.fn(),
getState: () => ({ security: { allowLoginWithRememberMe: true } }),
} as unknown as ReduxStore);
await Authentication.newWalletAndKeychain('1234', {
currentAuthType: AUTHENTICATION_TYPE.UNKNOWN,
});
expect(mockSnapClient.addDiscoveredAccounts).toHaveBeenCalledWith(
expect.any(String), // mock entropySource
);
});

it('calls discoverAccounts in newWalletVaultAndRestore', async () => {
jest.spyOn(ReduxService, 'store', 'get').mockReturnValue({
dispatch: jest.fn(),
getState: () => ({ security: { allowLoginWithRememberMe: true } }),
} as unknown as ReduxStore);
await Authentication.newWalletAndRestore(
'1234',
{
currentAuthType: AUTHENTICATION_TYPE.UNKNOWN,
},
'1234',
false,
);
expect(mockSnapClient.addDiscoveredAccounts).toHaveBeenCalledWith(
expect.any(String), // mock entropySource
);
});
});
});
29 changes: 29 additions & 0 deletions app/core/Authentication/Authentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ import NavigationService from '../NavigationService';
import Routes from '../../constants/navigation/Routes';
import { TraceName, TraceOperation, endTrace, trace } from '../../util/trace';
import ReduxService from '../redux';
///: BEGIN:ONLY_INCLUDE_IF(beta)
import {
MultichainWalletSnapFactory,
WalletClientType,
} from '../SnapKeyring/MultichainWalletSnapClient';
///: END:ONLY_INCLUDE_IF(beta)

/**
* Holds auth data used to determine auth configuration
Expand Down Expand Up @@ -85,6 +91,17 @@ class AuthenticationService {
const { KeyringController }: any = Engine.context;
if (clearEngine) await Engine.resetState();
await KeyringController.createNewVaultAndRestore(password, parsedSeed);
///: BEGIN:ONLY_INCLUDE_IF(beta)
const primaryHdKeyringId =
Engine.context.KeyringController.state.keyringsMetadata[0].id;
const client = MultichainWalletSnapFactory.createClient(
WalletClientType.Solana,
{
setSelectedAccount: false,
},
);
await client.addDiscoveredAccounts(primaryHdKeyringId);
///: END:ONLY_INCLUDE_IF(beta)
password = this.wipeSensitiveData();
parsedSeed = this.wipeSensitiveData();
};
Expand All @@ -101,6 +118,18 @@ class AuthenticationService {
const { KeyringController }: any = Engine.context;
await Engine.resetState();
await KeyringController.createNewVaultAndKeychain(password);

///: BEGIN:ONLY_INCLUDE_IF(beta)
const primaryHdKeyringId =
Engine.context.KeyringController.state.keyringsMetadata[0].id;
const client = MultichainWalletSnapFactory.createClient(
WalletClientType.Solana,
{
setSelectedAccount: false,
},
);
await client.addDiscoveredAccounts(primaryHdKeyringId);
///: END:ONLY_INCLUDE_IF(beta)
password = this.wipeSensitiveData();
};

Expand Down
Loading
Loading