Skip to content

Commit 1f3f7a1

Browse files
committed
Implement snap_listEntropySources
1 parent 4b6dadb commit 1f3f7a1

File tree

8 files changed

+280
-3
lines changed

8 files changed

+280
-3
lines changed

packages/snaps-rpc-methods/jest.config.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ module.exports = deepmerge(baseConfig, {
1010
],
1111
coverageThreshold: {
1212
global: {
13-
branches: 95.09,
13+
branches: 95.11,
1414
functions: 98.61,
15-
lines: 98.8,
16-
statements: 98.47,
15+
lines: 98.81,
16+
statements: 98.49,
1717
},
1818
},
1919
});

packages/snaps-rpc-methods/src/permitted/handlers.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { getSnapsHandler } from './getSnaps';
1313
import { getStateHandler } from './getState';
1414
import { invokeKeyringHandler } from './invokeKeyring';
1515
import { invokeSnapSugarHandler } from './invokeSnapSugar';
16+
import { listEntropySourcesHandler } from './listEntropySources';
1617
import { requestSnapsHandler } from './requestSnaps';
1718
import { resolveInterfaceHandler } from './resolveInterface';
1819
import { scheduleBackgroundEventHandler } from './scheduleBackgroundEvent';
@@ -34,6 +35,7 @@ export const methodHandlers = {
3435
snap_updateInterface: updateInterfaceHandler,
3536
snap_getInterfaceState: getInterfaceStateHandler,
3637
snap_getInterfaceContext: getInterfaceContextHandler,
38+
snap_listEntropySources: listEntropySourcesHandler,
3739
snap_resolveInterface: resolveInterfaceHandler,
3840
snap_getCurrencyRate: getCurrencyRateHandler,
3941
snap_experimentalProviderRequest: providerRequestHandler,

packages/snaps-rpc-methods/src/permitted/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type { GetCurrencyRateMethodHooks } from './getCurrencyRate';
99
import type { GetInterfaceStateMethodHooks } from './getInterfaceState';
1010
import type { GetSnapsHooks } from './getSnaps';
1111
import type { GetStateHooks } from './getState';
12+
import type { ListEntropySourcesHooks } from './listEntropySources';
1213
import type { RequestSnapsHooks } from './requestSnaps';
1314
import type { ResolveInterfaceMethodHooks } from './resolveInterface';
1415
import type { ScheduleBackgroundEventMethodHooks } from './scheduleBackgroundEvent';
@@ -20,6 +21,7 @@ export type PermittedRpcMethodHooks = ClearStateHooks &
2021
GetClientStatusHooks &
2122
GetSnapsHooks &
2223
GetStateHooks &
24+
ListEntropySourcesHooks &
2325
RequestSnapsHooks &
2426
CreateInterfaceMethodHooks &
2527
UpdateInterfaceMethodHooks &
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import { JsonRpcEngine } from '@metamask/json-rpc-engine';
2+
import type {
3+
ListEntropySourcesParams,
4+
ListEntropySourcesResult,
5+
} from '@metamask/snaps-sdk';
6+
import type { JsonRpcRequest, PendingJsonRpcResponse } from '@metamask/utils';
7+
8+
import { listEntropySourcesHandler } from './listEntropySources';
9+
10+
describe('snap_listEntropySources', () => {
11+
describe('listEntropySourcesHandler', () => {
12+
it('has the expected shape', () => {
13+
expect(listEntropySourcesHandler).toMatchObject({
14+
methodNames: ['snap_listEntropySources'],
15+
implementation: expect.any(Function),
16+
hookNames: {
17+
hasPermission: true,
18+
getEntropySources: true,
19+
},
20+
});
21+
});
22+
});
23+
24+
describe('implementation', () => {
25+
it('returns the result from the `getEntropySources` hook', async () => {
26+
const { implementation } = listEntropySourcesHandler;
27+
28+
const getEntropySources = jest.fn().mockReturnValue([
29+
{
30+
name: 'Secret recovery phrase 1',
31+
id: 'foo',
32+
type: 'mnemonic',
33+
primary: false,
34+
},
35+
{
36+
name: 'Secret recovery phrase 2',
37+
id: 'bar',
38+
type: 'mnemonic',
39+
primary: false,
40+
},
41+
{
42+
name: 'Primary secret recovery phrase',
43+
id: 'baz',
44+
type: 'mnemonic',
45+
primary: true,
46+
},
47+
]);
48+
49+
const hooks = {
50+
hasPermission: jest.fn().mockReturnValue(true),
51+
getEntropySources,
52+
};
53+
54+
const engine = new JsonRpcEngine();
55+
56+
engine.push((request, response, next, end) => {
57+
const result = implementation(
58+
request as JsonRpcRequest<ListEntropySourcesParams>,
59+
response as PendingJsonRpcResponse<ListEntropySourcesResult>,
60+
next,
61+
end,
62+
hooks,
63+
);
64+
65+
result?.catch(end);
66+
});
67+
68+
const response = await engine.handle({
69+
jsonrpc: '2.0',
70+
id: 1,
71+
method: 'snap_listEntropySources',
72+
});
73+
74+
expect(response).toStrictEqual({
75+
jsonrpc: '2.0',
76+
id: 1,
77+
result: [
78+
{
79+
name: 'Secret recovery phrase 1',
80+
id: 'foo',
81+
type: 'mnemonic',
82+
primary: false,
83+
},
84+
{
85+
name: 'Secret recovery phrase 2',
86+
id: 'bar',
87+
type: 'mnemonic',
88+
primary: false,
89+
},
90+
{
91+
name: 'Primary secret recovery phrase',
92+
id: 'baz',
93+
type: 'mnemonic',
94+
primary: true,
95+
},
96+
],
97+
});
98+
});
99+
100+
it('returns an unauthorized error if the requesting origin does not have the required permission', async () => {
101+
const { implementation } = listEntropySourcesHandler;
102+
103+
const hooks = {
104+
hasPermission: jest.fn().mockReturnValue(false),
105+
getEntropySources: jest.fn(),
106+
};
107+
108+
const engine = new JsonRpcEngine();
109+
110+
engine.push((request, response, next, end) => {
111+
const result = implementation(
112+
request as JsonRpcRequest<ListEntropySourcesParams>,
113+
response as PendingJsonRpcResponse<ListEntropySourcesResult>,
114+
next,
115+
end,
116+
hooks,
117+
);
118+
119+
result?.catch(end);
120+
});
121+
122+
const response = await engine.handle({
123+
jsonrpc: '2.0',
124+
id: 1,
125+
method: 'snap_listEntropySources',
126+
});
127+
128+
expect(response).toStrictEqual({
129+
jsonrpc: '2.0',
130+
id: 1,
131+
error: {
132+
code: 4100,
133+
message:
134+
'The requested account and/or method has not been authorized by the user.',
135+
stack: expect.any(String),
136+
},
137+
});
138+
});
139+
});
140+
});
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import type { JsonRpcEngineEndCallback } from '@metamask/json-rpc-engine';
2+
import type { PermittedHandlerExport } from '@metamask/permission-controller';
3+
import { providerErrors } from '@metamask/rpc-errors';
4+
import type {
5+
EntropySource,
6+
JsonRpcRequest,
7+
ListEntropySourcesParams,
8+
ListEntropySourcesResult,
9+
} from '@metamask/snaps-sdk';
10+
import type { PendingJsonRpcResponse } from '@metamask/utils';
11+
12+
import { getBip32EntropyBuilder } from '../restricted/getBip32Entropy';
13+
import { getBip32PublicKeyBuilder } from '../restricted/getBip32PublicKey';
14+
import { getBip44EntropyBuilder } from '../restricted/getBip44Entropy';
15+
import { getEntropyBuilder } from '../restricted/getEntropy';
16+
import type { MethodHooksObject } from '../utils';
17+
18+
/**
19+
* A list of permissions that the requesting origin must have at least one of
20+
* in order to call this method.
21+
*/
22+
const REQUIRED_PERMISSIONS = [
23+
getBip32EntropyBuilder.targetName,
24+
getBip32PublicKeyBuilder.targetName,
25+
getBip44EntropyBuilder.targetName,
26+
getEntropyBuilder.targetName,
27+
];
28+
29+
const hookNames: MethodHooksObject<ListEntropySourcesHooks> = {
30+
hasPermission: true,
31+
getEntropySources: true,
32+
};
33+
34+
export type ListEntropySourcesHooks = {
35+
/**
36+
* Check if the requesting origin has a given permission.
37+
*
38+
* @param permissionName - The name of the permission to check.
39+
* @returns Whether the origin has the permission.
40+
*/
41+
hasPermission: (permissionName: string) => boolean;
42+
43+
/**
44+
* Get the entropy sources from the client.
45+
*
46+
* @returns The entropy sources.
47+
*/
48+
getEntropySources: () => EntropySource[];
49+
};
50+
51+
export const listEntropySourcesHandler: PermittedHandlerExport<
52+
ListEntropySourcesHooks,
53+
ListEntropySourcesParams,
54+
ListEntropySourcesResult
55+
> = {
56+
methodNames: ['snap_listEntropySources'],
57+
implementation: listEntropySourcesImplementation,
58+
hookNames,
59+
};
60+
61+
/**
62+
* The `snap_getInterfaceContext` method implementation.
63+
*
64+
* @param _request - The JSON-RPC request object. Not used by this function.
65+
* @param response - The JSON-RPC response object.
66+
* @param _next - The `json-rpc-engine` "next" callback. Not used by this
67+
* function.
68+
* @param end - The `json-rpc-engine` "end" callback.
69+
* @param hooks - The RPC method hooks.
70+
* @param hooks.hasPermission - The function to check if the origin has a
71+
* permission.
72+
* @param hooks.getEntropySources - The function to get the entropy sources.
73+
* @returns Noting.
74+
*/
75+
function listEntropySourcesImplementation(
76+
_request: JsonRpcRequest<ListEntropySourcesParams>,
77+
response: PendingJsonRpcResponse<ListEntropySourcesResult>,
78+
_next: unknown,
79+
end: JsonRpcEngineEndCallback,
80+
{ hasPermission, getEntropySources }: ListEntropySourcesHooks,
81+
): void {
82+
const isPermitted = REQUIRED_PERMISSIONS.some(hasPermission);
83+
if (!isPermitted) {
84+
return end(providerErrors.unauthorized());
85+
}
86+
87+
response.result = getEntropySources();
88+
return end();
89+
}

packages/snaps-sdk/src/types/methods/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export type * from './get-snaps';
1616
export type * from './get-state';
1717
export type * from './invoke-keyring';
1818
export type * from './invoke-snap';
19+
export type * from './list-entropy-sources';
1920
export type * from './manage-accounts';
2021
export * from './manage-state';
2122
export type * from './methods';
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* An entropy source that can be used to retrieve entropy using the
3+
* `snap_get*Entropy` methods.
4+
*/
5+
export type EntropySource = {
6+
/**
7+
* The name of the entropy source.
8+
*/
9+
name: string;
10+
11+
/**
12+
* The ID of the entropy source
13+
*/
14+
id: string;
15+
16+
/**
17+
* The type of the entropy source. Currently, only `mnemonic` is supported.
18+
*/
19+
type: 'mnemonic';
20+
21+
/**
22+
* Whether the entropy source is the primary source.
23+
*/
24+
primary: boolean;
25+
};
26+
27+
/**
28+
* The request parameters for the `snap_listEntropySources` method.
29+
*
30+
* @property snapId - The ID of the snap to invoke.
31+
* @property request - The JSON-RPC request to send to the snap.
32+
*/
33+
export type ListEntropySourcesParams = never;
34+
35+
/**
36+
* The result returned by the `snap_listEntropySources` method.
37+
*/
38+
export type ListEntropySourcesResult = EntropySource[];

packages/snaps-sdk/src/types/methods/methods.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ import type {
5454
InvokeKeyringResult,
5555
} from './invoke-keyring';
5656
import type { InvokeSnapParams, InvokeSnapResult } from './invoke-snap';
57+
import type {
58+
ListEntropySourcesParams,
59+
ListEntropySourcesResult,
60+
} from './list-entropy-sources';
5761
import type {
5862
ManageAccountsParams,
5963
ManageAccountsResult,
@@ -94,6 +98,7 @@ export type SnapMethods = {
9498
snap_getLocale: [GetLocaleParams, GetLocaleResult];
9599
snap_getPreferences: [GetPreferencesParams, GetPreferencesResult];
96100
snap_getState: [GetStateParams, GetStateResult];
101+
snap_listEntropySources: [ListEntropySourcesParams, ListEntropySourcesResult];
97102
snap_manageAccounts: [ManageAccountsParams, ManageAccountsResult];
98103
snap_manageState: [ManageStateParams, ManageStateResult];
99104
snap_notify: [NotifyParams, NotifyResult];

0 commit comments

Comments
 (0)