Skip to content

Commit 3f5dc4b

Browse files
authored
refactor!: add SnapKeyringRpc v2 (#582)
Adding the new `capabilities.snap` field + Re-importing some of the old methods from keyring API v1 back to v2, so we can use them using a v2-only keyring. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Expands the public keyring Snap RPC contract and changes handler/client typings (breaking for consumers expecting `KeyringRpc` only), though snap methods remain optional with v1 RPC aliases preserved. > > **Overview** > Introduces **`KeyringSnapRpc`** as the Snap-facing RPC contract: it extends base **`KeyringRpc`** with optional **`setSelectedAccounts`**, **`getAccountTransactions`**, **`getAccountAssets`**, and **`getAccountBalances`**, plus v2 JSON-RPC method names, request/response structs, and **`isKeyringSnapRpcMethod`**. v1 **`rpc.ts`** also gains matching **`KeyringRpc`** / **`KeyringSnapRpc`** interface types. > > v2 **`KeyringCapabilities`** gains an optional **`snap`** block (assets metadata/balances, transactions, selected accounts) aligned with those RPCs. > > **`keyring-snap-client`** v2 **`KeyringClient`** now implements **`KeyringSnapRpc`** and sends the four snap-specific methods. **`keyring-snap-sdk`** v1/v2 **`handleKeyringRequest`** handlers are typed against **`KeyringSnapRpc`** instead of the full **`Keyring`** / bare **`KeyringRpc`**; v2 dispatch adds the new methods and **forwards deprecated v1 `keyring_listAccountTransactions` / `keyring_listAccountAssets` to the new `get*` implementations**. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit f744e7b. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 8ac8519 commit 3f5dc4b

14 files changed

Lines changed: 914 additions & 23 deletions

File tree

packages/keyring-api/CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Add `KeyringRpc` and `KeyringSnapRpc` RPC interfaces to v1 ([#582](https://github.com/MetaMask/accounts/pull/582))
13+
- `KeyringRpc` covers the standard account-management and request-handling methods.
14+
- `KeyringSnapRpc` extends `KeyringRpc` with optional snap-specific methods: `listAccountAssets`, `listAccountTransactions`, `getAccountBalances`, and `setSelectedAccounts`.
15+
- Add `KeyringSnapRpc`, `KeyringSnapRpcMethod`, `isKeyringSnapRpcMethod`, and related request/response structs to keyring v2 (`./v2`) ([#582](https://github.com/MetaMask/accounts/pull/582))
16+
- `KeyringSnapRpc` extends `KeyringRpc` (v2) with four optional snap-specific methods: `setSelectedAccounts`, `getAccountTransactions`, `getAccountAssets`, and `getAccountBalances`.
17+
- `KeyringSnapRpcMethod` spreads all base `KeyringRpcMethod` values and adds the four snap-specific method names.
18+
- Add optional `snap` capability field to `KeyringCapabilities` (v2) ([#582](https://github.com/MetaMask/accounts/pull/582))
19+
- Flags: `snap.assets.metadata` (`keyring_getAccountAssets`), `snap.assets.balances` (`keyring_getAccountBalances`), `snap.transactions` (`keyring_getAccountTransactions`), `snap.selectedAccounts` (`keyring_setSelectedAccounts`).
20+
1021
## [23.3.0]
1122

1223
### Added

packages/keyring-api/src/rpc.ts

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { AccountId, JsonRpcRequest } from '@metamask/keyring-utils';
12
import {
23
object,
34
UuidStruct,
@@ -14,7 +15,25 @@ import {
1415
union,
1516
} from '@metamask/superstruct';
1617
import { JsonStruct } from '@metamask/utils';
17-
18+
import type { Json } from '@metamask/utils';
19+
20+
import type {
21+
Balance,
22+
CaipAssetType,
23+
CaipAssetTypeOrId,
24+
CaipChainId,
25+
DiscoveredAccount,
26+
EntropySourceId,
27+
KeyringAccount,
28+
KeyringAccountData,
29+
KeyringRequest,
30+
KeyringResponse,
31+
MetaMaskOptions,
32+
Paginated,
33+
Pagination,
34+
ResolvedAccountAddress,
35+
Transaction,
36+
} from './api';
1837
import {
1938
CaipAssetTypeStruct,
2039
CaipAssetTypeOrIdStruct,
@@ -29,6 +48,7 @@ import {
2948
CaipAccountIdStruct,
3049
DiscoveredAccountStruct,
3150
} from './api';
51+
import type { CreateAccountOptions } from './v2/api/create-account';
3252
import { CreateAccountOptionsStruct } from './v2/api/create-account';
3353

3454
/**
@@ -436,3 +456,55 @@ export type RejectRequestRequest = Infer<typeof RejectRequestRequestStruct>;
436456
export const RejectRequestResponseStruct = literal(null);
437457

438458
export type RejectRequestResponse = Infer<typeof RejectRequestResponseStruct>;
459+
460+
// ----------------------------------------------------------------------------
461+
// Keyring RPC interfaces
462+
463+
/**
464+
* Keyring RPC interface - all standard account management and request
465+
* handling methods.
466+
*/
467+
export type KeyringRpc = {
468+
listAccounts(): Promise<KeyringAccount[]>;
469+
getAccount(id: string): Promise<KeyringAccount | undefined>;
470+
createAccount(
471+
options?: Record<string, Json> & MetaMaskOptions,
472+
): Promise<KeyringAccount>;
473+
createAccounts?(options: CreateAccountOptions): Promise<KeyringAccount[]>;
474+
discoverAccounts?(
475+
scopes: CaipChainId[],
476+
entropySource: EntropySourceId,
477+
groupIndex: number,
478+
): Promise<DiscoveredAccount[]>;
479+
filterAccountChains(id: string, chains: string[]): Promise<string[]>;
480+
updateAccount(account: KeyringAccount): Promise<void>;
481+
deleteAccount(id: string): Promise<void>;
482+
exportAccount?(id: string): Promise<KeyringAccountData>;
483+
listRequests?(): Promise<KeyringRequest[]>;
484+
getRequest?(id: string): Promise<KeyringRequest | undefined>;
485+
submitRequest(request: KeyringRequest): Promise<KeyringResponse>;
486+
approveRequest?(id: string, data?: Record<string, Json>): Promise<void>;
487+
rejectRequest?(id: string): Promise<void>;
488+
resolveAccountAddress?(
489+
scope: CaipChainId,
490+
request: JsonRpcRequest,
491+
): Promise<ResolvedAccountAddress | null>;
492+
};
493+
494+
/**
495+
* Snap keyring RPC interface - extends {@link KeyringRpc} with optional
496+
* snap-specific methods for assets, transactions, balances, and account
497+
* selection.
498+
*/
499+
export type KeyringSnapRpc = KeyringRpc & {
500+
listAccountAssets?(id: string): Promise<CaipAssetTypeOrId[]>;
501+
listAccountTransactions?(
502+
id: string,
503+
pagination: Pagination,
504+
): Promise<Paginated<Transaction>>;
505+
getAccountBalances?(
506+
id: string,
507+
assets: CaipAssetType[],
508+
): Promise<Record<CaipAssetType, Balance>>;
509+
setSelectedAccounts?(accounts: AccountId[]): Promise<void>;
510+
};

packages/keyring-api/src/v2/api/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export type * from './keyring';
22
export * from './keyring-capabilities';
33
export * from './keyring-type';
44
export * from './keyring-rpc';
5+
export * from './keyring-snap-rpc';
56
export * from './create-account';
67
export * from './export-account';
78
export * from './private-key';

packages/keyring-api/src/v2/api/keyring-capabilities.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,42 @@ export const KeyringCapabilitiesStruct = object({
7474
}),
7575
),
7676
),
77+
/**
78+
* Snap-specific capabilities supported by this keyring.
79+
*
80+
* Each flag maps to a Snap keyring RPC method that the Snap implements.
81+
*/
82+
snap: exactOptional(
83+
object({
84+
/**
85+
* Asset-related capabilities.
86+
*/
87+
assets: exactOptional(
88+
object({
89+
/**
90+
* Whether the keyring supports getting account assets
91+
* (`keyring_getAccountAssets`).
92+
*/
93+
metadata: exactOptional(boolean()),
94+
/**
95+
* Whether the keyring supports getting account balances
96+
* (`keyring_getAccountBalances`).
97+
*/
98+
balances: exactOptional(boolean()),
99+
}),
100+
),
101+
/**
102+
* Whether the keyring supports getting account transactions
103+
* (`keyring_getAccountTransactions`).
104+
*/
105+
transactions: exactOptional(boolean()),
106+
/**
107+
* Whether the keyring supports receiving selected accounts notifications
108+
* (`keyring_setSelectedAccounts`).
109+
*/
110+
selectedAccounts: exactOptional(boolean()),
111+
}),
112+
),
77113
});
78114

79115
/**
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { KeyringSnapRpcMethod, isKeyringSnapRpcMethod } from '.';
2+
3+
describe('isKeyringSnapRpcMethod', () => {
4+
it.each(Object.values(KeyringSnapRpcMethod))(
5+
'returns true for: "%s"',
6+
(method) => {
7+
expect(isKeyringSnapRpcMethod(method)).toBe(true);
8+
},
9+
);
10+
11+
it('returns false for unknown method', () => {
12+
expect(isKeyringSnapRpcMethod('keyring_unknownMethod')).toBe(false);
13+
});
14+
});
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
import type { AccountId } from '@metamask/keyring-utils';
2+
import { UuidStruct } from '@metamask/keyring-utils';
3+
import type { Infer } from '@metamask/superstruct';
4+
import {
5+
array,
6+
literal,
7+
number,
8+
object,
9+
record,
10+
string,
11+
union,
12+
} from '@metamask/superstruct';
13+
14+
import { BalanceStruct } from '../../api/balance';
15+
import type { Balance } from '../../api/balance';
16+
import { CaipAssetTypeOrIdStruct, CaipAssetTypeStruct } from '../../api/caip';
17+
import type { CaipAssetType, CaipAssetTypeOrId } from '../../api/caip';
18+
import { PaginationStruct } from '../../api/pagination';
19+
import type { Pagination } from '../../api/pagination';
20+
import { TransactionsPageStruct } from '../../api/transaction';
21+
import type { TransactionsPage } from '../../api/transaction';
22+
import { KeyringRpcMethod } from './keyring-rpc';
23+
import type { KeyringRpc, KeyringRpcRequests } from './keyring-rpc';
24+
25+
/**
26+
* All keyring RPC methods available to a Snap - includes the base
27+
* {@link KeyringRpcMethod} set plus snap-specific extensions.
28+
*/
29+
export const KeyringSnapRpcMethod = {
30+
...KeyringRpcMethod,
31+
SetSelectedAccounts: 'keyring_setSelectedAccounts',
32+
GetAccountTransactions: 'keyring_getAccountTransactions',
33+
GetAccountAssets: 'keyring_getAccountAssets',
34+
GetAccountBalances: 'keyring_getAccountBalances',
35+
} as const;
36+
37+
/**
38+
* All keyring RPC methods available to a Snap.
39+
*/
40+
export type KeyringSnapRpcMethod =
41+
(typeof KeyringSnapRpcMethod)[keyof typeof KeyringSnapRpcMethod];
42+
43+
/**
44+
* Check if a method is a Snap keyring RPC method (v2).
45+
*
46+
* @param method - Method to check.
47+
* @returns Whether the method is a Snap keyring RPC method (v2).
48+
*/
49+
export function isKeyringSnapRpcMethod(
50+
method: string,
51+
): method is KeyringSnapRpcMethod {
52+
return Object.values(KeyringSnapRpcMethod).includes(
53+
method as KeyringSnapRpcMethod,
54+
);
55+
}
56+
57+
// ----------------------------------------------------------------------------
58+
59+
const CommonHeader = {
60+
jsonrpc: literal('2.0'),
61+
id: union([string(), number(), literal(null)]),
62+
};
63+
64+
// ----------------------------------------------------------------------------
65+
// Set selected accounts
66+
67+
export const SetSelectedAccountsRequestStruct = object({
68+
...CommonHeader,
69+
method: literal(`${KeyringSnapRpcMethod.SetSelectedAccounts}`),
70+
params: object({
71+
accounts: array(string()),
72+
}),
73+
});
74+
75+
export type SetSelectedAccountsRequest = Infer<
76+
typeof SetSelectedAccountsRequestStruct
77+
>;
78+
79+
export const SetSelectedAccountsResponseStruct = literal(null);
80+
81+
export type SetSelectedAccountsResponse = Infer<
82+
typeof SetSelectedAccountsResponseStruct
83+
>;
84+
85+
// ----------------------------------------------------------------------------
86+
// Get account transactions
87+
88+
export const GetAccountTransactionsRequestStruct = object({
89+
...CommonHeader,
90+
method: literal(`${KeyringSnapRpcMethod.GetAccountTransactions}`),
91+
params: object({
92+
id: UuidStruct,
93+
pagination: PaginationStruct,
94+
}),
95+
});
96+
97+
export type GetAccountTransactionsRequest = Infer<
98+
typeof GetAccountTransactionsRequestStruct
99+
>;
100+
101+
export const GetAccountTransactionsResponseStruct = TransactionsPageStruct;
102+
103+
export type GetAccountTransactionsResponse = Infer<
104+
typeof GetAccountTransactionsResponseStruct
105+
>;
106+
107+
// ----------------------------------------------------------------------------
108+
// Get account assets
109+
110+
export const GetAccountAssetsRequestStruct = object({
111+
...CommonHeader,
112+
method: literal(`${KeyringSnapRpcMethod.GetAccountAssets}`),
113+
params: object({
114+
id: UuidStruct,
115+
}),
116+
});
117+
118+
export type GetAccountAssetsRequest = Infer<
119+
typeof GetAccountAssetsRequestStruct
120+
>;
121+
122+
export const GetAccountAssetsResponseStruct = array(CaipAssetTypeOrIdStruct);
123+
124+
export type GetAccountAssetsResponse = Infer<
125+
typeof GetAccountAssetsResponseStruct
126+
>;
127+
128+
// ----------------------------------------------------------------------------
129+
// Get account balances
130+
131+
export const GetAccountBalancesRequestStruct = object({
132+
...CommonHeader,
133+
method: literal(`${KeyringSnapRpcMethod.GetAccountBalances}`),
134+
params: object({
135+
id: UuidStruct,
136+
assets: array(CaipAssetTypeStruct),
137+
}),
138+
});
139+
140+
export type GetAccountBalancesRequest = Infer<
141+
typeof GetAccountBalancesRequestStruct
142+
>;
143+
144+
export const GetAccountBalancesResponseStruct = record(
145+
CaipAssetTypeStruct,
146+
BalanceStruct,
147+
);
148+
149+
export type GetAccountBalancesResponse = Infer<
150+
typeof GetAccountBalancesResponseStruct
151+
>;
152+
153+
// ----------------------------------------------------------------------------
154+
155+
/**
156+
* All keyring RPC requests available to a Snap - includes base
157+
* {@link KeyringRpcRequests} plus snap-specific request types.
158+
*/
159+
export type KeyringSnapRpcRequests =
160+
| KeyringRpcRequests
161+
| SetSelectedAccountsRequest
162+
| GetAccountTransactionsRequest
163+
| GetAccountAssetsRequest
164+
| GetAccountBalancesRequest;
165+
166+
/**
167+
* Extract the proper request type for a given {@link KeyringSnapRpcMethod}.
168+
*/
169+
export type KeyringSnapRpcRequest<RpcMethod extends KeyringSnapRpcMethod> =
170+
Extract<KeyringSnapRpcRequests, { method: `${RpcMethod}` }>;
171+
172+
// ----------------------------------------------------------------------------
173+
174+
/**
175+
* Snap keyring RPC interface - extends the base {@link KeyringRpc} with
176+
* optional snap-specific methods that a Snap may expose.
177+
*/
178+
export type KeyringSnapRpc = KeyringRpc & {
179+
/**
180+
* Notify the Snap of the currently selected accounts.
181+
* Maps to `keyring_setSelectedAccounts`.
182+
*/
183+
setSelectedAccounts?: (accounts: AccountId[]) => Promise<void>;
184+
185+
/**
186+
* Get transactions for an account with pagination.
187+
* Maps to `keyring_getAccountTransactions`.
188+
*/
189+
getAccountTransactions?: (
190+
id: AccountId,
191+
pagination: Pagination,
192+
) => Promise<TransactionsPage>;
193+
194+
/**
195+
* Get the asset types supported by an account.
196+
* Maps to `keyring_getAccountAssets`.
197+
*/
198+
getAccountAssets?: (id: AccountId) => Promise<CaipAssetTypeOrId[]>;
199+
200+
/**
201+
* Get balances for an account for the requested asset types.
202+
* Maps to `keyring_getAccountBalances`.
203+
*/
204+
getAccountBalances?: (
205+
id: AccountId,
206+
assets: CaipAssetType[],
207+
) => Promise<Record<CaipAssetType, Balance>>;
208+
};

0 commit comments

Comments
 (0)