Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 2 additions & 0 deletions packages/snaps-jest/src/helpers.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1062,6 +1062,7 @@ describe('getMockAccount', () => {
"address": "DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK",
"assets": [],
"id": "6b86b273-ff344ce1-9d6b804e-ff5a3f57",
"owned": false,
"scopes": [
"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
],
Expand All @@ -1087,6 +1088,7 @@ describe('getMockAccount', () => {
"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
],
"id": "d4735e3a-265e46ee-a03f5971-8b9b5d03",
"owned": false,
"scopes": [
"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
],
Expand Down
8 changes: 8 additions & 0 deletions packages/snaps-jest/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,11 @@ export type BaseMockAccountOptions = {
* Whether the account is selected by default.
*/
selected?: boolean;

/**
* Whether the account is owned by the snap.
*/
owned?: boolean;
};

/**
Expand Down Expand Up @@ -363,6 +368,7 @@ export type GetMockAccountOptions =
* @param options.assets - The assets associated with the account, in CAIP
* format. If not provided, it will default to an empty array.
* @param options.selected - Whether the account is selected by default.
* @param options.owned - Whether the account is owned by the snap.
* @param options.id - The ID of the account. If not provided, a pseudo-random
* UUID will be generated.
* @returns A mock account object with the specified properties.
Expand All @@ -371,6 +377,7 @@ export function getMockAccount({
address,
assets = [],
selected = false,
owned = false,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if the default should be owned or not 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a strong preference, we set selected to false and I treat those flags as optionally toggle-able if you really need them

id = getPseudoRandomUuid(),
scopes = getScopesFromAssets(assets),
}: GetMockAccountOptions): SimulationAccount {
Expand All @@ -379,6 +386,7 @@ export function getMockAccount({
id,
scopes,
selected,
owned,
assets,
};
}
2 changes: 2 additions & 0 deletions packages/snaps-simulation/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const DEFAULT_ACCOUNTS: SimulationAccount[] = [
id: '29bc7513-d1b9-4466-98a6-f5f9e0b90137',
scopes: ['eip155:0'],
selected: false,
owned: false,
// We don't expose assets for EVM accounts as it's not supported in the AssetSelector.
assets: [],
},
Expand All @@ -52,6 +53,7 @@ export const DEFAULT_ACCOUNTS: SimulationAccount[] = [
'solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z',
],
selected: true,
owned: true,
assets: [
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501',
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
Expand Down
4 changes: 4 additions & 0 deletions packages/snaps-simulation/src/options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ describe('getOptions', () => {
"address": "0x1234567890abcdef1234567890abcdef12345678",
"assets": [],
"id": "29bc7513-d1b9-4466-98a6-f5f9e0b90137",
"owned": false,
"scopes": [
"eip155:0",
],
Expand All @@ -23,6 +24,7 @@ describe('getOptions', () => {
"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
],
"id": "e051723c-85d0-43a3-b9bf-568a90d3f378",
"owned": true,
"scopes": [
"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
"solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
Expand Down Expand Up @@ -72,6 +74,7 @@ describe('getOptions', () => {
"address": "0x1234567890abcdef1234567890abcdef12345678",
"assets": [],
"id": "29bc7513-d1b9-4466-98a6-f5f9e0b90137",
"owned": false,
"scopes": [
"eip155:0",
],
Expand All @@ -84,6 +87,7 @@ describe('getOptions', () => {
"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
],
"id": "e051723c-85d0-43a3-b9bf-568a90d3f378",
"owned": true,
"scopes": [
"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
"solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
Expand Down
1 change: 1 addition & 0 deletions packages/snaps-simulation/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const SimulationAccountStruct = object({
id: string(),
scopes: array(CaipChainIdStruct),
selected: defaulted(optional(boolean()), false),
owned: defaulted(optional(boolean()), false),
assets: defaulted(optional(array(CaipAssetTypeStruct)), []),
});

Expand Down
49 changes: 38 additions & 11 deletions packages/snaps-simulation/src/simulation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import { DIALOG_APPROVAL_TYPES } from '@metamask/snaps-rpc-methods';
import type { CaipAssetType, CaipChainId } from '@metamask/snaps-sdk';
import { AuxiliaryFileEncoding, text } from '@metamask/snaps-sdk';
import { VirtualFile } from '@metamask/snaps-utils';
import { getSnapManifest } from '@metamask/snaps-utils/test-utils';
import {
getSnapManifest,
MOCK_SNAP_ID,
} from '@metamask/snaps-utils/test-utils';

import { DEFAULT_SRP } from './constants';
import {
Expand All @@ -26,6 +29,7 @@ import {
getRestrictedSnapInterfaceControllerMessenger,
getRootControllerMessenger,
} from './test-utils';
import { addSnapMetadataToAccount } from './utils/account';

describe('installSnap', () => {
it('installs a Snap and returns the execution service', async () => {
Expand Down Expand Up @@ -693,15 +697,15 @@ describe('registerActions', () => {
const controllerMessenger = getRootControllerMessenger(false);

it('registers `PhishingController:testOrigin`', async () => {
registerActions(controllerMessenger, runSaga, options);
registerActions(controllerMessenger, runSaga, options, MOCK_SNAP_ID);

expect(
controllerMessenger.call('PhishingController:testOrigin', 'foo'),
).toStrictEqual({ result: false, type: 'all' });
});

it('registers `ApprovalController:hasRequest`', async () => {
registerActions(controllerMessenger, runSaga, options);
registerActions(controllerMessenger, runSaga, options, MOCK_SNAP_ID);

store.dispatch(
setInterface({ type: DIALOG_APPROVAL_TYPES.default, id: 'foo' }),
Expand All @@ -713,7 +717,7 @@ describe('registerActions', () => {
});

it('registers `ApprovalController:acceptRequest`', async () => {
registerActions(controllerMessenger, runSaga, options);
registerActions(controllerMessenger, runSaga, options, MOCK_SNAP_ID);

store.dispatch(
setInterface({ type: DIALOG_APPROVAL_TYPES.default, id: 'foo' }),
Expand All @@ -729,36 +733,59 @@ describe('registerActions', () => {
});

it('registers `AccountsController:getAccountByAddress`', async () => {
registerActions(controllerMessenger, runSaga, options);
registerActions(controllerMessenger, runSaga, options, MOCK_SNAP_ID);

expect(
controllerMessenger.call(
'AccountsController:getAccountByAddress',
mockedAccounts[0].address,
),
).toStrictEqual(mockedAccounts[0]);
).toStrictEqual(addSnapMetadataToAccount(mockedAccounts[0], MOCK_SNAP_ID));

expect(
controllerMessenger.call('AccountsController:getAccountByAddress', 'foo'),
).toBeUndefined();
});

it('registers `AccountsController:getSelectedMultichainAccount`', async () => {
registerActions(controllerMessenger, runSaga, options);
registerActions(controllerMessenger, runSaga, options, MOCK_SNAP_ID);

expect(
controllerMessenger.call(
'AccountsController:getSelectedMultichainAccount',
),
).toStrictEqual(mockedAccounts[0]);
).toStrictEqual(addSnapMetadataToAccount(mockedAccounts[0], MOCK_SNAP_ID));
});

it('returns `undefined` in `AccountsController:getSelectedMultichainAccount` if no account is selected', async () => {
registerActions(
controllerMessenger,
runSaga,
{ ...options, accounts: [] },
MOCK_SNAP_ID,
);

expect(
controllerMessenger.call(
'AccountsController:getSelectedMultichainAccount',
),
).toBeUndefined();
});

it('registers `AccountsController:listMultichainAccounts`', async () => {
registerActions(controllerMessenger, runSaga, options);
registerActions(controllerMessenger, runSaga, options, MOCK_SNAP_ID);

expect(
controllerMessenger.call('AccountsController:listMultichainAccounts'),
).toStrictEqual(mockedAccounts);
).toStrictEqual(
mockedAccounts.map((account) =>
addSnapMetadataToAccount(account, MOCK_SNAP_ID),
),
);
});

it('registers `MultichainAssetsController:getState`', async () => {
registerActions(controllerMessenger, runSaga, options);
registerActions(controllerMessenger, runSaga, options, MOCK_SNAP_ID);

expect(
controllerMessenger.call('MultichainAssetsController:getState'),
Expand Down
44 changes: 35 additions & 9 deletions packages/snaps-simulation/src/simulation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import type { SimulationOptions, SimulationUserOptions } from './options';
import { getOptions } from './options';
import type { Interface, RunSagaFunction, Store } from './store';
import { createStore, getCurrentInterface } from './store';
import { addSnapMetadataToAccount } from './utils/account';

/**
* Options for the execution service, without the options that are shared
Expand Down Expand Up @@ -303,7 +304,7 @@ export async function installSnap<

const controllerMessenger = new Messenger<any, any>();

registerActions(controllerMessenger, runSaga, options);
registerActions(controllerMessenger, runSaga, options, snapId);

// Set up controllers and JSON-RPC stack.
const restrictedHooks = getRestrictedHooks(options);
Expand Down Expand Up @@ -474,11 +475,13 @@ export function getPermittedHooks(
* @param controllerMessenger - The controller messenger.
* @param runSaga - The run saga function.
* @param options - The simulation options.
* @param snapId - The ID of the Snap.
*/
export function registerActions(
controllerMessenger: RootControllerMessenger,
runSaga: RunSagaFunction,
options: SimulationOptions,
snapId: SnapId,
) {
controllerMessenger.registerActionHandler(
'PhishingController:testOrigin',
Expand All @@ -487,24 +490,47 @@ export function registerActions(

controllerMessenger.registerActionHandler(
'AccountsController:getAccountByAddress',
(address) =>
// @ts-expect-error - This is a partial account with only the necessary
// data used by the interface controller.
options.accounts.find((account) => address === account.address),
// @ts-expect-error - This is a partial account with only the necessary
// data used by the interface controller.
(address) => {
const matchingAccount = options.accounts.find(
(account) => address === account.address,
);

if (!matchingAccount) {
return undefined;
}

return addSnapMetadataToAccount(matchingAccount, snapId);
},
);

controllerMessenger.registerActionHandler(
'AccountsController:getSelectedMultichainAccount',
// @ts-expect-error - This is a partial account with only the necessary
// data used by the interface controller.
() => options.accounts.find((account) => account.selected),
() => {
const selectedAccount = options.accounts.find(
(account) => account.selected,
);

if (!selectedAccount) {
return undefined;
}

return addSnapMetadataToAccount(selectedAccount, snapId);
},
);

controllerMessenger.registerActionHandler(
'AccountsController:listMultichainAccounts',
// @ts-expect-error - These are partial accounts with only the necessary
// data used by the interface controller.
() => options.accounts,

() =>
// @ts-expect-error - These are partial accounts with only the necessary
// data used by the interface controller.
options.accounts.map((account) =>
addSnapMetadataToAccount(account, snapId),
),
);

controllerMessenger.registerActionHandler(
Expand Down
52 changes: 52 additions & 0 deletions packages/snaps-simulation/src/utils/account.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { MOCK_SNAP_ID } from '@metamask/snaps-utils/test-utils';

import { addSnapMetadataToAccount } from './account';
import type { SimulationAccount } from '../options';

describe('addSnapMetadataToAccount', () => {
it('adds snap metadata to an owned account', () => {
const account: SimulationAccount = {
id: 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
selected: true,
address: '7S3P4HxJpyyigGzodYwHtCxZyUQe9JiBMHyRWXArAaKv',
owned: true,
scopes: ['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'],
assets: [
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:105',
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
],
};

const result = addSnapMetadataToAccount(account, MOCK_SNAP_ID);

expect(result).toStrictEqual({
...account,
metadata: {
snap: {
id: MOCK_SNAP_ID,
},
},
});
});

it('returns an account without snap metadata if not owned', () => {
const account: SimulationAccount = {
id: 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
selected: true,
address: '7S3P4HxJpyyigGzodYwHtCxZyUQe9JiBMHyRWXArAaKv',
owned: false,
scopes: ['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'],
assets: [
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:105',
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
],
};

const result = addSnapMetadataToAccount(account, MOCK_SNAP_ID);

expect(result).toStrictEqual({
...account,
metadata: {},
});
});
});
24 changes: 24 additions & 0 deletions packages/snaps-simulation/src/utils/account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { SnapId } from '@metamask/snaps-sdk';

import type { SimulationAccount } from '../options';

export const addSnapMetadataToAccount = (
account: SimulationAccount,
snapId: SnapId,
) => {
if (!account.owned) {
return {
...account,
metadata: {},
};
}

return {
...account,
metadata: {
snap: {
id: snapId,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
id: snapId,
id: snapId,
enabled: true,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't care about enabled 🤔

},
},
};
};
Loading