Skip to content

Commit ac17fb8

Browse files
committed
Add snap_getState, snap_setState, snap_clearState methods
1 parent 51ab913 commit ac17fb8

File tree

29 files changed

+1117
-35
lines changed

29 files changed

+1117
-35
lines changed

packages/examples/packages/manage-state/src/index.test.ts

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ describe('onRpcRequest', () => {
2020
});
2121
});
2222

23-
describe('setState', () => {
23+
describe('legacy_legacy_setState', () => {
2424
it('sets the state to the params', async () => {
2525
const { request } = await installSnap();
2626

2727
expect(
2828
await request({
29-
method: 'setState',
29+
method: 'legacy_legacy_setState',
3030
params: {
3131
items: ['foo'],
3232
},
@@ -35,7 +35,7 @@ describe('onRpcRequest', () => {
3535

3636
expect(
3737
await request({
38-
method: 'getState',
38+
method: 'legacy_getState',
3939
}),
4040
).toRespondWith({
4141
items: ['foo'],
@@ -47,7 +47,7 @@ describe('onRpcRequest', () => {
4747

4848
expect(
4949
await request({
50-
method: 'setState',
50+
method: 'legacy_legacy_setState',
5151
params: {
5252
items: ['foo'],
5353
encrypted: false,
@@ -57,15 +57,15 @@ describe('onRpcRequest', () => {
5757

5858
expect(
5959
await request({
60-
method: 'getState',
60+
method: 'legacy_getState',
6161
}),
6262
).toRespondWith({
6363
items: [],
6464
});
6565

6666
expect(
6767
await request({
68-
method: 'getState',
68+
method: 'legacy_getState',
6969
params: {
7070
encrypted: false,
7171
},
@@ -76,12 +76,12 @@ describe('onRpcRequest', () => {
7676
});
7777
});
7878

79-
describe('getState', () => {
79+
describe('legacy_getState', () => {
8080
it('returns the state if no state has been set', async () => {
8181
const { request } = await installSnap();
8282

8383
const response = await request({
84-
method: 'getState',
84+
method: 'legacy_getState',
8585
});
8686

8787
expect(response).toRespondWith({
@@ -93,14 +93,14 @@ describe('onRpcRequest', () => {
9393
const { request } = await installSnap();
9494

9595
await request({
96-
method: 'setState',
96+
method: 'legacy_setState',
9797
params: {
9898
items: ['foo'],
9999
},
100100
});
101101

102102
const response = await request({
103-
method: 'getState',
103+
method: 'legacy_getState',
104104
});
105105

106106
expect(response).toRespondWith({
@@ -118,7 +118,7 @@ describe('onRpcRequest', () => {
118118
});
119119

120120
const response = await request({
121-
method: 'getState',
121+
method: 'legacy_getState',
122122
params: {
123123
encrypted: false,
124124
},
@@ -130,26 +130,26 @@ describe('onRpcRequest', () => {
130130
});
131131
});
132132

133-
describe('clearState', () => {
133+
describe('legacy_clearState', () => {
134134
it('clears the state', async () => {
135135
const { request } = await installSnap();
136136

137137
await request({
138-
method: 'setState',
138+
method: 'legacy_setState',
139139
params: {
140140
items: ['foo'],
141141
},
142142
});
143143

144144
expect(
145145
await request({
146-
method: 'clearState',
146+
method: 'legacy_clearState',
147147
}),
148148
).toRespondWith(true);
149149

150150
expect(
151151
await request({
152-
method: 'getState',
152+
method: 'legacy_getState',
153153
}),
154154
).toRespondWith({
155155
items: [],
@@ -160,7 +160,7 @@ describe('onRpcRequest', () => {
160160
const { request } = await installSnap();
161161

162162
await request({
163-
method: 'setState',
163+
method: 'legacy_setState',
164164
params: {
165165
items: ['foo'],
166166
encrypted: false,
@@ -169,7 +169,7 @@ describe('onRpcRequest', () => {
169169

170170
expect(
171171
await request({
172-
method: 'clearState',
172+
method: 'legacy_clearState',
173173
params: {
174174
encrypted: false,
175175
},
@@ -178,7 +178,7 @@ describe('onRpcRequest', () => {
178178

179179
expect(
180180
await request({
181-
method: 'getState',
181+
method: 'legacy_getState',
182182
params: {
183183
encrypted: false,
184184
},

packages/examples/packages/manage-state/src/index.ts

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
type OnRpcRequestHandler,
44
} from '@metamask/snaps-sdk';
55

6-
import type { BaseParams, SetStateParams } from './types';
6+
import type { BaseParams, LegacySetStateParams, SetStateParams } from './types';
77
import { clearState, getState, setState } from './utils';
88

99
/**
@@ -34,21 +34,55 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => {
3434
switch (request.method) {
3535
case 'setState': {
3636
const params = request.params as SetStateParams;
37+
return await snap.request({
38+
method: 'snap_setState',
39+
params: {
40+
key: params?.key,
41+
value: params?.value,
42+
encrypted: params?.encrypted,
43+
},
44+
});
45+
}
3746

47+
case 'getState': {
48+
const params = request.params as BaseParams;
49+
return await snap.request({
50+
method: 'snap_getState',
51+
params: {
52+
key: params?.key,
53+
encrypted: params?.encrypted,
54+
},
55+
});
56+
}
57+
58+
case 'clearState': {
59+
const params = request.params as BaseParams;
60+
return await snap.request({
61+
method: 'snap_clearState',
62+
params: {
63+
encrypted: params?.encrypted,
64+
},
65+
});
66+
}
67+
68+
case 'legacy_setState': {
69+
const params = request.params as LegacySetStateParams;
3870
if (params.items) {
3971
await setState({ items: params.items }, params.encrypted);
4072
}
73+
4174
return true;
4275
}
4376

44-
case 'getState': {
77+
case 'legacy_getState': {
4578
const params = request.params as BaseParams;
46-
return await getState(params?.encrypted);
79+
return await getState(params.encrypted);
4780
}
4881

49-
case 'clearState': {
82+
case 'legacy_clearState': {
5083
const params = request.params as BaseParams;
5184
await clearState(params?.encrypted);
85+
5286
return true;
5387
}
5488

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1+
import type { Json } from '@metamask/utils';
2+
13
import type { State } from './utils';
24

3-
export type BaseParams = { encrypted?: boolean };
5+
export type BaseParams = { key?: string; encrypted?: boolean };
46

57
/**
68
* The parameters for the `setState` JSON-RPC method.
7-
*
8-
* The current state will be merged with the new state.
99
*/
10-
export type SetStateParams = BaseParams & Partial<State>;
10+
export type SetStateParams = BaseParams & {
11+
value: Json;
12+
};
13+
14+
/**
15+
* The parameters for the `legacy_setState` JSON-RPC method.
16+
*/
17+
export type LegacySetStateParams = BaseParams & Partial<State>;
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import type { JsonRpcEngineEndCallback } from '@metamask/json-rpc-engine';
2+
import type { PermittedHandlerExport } from '@metamask/permission-controller';
3+
import { providerErrors, rpcErrors } from '@metamask/rpc-errors';
4+
import type { ClearStateParams, ClearStateResult } from '@metamask/snaps-sdk';
5+
import { type InferMatching } from '@metamask/snaps-utils';
6+
import {
7+
boolean,
8+
create,
9+
object,
10+
optional,
11+
StructError,
12+
} from '@metamask/superstruct';
13+
import type { PendingJsonRpcResponse, JsonRpcRequest } from '@metamask/utils';
14+
15+
import type { MethodHooksObject } from '../utils';
16+
17+
const hookNames: MethodHooksObject<ClearStateHooks> = {
18+
clearSnapState: true,
19+
hasPermission: true,
20+
};
21+
22+
/**
23+
* `snap_clearState` clears the state of the Snap.
24+
*/
25+
export const clearStateHandler: PermittedHandlerExport<
26+
ClearStateHooks,
27+
ClearStateParameters,
28+
ClearStateResult
29+
> = {
30+
methodNames: ['snap_clearState'],
31+
implementation: clearStateImplementation,
32+
hookNames,
33+
};
34+
35+
export type ClearStateHooks = {
36+
/**
37+
* A function that clears the state of the requesting Snap.
38+
*/
39+
clearSnapState: (snapId: string, encrypted: boolean) => void;
40+
41+
/**
42+
* Check if the requesting origin has a given permission.
43+
*
44+
* @param permissionName - The name of the permission to check.
45+
* @returns Whether the origin has the permission.
46+
*/
47+
hasPermission: (permissionName: string) => boolean;
48+
};
49+
50+
const ClearStateParametersStruct = object({
51+
encrypted: optional(boolean()),
52+
});
53+
54+
export type ClearStateParameters = InferMatching<
55+
typeof ClearStateParametersStruct,
56+
ClearStateParams
57+
>;
58+
59+
/**
60+
* The `snap_clearState` method implementation.
61+
*
62+
* @param request - The JSON-RPC request object.
63+
* @param response - The JSON-RPC response object.
64+
* @param _next - The `json-rpc-engine` "next" callback. Not used by this
65+
* function.
66+
* @param end - The `json-rpc-engine` "end" callback.
67+
* @param hooks - The RPC method hooks.
68+
* @param hooks.clearSnapState - A function that clears the state of the
69+
* requesting Snap.
70+
* @param hooks.hasPermission - Check whether a given origin has a given
71+
* permission.
72+
* @returns Nothing.
73+
*/
74+
async function clearStateImplementation(
75+
request: JsonRpcRequest<ClearStateParameters>,
76+
response: PendingJsonRpcResponse<ClearStateResult>,
77+
_next: unknown,
78+
end: JsonRpcEngineEndCallback,
79+
{ clearSnapState, hasPermission }: ClearStateHooks,
80+
): Promise<void> {
81+
const { params } = request;
82+
83+
if (!hasPermission('snap_manageState')) {
84+
return end(providerErrors.unauthorized());
85+
}
86+
87+
try {
88+
const validatedParams = getValidatedParams(params);
89+
const { encrypted = true } = validatedParams;
90+
91+
// We expect the MM middleware stack to always add the origin to requests
92+
const { origin } = request as JsonRpcRequest & { origin: string };
93+
clearSnapState(origin, encrypted);
94+
95+
response.result = null;
96+
} catch (error) {
97+
return end(error);
98+
}
99+
100+
return end();
101+
}
102+
103+
/**
104+
* Validate the parameters of the `snap_clearState` method.
105+
*
106+
* @param params - The parameters to validate.
107+
* @returns The validated parameters.
108+
*/
109+
function getValidatedParams(params?: unknown) {
110+
try {
111+
return create(params, ClearStateParametersStruct);
112+
} catch (error) {
113+
if (error instanceof StructError) {
114+
throw rpcErrors.invalidParams({
115+
message: `Invalid params: ${error.message}.`,
116+
});
117+
}
118+
119+
throw error;
120+
}
121+
}

0 commit comments

Comments
 (0)