-
Notifications
You must be signed in to change notification settings - Fork 542
/
invokeSnap.ts
125 lines (112 loc) · 3.66 KB
/
invokeSnap.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import {
PermissionSpecificationBuilder,
RestrictedMethodOptions,
ValidPermissionSpecification,
PermissionType,
} from '@metamask/permission-controller';
import {
Snap,
SNAP_PREFIX,
SnapId,
HandlerType,
SnapRpcHookArgs,
} from '@metamask/snaps-utils';
import { isJsonRpcRequest, Json, NonEmptyArray } from '@metamask/utils';
import { ethErrors } from 'eth-rpc-errors';
import { nanoid } from 'nanoid';
const methodPrefix = SNAP_PREFIX;
const targetKey = `${methodPrefix}*` as const;
export type InvokeSnapMethodHooks = {
getSnap: (snapId: SnapId) => Snap | undefined;
handleSnapRpcRequest: ({
snapId,
origin,
handler,
request,
}: SnapRpcHookArgs & { snapId: SnapId }) => Promise<unknown>;
};
type InvokeSnapSpecificationBuilderOptions = {
allowedCaveats?: Readonly<NonEmptyArray<string>> | null;
methodHooks: InvokeSnapMethodHooks;
};
type InvokeSnapSpecification = ValidPermissionSpecification<{
permissionType: PermissionType.RestrictedMethod;
targetKey: typeof targetKey;
methodImplementation: ReturnType<typeof getInvokeSnapImplementation>;
allowedCaveats: Readonly<NonEmptyArray<string>> | null;
}>;
/**
* The specification builder for the `wallet_snap_*` permission.
*
* `wallet_snap_*` attempts to invoke an RPC method of the specified Snap.
*
* Requesting its corresponding permission will attempt to connect to the Snap,
* and install it if it's not avaialble yet.
*
* @param options - The specification builder options.
* @param options.allowedCaveats - The optional allowed caveats for the permission.
* @param options.methodHooks - The RPC method hooks needed by the method implementation.
* @returns The specification for the `wallet_snap_*` permission.
*/
const specificationBuilder: PermissionSpecificationBuilder<
PermissionType.RestrictedMethod,
InvokeSnapSpecificationBuilderOptions,
InvokeSnapSpecification
> = ({
allowedCaveats = null,
methodHooks,
}: InvokeSnapSpecificationBuilderOptions) => {
return {
permissionType: PermissionType.RestrictedMethod,
targetKey,
allowedCaveats,
methodImplementation: getInvokeSnapImplementation(methodHooks),
};
};
export const invokeSnapBuilder = Object.freeze({
targetKey,
specificationBuilder,
methodHooks: {
getSnap: true,
handleSnapRpcRequest: true,
},
} as const);
/**
* Builds the method implementation for `wallet_snap_*`.
*
* @param hooks - The RPC method hooks.
* @param hooks.getSnap - A function that retrieves all information stored about a snap.
* @param hooks.handleSnapRpcRequest - A function that sends an RPC request to a snap's RPC handler or throws if that fails.
* @returns The method implementation which returns the result of `handleSnapRpcRequest`.
* @throws If the params are invalid.
*/
export function getInvokeSnapImplementation({
getSnap,
handleSnapRpcRequest,
}: InvokeSnapMethodHooks) {
return async function invokeSnap(
options: RestrictedMethodOptions<Record<string, Json>>,
): Promise<Json> {
const { params = {}, method, context } = options;
const request = { jsonrpc: '2.0', id: nanoid(), ...params };
if (!isJsonRpcRequest(request)) {
throw ethErrors.rpc.invalidParams({
message:
'Must specify a valid JSON-RPC request object as single parameter.',
});
}
const snapId = method.slice(SNAP_PREFIX.length);
if (!getSnap(snapId)) {
throw ethErrors.rpc.invalidRequest({
message: `The snap "${snapId}" is not installed. This is a bug, please report it.`,
});
}
const { origin } = context;
return (await handleSnapRpcRequest({
snapId,
origin,
request,
handler: HandlerType.OnRpcRequest,
})) as Json;
};
}