Skip to content

Commit 852f7e0

Browse files
feat: Add AccountSelector component (#3088)
This PR adds a new component called `AccountSelector`. This component uses the account list of the client to display and select an account. The list can be filtered via the component props. Progresses: #2810 Related extension PR: MetaMask/metamask-extension#30323 --------- Co-authored-by: Frederik Bolding <[email protected]>
1 parent cdb36fa commit 852f7e0

File tree

25 files changed

+1580
-41
lines changed

25 files changed

+1580
-41
lines changed

packages/examples/packages/dialogs/snap.manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"url": "https://github.com/MetaMask/snaps.git"
88
},
99
"source": {
10-
"shasum": "uCUq9PpdqnbX61m+h7IfPWoZafKw3gsb6dvSLJse63k=",
10+
"shasum": "vvzCJ8B00GXoZDq0/ZeMzguNiPRmim1qcbD27rbYH8o=",
1111
"location": {
1212
"npm": {
1313
"filePath": "dist/bundle.js",

packages/examples/packages/file-upload/snap.manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"url": "https://github.com/MetaMask/snaps.git"
88
},
99
"source": {
10-
"shasum": "4k6xK8xISA7UOVam45VhyZbIsnivQetJf+zYSliy0uE=",
10+
"shasum": "YOWrI9oMzaIWvmV9LgcN26us5sYglQDbBX07W6XljnY=",
1111
"location": {
1212
"npm": {
1313
"filePath": "dist/bundle.js",

packages/examples/packages/home-page/snap.manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"url": "https://github.com/MetaMask/snaps.git"
88
},
99
"source": {
10-
"shasum": "XMyDMaJ0vTcAHOZLQ47QaJ5vvpJZ7gdJ1prS96WuF5A=",
10+
"shasum": "z5wfUL3L05s9oQcF9DqPC51v52oJAMm6xKSg1G+Oybw=",
1111
"location": {
1212
"npm": {
1313
"filePath": "dist/bundle.js",

packages/examples/packages/interactive-ui/snap.manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"url": "https://github.com/MetaMask/snaps.git"
88
},
99
"source": {
10-
"shasum": "drB7P0k6b6CJojJ2jGPD7WIaDQBSIXIn0+0DeK+8GMQ=",
10+
"shasum": "f5KnTUI4ZIwhE+lJrFhfiOQfx1ZmtoFnkmzKKcPXEdo=",
1111
"location": {
1212
"npm": {
1313
"filePath": "dist/bundle.js",

packages/examples/packages/jsx/snap.manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"url": "https://github.com/MetaMask/snaps.git"
88
},
99
"source": {
10-
"shasum": "19gK/VOwtR4It+Okzr3egNGj0+rg3LdI1Up9iXxFAKE=",
10+
"shasum": "9qEMnlcHK3ETO9tVCsjN7c6NGvAPJJHZmPC/Mcp4TIs=",
1111
"location": {
1212
"npm": {
1313
"filePath": "dist/bundle.js",

packages/examples/packages/preinstalled/snap.manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"url": "https://github.com/MetaMask/snaps.git"
88
},
99
"source": {
10-
"shasum": "I6UW3+TF1AWBF62h/1NiJNuKG1IA2R/R3o3xHzJCdrA=",
10+
"shasum": "tUcUWcyZ9IOY/BYxXSrTfMFcn96KBE2UUNTBVahNT/Y=",
1111
"location": {
1212
"npm": {
1313
"filePath": "dist/bundle.js",

packages/examples/packages/send-flow/snap.manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"url": "https://github.com/MetaMask/snaps.git"
88
},
99
"source": {
10-
"shasum": "6lChQQfDqYhY5N/FRsogbHI8+a4eWJuojcRS2yRi66Q=",
10+
"shasum": "CJT5E1MntaPZkK6LBvAayvsc0q1AVGVVMRGh0gkxRHY=",
1111
"location": {
1212
"npm": {
1313
"filePath": "dist/bundle.js",
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"branches": 94.97,
3-
"functions": 98.38,
4-
"lines": 98.76,
5-
"statements": 98.59
2+
"branches": 95.14,
3+
"functions": 98.43,
4+
"lines": 98.79,
5+
"statements": 98.62
66
}

packages/snaps-controllers/src/interface/SnapInterfaceController.test.tsx

Lines changed: 297 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
} from '@metamask/snaps-sdk';
1111
import {
1212
AssetSelector,
13+
AccountSelector,
1314
Box,
1415
Field,
1516
FileInput,
@@ -27,6 +28,7 @@ import { MOCK_SNAP_ID } from '@metamask/snaps-utils/test-utils';
2728

2829
import { SnapInterfaceController } from './SnapInterfaceController';
2930
import {
31+
MOCK_ACCOUNT_ID,
3032
MockApprovalController,
3133
getRestrictedSnapInterfaceControllerMessenger,
3234
getRootSnapInterfaceControllerMessenger,
@@ -243,7 +245,213 @@ describe('SnapInterfaceController', () => {
243245
);
244246

245247
expect(content).toStrictEqual(element);
246-
expect(state).toStrictEqual({ foo: { bar: null } });
248+
expect(state).toStrictEqual({
249+
foo: {
250+
bar: null,
251+
},
252+
});
253+
});
254+
255+
it('can retrieve the selected account from the client', async () => {
256+
const rootMessenger = getRootSnapInterfaceControllerMessenger();
257+
const controllerMessenger =
258+
getRestrictedSnapInterfaceControllerMessenger(rootMessenger);
259+
260+
// eslint-disable-next-line no-new
261+
new SnapInterfaceController({
262+
messenger: controllerMessenger,
263+
});
264+
265+
const element = (
266+
<Box>
267+
<AccountSelector name="foo" />
268+
</Box>
269+
);
270+
271+
const id = await rootMessenger.call(
272+
'SnapInterfaceController:createInterface',
273+
MOCK_SNAP_ID,
274+
element,
275+
);
276+
277+
const { content, state } = rootMessenger.call(
278+
'SnapInterfaceController:getInterface',
279+
MOCK_SNAP_ID,
280+
id,
281+
);
282+
283+
expect(rootMessenger.call).toHaveBeenNthCalledWith(
284+
2,
285+
'AccountsController:getSelectedMultichainAccount',
286+
);
287+
288+
expect(content).toStrictEqual(element);
289+
expect(state).toStrictEqual({
290+
foo: {
291+
accountId: MOCK_ACCOUNT_ID,
292+
addresses: ['eip155:0:0x1234567890123456789012345678901234567890'],
293+
},
294+
});
295+
});
296+
297+
it('can select an account owned by the snap', async () => {
298+
const rootMessenger = getRootSnapInterfaceControllerMessenger();
299+
const controllerMessenger = getRestrictedSnapInterfaceControllerMessenger(
300+
rootMessenger,
301+
false,
302+
);
303+
304+
rootMessenger.registerActionHandler(
305+
'AccountsController:getSelectedMultichainAccount',
306+
() => ({
307+
id: MOCK_ACCOUNT_ID,
308+
address: '0x1234567890123456789012345678901234567890',
309+
scopes: ['eip155:0'],
310+
metadata: {
311+
// @ts-expect-error partial mock
312+
snap: {
313+
id: 'npm:[email protected]' as SnapId,
314+
},
315+
},
316+
}),
317+
);
318+
319+
rootMessenger.registerActionHandler(
320+
'AccountsController:listMultichainAccounts',
321+
() => [
322+
{
323+
id: MOCK_ACCOUNT_ID,
324+
address: '7S3P4HxJpyyigGzodYwHtCxZyUQe9JiBMHyRWXArAaKv',
325+
scopes: ['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'],
326+
metadata: {
327+
// @ts-expect-error partial mock
328+
snap: {
329+
id: MOCK_SNAP_ID,
330+
},
331+
},
332+
},
333+
],
334+
);
335+
336+
// eslint-disable-next-line no-new
337+
new SnapInterfaceController({
338+
messenger: controllerMessenger,
339+
});
340+
341+
const element = (
342+
<Box>
343+
<AccountSelector name="foo" hideExternalAccounts />
344+
</Box>
345+
);
346+
347+
const id = await rootMessenger.call(
348+
'SnapInterfaceController:createInterface',
349+
MOCK_SNAP_ID,
350+
element,
351+
);
352+
353+
const { content, state } = rootMessenger.call(
354+
'SnapInterfaceController:getInterface',
355+
MOCK_SNAP_ID,
356+
id,
357+
);
358+
359+
expect(rootMessenger.call).toHaveBeenNthCalledWith(
360+
2,
361+
'AccountsController:getSelectedMultichainAccount',
362+
);
363+
364+
expect(rootMessenger.call).toHaveBeenNthCalledWith(
365+
3,
366+
'AccountsController:listMultichainAccounts',
367+
);
368+
369+
expect(content).toStrictEqual(element);
370+
expect(state).toStrictEqual({
371+
foo: {
372+
accountId: MOCK_ACCOUNT_ID,
373+
addresses: [
374+
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:7S3P4HxJpyyigGzodYwHtCxZyUQe9JiBMHyRWXArAaKv',
375+
],
376+
},
377+
});
378+
});
379+
380+
it('can get accounts of a specific chain ID from the client', async () => {
381+
const rootMessenger = getRootSnapInterfaceControllerMessenger();
382+
const controllerMessenger = getRestrictedSnapInterfaceControllerMessenger(
383+
rootMessenger,
384+
false,
385+
);
386+
387+
rootMessenger.registerActionHandler(
388+
'AccountsController:getSelectedMultichainAccount',
389+
// @ts-expect-error partial mock
390+
() => ({
391+
id: MOCK_ACCOUNT_ID,
392+
address: '0x1234567890123456789012345678901234567890',
393+
scopes: ['eip155:0'],
394+
}),
395+
);
396+
397+
rootMessenger.registerActionHandler(
398+
'AccountsController:listMultichainAccounts',
399+
() => [
400+
// @ts-expect-error partial mock
401+
{
402+
id: MOCK_ACCOUNT_ID,
403+
address: '7S3P4HxJpyyigGzodYwHtCxZyUQe9JiBMHyRWXArAaKv',
404+
scopes: ['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'],
405+
},
406+
],
407+
);
408+
409+
// eslint-disable-next-line no-new
410+
new SnapInterfaceController({
411+
messenger: controllerMessenger,
412+
});
413+
414+
const element = (
415+
<Box>
416+
<AccountSelector
417+
name="foo"
418+
switchGlobalAccount
419+
chainIds={['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp']}
420+
/>
421+
</Box>
422+
);
423+
424+
const id = await rootMessenger.call(
425+
'SnapInterfaceController:createInterface',
426+
MOCK_SNAP_ID,
427+
element,
428+
);
429+
430+
const { content, state } = rootMessenger.call(
431+
'SnapInterfaceController:getInterface',
432+
MOCK_SNAP_ID,
433+
id,
434+
);
435+
436+
expect(rootMessenger.call).toHaveBeenNthCalledWith(
437+
2,
438+
'AccountsController:getSelectedMultichainAccount',
439+
);
440+
441+
expect(rootMessenger.call).toHaveBeenNthCalledWith(
442+
3,
443+
'AccountsController:listMultichainAccounts',
444+
);
445+
446+
expect(content).toStrictEqual(element);
447+
expect(state).toStrictEqual({
448+
foo: {
449+
accountId: MOCK_ACCOUNT_ID,
450+
addresses: [
451+
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:7S3P4HxJpyyigGzodYwHtCxZyUQe9JiBMHyRWXArAaKv',
452+
],
453+
},
454+
});
247455
});
248456

249457
it('supports providing interface context', async () => {
@@ -1164,6 +1372,94 @@ describe('SnapInterfaceController', () => {
11641372
),
11651373
).rejects.toThrow('Interface not created by foo.');
11661374
});
1375+
1376+
it('can select an account owned by the snap', async () => {
1377+
const rootMessenger = getRootSnapInterfaceControllerMessenger();
1378+
const controllerMessenger = getRestrictedSnapInterfaceControllerMessenger(
1379+
rootMessenger,
1380+
false,
1381+
);
1382+
1383+
rootMessenger.registerActionHandler(
1384+
'AccountsController:getSelectedMultichainAccount',
1385+
() => ({
1386+
id: MOCK_ACCOUNT_ID,
1387+
address: '0x1234567890123456789012345678901234567890',
1388+
scopes: ['eip155:0'],
1389+
metadata: {
1390+
// @ts-expect-error partial mock
1391+
snap: {
1392+
id: 'npm:[email protected]' as SnapId,
1393+
},
1394+
},
1395+
}),
1396+
);
1397+
1398+
rootMessenger.registerActionHandler(
1399+
'AccountsController:getAccountByAddress',
1400+
() => ({
1401+
id: MOCK_ACCOUNT_ID,
1402+
address: '7S3P4HxJpyyigGzodYwHtCxZyUQe9JiBMHyRWXArAaKv',
1403+
scopes: ['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'],
1404+
metadata: {
1405+
// @ts-expect-error partial mock
1406+
snap: {
1407+
id: MOCK_SNAP_ID,
1408+
},
1409+
},
1410+
}),
1411+
);
1412+
1413+
// eslint-disable-next-line no-new
1414+
new SnapInterfaceController({
1415+
messenger: controllerMessenger,
1416+
});
1417+
1418+
const element = (
1419+
<Box>
1420+
<AccountSelector name="foo" />
1421+
</Box>
1422+
);
1423+
1424+
const newElement = (
1425+
<Box>
1426+
<AccountSelector
1427+
name="foo"
1428+
hideExternalAccounts
1429+
value="solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:7S3P4HxJpyyigGzodYwHtCxZyUQe9JiBMHyRWXArAaKv"
1430+
/>
1431+
</Box>
1432+
);
1433+
1434+
const id = await rootMessenger.call(
1435+
'SnapInterfaceController:createInterface',
1436+
MOCK_SNAP_ID,
1437+
element,
1438+
);
1439+
1440+
await rootMessenger.call(
1441+
'SnapInterfaceController:updateInterface',
1442+
MOCK_SNAP_ID,
1443+
id,
1444+
newElement,
1445+
);
1446+
1447+
const { content, state } = rootMessenger.call(
1448+
'SnapInterfaceController:getInterface',
1449+
MOCK_SNAP_ID,
1450+
id,
1451+
);
1452+
1453+
expect(content).toStrictEqual(newElement);
1454+
expect(state).toStrictEqual({
1455+
foo: {
1456+
accountId: MOCK_ACCOUNT_ID,
1457+
addresses: [
1458+
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:7S3P4HxJpyyigGzodYwHtCxZyUQe9JiBMHyRWXArAaKv',
1459+
],
1460+
},
1461+
});
1462+
});
11671463
});
11681464

11691465
describe('updateInterfaceState', () => {

0 commit comments

Comments
 (0)