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

feat: Add CAIP-25 permission and adapters to @metamask/multichain #4784

Open
wants to merge 47 commits into
base: initialize-caip-multichain
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
bc56104
Initial package
jiexi Oct 10, 2024
a4b52ad
Fix caip25Permission
jiexi Oct 10, 2024
86ab58a
scopes
jiexi Oct 10, 2024
186d4b3
Fix middlewares
jiexi Oct 10, 2024
6a93d1d
Fix adapters except permission middleware
jiexi Oct 10, 2024
231bcaa
Rename handlers js to ts
jiexi Oct 10, 2024
4e52fc0
permission middleware test js ts rename
jiexi Oct 10, 2024
151eaac
fix: typescript + linting (#4788)
shanejonas Oct 11, 2024
85a723e
Added exports for multichain package (#4789)
shanejonas Oct 11, 2024
a0bb278
Add requires
jiexi Oct 11, 2024
1cd8ef2
add permittedChains adapter to exports
jiexi Oct 11, 2024
c636def
kill scope index barrel
jiexi Oct 11, 2024
a6aa7c1
barrel
jiexi Oct 14, 2024
42134f7
Merge branch 'main' into caip-multichain
jiexi Oct 14, 2024
a13b9c7
remove subjectTypes from CAIP-25 permission
jiexi Oct 14, 2024
81db877
update eth accounts adapter with empty wallet and wallet:eip155 Scope…
jiexi Oct 14, 2024
a3fc263
fix caip25permission spec
jiexi Oct 14, 2024
afe5cb9
upsert empty wallet:eip155 when setting permittedChains
jiexi Oct 14, 2024
9ea78be
lint
jiexi Oct 14, 2024
5650d31
Rename scope.test.ts to types.test.ts
jiexi Oct 14, 2024
df5167e
add networkClientId type to permission-adapter-middleware
jiexi Oct 15, 2024
9446b0b
fix snapshot
jiexi Oct 15, 2024
f65ca24
Merge branch 'main' into caip-multichain
jiexi Oct 15, 2024
941849d
add KnownWalletScopeString enum
jiexi Oct 15, 2024
a250de2
stop upserting wallet scope in setEthAccounts()
jiexi Oct 15, 2024
aa7ba39
lint
jiexi Oct 15, 2024
8415be6
update removeScope mutator to not revoke permission if requiredScope …
jiexi Oct 15, 2024
c7f510e
Fix specificationBuilder jsdoc and typing
jiexi Oct 15, 2024
4afa8f8
Fix caip25permission type
jiexi Oct 15, 2024
597e837
Fix caip25permission type
jiexi Oct 15, 2024
1872188
small dry todo fix
adonesky1 Oct 15, 2024
c5d8005
another small dry
adonesky1 Oct 15, 2024
54f7c49
Added handler wrappers to better integrate with existing middleware (…
shanejonas Oct 15, 2024
ada451d
change subscriptionManager require to import
jiexi Oct 15, 2024
2322e39
lint
jiexi Oct 15, 2024
9b21677
Merge branch 'main' into caip-multichain
jiexi Oct 16, 2024
00a41cb
Jl/caip multichain/update scope object account types (#4803)
jiexi Oct 16, 2024
f30dd8b
update @metamask/rpc-errors version
adonesky1 Oct 17, 2024
71085a0
fix merge
adonesky1 Oct 17, 2024
f2be8f0
Merge branch 'main' into caip-multichain
jiexi Oct 17, 2024
fc28c89
remove API related logic
jiexi Oct 17, 2024
ba25ce5
Merge branch 'initialize-caip-multichain' into caip-multichain
jiexi Oct 17, 2024
4ceb118
remove api related deps
jiexi Oct 17, 2024
633485c
Merge branch 'initialize-caip-multichain' into caip-multichain
jiexi Oct 17, 2024
afb2e2c
fix bad merge
jiexi Oct 17, 2024
32b213a
Merge branch 'initialize-caip-multichain' into caip-multichain
jiexi Oct 17, 2024
dae4f73
add account support check in validator (#4816)
jiexi Oct 17, 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
8 changes: 4 additions & 4 deletions packages/multichain/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ module.exports = merge(baseConfig, {
// An object that configures minimum threshold enforcement for coverage results
coverageThreshold: {
global: {
branches: 100,
functions: 100,
lines: 100,
statements: 100,
branches: 82.38,
functions: 87.37,
lines: 86.65,
statements: 87.09,
},
},
});
14 changes: 14 additions & 0 deletions packages/multichain/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,18 @@
"test:verbose": "NODE_OPTIONS=--experimental-vm-modules jest --verbose",
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch"
},
"dependencies": {
"@metamask/api-specs": "^0.10.12",
"@metamask/controller-utils": "^11.3.0",
"@metamask/eth-json-rpc-filters": "^7.0.0",
"@metamask/rpc-errors": "^7.0.0",
"@metamask/utils": "^9.1.0",
"lodash": "^4.17.21"
},
"devDependencies": {
"@metamask/auto-changelog": "^3.4.4",
"@metamask/network-controller": "^21.0.1",
"@metamask/permission-controller": "^11.0.2",
"@types/jest": "^27.4.1",
"deepmerge": "^4.2.2",
"jest": "^27.5.1",
Expand All @@ -56,6 +66,10 @@
"typedoc-plugin-missing-exports": "^2.0.0",
"typescript": "~5.2.2"
},
"peerDependencies": {
"@metamask/network-controller": "^21.0.0",
"@metamask/permission-controller": "^11.0.0"
},
"engines": {
"node": "^18.18 || >=20"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
import type { Caip25CaveatValue } from '../caip25Permission';
import {
getEthAccounts,
setEthAccounts,
} from './caip-permission-adapter-eth-accounts';

describe('CAIP-25 eth_accounts adapters', () => {
describe('getEthAccounts', () => {
it('returns the unique set of EIP155 accounts from the CAIP-25 caveat value', () => {
const ethAccounts = getEthAccounts({
requiredScopes: {
'eip155:1': {
methods: [],
notifications: [],
accounts: ['eip155:1:0x1', 'eip155:1:0x2'],
},
'eip155:5': {
methods: [],
notifications: [],
accounts: ['eip155:5:0x2', 'eip155:1:0x3'],
},
'bip122:000000000019d6689c085ae165831e93': {
methods: [],
notifications: [],
accounts: [
'bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6',
],
},
},
optionalScopes: {
'eip155:1': {
methods: [],
notifications: [],
accounts: ['eip155:1:0x1', 'eip155:1:0x4'],
},
'eip155:10': {
methods: [],
notifications: [],
accounts: [],
},
'eip155:100': {
methods: [],
notifications: [],
accounts: ['eip155:100:0x100'],
},
'wallet:eip155': {
methods: [],
notifications: [],
accounts: ['wallet:eip155:0x5'],
},
},
isMultichainOrigin: false,
});

expect(ethAccounts).toStrictEqual([
'0x1',
'0x2',
'0x4',
'0x3',
'0x100',
'0x5',
]);
});
});

describe('setEthAccounts', () => {
it('returns a CAIP-25 caveat value with all EIP-155 scopeObject.accounts set to CAIP-10 account addresses formed from the accounts param', () => {
const input: Caip25CaveatValue = {
requiredScopes: {
'eip155:1': {
methods: [],
notifications: [],
accounts: ['eip155:1:0x1', 'eip155:1:0x2'],
},
'eip155:5': {
methods: [],
notifications: [],
accounts: ['eip155:5:0x2', 'eip155:1:0x3'],
},
'bip122:000000000019d6689c085ae165831e93': {
methods: [],
notifications: [],
accounts: [
'bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6',
],
},
},
optionalScopes: {
'eip155:1': {
methods: [],
notifications: [],
accounts: ['eip155:1:0x1', 'eip155:1:0x4'],
},
'eip155:10': {
methods: [],
notifications: [],
accounts: [],
},
'eip155:100': {
methods: [],
notifications: [],
accounts: ['eip155:100:0x100'],
},
'wallet:eip155': {
methods: [],
notifications: [],
accounts: [],
},
},
isMultichainOrigin: false,
};

const result = setEthAccounts(input, ['0x1', '0x2', '0x3']);
expect(result).toStrictEqual({
requiredScopes: {
'eip155:1': {
methods: [],
notifications: [],
accounts: ['eip155:1:0x1', 'eip155:1:0x2', 'eip155:1:0x3'],
},
'eip155:5': {
methods: [],
notifications: [],
accounts: ['eip155:5:0x1', 'eip155:5:0x2', 'eip155:5:0x3'],
},
'bip122:000000000019d6689c085ae165831e93': {
methods: [],
notifications: [],
accounts: [
'bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6',
],
},
},
optionalScopes: {
'eip155:1': {
methods: [],
notifications: [],
accounts: ['eip155:1:0x1', 'eip155:1:0x2', 'eip155:1:0x3'],
},
'eip155:10': {
methods: [],
notifications: [],
accounts: ['eip155:10:0x1', 'eip155:10:0x2', 'eip155:10:0x3'],
},
'eip155:100': {
methods: [],
notifications: [],
accounts: ['eip155:100:0x1', 'eip155:100:0x2', 'eip155:100:0x3'],
},
'wallet:eip155': {
methods: [],
notifications: [],
accounts: [
'wallet:eip155:0x1',
'wallet:eip155:0x2',
'wallet:eip155:0x3',
],
},
},
isMultichainOrigin: false,
});
});

it('returns a CAIP-25 caveat value with upserted "wallet:eip155" optional scope with CAIP-10 account addresses formed from the accounts param', () => {
const input: Caip25CaveatValue = {
requiredScopes: {},
optionalScopes: {},
isMultichainOrigin: false,
};

const result = setEthAccounts(input, ['0x1', '0x2', '0x3']);
expect(result).toStrictEqual({
requiredScopes: {},
optionalScopes: {
'wallet:eip155': {
methods: [],
notifications: [],
accounts: [
'wallet:eip155:0x1',
'wallet:eip155:0x2',
'wallet:eip155:0x3',
],
},
},
isMultichainOrigin: false,
});
});

it('does not modify the input CAIP-25 caveat value object in place', () => {
const input: Caip25CaveatValue = {
requiredScopes: {
'eip155:1': {
methods: [],
notifications: [],
accounts: [],
},
},
optionalScopes: {},
isMultichainOrigin: false,
};

const result = setEthAccounts(input, ['0x1', '0x2', '0x3']);
expect(input).toStrictEqual({
requiredScopes: {
'eip155:1': {
methods: [],
notifications: [],
accounts: [],
},
},
optionalScopes: {},
isMultichainOrigin: false,
});
expect(input).not.toStrictEqual(result);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import {
type CaipAccountId,
type Hex,
KnownCaipNamespace,
parseCaipAccountId,
} from '@metamask/utils';

import type { Caip25CaveatValue } from '../caip25Permission';
import { getUniqueArrayItems, mergeScopes } from '../scope/transform';
import type { ScopesObject, ScopeString } from '../scope/types';
import { KnownWalletScopeString, parseScopeString } from '../scope/types';

const isEip155ScopeString = (scopeString: ScopeString) => {
const { namespace } = parseScopeString(scopeString);

return (
namespace === KnownCaipNamespace.Eip155 ||
scopeString === KnownWalletScopeString.Eip155
);
};

export const getEthAccounts = (caip25CaveatValue: Caip25CaveatValue) => {
const ethAccounts: string[] = [];
const sessionScopes = mergeScopes(
caip25CaveatValue.requiredScopes,
caip25CaveatValue.optionalScopes,
);

Object.entries(sessionScopes).forEach(([_, { accounts }]) => {
accounts?.forEach((account) => {
const { address, chainId } = parseCaipAccountId(account);

if (isEip155ScopeString(chainId)) {
ethAccounts.push(address);
}
});
});

return getUniqueArrayItems(ethAccounts);
};

const setEthAccountsForScopesObject = (
scopesObject: ScopesObject,
accounts: Hex[],
) => {
const updatedScopesObject: ScopesObject = {};

Object.entries(scopesObject).forEach(([scopeString, scopeObject]) => {
const isWalletNamespace = scopeString === KnownCaipNamespace.Wallet;

if (
!isEip155ScopeString(scopeString as ScopeString) &&
!isWalletNamespace
) {
updatedScopesObject[scopeString as ScopeString] = scopeObject;
return;
}

const caipAccounts = accounts.map(
(account) =>
(isWalletNamespace
? `${KnownWalletScopeString.Eip155}:${account}`
: `${scopeString}:${account}`) as CaipAccountId,
);

updatedScopesObject[scopeString as ScopeString] = {
...scopeObject,
accounts: caipAccounts,
};
});

return updatedScopesObject;
};

export const setEthAccounts = (
caip25CaveatValue: Caip25CaveatValue,
accounts: Hex[],
) => {
return {
...caip25CaveatValue,
requiredScopes: setEthAccountsForScopesObject(
caip25CaveatValue.requiredScopes,
accounts,
),
optionalScopes: setEthAccountsForScopesObject(
{
[KnownWalletScopeString.Eip155]: {
methods: [],
notifications: [],
accounts: [],
},
...caip25CaveatValue.optionalScopes,
},
accounts,
),
};
};
Loading
Loading