diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json
index f62b15c08c93..e8de6564304b 100644
--- a/app/_locales/de/messages.json
+++ b/app/_locales/de/messages.json
@@ -256,6 +256,9 @@
"addNewBitcoinTestnetAccount": {
"message": "Ein neues Bitcoin-Konto hinzufügen (Testnet)"
},
+ "addNewSolanaAccount": {
+ "message": "Ein neues Solana-Konto hinzufügen (Beta)"
+ },
"addNewToken": {
"message": "Neues Token hinzufügen"
},
diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json
index 3bede7c85edb..5d2ba61516fa 100644
--- a/app/_locales/el/messages.json
+++ b/app/_locales/el/messages.json
@@ -256,6 +256,9 @@
"addNewBitcoinTestnetAccount": {
"message": "Προσθήκη νέου λογαριασμού Bitcoin (Testnet)"
},
+ "addNewSolanaAccount": {
+ "message": "Προσθήκη νέου λογαριασμού Solana (Beta)"
+ },
"addNewToken": {
"message": "Προσθήκη νέου token"
},
diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index 56e3614e3f39..610ddba3a4dd 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -287,6 +287,9 @@
"addNewBitcoinTestnetAccount": {
"message": "Add a new Bitcoin account (Testnet)"
},
+ "addNewSolanaAccount": {
+ "message": "Add a new Solana account (Beta)"
+ },
"addNewToken": {
"message": "Add new token"
},
diff --git a/app/_locales/en_GB/messages.json b/app/_locales/en_GB/messages.json
index 9ee7771947ba..92ad7c646a36 100644
--- a/app/_locales/en_GB/messages.json
+++ b/app/_locales/en_GB/messages.json
@@ -289,6 +289,9 @@
"addNewBitcoinTestnetAccount": {
"message": "Add a new Bitcoin account (Testnet)"
},
+ "addNewSolanaAccount": {
+ "message": "Add a new Solana account (Beta)"
+ },
"addNewToken": {
"message": "Add new token"
},
diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json
index f13313976e74..604396f2c6eb 100644
--- a/app/_locales/es/messages.json
+++ b/app/_locales/es/messages.json
@@ -256,6 +256,9 @@
"addNewBitcoinTestnetAccount": {
"message": "Añadir una nueva cuenta de Bitcoin (Testnet)"
},
+ "addNewSolanaAccount": {
+ "message": "Añadir una nueva cuenta de Solana (Beta)"
+ },
"addNewToken": {
"message": "Agregar nuevo token"
},
diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json
index c785d2e0a00a..7c4db83af9e8 100644
--- a/app/_locales/fr/messages.json
+++ b/app/_locales/fr/messages.json
@@ -256,6 +256,9 @@
"addNewBitcoinTestnetAccount": {
"message": "Ajouter un nouveau compte Bitcoin (Testnet)"
},
+ "addNewSolanaAccount": {
+ "message": "Ajouter un nouveau compte Solana (Bêta)"
+ },
"addNewToken": {
"message": "Ajouter un nouveau jeton"
},
diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json
index 554bb2e91b84..9f3800ef6b61 100644
--- a/app/_locales/hi/messages.json
+++ b/app/_locales/hi/messages.json
@@ -256,6 +256,9 @@
"addNewBitcoinTestnetAccount": {
"message": "एक नया Bitcoin अकाउंट जोड़ें (टैस्टनेट (testnet))"
},
+ "addNewSolanaAccount": {
+ "message": "एक नया Solana अकाउंट जोड़ें (बीटा)"
+ },
"addNewToken": {
"message": "नया टोकन जोड़ें"
},
diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json
index 8e60e6fe5a50..b070fb14cda9 100644
--- a/app/_locales/id/messages.json
+++ b/app/_locales/id/messages.json
@@ -256,6 +256,9 @@
"addNewBitcoinTestnetAccount": {
"message": "Tambahkan akun Bitcoin baru (Testnet)"
},
+ "addNewSolanaAccount": {
+ "message": "Tambahkan akun Solana baru (Beta)"
+ },
"addNewToken": {
"message": "Tambahkan token baru"
},
diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json
index 69824ae33b52..ad77733372b6 100644
--- a/app/_locales/ja/messages.json
+++ b/app/_locales/ja/messages.json
@@ -256,6 +256,9 @@
"addNewBitcoinTestnetAccount": {
"message": "新しいビットコインアカウントの追加 (テストネット)"
},
+ "addNewSolanaAccount": {
+ "message": "新しいSolanaアカウントの追加 (ベータ版)"
+ },
"addNewToken": {
"message": "新しいトークンを追加"
},
diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json
index f160b3dccbc2..86077780926a 100644
--- a/app/_locales/ko/messages.json
+++ b/app/_locales/ko/messages.json
@@ -256,6 +256,9 @@
"addNewBitcoinTestnetAccount": {
"message": "새 비트코인 계정 추가(테스트넷)"
},
+ "addNewSolanaAccount": {
+ "message": "새 솔라나 계정 추가(베타)"
+ },
"addNewToken": {
"message": "신규 토큰 추가"
},
diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json
index 589a58e94907..e868c22a817e 100644
--- a/app/_locales/pt/messages.json
+++ b/app/_locales/pt/messages.json
@@ -256,6 +256,9 @@
"addNewBitcoinTestnetAccount": {
"message": "Adicionar uma nova conta Bitcoin (Testnet)"
},
+ "addNewSolanaAccount": {
+ "message": "Adicionar uma nova conta Solana (Beta)"
+ },
"addNewToken": {
"message": "Adicionar novo token"
},
diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json
index 9462d9c6eb4a..f995ce06a662 100644
--- a/app/_locales/ru/messages.json
+++ b/app/_locales/ru/messages.json
@@ -256,6 +256,9 @@
"addNewBitcoinTestnetAccount": {
"message": "Добавить новый счет в биткойнах (тестнет)"
},
+ "addNewSolanaAccount": {
+ "message": "Добавить новый счет в Solana (бета-версия)"
+ },
"addNewToken": {
"message": "Добавить новый токен"
},
diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json
index 41612a6ce177..f49c14518dc3 100644
--- a/app/_locales/tl/messages.json
+++ b/app/_locales/tl/messages.json
@@ -256,6 +256,9 @@
"addNewBitcoinTestnetAccount": {
"message": "Magdagdag ng bagong account sa Bitcoin (Testnet)"
},
+ "addNewSolanaAccount": {
+ "message": "Magdagdag ng bagong account sa Solana (Beta)"
+ },
"addNewToken": {
"message": "Magdagdag ng bagong token"
},
diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json
index f11cc0d17523..7af54fd3eedd 100644
--- a/app/_locales/tr/messages.json
+++ b/app/_locales/tr/messages.json
@@ -256,6 +256,9 @@
"addNewBitcoinTestnetAccount": {
"message": "Yeni bir Bitcoin hesabı ekle (Test Ağı)"
},
+ "addNewSolanaAccount": {
+ "message": "Yeni bir Solana hesabı ekle (Beta)"
+ },
"addNewToken": {
"message": "Yeni token ekleyin"
},
diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json
index dd518df1bf22..700d73f11649 100644
--- a/app/_locales/vi/messages.json
+++ b/app/_locales/vi/messages.json
@@ -256,6 +256,9 @@
"addNewBitcoinTestnetAccount": {
"message": "Thêm tài khoản Bitcoin mới (Mạng thử nghiệm)"
},
+ "addNewSolanaAccount": {
+ "message": "Thêm tài khoản Solana mới (Beta)"
+ },
"addNewToken": {
"message": "Thêm token mới"
},
diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json
index 818f1ffdb82b..a24b993874a0 100644
--- a/app/_locales/zh_CN/messages.json
+++ b/app/_locales/zh_CN/messages.json
@@ -256,6 +256,9 @@
"addNewBitcoinTestnetAccount": {
"message": "添加新的比特币账户(测试网)"
},
+ "addNewSolanaAccount": {
+ "message": "添加新的Solana账户(测试版)"
+ },
"addNewToken": {
"message": "添加新代币"
},
diff --git a/test/e2e/constants.ts b/test/e2e/constants.ts
index 8bf39d261bcb..a1388b93a512 100644
--- a/test/e2e/constants.ts
+++ b/test/e2e/constants.ts
@@ -50,3 +50,10 @@ export const DEFAULT_BTC_ACCOUNT = 'bc1qg6whd6pc0cguh6gpp3ewujm53hv32ta9hdp252';
/* Default (mocked) BTC balance used by the Bitcoin RPC provider */
export const DEFAULT_BTC_BALANCE = 1; // BTC
+
+/* Default (mocked) SOLANA address created using test SRP */
+export const DEFAULT_SOLANA_ACCOUNT =
+ 'E6Aa9DDv7zsePJHosoqiNb3cFuup3fkXTyRH2pZ1nVzP';
+
+/* Default (mocked) SOLANA balance used by the Solana RPC provider */
+export const DEFAULT_SOLANA_BALANCE = 1; // SOL
diff --git a/ui/components/multichain/account-list-menu/account-list-menu.tsx b/ui/components/multichain/account-list-menu/account-list-menu.tsx
index cfb49d246ca6..75d04e9e60e0 100644
--- a/ui/components/multichain/account-list-menu/account-list-menu.tsx
+++ b/ui/components/multichain/account-list-menu/account-list-menu.tsx
@@ -68,6 +68,9 @@ import {
getOriginOfCurrentTab,
getSelectedInternalAccount,
getUpdatedAndSortedAccounts,
+ ///: BEGIN:ONLY_INCLUDE_IF(solana)
+ getIsSolanaSupportEnabled,
+ ///: END:ONLY_INCLUDE_IF
} from '../../../selectors';
import { setSelectedAccount } from '../../../store/actions';
import {
@@ -97,8 +100,14 @@ import {
hasCreatedBtcMainnetAccount,
hasCreatedBtcTestnetAccount,
} from '../../../selectors/accounts';
+///: END:ONLY_INCLUDE_IF
+
+///: BEGIN:ONLY_INCLUDE_IF(build-flask,solana)
import { MultichainNetworks } from '../../../../shared/constants/multichain/networks';
-import { useBitcoinWalletSnapClient } from '../../../hooks/accounts/useBitcoinWalletSnapClient';
+import {
+ WalletClientType,
+ useMultichainWalletSnapClient,
+} from '../../../hooks/accounts/useMultichainWalletSnapClient';
///: END:ONLY_INCLUDE_IF
import {
InternalAccountWithBalance,
@@ -106,6 +115,12 @@ import {
MergedInternalAccount,
} from '../../../selectors/selectors.types';
import { endTrace, TraceName } from '../../../../shared/lib/trace';
+///: BEGIN:ONLY_INCLUDE_IF(solana)
+import {
+ SOLANA_WALLET_NAME,
+ SOLANA_WALLET_SNAP_ID,
+} from '../../../../shared/lib/accounts/solana-wallet-snap';
+///: END:ONLY_INCLUDE_IF
import { HiddenAccountList } from './hidden-account-list';
const ACTION_MODES = {
@@ -269,7 +284,16 @@ export const AccountListMenu = ({
hasCreatedBtcTestnetAccount,
);
- const bitcoinWalletSnapClient = useBitcoinWalletSnapClient();
+ const bitcoinWalletSnapClient = useMultichainWalletSnapClient(
+ WalletClientType.Bitcoin,
+ );
+ ///: END:ONLY_INCLUDE_IF
+
+ ///: BEGIN:ONLY_INCLUDE_IF(solana)
+ const solanaSupportEnabled = useSelector(getIsSolanaSupportEnabled);
+ const solanaWalletSnapClient = useMultichainWalletSnapClient(
+ WalletClientType.Solana,
+ );
///: END:ONLY_INCLUDE_IF
const [searchQuery, setSearchQuery] = useState('');
@@ -453,6 +477,42 @@ export const AccountListMenu = ({
) : null
///: END:ONLY_INCLUDE_IF
}
+ {
+ ///: BEGIN:ONLY_INCLUDE_IF(solana)
+ solanaSupportEnabled && (
+
+ {
+ trackEvent({
+ category: MetaMetricsEventCategory.Navigation,
+ event: MetaMetricsEventName.AccountAddSelected,
+ properties: {
+ account_type: MetaMetricsEventAccountType.Snap,
+ snap_id: SOLANA_WALLET_SNAP_ID,
+ snap_name: SOLANA_WALLET_NAME,
+ location: 'Main Menu',
+ },
+ });
+
+ // The account creation + renaming is handled by the
+ // Snap account bridge, so we need to close the current
+ // modal
+ onClose();
+
+ await solanaWalletSnapClient.createAccount(
+ MultichainNetworks.SOLANA,
+ );
+ }}
+ data-testid="multichain-account-menu-popover-add-solana-account"
+ >
+ {t('addNewSolanaAccount')}
+
+
+ )
+ ///: END:ONLY_INCLUDE_IF
+ }
({
- handleSnapRequest: jest.fn(),
- multichainUpdateBalance: jest.fn(),
-}));
-
-const mockHandleSnapRequest = handleSnapRequest as jest.Mock;
-const mockMultichainUpdateBalance = multichainUpdateBalance as jest.Mock;
-
-describe('useBitcoinWalletSnapClient', () => {
- beforeEach(() => {
- jest.clearAllMocks();
- });
-
- const mockAccount = {
- address: 'tb1q2hjrlnf8kmtt5dj6e49gqzy6jnpe0sj7ty50cl',
- id: '11a33c6b-0d46-43f4-a401-01587d575fd0',
- options: {},
- methods: [BtcMethod.SendMany],
- type: BtcAccountType.P2wpkh,
- };
-
- it('dispatch a Snap keyring request to create a Bitcoin account', async () => {
- const { result } = renderHook(() => useBitcoinWalletSnapClient());
- const bitcoinWalletSnapClient = result.current;
-
- mockHandleSnapRequest.mockResolvedValue(mockAccount);
-
- await bitcoinWalletSnapClient.createAccount(MultichainNetworks.BITCOIN);
- expect(mockHandleSnapRequest).toHaveBeenCalledWith({
- origin: 'metamask',
- snapId: BITCOIN_WALLET_SNAP_ID,
- handler: HandlerType.OnKeyringRequest,
- request: expect.any(Object),
- });
- });
-
- it('force fetches the balance after creating a Bitcoin account', async () => {
- const { result } = renderHook(() => useBitcoinWalletSnapClient());
- const bitcoinWalletSnapClient = result.current;
-
- mockHandleSnapRequest.mockResolvedValue(mockAccount);
-
- await bitcoinWalletSnapClient.createAccount(MultichainNetworks.BITCOIN);
- expect(mockMultichainUpdateBalance).toHaveBeenCalledWith(mockAccount.id);
- });
-});
diff --git a/ui/hooks/accounts/useMultichainWalletSnapClient.test.ts b/ui/hooks/accounts/useMultichainWalletSnapClient.test.ts
new file mode 100644
index 000000000000..d177986fee44
--- /dev/null
+++ b/ui/hooks/accounts/useMultichainWalletSnapClient.test.ts
@@ -0,0 +1,87 @@
+import { renderHook } from '@testing-library/react-hooks';
+import { HandlerType } from '@metamask/snaps-utils';
+import { BtcAccountType, BtcMethod } from '@metamask/keyring-api';
+import { MultichainNetworks } from '../../../shared/constants/multichain/networks';
+import { BITCOIN_WALLET_SNAP_ID } from '../../../shared/lib/accounts/bitcoin-wallet-snap';
+import { SOLANA_WALLET_SNAP_ID } from '../../../shared/lib/accounts/solana-wallet-snap';
+import {
+ handleSnapRequest,
+ multichainUpdateBalance,
+} from '../../store/actions';
+import {
+ useMultichainWalletSnapClient,
+ WalletClientType,
+} from './useMultichainWalletSnapClient';
+
+jest.mock('../../store/actions', () => ({
+ handleSnapRequest: jest.fn(),
+ multichainUpdateBalance: jest.fn(),
+}));
+
+const mockHandleSnapRequest = handleSnapRequest as jest.Mock;
+const mockMultichainUpdateBalance = multichainUpdateBalance as jest.Mock;
+
+describe('useMultichainWalletSnapClient', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ const testCases = [
+ {
+ clientType: WalletClientType.Bitcoin,
+ network: MultichainNetworks.BITCOIN,
+ snapId: BITCOIN_WALLET_SNAP_ID,
+ mockAccount: {
+ address: 'tb1q2hjrlnf8kmtt5dj6e49gqzy6jnpe0sj7ty50cl',
+ id: '11a33c6b-0d46-43f4-a401-01587d575fd0',
+ options: {},
+ methods: [BtcMethod.SendMany],
+ type: BtcAccountType.P2wpkh,
+ },
+ },
+ {
+ clientType: WalletClientType.Solana,
+ network: MultichainNetworks.SOLANA,
+ snapId: SOLANA_WALLET_SNAP_ID,
+ mockAccount: {
+ address: '4mip4tgbhxf8dpqvtb3zhzzapwfvznanhssqzgjyp7ha',
+ id: '22b44d7c-1e57-4b5b-8502-02698e686fd1',
+ options: {},
+ methods: ['someMethod'],
+ // TODO: Update when keyring-api is published with Solana types
+ type: BtcAccountType.P2wpkh,
+ },
+ },
+ ];
+
+ testCases.forEach(({ clientType, network, snapId, mockAccount }) => {
+ it(`dispatches a Snap keyring request to create a ${clientType} account`, async () => {
+ const { result } = renderHook(() =>
+ useMultichainWalletSnapClient(clientType),
+ );
+ const multichainWalletSnapClient = result.current;
+
+ mockHandleSnapRequest.mockResolvedValue(mockAccount);
+
+ await multichainWalletSnapClient.createAccount(network);
+ expect(mockHandleSnapRequest).toHaveBeenCalledWith({
+ origin: 'metamask',
+ snapId,
+ handler: HandlerType.OnKeyringRequest,
+ request: expect.any(Object),
+ });
+ });
+
+ it(`force fetches the balance after creating a ${clientType} account`, async () => {
+ const { result } = renderHook(() =>
+ useMultichainWalletSnapClient(clientType),
+ );
+ const multichainWalletSnapClient = result.current;
+
+ mockHandleSnapRequest.mockResolvedValue(mockAccount);
+
+ await multichainWalletSnapClient.createAccount(network);
+ expect(mockMultichainUpdateBalance).toHaveBeenCalledWith(mockAccount.id);
+ });
+ });
+});
diff --git a/ui/hooks/accounts/useBitcoinWalletSnapClient.ts b/ui/hooks/accounts/useMultichainWalletSnapClient.ts
similarity index 60%
rename from ui/hooks/accounts/useBitcoinWalletSnapClient.ts
rename to ui/hooks/accounts/useMultichainWalletSnapClient.ts
index debe911ac391..98dfa9b429d3 100644
--- a/ui/hooks/accounts/useBitcoinWalletSnapClient.ts
+++ b/ui/hooks/accounts/useMultichainWalletSnapClient.ts
@@ -1,32 +1,54 @@
import { KeyringClient, Sender } from '@metamask/keyring-api';
import { HandlerType } from '@metamask/snaps-utils';
import { CaipChainId, Json, JsonRpcRequest } from '@metamask/utils';
+import { SnapId } from '@metamask/snaps-sdk';
import { useMemo } from 'react';
import {
handleSnapRequest,
multichainUpdateBalance,
} from '../../store/actions';
import { BITCOIN_WALLET_SNAP_ID } from '../../../shared/lib/accounts/bitcoin-wallet-snap';
+import { SOLANA_WALLET_SNAP_ID } from '../../../shared/lib/accounts/solana-wallet-snap';
+
+export enum WalletClientType {
+ Bitcoin = 'bitcoin-wallet-snap',
+ Solana = 'solana-wallet-snap',
+}
+
+const SNAP_ID_MAP: Record = {
+ [WalletClientType.Bitcoin]: BITCOIN_WALLET_SNAP_ID,
+ [WalletClientType.Solana]: SOLANA_WALLET_SNAP_ID,
+};
+
+export class MultichainWalletSnapSender implements Sender {
+ private snapId: SnapId;
+
+ constructor(snapId: SnapId) {
+ this.snapId = snapId;
+ }
-export class BitcoinWalletSnapSender implements Sender {
send = async (request: JsonRpcRequest): Promise => {
// We assume the caller of this module is aware of this. If we try to use this module
// without having the pre-installed Snap, this will likely throw an error in
// the `handleSnapRequest` action.
return (await handleSnapRequest({
origin: 'metamask',
- snapId: BITCOIN_WALLET_SNAP_ID,
+ snapId: this.snapId,
handler: HandlerType.OnKeyringRequest,
request,
})) as Json;
};
}
-export class BitcoinWalletSnapClient {
+export class MultichainWalletSnapClient {
readonly #client: KeyringClient;
- constructor() {
- this.#client = new KeyringClient(new BitcoinWalletSnapSender());
+ constructor(clientType: WalletClientType) {
+ const snapId = SNAP_ID_MAP[clientType];
+ if (!snapId) {
+ throw new Error(`Unsupported client type: ${clientType}`);
+ }
+ this.#client = new KeyringClient(new MultichainWalletSnapSender(snapId));
}
async createAccount(scope: CaipChainId) {
@@ -43,10 +65,10 @@ export class BitcoinWalletSnapClient {
}
}
-export function useBitcoinWalletSnapClient() {
+export function useMultichainWalletSnapClient(clientType: WalletClientType) {
const client = useMemo(() => {
- return new BitcoinWalletSnapClient();
- }, []);
+ return new MultichainWalletSnapClient(clientType);
+ }, [clientType]);
return client;
}