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(keyring-api)!: add scopes field to KeyringAccount #101

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ linkStyle default opacity:0.5
eth_snap_keyring --> keyring_api;
eth_snap_keyring --> keyring_internal_api;
eth_snap_keyring --> keyring_internal_snap_client;
eth_snap_keyring --> keyring_utils;
keyring_snap_client --> keyring_api;
keyring_snap_client --> keyring_utils;
keyring_snap_sdk --> keyring_utils;
Expand Down
24 changes: 23 additions & 1 deletion packages/keyring-api/src/api/account.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { assert } from '@metamask/superstruct';

import { KeyringAccountStruct } from './account';
import { EthAccountType, KeyringAccountStruct } from './account';

const supportedKeyringAccountTypes = Object.keys(
KeyringAccountStruct.schema.type.schema,
Expand All @@ -12,6 +12,7 @@ describe('api', () => {
const baseAccount = {
id: '606a7759-b0fb-48e4-9874-bab62ff8e7eb',
address: '0x000',
scopes: [],
options: {},
methods: [],
};
Expand All @@ -33,5 +34,26 @@ describe('api', () => {
);
},
);

it.each([
// Namespace too short (< 3):
'',
'a',
'ei',
'bi',
'bi:p122something',
// Namespace too long (> 8):
'eip11155111',
'eip11155111:11155111',
])('throws an error if account scopes is: %s', (scope: string) => {
const account = {
...baseAccount,
type: EthAccountType.Eoa,
scopes: [scope],
};
expect(() => assert(account, KeyringAccountStruct)).toThrow(
`At path: scopes.0 -- Expected the value to satisfy a union of \`string | string\`, but received: "${scope}"`,
);
});
});
});
20 changes: 18 additions & 2 deletions packages/keyring-api/src/api/account.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
import { object, UuidStruct } from '@metamask/keyring-utils';
import type { Infer } from '@metamask/superstruct';
import { array, enums, record, string } from '@metamask/superstruct';
import { JsonStruct } from '@metamask/utils';
import {
nonempty,
array,
enums,
record,
string,
union,
} from '@metamask/superstruct';
import {
CaipChainIdStruct,
CaipNamespaceStruct,
JsonStruct,
} from '@metamask/utils';

/**
* Supported Ethereum account types.
Expand Down Expand Up @@ -62,6 +73,11 @@ export const KeyringAccountStruct = object({
*/
address: string(),

/**
* Account supported scopes (CAIP-2 chain IDs).
*/
scopes: nonempty(array(union([CaipNamespaceStruct, CaipChainIdStruct]))),

/**
* Account options.
*/
Expand Down
10 changes: 10 additions & 0 deletions packages/keyring-api/src/btc/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* istanbul ignore file */

/**
* Scopes for Bitcoin account type. See {@link KeyringAccount.scopes}.
*/
export enum BtcScopes {
Namespace = 'bip122',
Mainnet = 'bip122:000000000019d6689c085ae165831e93',
Testnet = 'bip122:000000000933ea01ad0ee984209779ba',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Testnet = 'bip122:000000000933ea01ad0ee984209779ba',
Testnet = 'bip122:000000000933ea01ad0ee984209779ba',
Testnet4 = 'bip122:00000000da84f2bafbbc53dee25a72ae',
Signet = 'bip122:00000008819873e925422c1ff0f99f7c',
Regtest = 'bip122:regtest',

}
1 change: 1 addition & 0 deletions packages/keyring-api/src/btc/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './constants';
export * from './types';
9 changes: 9 additions & 0 deletions packages/keyring-api/src/eth/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/* istanbul ignore file */

/**
* Scopes for EVM account type. See {@link KeyringAccount.scopes}.
*/
export enum EthScopes {
Namespace = 'eip155',
Mainnet = 'eip155:1',
}
1 change: 1 addition & 0 deletions packages/keyring-api/src/eth/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './constants';
export * from './erc4337';
export * from './types';
export * from './utils';
8 changes: 7 additions & 1 deletion packages/keyring-api/src/eth/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { object, definePattern } from '@metamask/keyring-utils';
import type { Infer } from '@metamask/superstruct';
import { array, enums, literal } from '@metamask/superstruct';
import { nonempty, array, enums, literal } from '@metamask/superstruct';

import { EthScopes } from '.';
import { EthAccountType, KeyringAccountStruct } from '../api';

export const EthBytesStruct = definePattern('EthBytes', /^0x[0-9a-f]*$/iu);
Expand Down Expand Up @@ -46,6 +47,11 @@ export const EthEoaAccountStruct = object({
*/
type: literal(`${EthAccountType.Eoa}`),

/**
* Account scopes (must be ['eip155']).
*/
scopes: nonempty(array(literal(EthScopes.Namespace))),

/**
* Account supported methods.
*/
Expand Down
6 changes: 6 additions & 0 deletions packages/keyring-api/src/events.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { is } from '@metamask/superstruct';

import { EthAccountType } from './api';
import { EthScopes } from './eth/constants';
import {
AccountCreatedEventStruct,
AccountDeletedEventStruct,
Expand All @@ -21,6 +22,7 @@ describe('events', () => {
address: '0x0123',
methods: [],
options: {},
scopes: [EthScopes.Namespace],
type: EthAccountType.Eoa,
},
},
Expand All @@ -38,6 +40,7 @@ describe('events', () => {
address: '0x0123',
methods: [],
options: {},
scopes: [EthScopes.Namespace],
type: EthAccountType.Eoa,
},
},
Expand All @@ -55,6 +58,7 @@ describe('events', () => {
address: '0x0123',
methods: [],
options: {},
scopes: [EthScopes.Namespace],
type: EthAccountType.Eoa,
},
displayConfirmation: true,
Expand All @@ -75,6 +79,7 @@ describe('events', () => {
address: '0x0123',
methods: [],
options: {},
scopes: [EthScopes.Namespace],
type: EthAccountType.Eoa,
},
},
Expand All @@ -92,6 +97,7 @@ describe('events', () => {
address: '0x0123',
methods: [],
options: {},
scopes: [EthScopes.Namespace],
type: EthAccountType.Eoa,
},
},
Expand Down
11 changes: 11 additions & 0 deletions packages/keyring-api/src/sol/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* istanbul ignore file */

/**
* Scopes for Solana account type. See {@link KeyringAccount.scopes}.
*/
export enum SolScopes {
Namespace = 'solana',
Devnet = 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1',
Mainnet = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
Testnet = 'solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z',
}
1 change: 1 addition & 0 deletions packages/keyring-api/src/sol/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './constants';
export * from './types';
36 changes: 30 additions & 6 deletions packages/keyring-internal-api/src/types.test.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import { assert } from '@metamask/superstruct';

import type { InternalAccount } from '.';
import { InternalAccountStruct } from '.';

describe('InternalAccount', () => {
it.each([
{ type: 'eip155:eoa', address: '0x000' },
{ type: 'eip155:eoa', address: '0x000', scopes: ['eip155'] },
{
type: 'bip122:p2wpkh',
address: 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4',
scopes: ['bip122:000000000019d6689c085ae165831e93'],
},
])('should have the correct structure: %s', ({ type, address }) => {
])('should have the correct structure: %s', ({ type, address, scopes }) => {
const account = {
id: '606a7759-b0fb-48e4-9874-bab62ff8e7eb',
address,
options: {},
methods: [],
scopes,
type,
metadata: {
keyring: {
Expand All @@ -34,6 +37,7 @@ describe('InternalAccount', () => {
address: '0x000',
options: {},
methods: [],
scopes: ['eip155'],
type: 'eip155:eoa',
metadata: {
keyring: {},
Expand All @@ -53,6 +57,7 @@ describe('InternalAccount', () => {
address: '0x000',
options: {},
methods: [],
scopes: ['eip155'],
type: 'eip155:eoa',
metadata: {
name: 'Account 1',
Expand All @@ -71,6 +76,7 @@ describe('InternalAccount', () => {
address: '0x000',
options: {},
methods: [],
scopes: ['eip155'],
type: 'eip155:eoa',
};

Expand All @@ -79,12 +85,27 @@ describe('InternalAccount', () => {
);
});

it('should throw if scopes is not set', () => {
const account = {
id: '606a7759-b0fb-48e4-9874-bab62ff8e7eb',
address: '0x000',
options: {},
methods: [],
type: 'eip155:eoa',
};

expect(() => assert(account, InternalAccountStruct)).toThrow(
'At path: scopes -- Expected an array value, but received: undefined',
);
});

it('should throw if there are extra fields', () => {
const account = {
id: '606a7759-b0fb-48e4-9874-bab62ff8e7eb',
address: '0x000',
options: {},
methods: [],
scopes: ['eip155'],
type: 'eip155:eoa',
metadata: {
keyring: {
Expand All @@ -102,12 +123,13 @@ describe('InternalAccount', () => {
});

it('should contain snap name, id and enabled if the snap metadata exists', () => {
const account = {
const account: InternalAccount = {
id: '606a7759-b0fb-48e4-9874-bab62ff8e7eb',
address: '0x000',
options: {},
methods: [],
type: 'eip155:eoa',
scopes: ['eip155'],
metadata: {
keyring: {
type: 'Test Keyring',
Expand All @@ -126,13 +148,14 @@ describe('InternalAccount', () => {
});

it.each([['name', 'enabled', 'id']])(
'should throw if snap.%i is not set',
'should throw if snap.%s is not set',
(key: string) => {
const account = {
const account: InternalAccount = {
id: '606a7759-b0fb-48e4-9874-bab62ff8e7eb',
address: '0x000',
options: {},
methods: [],
scopes: ['eip155'],
type: 'eip155:eoa',
metadata: {
keyring: {
Expand All @@ -148,7 +171,8 @@ describe('InternalAccount', () => {
},
};

delete account.metadata.snap[key as keyof typeof account.metadata.snap];
// On `InternalAccount` the `metadata.snap` is optional, hence the `?.` here.
delete account.metadata.snap?.[key as keyof typeof account.metadata.snap];

const regex = new RegExp(`At path: metadata.snap.${key}`, 'u');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe('KeyringSnapControllerClient', () => {
address: '0xE9A74AACd7df8112911ca93260fC5a046f8a64Ae',
options: {},
methods: [],
scopes: ['eip155'],
type: 'eip155:eoa',
},
];
Expand Down
1 change: 1 addition & 0 deletions packages/keyring-snap-bridge/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"@metamask/keyring-api": "workspace:^",
"@metamask/keyring-internal-api": "workspace:^",
"@metamask/keyring-internal-snap-client": "workspace:^",
"@metamask/keyring-utils": "workspace:^",
"@metamask/snaps-controllers": "^9.10.0",
"@metamask/snaps-sdk": "^6.7.0",
"@metamask/snaps-utils": "^8.3.0",
Expand Down
Loading
Loading