Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
16d5acc
[WIP] `AccountSelector`
GuillaumeRx Feb 3, 2025
a7c9234
rebase
GuillaumeRx Feb 11, 2025
c9578e7
finish tests and fix bugs
GuillaumeRx Feb 12, 2025
1d2a04e
move function to `snap-utils` and add JSDocs
GuillaumeRx Feb 12, 2025
3fd96f4
address requested changes
GuillaumeRx Feb 13, 2025
c968ab3
move `keyring-internal-api` to regular deps
GuillaumeRx Feb 13, 2025
9b98118
rebase
GuillaumeRx Mar 27, 2025
b7b8628
[WIP] update
GuillaumeRx Mar 31, 2025
d1b7b6a
improve logic
GuillaumeRx Apr 9, 2025
6ea26db
add missing JSDoc
GuillaumeRx Apr 9, 2025
e8633ac
[WIP] Add support for `hideExternalAccounts`
GuillaumeRx Apr 15, 2025
9c34381
support `hideExternalAccounts`
GuillaumeRx May 20, 2025
c56d255
cleanup
GuillaumeRx May 20, 2025
d2a05c8
address requested changes
GuillaumeRx May 21, 2025
70411d0
update manifests and coverage after rebase
GuillaumeRx May 21, 2025
5e2980c
address requested changes
GuillaumeRx May 21, 2025
ed3d2e9
update `listAccounts` method, remove useless test and add comments
GuillaumeRx May 22, 2025
6a7c8d2
update coverage
GuillaumeRx May 22, 2025
e3f4119
apply suggested changes
GuillaumeRx May 22, 2025
b845989
update `snaps-utils` coverage
GuillaumeRx May 22, 2025
e132af9
remove duplicated type
GuillaumeRx May 22, 2025
d3ca8c6
Update packages/snaps-controllers/src/interface/utils.test.tsx
GuillaumeRx May 22, 2025
1f8a9ca
Update packages/snaps-controllers/src/interface/utils.test.tsx
GuillaumeRx May 22, 2025
aad70f4
Update packages/snaps-controllers/src/interface/utils.test.tsx
GuillaumeRx May 22, 2025
8a7e387
return `null` instead of throwing if no accounts are found
GuillaumeRx May 22, 2025
c3aec1c
update coverage after rebase
GuillaumeRx May 22, 2025
0fb7cd2
update test names
GuillaumeRx May 22, 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
2 changes: 1 addition & 1 deletion packages/examples/packages/dialogs/snap.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
"shasum": "uCUq9PpdqnbX61m+h7IfPWoZafKw3gsb6dvSLJse63k=",
"shasum": "vvzCJ8B00GXoZDq0/ZeMzguNiPRmim1qcbD27rbYH8o=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
"shasum": "4k6xK8xISA7UOVam45VhyZbIsnivQetJf+zYSliy0uE=",
"shasum": "YOWrI9oMzaIWvmV9LgcN26us5sYglQDbBX07W6XljnY=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
2 changes: 1 addition & 1 deletion packages/examples/packages/home-page/snap.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
"shasum": "XMyDMaJ0vTcAHOZLQ47QaJ5vvpJZ7gdJ1prS96WuF5A=",
"shasum": "z5wfUL3L05s9oQcF9DqPC51v52oJAMm6xKSg1G+Oybw=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
"shasum": "drB7P0k6b6CJojJ2jGPD7WIaDQBSIXIn0+0DeK+8GMQ=",
"shasum": "f5KnTUI4ZIwhE+lJrFhfiOQfx1ZmtoFnkmzKKcPXEdo=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
2 changes: 1 addition & 1 deletion packages/examples/packages/jsx/snap.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
"shasum": "19gK/VOwtR4It+Okzr3egNGj0+rg3LdI1Up9iXxFAKE=",
"shasum": "9qEMnlcHK3ETO9tVCsjN7c6NGvAPJJHZmPC/Mcp4TIs=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
"shasum": "I6UW3+TF1AWBF62h/1NiJNuKG1IA2R/R3o3xHzJCdrA=",
"shasum": "tUcUWcyZ9IOY/BYxXSrTfMFcn96KBE2UUNTBVahNT/Y=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
2 changes: 1 addition & 1 deletion packages/examples/packages/send-flow/snap.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
"shasum": "6lChQQfDqYhY5N/FRsogbHI8+a4eWJuojcRS2yRi66Q=",
"shasum": "CJT5E1MntaPZkK6LBvAayvsc0q1AVGVVMRGh0gkxRHY=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
8 changes: 4 additions & 4 deletions packages/snaps-controllers/coverage.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"branches": 94.97,
"functions": 98.38,
"lines": 98.76,
"statements": 98.59
"branches": 95.14,
"functions": 98.43,
"lines": 98.79,
"statements": 98.62
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from '@metamask/snaps-sdk';
import {
AssetSelector,
AccountSelector,
Box,
Field,
FileInput,
Expand All @@ -27,6 +28,7 @@ import { MOCK_SNAP_ID } from '@metamask/snaps-utils/test-utils';

import { SnapInterfaceController } from './SnapInterfaceController';
import {
MOCK_ACCOUNT_ID,
MockApprovalController,
getRestrictedSnapInterfaceControllerMessenger,
getRootSnapInterfaceControllerMessenger,
Expand Down Expand Up @@ -243,7 +245,213 @@ describe('SnapInterfaceController', () => {
);

expect(content).toStrictEqual(element);
expect(state).toStrictEqual({ foo: { bar: null } });
expect(state).toStrictEqual({
foo: {
bar: null,
},
});
});

it('can retrieve the selected account from the client', async () => {
const rootMessenger = getRootSnapInterfaceControllerMessenger();
const controllerMessenger =
getRestrictedSnapInterfaceControllerMessenger(rootMessenger);

// eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});

const element = (
<Box>
<AccountSelector name="foo" />
</Box>
);

const id = await rootMessenger.call(
'SnapInterfaceController:createInterface',
MOCK_SNAP_ID,
element,
);

const { content, state } = rootMessenger.call(
'SnapInterfaceController:getInterface',
MOCK_SNAP_ID,
id,
);

expect(rootMessenger.call).toHaveBeenNthCalledWith(
2,
'AccountsController:getSelectedMultichainAccount',
);

expect(content).toStrictEqual(element);
expect(state).toStrictEqual({
foo: {
accountId: MOCK_ACCOUNT_ID,
addresses: ['eip155:0:0x1234567890123456789012345678901234567890'],
},
});
});

it('can select an account owned by the snap', async () => {
const rootMessenger = getRootSnapInterfaceControllerMessenger();
const controllerMessenger = getRestrictedSnapInterfaceControllerMessenger(
rootMessenger,
false,
);

rootMessenger.registerActionHandler(
'AccountsController:getSelectedMultichainAccount',
() => ({
id: MOCK_ACCOUNT_ID,
address: '0x1234567890123456789012345678901234567890',
scopes: ['eip155:0'],
metadata: {
// @ts-expect-error partial mock
snap: {
id: 'npm:[email protected]' as SnapId,
},
},
}),
);

rootMessenger.registerActionHandler(
'AccountsController:listMultichainAccounts',
() => [
{
id: MOCK_ACCOUNT_ID,
address: '7S3P4HxJpyyigGzodYwHtCxZyUQe9JiBMHyRWXArAaKv',
scopes: ['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'],
metadata: {
// @ts-expect-error partial mock
snap: {
id: MOCK_SNAP_ID,
},
},
},
],
);

// eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});

const element = (
<Box>
<AccountSelector name="foo" hideExternalAccounts />
</Box>
);

const id = await rootMessenger.call(
'SnapInterfaceController:createInterface',
MOCK_SNAP_ID,
element,
);

const { content, state } = rootMessenger.call(
'SnapInterfaceController:getInterface',
MOCK_SNAP_ID,
id,
);

expect(rootMessenger.call).toHaveBeenNthCalledWith(
2,
'AccountsController:getSelectedMultichainAccount',
);

expect(rootMessenger.call).toHaveBeenNthCalledWith(
3,
'AccountsController:listMultichainAccounts',
);

expect(content).toStrictEqual(element);
expect(state).toStrictEqual({
foo: {
accountId: MOCK_ACCOUNT_ID,
addresses: [
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:7S3P4HxJpyyigGzodYwHtCxZyUQe9JiBMHyRWXArAaKv',
],
},
});
});

it('can get accounts of a specific chain ID from the client', async () => {
const rootMessenger = getRootSnapInterfaceControllerMessenger();
const controllerMessenger = getRestrictedSnapInterfaceControllerMessenger(
rootMessenger,
false,
);

rootMessenger.registerActionHandler(
'AccountsController:getSelectedMultichainAccount',
// @ts-expect-error partial mock
() => ({
id: MOCK_ACCOUNT_ID,
address: '0x1234567890123456789012345678901234567890',
scopes: ['eip155:0'],
}),
);

rootMessenger.registerActionHandler(
'AccountsController:listMultichainAccounts',
() => [
// @ts-expect-error partial mock
{
id: MOCK_ACCOUNT_ID,
address: '7S3P4HxJpyyigGzodYwHtCxZyUQe9JiBMHyRWXArAaKv',
scopes: ['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'],
},
],
);

// eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});

const element = (
<Box>
<AccountSelector
name="foo"
switchGlobalAccount
chainIds={['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp']}
/>
</Box>
);

const id = await rootMessenger.call(
'SnapInterfaceController:createInterface',
MOCK_SNAP_ID,
element,
);

const { content, state } = rootMessenger.call(
'SnapInterfaceController:getInterface',
MOCK_SNAP_ID,
id,
);

expect(rootMessenger.call).toHaveBeenNthCalledWith(
2,
'AccountsController:getSelectedMultichainAccount',
);

expect(rootMessenger.call).toHaveBeenNthCalledWith(
3,
'AccountsController:listMultichainAccounts',
);

expect(content).toStrictEqual(element);
expect(state).toStrictEqual({
foo: {
accountId: MOCK_ACCOUNT_ID,
addresses: [
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:7S3P4HxJpyyigGzodYwHtCxZyUQe9JiBMHyRWXArAaKv',
],
},
});
});

it('supports providing interface context', async () => {
Expand Down Expand Up @@ -1164,6 +1372,94 @@ describe('SnapInterfaceController', () => {
),
).rejects.toThrow('Interface not created by foo.');
});

it('can select an account owned by the snap', async () => {
const rootMessenger = getRootSnapInterfaceControllerMessenger();
const controllerMessenger = getRestrictedSnapInterfaceControllerMessenger(
rootMessenger,
false,
);

rootMessenger.registerActionHandler(
'AccountsController:getSelectedMultichainAccount',
() => ({
id: MOCK_ACCOUNT_ID,
address: '0x1234567890123456789012345678901234567890',
scopes: ['eip155:0'],
metadata: {
// @ts-expect-error partial mock
snap: {
id: 'npm:[email protected]' as SnapId,
},
},
}),
);

rootMessenger.registerActionHandler(
'AccountsController:getAccountByAddress',
() => ({
id: MOCK_ACCOUNT_ID,
address: '7S3P4HxJpyyigGzodYwHtCxZyUQe9JiBMHyRWXArAaKv',
scopes: ['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'],
metadata: {
// @ts-expect-error partial mock
snap: {
id: MOCK_SNAP_ID,
},
},
}),
);

// eslint-disable-next-line no-new
new SnapInterfaceController({
messenger: controllerMessenger,
});

const element = (
<Box>
<AccountSelector name="foo" />
</Box>
);

const newElement = (
<Box>
<AccountSelector
name="foo"
hideExternalAccounts
value="solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:7S3P4HxJpyyigGzodYwHtCxZyUQe9JiBMHyRWXArAaKv"
/>
</Box>
);

const id = await rootMessenger.call(
'SnapInterfaceController:createInterface',
MOCK_SNAP_ID,
element,
);

await rootMessenger.call(
'SnapInterfaceController:updateInterface',
MOCK_SNAP_ID,
id,
newElement,
);

const { content, state } = rootMessenger.call(
'SnapInterfaceController:getInterface',
MOCK_SNAP_ID,
id,
);

expect(content).toStrictEqual(newElement);
expect(state).toStrictEqual({
foo: {
accountId: MOCK_ACCOUNT_ID,
addresses: [
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:7S3P4HxJpyyigGzodYwHtCxZyUQe9JiBMHyRWXArAaKv',
],
},
});
});
});

describe('updateInterfaceState', () => {
Expand Down
Loading
Loading