Skip to content

Commit

Permalink
feat: Add CAIP-25 permission and adapters to @metamask/multichain (#…
Browse files Browse the repository at this point in the history
…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 <[email protected]>
Co-authored-by: Mark Stacey <[email protected]>
Co-authored-by: Elliot Winkler <[email protected]>
  • Loading branch information
4 people authored Nov 13, 2024
1 parent db83e28 commit 1e7d70e
Show file tree
Hide file tree
Showing 29 changed files with 4,666 additions and 20 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ linkStyle default opacity:0.5
logging_controller --> controller_utils;
message_manager --> base_controller;
message_manager --> controller_utils;
multichain --> controller_utils;
multichain --> network_controller;
multichain --> permission_controller;
name_controller --> base_controller;
name_controller --> controller_utils;
network_controller --> base_controller;
Expand Down
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.4.3",
"@metamask/eth-json-rpc-filters": "^7.0.0",
"@metamask/rpc-errors": "^7.0.1",
"@metamask/utils": "^10.0.0",
"lodash": "^4.17.21"
},
"devDependencies": {
"@metamask/auto-changelog": "^3.4.4",
"@metamask/network-controller": "^22.0.2",
"@metamask/permission-controller": "^11.0.3",
"@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": "^22.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,263 @@
import type { Caip25CaveatValue } from '../caip25Permission';
import {
getEthAccounts,
setEthAccounts,
} from './caip-permission-adapter-eth-accounts';

describe('CAIP-25 eth_accounts adapters', () => {
describe('getEthAccounts', () => {
it('returns an empty array if the required scopes are empty', () => {
const ethAccounts = getEthAccounts({
requiredScopes: {},
optionalScopes: {},
});
expect(ethAccounts).toStrictEqual([]);
});
it('returns an empty array if the scope objects have no accounts', () => {
const ethAccounts = getEthAccounts({
requiredScopes: {
'eip155:1': { methods: [], notifications: [], accounts: [] },
'eip155:2': { methods: [], notifications: [], accounts: [] },
},
optionalScopes: {},
});
expect(ethAccounts).toStrictEqual([]);
});
it('returns an empty array if the scope objects have no eth accounts', () => {
const ethAccounts = getEthAccounts({
requiredScopes: {
'bip122:000000000019d6689c085ae165831e93': {
methods: [],
notifications: [],
accounts: [
'bip122:000000000019d6689c085ae165831e93:128Lkh3S7CkDTBZ8W7BbpsN3YYizJMp8p6',
],
},
},
optionalScopes: {},
});
expect(ethAccounts).toStrictEqual([]);
});

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'],
},
},
});

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: [],
},
wallet: {
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',
],
},
wallet: {
methods: [],
notifications: [],
accounts: [
'wallet:eip155:0x1',
'wallet:eip155:0x2',
'wallet:eip155:0x3',
],
},
},
isMultichainOrigin: false,
});
});

it('returns a CAIP-25 caveat value with missing "wallet:eip155" optional scope filled in, forming CAIP-10 account addresses 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);
});
});
});
Loading

0 comments on commit 1e7d70e

Please sign in to comment.