Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CAIP Multichain API with permission refactor changes #4961

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
8c4aa0f
Rename Internal -> Normalized
jiexi Nov 20, 2024
6d5185f
Add new Internal. Update adapters
jiexi Nov 20, 2024
2611871
Merge branch 'main' into caip25-permission-refactor
jiexi Nov 20, 2024
c917b7c
Add internal types. Update comments
jiexi Nov 20, 2024
8241201
export Internal types in package
jiexi Nov 20, 2024
1e546fb
cleanup adapters
jiexi Nov 20, 2024
0fe2f6d
Add assertIsInternalScopesObject
jiexi Nov 20, 2024
14cc4ea
fix caip25 permission validator
jiexi Nov 20, 2024
bc072ca
Merge branch 'main' into caip25-permission-refactor
jiexi Nov 20, 2024
89093f3
Update packages/multichain/src/caip25Permission.ts
jiexi Nov 20, 2024
0ae1ef2
Update packages/multichain/src/adapters/caip-permission-adapter-eth-a…
jiexi Nov 20, 2024
0a05439
Update packages/multichain/src/scope/assert.test.ts
jiexi Nov 20, 2024
6d7f0fa
Update packages/multichain/src/scope/types.ts
jiexi Nov 20, 2024
ae15726
Update packages/multichain/src/scope/types.ts
jiexi Nov 20, 2024
94dd734
Update packages/multichain/src/scope/types.ts
jiexi Nov 20, 2024
340d6e0
Update packages/multichain/src/scope/types.ts
jiexi Nov 20, 2024
1500443
cleanup assert.test.ts
jiexi Nov 20, 2024
3e03960
Merge remote-tracking branch 'origin/caip25-permission-refactor' into…
jiexi Nov 20, 2024
010c346
Merge branch 'main' into caip25-permission-refactor
jiexi Nov 20, 2024
0e623f0
throw better error in CAIP-25 permission validator if not all scopeSt…
jiexi Nov 20, 2024
510c0b9
lint
jiexi Nov 20, 2024
400581a
Trigger
jiexi Nov 21, 2024
0951bf8
Merge branch 'caip25-permission-refactor' into caip-multichain-api-wi…
jiexi Nov 21, 2024
243a140
add getSessionScopes
jiexi Nov 21, 2024
a97703e
fix types after merge
jiexi Nov 21, 2024
45bac7c
update session scope adapter. add tests
jiexi Nov 21, 2024
a9532e5
use getSessionScopes
jiexi Nov 21, 2024
b413bb6
update package exports
jiexi Nov 21, 2024
b3cf6ab
yarn
jiexi Nov 21, 2024
5ee2fae
Merge branch 'caip-multichain-api' into caip-multichain-api-with-perm…
jiexi Nov 21, 2024
f830c42
Merge branch 'caip-multichain-api' into caip-multichain-api-with-perm…
jiexi Nov 21, 2024
238a770
Update packages/multichain/src/adapters/caip-permission-adapter-sessi…
jiexi Nov 21, 2024
f33802f
Update packages/multichain/src/adapters/caip-permission-adapter-sessi…
jiexi Nov 21, 2024
e7373ae
export assertIsInternalScopeString. use assertIsInternalScopeString i…
jiexi Nov 21, 2024
f9c036b
Merge remote-tracking branch 'origin/caip-multichain-api-with-permiss…
jiexi Nov 21, 2024
1e65d33
Merge branch 'caip-multichain-api' into caip-multichain-api-with-perm…
jiexi Nov 21, 2024
e672262
Update type Normalized usage comments
jiexi Nov 21, 2024
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/multichain/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
},
"devDependencies": {
"@metamask/auto-changelog": "^3.4.4",
"@metamask/json-rpc-engine": "^9.0.3",
"@metamask/json-rpc-engine": "^10.0.1",
"@metamask/network-controller": "^22.0.2",
"@metamask/permission-controller": "^11.0.3",
"@open-rpc/meta-schema": "^1.14.6",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import {
Caip25CaveatType,
Caip25EndowmentPermissionName,
} from '../caip25Permission';
import { mergeScopes } from '../scope/transform';
import { KnownWalletScopeString, type ScopeString } from '../scope/types';
import { KnownWalletScopeString } from '../scope/constants';
import type { InternalScopeString } from '../scope/types';
import { getSessionScopes } from './caip-permission-adapter-session-scopes';

/**
* Middleware to handle CAIP-25 permission requests.
Expand Down Expand Up @@ -61,17 +62,14 @@ export async function caipPermissionAdapterMiddleware(
const { chainId } =
hooks.getNetworkConfigurationByNetworkClientId(networkClientId);

const scope: ScopeString = `eip155:${parseInt(chainId, 16)}`;
const scope: InternalScopeString = `eip155:${parseInt(chainId, 16)}`;

const scopesObject = mergeScopes(
caveat.value.requiredScopes,
caveat.value.optionalScopes,
);
const sesionScopes = getSessionScopes(caveat.value);

if (
!scopesObject[scope]?.methods?.includes(method) &&
!scopesObject[KnownWalletScopeString.Eip155]?.methods?.includes(method) &&
!scopesObject.wallet?.methods?.includes(method)
!sesionScopes[scope]?.methods?.includes(method) &&
!sesionScopes[KnownWalletScopeString.Eip155]?.methods?.includes(method) &&
!sesionScopes.wallet?.methods?.includes(method)
) {
return end(providerErrors.unauthorized());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import {
KnownNotifications,
KnownRpcMethods,
KnownWalletNamespaceRpcMethods,
KnownWalletRpcMethods,
} from '../scope/constants';
import { getSessionScopes } from './caip-permission-adapter-session-scopes';

describe('CAIP-25 session scopes adapters', () => {
describe('getSessionScopes', () => {
it('returns a NormalizedScopesObject for the wallet scope', () => {
const result = getSessionScopes({
requiredScopes: {},
optionalScopes: {
wallet: {
accounts: [],
},
},
});

expect(result).toStrictEqual({
wallet: {
methods: KnownWalletRpcMethods,
notifications: [],
accounts: [],
},
});
});

it('returns a NormalizedScopesObject for the wallet:eip155 scope', () => {
const result = getSessionScopes({
requiredScopes: {},
optionalScopes: {
'wallet:eip155': {
accounts: ['wallet:eip155:0xdeadbeef'],
},
},
});

expect(result).toStrictEqual({
'wallet:eip155': {
methods: KnownWalletNamespaceRpcMethods.eip155,
notifications: [],
accounts: ['wallet:eip155:0xdeadbeef'],
},
});
});

it('returns a NormalizedScopesObject with empty methods and notifications for scope with wallet namespace and unknown reference', () => {
const result = getSessionScopes({
requiredScopes: {},
optionalScopes: {
'wallet:foobar': {
accounts: ['wallet:foobar:0xdeadbeef'],
},
},
});

expect(result).toStrictEqual({
'wallet:foobar': {
methods: [],
notifications: [],
accounts: ['wallet:foobar:0xdeadbeef'],
},
});
});

it('returns a NormalizedScopesObject for a eip155 namespaced scope', () => {
const result = getSessionScopes({
requiredScopes: {},
optionalScopes: {
'eip155:1': {
accounts: ['eip155:1:0xdeadbeef'],
},
},
});

expect(result).toStrictEqual({
'eip155:1': {
methods: KnownRpcMethods.eip155,
notifications: KnownNotifications.eip155,
accounts: ['eip155:1:0xdeadbeef'],
},
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { KnownCaipNamespace } from '@metamask/utils';

import type { Caip25CaveatValue } from '../caip25Permission';
import {
KnownNotifications,
KnownRpcMethods,
KnownWalletNamespaceRpcMethods,
KnownWalletRpcMethods,
} from '../scope/constants';
import { mergeScopes } from '../scope/transform';
import type {
InternalScopesObject,
NonWalletKnownCaipNamespace,
NormalizedScopesObject,
} from '../scope/types';
import { parseScopeString } from '../scope/types';

/**
* Converts an InternalScopesObject to a NormalizedScopesObject.
* @param internalScopesObject - The InternalScopesObject to convert.
* @returns A NormalizedScopesObject.
*/
const getNormalizedScopesObject = (
jiexi marked this conversation as resolved.
Show resolved Hide resolved
internalScopesObject: InternalScopesObject,
) => {
const normalizedScopes: NormalizedScopesObject = {};

Object.entries(internalScopesObject).forEach(
([_scopeString, { accounts }]) => {
const scopeString = _scopeString as keyof typeof internalScopesObject;
const { namespace, reference } = parseScopeString(scopeString);
let methods: string[] = [];
let notifications: string[] = [];

if (namespace === KnownCaipNamespace.Wallet) {
if (reference) {
methods =
KnownWalletNamespaceRpcMethods[
reference as NonWalletKnownCaipNamespace
] ?? [];
} else {
methods = KnownWalletRpcMethods;
}
} else {
methods =
KnownRpcMethods[namespace as NonWalletKnownCaipNamespace] ?? [];
notifications =
KnownNotifications[namespace as NonWalletKnownCaipNamespace] ?? [];
}

normalizedScopes[scopeString] = {
methods,
notifications,
accounts,
};
},
);

return normalizedScopes;
};

/**
* Takes the scopes from an endowment:caip25 permission caveat value,
* hydrates them with supported methods and notifications, and returns a NormalizedScopesObject.
* @param caip25CaveatValue - The CAIP-25 CaveatValue to convert.
* @returns A NormalizedScopesObject.
*/
export const getSessionScopes = (
jiexi marked this conversation as resolved.
Show resolved Hide resolved
caip25CaveatValue: Pick<
Caip25CaveatValue,
'requiredScopes' | 'optionalScopes'
>,
) => {
return mergeScopes(
getNormalizedScopesObject(caip25CaveatValue.requiredScopes),
getNormalizedScopesObject(caip25CaveatValue.optionalScopes),
);
};
66 changes: 57 additions & 9 deletions packages/multichain/src/handlers/wallet-getSession.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import type { JsonRpcRequest } from '@metamask/utils';

import * as PermissionAdapterSessionScopes from '../adapters/caip-permission-adapter-session-scopes';
import {
Caip25CaveatType,
Caip25EndowmentPermissionName,
} from '../caip25Permission';
import { walletGetSession } from './wallet-getSession';

jest.mock('../adapters/caip-permission-adapter-session-scopes', () => ({
getSessionScopes: jest.fn(),
}));
const MockPermissionAdapterSessionScopes = jest.mocked(
PermissionAdapterSessionScopes,
);

const baseRequest: JsonRpcRequest & { origin: string } = {
origin: 'http://test.com',
jsonrpc: '2.0' as const,
Expand All @@ -21,25 +29,17 @@ const createMockedHandler = () => {
value: {
requiredScopes: {
'eip155:1': {
methods: ['eth_call'],
notifications: [],
accounts: [],
},
'eip155:5': {
methods: ['eth_chainId'],
notifications: [],
accounts: [],
},
},
optionalScopes: {
'eip155:1': {
methods: ['net_version'],
notifications: ['chainChanged'],
accounts: [],
},
wallet: {
methods: ['wallet_watchAsset'],
notifications: [],
accounts: [],
},
},
Expand Down Expand Up @@ -67,6 +67,10 @@ const createMockedHandler = () => {
};

describe('wallet_getSession', () => {
afterEach(() => {
jest.restoreAllMocks();
});

it('gets the authorized scopes from the CAIP-25 endowment permission', async () => {
const { handler, getCaveat } = createMockedHandler();

Expand All @@ -90,9 +94,53 @@ describe('wallet_getSession', () => {
});
});

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

await handler(baseRequest);
expect(
MockPermissionAdapterSessionScopes.getSessionScopes,
).toHaveBeenCalledWith({
requiredScopes: {
'eip155:1': {
accounts: [],
},
'eip155:5': {
accounts: [],
},
},
optionalScopes: {
'eip155:1': {
accounts: [],
},
wallet: {
accounts: [],
},
},
});
});

it('returns the session scopes', async () => {
const { handler, response } = createMockedHandler();

MockPermissionAdapterSessionScopes.getSessionScopes.mockReturnValue({
'eip155:1': {
methods: ['eth_call', 'net_version'],
notifications: ['chainChanged'],
accounts: [],
},
'eip155:5': {
methods: ['eth_chainId'],
notifications: [],
accounts: [],
},
wallet: {
methods: ['wallet_watchAsset'],
notifications: [],
accounts: [],
},
});

await handler(baseRequest);
expect(response.result).toStrictEqual({
sessionScopes: {
Expand Down
11 changes: 4 additions & 7 deletions packages/multichain/src/handlers/wallet-getSession.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { Caveat } from '@metamask/permission-controller';
import type { JsonRpcRequest, JsonRpcSuccess } from '@metamask/utils';
import type { NormalizedScopesObject } from 'src/scope/types';

import { getSessionScopes } from '../adapters/caip-permission-adapter-session-scopes';
import type { Caip25CaveatValue } from '../caip25Permission';
import {
Caip25CaveatType,
Caip25EndowmentPermissionName,
} from '../caip25Permission';
import { mergeScopes } from '../scope/transform';
import type { ScopesObject } from '../scope/types';

/**
* Handler for the `wallet_getSession` RPC method.
Expand All @@ -21,7 +21,7 @@ import type { ScopesObject } from '../scope/types';
*/
async function walletGetSessionHandler(
request: JsonRpcRequest & { origin: string },
response: JsonRpcSuccess<{ sessionScopes: ScopesObject }>,
response: JsonRpcSuccess<{ sessionScopes: NormalizedScopesObject }>,
_next: () => void,
end: () => void,
hooks: {
Expand Down Expand Up @@ -49,10 +49,7 @@ async function walletGetSessionHandler(
}

response.result = {
sessionScopes: mergeScopes(
caveat.value.requiredScopes,
caveat.value.optionalScopes,
),
sessionScopes: getSessionScopes(caveat.value),
};
return end();
}
Expand Down
Loading
Loading