-
Notifications
You must be signed in to change notification settings - Fork 542
/
namespace.ts
298 lines (264 loc) · 8.14 KB
/
namespace.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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
import {
array,
Infer,
is,
object,
optional,
pattern,
record,
size,
string,
omit,
assign,
partial,
pick,
} from 'superstruct';
import { JsonRpcRequestStruct } from '@metamask/utils';
import { AssertionErrorConstructor, assertStruct } from './assert';
export const CHAIN_ID_REGEX =
/^(?<namespace>[-a-z0-9]{3,8}):(?<reference>[-a-zA-Z0-9]{1,32})$/u;
export const ACCOUNT_ID_REGEX =
/^(?<chainId>(?<namespace>[-a-z0-9]{3,8}):(?<reference>[-a-zA-Z0-9]{1,32})):(?<accountAddress>[a-zA-Z0-9]{1,64})$/u;
/**
* Parse a chain ID string to an object containing the namespace and reference.
* This validates the chain ID before parsing it.
*
* @param chainId - The chain ID to validate and parse.
* @returns The parsed chain ID.
*/
export function parseChainId(chainId: ChainId): {
namespace: NamespaceId;
reference: string;
} {
const match = CHAIN_ID_REGEX.exec(chainId);
if (!match?.groups) {
throw new Error('Invalid chain ID.');
}
return {
namespace: match.groups.namespace,
reference: match.groups.reference,
};
}
/**
* Parse an account ID to an object containing the chain, chain ID and address.
* This validates the account ID before parsing it.
*
* @param accountId - The account ID to validate and parse.
* @returns The parsed account ID.
*/
export function parseAccountId(accountId: AccountId): {
chain: { namespace: NamespaceId; reference: string };
chainId: ChainId;
address: string;
} {
const match = ACCOUNT_ID_REGEX.exec(accountId);
if (!match?.groups) {
throw new Error('Invalid account ID.');
}
return {
address: match.groups.accountAddress,
chainId: match.groups.chainId as ChainId,
chain: {
namespace: match.groups.namespace,
reference: match.groups.reference,
},
};
}
/**
* A helper struct for a string with a minimum length of 1 and a maximum length
* of 40.
*/
export const LimitedString = size(string(), 1, 40);
/**
* A CAIP-2 chain ID, i.e., a human-readable namespace and reference.
*/
export const ChainIdStruct = pattern(string(), CHAIN_ID_REGEX);
export type ChainId = `${string}:${string}`;
export const AccountIdStruct = pattern(string(), ACCOUNT_ID_REGEX);
export type AccountId = `${ChainId}:${string}`;
export const AccountIdArrayStruct = array(AccountIdStruct);
/**
* A chain descriptor.
*/
export const ChainStruct = object({
id: ChainIdStruct,
name: LimitedString,
});
export type Chain = Infer<typeof ChainStruct>;
export const NamespaceStruct = object({
/**
* A list of supported chains in the namespace.
*/
chains: array(ChainStruct),
/**
* A list of supported RPC methods on the namespace, that a DApp can call.
*/
methods: optional(array(LimitedString)),
/**
* A list of supported RPC events on the namespace, that a DApp can listen to.
*/
events: optional(array(LimitedString)),
});
export type Namespace = Infer<typeof NamespaceStruct>;
export const RequestNamespaceStruct = assign(
omit(NamespaceStruct, ['chains']),
object({ chains: array(ChainIdStruct) }),
);
export type RequestNamespace = Infer<typeof RequestNamespaceStruct>;
export const SessionNamespaceStruct = assign(
RequestNamespaceStruct,
object({ accounts: array(AccountIdStruct) }),
);
export type SessionNamespace = Infer<typeof SessionNamespaceStruct>;
/**
* A CAIP-2 namespace, i.e., the first part of a chain ID.
*/
export const NamespaceIdStruct = pattern(string(), /^[-a-z0-9]{3,8}$/u);
export type NamespaceId = Infer<typeof NamespaceIdStruct>;
/**
* An object mapping CAIP-2 namespaces to their values.
*/
export const NamespacesStruct = record(NamespaceIdStruct, NamespaceStruct);
export type Namespaces = Infer<typeof NamespacesStruct>;
export const SessionStruct = object({
namespaces: record(NamespaceIdStruct, SessionNamespaceStruct),
});
export type Session = Infer<typeof SessionStruct>;
/**
* Asserts that the given value is a valid {@link Session}.
*
* @param value - The value to assert.
* @throws If the value is not a valid {@link Session}.
*/
export function assertIsSession(value: unknown): asserts value is Session {
assertStruct(value, SessionStruct, 'Invalid session');
}
export const ConnectArgumentsStruct = object({
requiredNamespaces: record(NamespaceIdStruct, RequestNamespaceStruct),
});
export type ConnectArguments = Infer<typeof ConnectArgumentsStruct>;
export const RequestArgumentsStruct = assign(
partial(pick(JsonRpcRequestStruct, ['id', 'jsonrpc'])),
omit(JsonRpcRequestStruct, ['id', 'jsonrpc']),
);
export type RequestArguments = Infer<typeof RequestArgumentsStruct>;
export const MultiChainRequestStruct = object({
chainId: ChainIdStruct,
request: RequestArgumentsStruct,
});
export type MultiChainRequest = Infer<typeof MultiChainRequestStruct>;
/**
* Check if the given value is a CAIP-2 namespace ID.
*
* @param value - The value to check.
* @returns Whether the value is a CAIP-2 namespace ID.
*/
export function isNamespaceId(value: unknown): value is NamespaceId {
return is(value, NamespaceIdStruct);
}
/**
* Check if the given value is a CAIP-2 chain ID.
*
* @param value - The value to check.
* @returns Whether the value is a CAIP-2 chain ID.
*/
export function isChainId(value: unknown): value is ChainId {
return is(value, ChainIdStruct);
}
/**
* Check if the given value is a CAIP-10 account ID.
*
* @param value - The value to check.
* @returns Whether the value is a CAIP-10 account ID.
*/
export function isAccountId(value: unknown): value is AccountId {
return is(value, AccountIdStruct);
}
/**
* Check if the given value is an array of CAIP-10 account IDs.
*
* @param value - The value to check.
* @returns Whether the value is an array of CAIP-10 account IDs.
*/
export function isAccountIdArray(value: unknown): value is AccountId[] {
return is(value, AccountIdArrayStruct);
}
/**
* Check if the given value is a {@link ConnectArguments} object.
*
* @param value - The value to check.
* @returns Whether the value is a valid {@link ConnectArguments} object.
*/
export function isConnectArguments(value: unknown): value is ConnectArguments {
return is(value, ConnectArgumentsStruct);
}
/**
* Assert that the given value is a {@link ConnectArguments} object.
*
* @param value - The value to check.
* @throws If the value is not a valid {@link ConnectArguments} object.
*/
export function assertIsConnectArguments(
value: unknown,
): asserts value is ConnectArguments {
assertStruct(value, ConnectArgumentsStruct, 'Invalid connect arguments');
}
/**
* Check if the given value is a {@link MultiChainRequest} object.
*
* @param value - The value to check.
* @returns Whether the value is a valid {@link MultiChainRequest} object.
*/
export function isMultiChainRequest(
value: unknown,
): value is MultiChainRequest {
return is(value, MultiChainRequestStruct);
}
/**
* Assert that the given value is a {@link MultiChainRequest} object.
*
* @param value - The value to check.
* @throws If the value is not a valid {@link MultiChainRequest} object.
*/
export function assertIsMultiChainRequest(
value: unknown,
): asserts value is MultiChainRequest {
assertStruct(value, MultiChainRequestStruct, 'Invalid request arguments');
}
/**
* Check if a value is a {@link Namespace}.
*
* @param value - The value to validate.
* @returns True if the value is a valid {@link Namespace}.
*/
export function isNamespace(value: unknown): value is Namespace {
return is(value, NamespaceStruct);
}
/**
* Check if a value is an object containing {@link Namespace}s.
*
* @param value - The value to validate.
* @returns True if the value is a valid object containing namespaces.
*/
export function isNamespacesObject(value: unknown): value is Namespaces {
return is(value, NamespacesStruct);
}
/**
* Assert that the given value is a {@link Namespaces} object.
*
* @param value - The value to check.
* @param ErrorWrapper - The error wrapper to use. Defaults to
* {@link AssertionError}.
* @throws If the value is not a valid {@link Namespaces} object.
*/
export function assertIsNamespacesObject(
value: unknown,
ErrorWrapper?: AssertionErrorConstructor,
): asserts value is Namespaces {
assertStruct(
value,
NamespacesStruct,
'Invalid namespaces object',
ErrorWrapper,
);
}