Skip to content
Open
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
4 changes: 4 additions & 0 deletions packages/chain-agnostic-permission/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Add optional `sortAccountIdsByLastSelected` parameter to `getSessionScopes` function to enable custom account ordering within session scopes ([#8255](https://github.com/MetaMask/core/pull/8255))

### Changed

- Bump `@metamask/permission-controller` from `^12.2.0` to `^12.2.1` ([#8225](https://github.com/MetaMask/core/pull/8225))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

describe('getSessionScopes', () => {
const getNonEvmSupportedMethods = jest.fn();
const mockSortAccountIdsByLastSelected = jest.fn();

it('returns a NormalizedScopesObject for the wallet scope', () => {
const result = getSessionScopes(
Expand Down Expand Up @@ -203,6 +204,110 @@
},
});
});

it('sorts accounts using sortAccountIdsByLastSelected when provided', () => {
const unsortedAccounts = ['eip155:1:0xbeef', 'eip155:1:0xdead'];
const sortedAccounts = ['eip155:1:0xdead', 'eip155:1:0xbeef'];

Check failure on line 211 in packages/chain-agnostic-permission/src/operators/caip-permission-operator-session-scopes.test.ts

View workflow job for this annotation

GitHub Actions / Lint, build, and test / Lint (22.x)

Delete `······`
mockSortAccountIdsByLastSelected.mockReturnValue(sortedAccounts);

const result = getSessionScopes(
{
requiredScopes: {
'eip155:1': {
accounts: unsortedAccounts,
},
},
optionalScopes: {},
},
{
getNonEvmSupportedMethods,
sortAccountIdsByLastSelected: mockSortAccountIdsByLastSelected,
},
);

expect(mockSortAccountIdsByLastSelected).toHaveBeenCalledWith(unsortedAccounts);

Check failure on line 229 in packages/chain-agnostic-permission/src/operators/caip-permission-operator-session-scopes.test.ts

View workflow job for this annotation

GitHub Actions / Lint, build, and test / Lint (22.x)

Replace `unsortedAccounts` with `⏎········unsortedAccounts,⏎······`
expect(result).toStrictEqual({
'eip155:1': {
methods: KnownRpcMethods.eip155,
notifications: KnownNotifications.eip155,
accounts: sortedAccounts,
},
});
});

it('does not sort accounts when sortAccountIdsByLastSelected is not provided', () => {
const accounts = ['eip155:1:0xbeef', 'eip155:1:0xdead'];

const result = getSessionScopes(
{
requiredScopes: {
'eip155:1': {
accounts,
},
},
optionalScopes: {},
},
{
getNonEvmSupportedMethods,
},
);

expect(mockSortAccountIdsByLastSelected).not.toHaveBeenCalled();
expect(result).toStrictEqual({
'eip155:1': {
methods: KnownRpcMethods.eip155,
notifications: KnownNotifications.eip155,
accounts, // Original order preserved
},
});
});

it('sorts accounts in both required and optional scopes', () => {
const unsortedAccounts1 = ['eip155:1:0xbeef', 'eip155:1:0xdead'];
const unsortedAccounts2 = ['eip155:137:0xcafe', 'eip155:137:0xbabe'];
const sortedAccounts1 = ['eip155:1:0xdead', 'eip155:1:0xbeef'];
const sortedAccounts2 = ['eip155:137:0xbabe', 'eip155:137:0xcafe'];

Check failure on line 271 in packages/chain-agnostic-permission/src/operators/caip-permission-operator-session-scopes.test.ts

View workflow job for this annotation

GitHub Actions / Lint, build, and test / Lint (22.x)

Delete `······`
mockSortAccountIdsByLastSelected
.mockReturnValueOnce(sortedAccounts1)
.mockReturnValueOnce(sortedAccounts2);

const result = getSessionScopes(
{
requiredScopes: {
'eip155:1': {
accounts: unsortedAccounts1,
},
},
optionalScopes: {
'eip155:137': {
accounts: unsortedAccounts2,
},
},
},
{
getNonEvmSupportedMethods,
sortAccountIdsByLastSelected: mockSortAccountIdsByLastSelected,
},
);

expect(mockSortAccountIdsByLastSelected).toHaveBeenCalledTimes(2);
expect(mockSortAccountIdsByLastSelected).toHaveBeenNthCalledWith(1, unsortedAccounts1);

Check failure on line 296 in packages/chain-agnostic-permission/src/operators/caip-permission-operator-session-scopes.test.ts

View workflow job for this annotation

GitHub Actions / Lint, build, and test / Lint (22.x)

Replace `1,·unsortedAccounts1` with `⏎········1,⏎········unsortedAccounts1,⏎······`
expect(mockSortAccountIdsByLastSelected).toHaveBeenNthCalledWith(2, unsortedAccounts2);

Check failure on line 297 in packages/chain-agnostic-permission/src/operators/caip-permission-operator-session-scopes.test.ts

View workflow job for this annotation

GitHub Actions / Lint, build, and test / Lint (22.x)

Replace `2,·unsortedAccounts2` with `⏎········2,⏎········unsortedAccounts2,⏎······`
expect(result).toStrictEqual({
'eip155:1': {
methods: KnownRpcMethods.eip155,
notifications: KnownNotifications.eip155,
accounts: sortedAccounts1,
},
'eip155:137': {
methods: KnownRpcMethods.eip155,
notifications: KnownNotifications.eip155,
accounts: sortedAccounts2,
},
});
});
});

describe('getPermittedAccountsForScopes', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,19 @@ export const getInternalScopesObject = (
* @param internalScopesObject - The InternalScopesObject to convert.
* @param hooks - An object containing the following properties:
* @param hooks.getNonEvmSupportedMethods - A function that returns the supported methods for a non EVM scope.
* @param [hooks.sortAccountIdsByLastSelected] - Optional function that accepts an array of CaipAccountId and returns an array of CaipAccountId sorted by last selected.
* @returns A NormalizedScopesObject.
*/
const getNormalizedScopesObject = (
internalScopesObject: InternalScopesObject,
{
getNonEvmSupportedMethods,
sortAccountIdsByLastSelected,
}: {
getNonEvmSupportedMethods: (scope: CaipChainId) => string[];
sortAccountIdsByLastSelected?: (
accounts: CaipAccountId[],
) => CaipAccountId[];
},
) => {
const normalizedScopes: NormalizedScopesObject = {};
Expand Down Expand Up @@ -83,10 +88,14 @@ const getNormalizedScopesObject = (
notifications = [];
}

const sortedAccounts = sortAccountIdsByLastSelected
? sortAccountIdsByLastSelected(accounts)
: accounts;

normalizedScopes[scopeString] = {
methods,
notifications,
accounts,
accounts: sortedAccounts,
};
},
);
Expand All @@ -101,6 +110,7 @@ const getNormalizedScopesObject = (
* @param caip25CaveatValue - The CAIP-25 CaveatValue to convert.
* @param hooks - An object containing the following properties:
* @param hooks.getNonEvmSupportedMethods - A function that returns the supported methods for a non EVM scope.
* @param [hooks.sortAccountIdsByLastSelected] - Optional function that accepts an array of CaipAccountId and returns an array of CaipAccountId sorted by last selected.
* @returns A NormalizedScopesObject.
*/
export const getSessionScopes = (
Expand All @@ -110,16 +120,22 @@ export const getSessionScopes = (
>,
{
getNonEvmSupportedMethods,
sortAccountIdsByLastSelected,
}: {
getNonEvmSupportedMethods: (scope: CaipChainId) => string[];
sortAccountIdsByLastSelected?: (
accounts: CaipAccountId[],
) => CaipAccountId[];
},
) => {
return mergeNormalizedScopes(
getNormalizedScopesObject(caip25CaveatValue.requiredScopes, {
getNonEvmSupportedMethods,
sortAccountIdsByLastSelected,
}),
getNormalizedScopesObject(caip25CaveatValue.optionalScopes, {
getNonEvmSupportedMethods,
sortAccountIdsByLastSelected,
}),
);
};
Expand Down
4 changes: 4 additions & 0 deletions packages/multichain-api-middleware/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Add required `sortAccountIdsByLastSelected` hook to `wallet_getSession` and `wallet_createSession` handlers to enable custom account ordering in session scopes ([#8255](https://github.com/MetaMask/core/pull/8255))

### Changed

- Bump `@metamask/permission-controller` from `^12.2.0` to `^12.2.1` ([#8225](https://github.com/MetaMask/core/pull/8225))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ const createMockedHandler = () => {
sessionProperties?: Record<string, Json>;
}>;
const getNonEvmAccountAddresses = jest.fn().mockReturnValue([]);
const sortAccountIdsByLastSelected = jest.fn((accounts) => accounts);
const handler = (
request: JsonRpcRequest<Caip25Authorization> & { origin: string },
) =>
Expand All @@ -114,6 +115,7 @@ const createMockedHandler = () => {
getNonEvmSupportedMethods,
isNonEvmScopeSupported,
getNonEvmAccountAddresses,
sortAccountIdsByLastSelected,
trackSessionCreatedEvent,
});

Expand All @@ -128,6 +130,7 @@ const createMockedHandler = () => {
getNonEvmSupportedMethods,
isNonEvmScopeSupported,
getNonEvmAccountAddresses,
sortAccountIdsByLastSelected,
handler,
};
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ const SOLANA_CAIP_CHAIN_ID = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp';
* @param hooks.getNonEvmSupportedMethods - The hook that returns the supported methods for a non EVM scope.
* @param hooks.isNonEvmScopeSupported - The hook that returns true if a non EVM scope is supported.
* @param hooks.getNonEvmAccountAddresses - The hook that returns a list of CaipAccountIds that are supported for a CaipChainId.
* @param hooks.sortAccountIdsByLastSelected - A function that accepts an array of CaipAccountId and returns an array of CaipAccountId sorted by last selected.
* @param hooks.trackSessionCreatedEvent - An optional hook for platform specific logic to run. Can be undefined.
* @returns A promise with wallet_createSession handler
*/
Expand All @@ -87,6 +88,9 @@ async function walletCreateSessionHandler(
getNonEvmSupportedMethods: (scope: CaipChainId) => string[];
isNonEvmScopeSupported: (scope: CaipChainId) => boolean;
getNonEvmAccountAddresses: (scope: CaipChainId) => CaipAccountId[];
sortAccountIdsByLastSelected: (
accounts: CaipAccountId[],
) => CaipAccountId[];
trackSessionCreatedEvent?: (
approvedCaip25CaveatValue: Caip25CaveatValue,
) => void;
Expand Down Expand Up @@ -263,6 +267,7 @@ async function walletCreateSessionHandler(

const sessionScopes = getSessionScopes(approvedCaip25CaveatValue, {
getNonEvmSupportedMethods: hooks.getNonEvmSupportedMethods,
sortAccountIdsByLastSelected: hooks.sortAccountIdsByLastSelected,
});

const { sessionProperties: approvedSessionProperties = {} } =
Expand Down Expand Up @@ -290,6 +295,7 @@ export const walletCreateSession = {
getNonEvmSupportedMethods: true,
isNonEvmScopeSupported: true,
getNonEvmAccountAddresses: true,
sortAccountIdsByLastSelected: true,
trackSessionCreatedEvent: true,
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
const next = jest.fn();
const end = jest.fn();
const getNonEvmSupportedMethods = jest.fn();
const sortAccountIdsByLastSelected = jest.fn((accounts) => accounts);
const getCaveatForOrigin = jest.fn().mockReturnValue({
value: {
requiredScopes: {
Expand Down Expand Up @@ -54,6 +55,7 @@
walletGetSession.implementation(request, response, next, end, {
getCaveatForOrigin,
getNonEvmSupportedMethods,
sortAccountIdsByLastSelected,
});

return {
Expand All @@ -62,6 +64,7 @@
end,
getCaveatForOrigin,
getNonEvmSupportedMethods,
sortAccountIdsByLastSelected,
handler,
};
};
Expand Down Expand Up @@ -96,7 +99,7 @@
});

it('gets the session scopes from the CAIP-25 caveat value', async () => {
const { handler, getNonEvmSupportedMethods } = createMockedHandler();
const { handler, getNonEvmSupportedMethods, sortAccountIdsByLastSelected } = createMockedHandler();

Check failure on line 102 in packages/multichain-api-middleware/src/handlers/wallet-getSession.test.ts

View workflow job for this annotation

GitHub Actions / Lint, build, and test / Lint (22.x)

Insert `⏎·····`

await handler(baseRequest);
expect(chainAgnosticPermissionModule.getSessionScopes).toHaveBeenCalledWith(
Expand All @@ -120,6 +123,7 @@
},
{
getNonEvmSupportedMethods,
sortAccountIdsByLastSelected,
},
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Caip25CaveatValue,
NormalizedScopesObject,
} from '@metamask/chain-agnostic-permission';
import type { CaipAccountId } from '@metamask/utils';

Check failure on line 5 in packages/multichain-api-middleware/src/handlers/wallet-getSession.ts

View workflow job for this annotation

GitHub Actions / Lint, build, and test / Lint (22.x)

`@metamask/utils` type import should occur after type import of `@metamask/permission-controller`
import {
Caip25CaveatType,
Caip25EndowmentPermissionName,
Expand All @@ -27,6 +28,7 @@
* @param hooks - The hooks object.
* @param hooks.getCaveatForOrigin - Function to retrieve a caveat for the origin.
* @param hooks.getNonEvmSupportedMethods - A function that returns the supported methods for a non EVM scope.
* @param hooks.sortAccountIdsByLastSelected - A function that accepts an array of CaipAccountId and returns an array of CaipAccountId sorted by last selected.
* @returns Nothing.
*/
async function walletGetSessionHandler(
Expand All @@ -40,6 +42,9 @@
caveatType: string,
) => Caveat<typeof Caip25CaveatType, Caip25CaveatValue>;
getNonEvmSupportedMethods: (scope: CaipChainId) => string[];
sortAccountIdsByLastSelected: (
accounts: CaipAccountId[],
) => CaipAccountId[];
},
) {
let caveat;
Expand All @@ -60,6 +65,7 @@
response.result = {
sessionScopes: getSessionScopes(caveat.value, {
getNonEvmSupportedMethods: hooks.getNonEvmSupportedMethods,
sortAccountIdsByLastSelected: hooks.sortAccountIdsByLastSelected,
}),
};
return end();
Expand All @@ -71,5 +77,6 @@
hookNames: {
getCaveatForOrigin: true,
getNonEvmSupportedMethods: true,
sortAccountIdsByLastSelected: true,
},
};
Loading