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 `getSessionProperties` function that hydrates the persisted session properties of a CAIP-25 caveat value with a `capabilities` record built by invoking the provided `getCapabilities` hook for each permitted EVM account ([#9294](https://github.com/MetaMask/core/pull/9294))

### Changed

- Bump `@metamask/controller-utils` from `^12.2.0` to `^12.3.0` ([#9218](https://github.com/MetaMask/core/pull/9218))
Expand Down
1 change: 1 addition & 0 deletions packages/chain-agnostic-permission/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ describe('@metamask/chain-agnostic-permission', () => {
"getAllScopesFromScopesObjects",
"getInternalScopesObject",
"getSessionScopes",
"getSessionProperties",
"getPermittedAccountsForScopes",
"validateAndNormalizeScopes",
"bucketScopes",
Expand Down
1 change: 1 addition & 0 deletions packages/chain-agnostic-permission/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export {
export {
getInternalScopesObject,
getSessionScopes,
getSessionProperties,
getPermittedAccountsForScopes,
} from './operators/caip-permission-operator-session-scopes';
export type { Caip25Authorization } from './scope/authorization';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import {
getInternalScopesObject,
getPermittedAccountsForScopes,
getSessionProperties,
getSessionScopes,
} from './caip-permission-operator-session-scopes';

Expand Down Expand Up @@ -52,6 +53,7 @@ describe('CAIP-25 session scopes adapters', () => {
accounts: [],
},
},
sessionProperties: {},
},
{
getNonEvmSupportedMethods,
Expand All @@ -76,6 +78,7 @@ describe('CAIP-25 session scopes adapters', () => {
accounts: ['wallet:eip155:0xdeadbeef'],
},
},
sessionProperties: {},
},
{
getNonEvmSupportedMethods,
Expand All @@ -102,6 +105,7 @@ describe('CAIP-25 session scopes adapters', () => {
accounts: ['wallet:foobar:0xdeadbeef'],
},
},
sessionProperties: {},
},
{
getNonEvmSupportedMethods,
Expand All @@ -122,6 +126,7 @@ describe('CAIP-25 session scopes adapters', () => {
accounts: ['wallet:foobar:0xdeadbeef'],
},
},
sessionProperties: {},
},
{
getNonEvmSupportedMethods,
Expand All @@ -148,6 +153,7 @@ describe('CAIP-25 session scopes adapters', () => {
accounts: ['foo:1:0xdeadbeef'],
},
},
sessionProperties: {},
},
{
getNonEvmSupportedMethods,
Expand All @@ -168,6 +174,7 @@ describe('CAIP-25 session scopes adapters', () => {
accounts: ['foo:1:0xdeadbeef'],
},
},
sessionProperties: {},
},
{
getNonEvmSupportedMethods,
Expand All @@ -192,6 +199,7 @@ describe('CAIP-25 session scopes adapters', () => {
accounts: ['eip155:1:0xdeadbeef'],
},
},
sessionProperties: {},
},
{
getNonEvmSupportedMethods,
Expand Down Expand Up @@ -227,6 +235,7 @@ describe('CAIP-25 session scopes adapters', () => {
},
},
optionalScopes: {},
sessionProperties: {},
},
{
getNonEvmSupportedMethods,
Expand Down Expand Up @@ -257,6 +266,7 @@ describe('CAIP-25 session scopes adapters', () => {
},
},
optionalScopes: {},
sessionProperties: {},
},
{
getNonEvmSupportedMethods,
Expand Down Expand Up @@ -307,6 +317,7 @@ describe('CAIP-25 session scopes adapters', () => {
accounts: unsortedAccounts2,
},
},
sessionProperties: {},
},
{
getNonEvmSupportedMethods,
Expand Down Expand Up @@ -338,6 +349,114 @@ describe('CAIP-25 session scopes adapters', () => {
});
});

describe('getSessionProperties', () => {
it('returns the persisted session properties merged with an empty capabilities record when there are no permitted accounts', () => {
const getCapabilities = jest.fn();

const result = getSessionProperties(
{
requiredScopes: {},
optionalScopes: {},
sessionProperties: { 'eip1193-compatible': true },
},
{
getCapabilities,
},
);

expect(getCapabilities).not.toHaveBeenCalled();
expect(result).toStrictEqual({
'eip1193-compatible': true,
capabilities: {},
});
});

it('calls getCapabilities with each unique permitted EVM address', () => {
const getCapabilities = jest
.fn()
.mockReturnValue({ atomic: 'supported' });

getSessionProperties(
{
requiredScopes: {
'eip155:1': {
accounts: ['eip155:1:0xdead'],
},
},
optionalScopes: {
'eip155:137': {
accounts: ['eip155:137:0xdead', 'eip155:137:0xbeef'],
},
},
sessionProperties: {},
},
{
getCapabilities,
},
);

expect(getCapabilities).toHaveBeenCalledTimes(2);
expect(getCapabilities).toHaveBeenCalledWith({ address: '0xdead' });
expect(getCapabilities).toHaveBeenCalledWith({ address: '0xbeef' });
});

it('returns the session properties with a capabilities record keyed by address', () => {
const getCapabilities = jest.fn((params: { address: string }) => ({
address: params.address,
atomic: { status: 'supported' },
}));

const result = getSessionProperties(
{
requiredScopes: {
'eip155:1': {
accounts: ['eip155:1:0xdead'],
},
},
optionalScopes: {},
sessionProperties: { expiry: '2025-01-01T00:00:00.000Z' },
},
{
getCapabilities,
},
);

expect(result).toStrictEqual({
expiry: '2025-01-01T00:00:00.000Z',
capabilities: {
'0xdead': {
address: '0xdead',
atomic: { status: 'supported' },
},
},
});
});

it('does not call getCapabilities for non-EVM accounts', () => {
const getCapabilities = jest.fn();

const result = getSessionProperties(
{
requiredScopes: {},
optionalScopes: {
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp': {
accounts: [
'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:DdpL8XNK9hSn8m6ycGAQvBwHJVgVz9eL5tWGtZ8L',
],
},
},
sessionProperties: {},
},
{
getCapabilities,
},
);

expect(getCapabilities).not.toHaveBeenCalled();
expect(result).toStrictEqual({ capabilities: {} });
});
});

describe('getPermittedAccountsForScopes', () => {
it('returns an array of permitted accounts for a given scope', () => {
const result = getPermittedAccountsForScopes(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { isCaipChainId, KnownCaipNamespace } from '@metamask/utils';
import type { CaipAccountId, CaipChainId } from '@metamask/utils';

import { getEthAccounts } from './caip-permission-operator-accounts';
import type { Caip25CaveatValue } from '../caip25Permission';
import {
KnownNotifications,
Expand Down Expand Up @@ -107,7 +108,7 @@ const getNormalizedScopesObject = (
export const getSessionScopes = (
caip25CaveatValue: Pick<
Caip25CaveatValue,
'requiredScopes' | 'optionalScopes'
'requiredScopes' | 'optionalScopes' | 'sessionProperties'
>,
{
getNonEvmSupportedMethods,
Expand Down Expand Up @@ -143,6 +144,43 @@ export const getSessionScopes = (
return mergedScopes;
};

/**
* Builds the session properties for an endowment:caip25 permission caveat value,
* hydrating the persisted session properties with the capabilities for each
* permitted EVM account.
*
* @param caip25CaveatValue - The CAIP-25 CaveatValue to get the session properties from.
* @param hooks - An object containing the following properties:
* @param hooks.getCapabilities - A function that returns the capabilities for a given address.
* @returns The session properties merged with a `capabilities` record keyed by account address.
*/
export const getSessionProperties = (
caip25CaveatValue: Pick<
Caip25CaveatValue,
'requiredScopes' | 'optionalScopes' | 'sessionProperties'
>,
{
getCapabilities,
}: {
getCapabilities: (params: { address: string }) => unknown;
},
): Record<string, unknown> => {
const addresses = getEthAccounts(caip25CaveatValue);

const capabilities = addresses.reduce<Record<string, unknown>>(
(acc, address) => {
acc[address] = getCapabilities({ address });
return acc;
},
{},
);

return {
...caip25CaveatValue.sessionProperties,
capabilities,
};
};

/**
* Get the permitted accounts for the given scopes.
*
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 @@ -13,6 +13,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- **BREAKING:** The `wallet_getSession` and `wallet_createSession` handlers now require a `getCapabilities` hook (`(params: { address: string }) => unknown`) ([#9294](https://github.com/MetaMask/core/pull/9294))
- `WalletGetSessionHooks` and `WalletCreateSessionHooks` now include this hook, which must be provided when wiring up the handlers.
- The `wallet_getSession` and `wallet_createSession` handlers now derive the returned `sessionProperties` via `getSessionProperties`, hydrating the persisted session properties with a `capabilities` record built from the `getCapabilities` hook for each permitted EVM account ([#9294](https://github.com/MetaMask/core/pull/9294))
- `wallet_getSession` now always includes a `sessionProperties` field in its result (an empty object when there is no active session).
- Bump `@metamask/accounts-controller` from `^39.0.2` to `^39.0.3` ([#9231](https://github.com/MetaMask/core/pull/9231))

## [3.1.5]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const makeMockHooks = () =>
isNonEvmScopeSupported: () => false,
getNonEvmAccountAddresses: () => [],
sortAccountIdsByLastSelected: () => [],
getCapabilities: () => ({}),
getCaveatForOrigin: (() => ({}) as unknown) as Hooks['getCaveatForOrigin'],
getSelectedNetworkClientId: () => 'mainnet',
handleNonEvmRequestForOrigin: () => Promise.resolve(null),
Expand Down
4 changes: 4 additions & 0 deletions packages/multichain-api-middleware/src/handlers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@ export type GetNonEvmSupportedMethodsHook = {
export type SortAccountIdsByLastSelectedHook = {
sortAccountIdsByLastSelected: (accounts: CaipAccountId[]) => CaipAccountId[];
};

export type GetCapabilitiesHook = {
getCapabilities: (params: { address: string }) => unknown;
};
Loading
Loading