Skip to content

Commit 1e7d70e

Browse files
jiexiadonesky1Gudahttmcmire
authored
feat: Add CAIP-25 permission and adapters to @metamask/multichain (#4784)
## Explanation This PR updates `@metamask/multichain` to provide types, CAIP-25 permission, and helpers/adapters for the new permission, which can be shared across the extension & mobile clients. These tools and utilities will be used in both clients (mobile + extension)'s multichain API implementations. ### File Overview * `packages/multichain/src/adapters/`: Helpers that get and set legacy permission values from and to the new CAIP-25 permission * `packages/multichain/src/caip25Permission.ts`: Constants, types, mutators, and a specification builder for a CAIP-25 permission * `packages/multichain/src/index.ts`: Barrel export * `packages/multichain/src/scope/`: Types for [CAIP-217 ](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-217.md) and our internal normalized/flattened version of them. Additionally contains helpers for validating shape, normalizing/merging, and checking support (i.e. if the wallet is able to serve the chain with it's requested methods and notifications) ## References Upstream: #4812 Downstream: #4813 Key Multichain API Standards implemented here: - [CAIP-217](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-217.md) - `scope` defintion Open PR that uses this new package for migrating the legacy permissions to CAIP-25 permission in the extension: MetaMask/metamask-extension#27847 ## Changelog ### `@metamask/multichain` - **ADDED**: TODO ## Checklist - [X] I've updated the test suite for new or updated code as appropriate - [X] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've highlighted breaking changes using the "BREAKING" category above as appropriate - [X] I've prepared draft pull requests for clients and consumer packages to resolve any breaking changes --------- Co-authored-by: Alex <adonesky@gmail.com> Co-authored-by: Mark Stacey <markjstacey@gmail.com> Co-authored-by: Elliot Winkler <elliot.winkler@gmail.com>
1 parent db83e28 commit 1e7d70e

29 files changed

+4666
-20
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ linkStyle default opacity:0.5
122122
logging_controller --> controller_utils;
123123
message_manager --> base_controller;
124124
message_manager --> controller_utils;
125+
multichain --> controller_utils;
126+
multichain --> network_controller;
127+
multichain --> permission_controller;
125128
name_controller --> base_controller;
126129
name_controller --> controller_utils;
127130
network_controller --> base_controller;

packages/multichain/package.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,18 @@
4646
"test:verbose": "NODE_OPTIONS=--experimental-vm-modules jest --verbose",
4747
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch"
4848
},
49+
"dependencies": {
50+
"@metamask/api-specs": "^0.10.12",
51+
"@metamask/controller-utils": "^11.4.3",
52+
"@metamask/eth-json-rpc-filters": "^7.0.0",
53+
"@metamask/rpc-errors": "^7.0.1",
54+
"@metamask/utils": "^10.0.0",
55+
"lodash": "^4.17.21"
56+
},
4957
"devDependencies": {
5058
"@metamask/auto-changelog": "^3.4.4",
59+
"@metamask/network-controller": "^22.0.2",
60+
"@metamask/permission-controller": "^11.0.3",
5161
"@types/jest": "^27.4.1",
5262
"deepmerge": "^4.2.2",
5363
"jest": "^27.5.1",
@@ -56,6 +66,10 @@
5666
"typedoc-plugin-missing-exports": "^2.0.0",
5767
"typescript": "~5.2.2"
5868
},
69+
"peerDependencies": {
70+
"@metamask/network-controller": "^22.0.0",
71+
"@metamask/permission-controller": "^11.0.0"
72+
},
5973
"engines": {
6074
"node": "^18.18 || >=20"
6175
},
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
import type { Caip25CaveatValue } from '../caip25Permission';
2+
import {
3+
getEthAccounts,
4+
setEthAccounts,
5+
} from './caip-permission-adapter-eth-accounts';
6+
7+
describe('CAIP-25 eth_accounts adapters', () => {
8+
describe('getEthAccounts', () => {
9+
it('returns an empty array if the required scopes are empty', () => {
10+
const ethAccounts = getEthAccounts({
11+
requiredScopes: {},
12+
optionalScopes: {},
13+
});
14+
expect(ethAccounts).toStrictEqual([]);
15+
});
16+
it('returns an empty array if the scope objects have no accounts', () => {
17+
const ethAccounts = getEthAccounts({
18+
requiredScopes: {
19+
'eip155:1': { methods: [], notifications: [], accounts: [] },
20+
'eip155:2': { methods: [], notifications: [], accounts: [] },
21+
},
22+
optionalScopes: {},
23+
});
24+
expect(ethAccounts).toStrictEqual([]);
25+
});
26+
it('returns an empty array if the scope objects have no eth accounts', () => {
27+
const ethAccounts = getEthAccounts({
28+
requiredScopes: {
29+
'bip122:000000000019d6689c085ae165831e93': {
30+
methods: [],
31+
notifications: [],
32+
accounts: [
33+
'bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6',
34+
],
35+
},
36+
},
37+
optionalScopes: {},
38+
});
39+
expect(ethAccounts).toStrictEqual([]);
40+
});
41+
42+
it('returns the unique set of EIP155 accounts from the CAIP-25 caveat value', () => {
43+
const ethAccounts = getEthAccounts({
44+
requiredScopes: {
45+
'eip155:1': {
46+
methods: [],
47+
notifications: [],
48+
accounts: ['eip155:1:0x1', 'eip155:1:0x2'],
49+
},
50+
'eip155:5': {
51+
methods: [],
52+
notifications: [],
53+
accounts: ['eip155:5:0x2', 'eip155:1:0x3'],
54+
},
55+
'bip122:000000000019d6689c085ae165831e93': {
56+
methods: [],
57+
notifications: [],
58+
accounts: [
59+
'bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6',
60+
],
61+
},
62+
},
63+
optionalScopes: {
64+
'eip155:1': {
65+
methods: [],
66+
notifications: [],
67+
accounts: ['eip155:1:0x1', 'eip155:1:0x4'],
68+
},
69+
'eip155:10': {
70+
methods: [],
71+
notifications: [],
72+
accounts: [],
73+
},
74+
'eip155:100': {
75+
methods: [],
76+
notifications: [],
77+
accounts: ['eip155:100:0x100'],
78+
},
79+
'wallet:eip155': {
80+
methods: [],
81+
notifications: [],
82+
accounts: ['wallet:eip155:0x5'],
83+
},
84+
},
85+
});
86+
87+
expect(ethAccounts).toStrictEqual([
88+
'0x1',
89+
'0x2',
90+
'0x4',
91+
'0x3',
92+
'0x100',
93+
'0x5',
94+
]);
95+
});
96+
});
97+
98+
describe('setEthAccounts', () => {
99+
it('returns a CAIP-25 caveat value with all EIP-155 scopeObject.accounts set to CAIP-10 account addresses formed from the accounts param', () => {
100+
const input: Caip25CaveatValue = {
101+
requiredScopes: {
102+
'eip155:1': {
103+
methods: [],
104+
notifications: [],
105+
accounts: ['eip155:1:0x1', 'eip155:1:0x2'],
106+
},
107+
'eip155:5': {
108+
methods: [],
109+
notifications: [],
110+
accounts: ['eip155:5:0x2', 'eip155:1:0x3'],
111+
},
112+
'bip122:000000000019d6689c085ae165831e93': {
113+
methods: [],
114+
notifications: [],
115+
accounts: [
116+
'bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6',
117+
],
118+
},
119+
},
120+
optionalScopes: {
121+
'eip155:1': {
122+
methods: [],
123+
notifications: [],
124+
accounts: ['eip155:1:0x1', 'eip155:1:0x4'],
125+
},
126+
'eip155:10': {
127+
methods: [],
128+
notifications: [],
129+
accounts: [],
130+
},
131+
'eip155:100': {
132+
methods: [],
133+
notifications: [],
134+
accounts: ['eip155:100:0x100'],
135+
},
136+
'wallet:eip155': {
137+
methods: [],
138+
notifications: [],
139+
accounts: [],
140+
},
141+
wallet: {
142+
methods: [],
143+
notifications: [],
144+
accounts: [],
145+
},
146+
},
147+
isMultichainOrigin: false,
148+
};
149+
150+
const result = setEthAccounts(input, ['0x1', '0x2', '0x3']);
151+
expect(result).toStrictEqual({
152+
requiredScopes: {
153+
'eip155:1': {
154+
methods: [],
155+
notifications: [],
156+
accounts: ['eip155:1:0x1', 'eip155:1:0x2', 'eip155:1:0x3'],
157+
},
158+
'eip155:5': {
159+
methods: [],
160+
notifications: [],
161+
accounts: ['eip155:5:0x1', 'eip155:5:0x2', 'eip155:5:0x3'],
162+
},
163+
'bip122:000000000019d6689c085ae165831e93': {
164+
methods: [],
165+
notifications: [],
166+
accounts: [
167+
'bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6',
168+
],
169+
},
170+
},
171+
optionalScopes: {
172+
'eip155:1': {
173+
methods: [],
174+
notifications: [],
175+
accounts: ['eip155:1:0x1', 'eip155:1:0x2', 'eip155:1:0x3'],
176+
},
177+
'eip155:10': {
178+
methods: [],
179+
notifications: [],
180+
accounts: ['eip155:10:0x1', 'eip155:10:0x2', 'eip155:10:0x3'],
181+
},
182+
'eip155:100': {
183+
methods: [],
184+
notifications: [],
185+
accounts: ['eip155:100:0x1', 'eip155:100:0x2', 'eip155:100:0x3'],
186+
},
187+
'wallet:eip155': {
188+
methods: [],
189+
notifications: [],
190+
accounts: [
191+
'wallet:eip155:0x1',
192+
'wallet:eip155:0x2',
193+
'wallet:eip155:0x3',
194+
],
195+
},
196+
wallet: {
197+
methods: [],
198+
notifications: [],
199+
accounts: [
200+
'wallet:eip155:0x1',
201+
'wallet:eip155:0x2',
202+
'wallet:eip155:0x3',
203+
],
204+
},
205+
},
206+
isMultichainOrigin: false,
207+
});
208+
});
209+
210+
it('returns a CAIP-25 caveat value with missing "wallet:eip155" optional scope filled in, forming CAIP-10 account addresses from the accounts param', () => {
211+
const input: Caip25CaveatValue = {
212+
requiredScopes: {},
213+
optionalScopes: {},
214+
isMultichainOrigin: false,
215+
};
216+
217+
const result = setEthAccounts(input, ['0x1', '0x2', '0x3']);
218+
expect(result).toStrictEqual({
219+
requiredScopes: {},
220+
optionalScopes: {
221+
'wallet:eip155': {
222+
methods: [],
223+
notifications: [],
224+
accounts: [
225+
'wallet:eip155:0x1',
226+
'wallet:eip155:0x2',
227+
'wallet:eip155:0x3',
228+
],
229+
},
230+
},
231+
isMultichainOrigin: false,
232+
});
233+
});
234+
235+
it('does not modify the input CAIP-25 caveat value object in place', () => {
236+
const input: Caip25CaveatValue = {
237+
requiredScopes: {
238+
'eip155:1': {
239+
methods: [],
240+
notifications: [],
241+
accounts: [],
242+
},
243+
},
244+
optionalScopes: {},
245+
isMultichainOrigin: false,
246+
};
247+
248+
const result = setEthAccounts(input, ['0x1', '0x2', '0x3']);
249+
expect(input).toStrictEqual({
250+
requiredScopes: {
251+
'eip155:1': {
252+
methods: [],
253+
notifications: [],
254+
accounts: [],
255+
},
256+
},
257+
optionalScopes: {},
258+
isMultichainOrigin: false,
259+
});
260+
expect(input).not.toStrictEqual(result);
261+
});
262+
});
263+
});

0 commit comments

Comments
 (0)