@jamscript/client 0.1.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/rpc.js ADDED
@@ -0,0 +1,83 @@
1
+ export class RpcError extends Error {
2
+ code;
3
+ data;
4
+ constructor(message, code, data) {
5
+ super(message);
6
+ this.code = code;
7
+ this.data = data;
8
+ }
9
+ }
10
+ const FORMAL_WORK_METHODS = new Set([
11
+ "minijam_submitWorkV1",
12
+ "minijam_getWorkStatusV1",
13
+ ]);
14
+ const STATE_PROVIDER_METHODS = new Set([
15
+ "jamscript_getStateV1",
16
+ "jamscript_getStateProofV1",
17
+ "minijam_getManagedStateV1",
18
+ ]);
19
+ export class SplitRpcTransport {
20
+ node;
21
+ work;
22
+ state;
23
+ constructor(node, work, state = node) {
24
+ this.node = node;
25
+ this.work = work;
26
+ this.state = state;
27
+ }
28
+ call(method, params) {
29
+ const transport = FORMAL_WORK_METHODS.has(method)
30
+ ? this.work
31
+ : STATE_PROVIDER_METHODS.has(method)
32
+ ? this.state
33
+ : this.node;
34
+ return transport.call(method, params);
35
+ }
36
+ }
37
+ export class FetchRpcTransport {
38
+ endpoint;
39
+ fetchImpl;
40
+ nextId = 1;
41
+ constructor(endpoint, fetchImpl = fetch) {
42
+ this.endpoint = endpoint;
43
+ this.fetchImpl = fetchImpl;
44
+ }
45
+ async call(method, params = []) {
46
+ const response = await this.fetchImpl(this.endpoint, {
47
+ method: "POST",
48
+ headers: { "content-type": "application/json" },
49
+ body: JSON.stringify({ jsonrpc: "2.0", id: this.nextId++, method, params }),
50
+ });
51
+ if (!response.ok)
52
+ throw new RpcError("RPC HTTP " + response.status, response.status);
53
+ const body = (await response.json());
54
+ if (body.error)
55
+ throw new RpcError(body.error.message, body.error.code, body.error.data);
56
+ if (!("result" in body))
57
+ throw new RpcError("RPC response has no result", -32000);
58
+ return body.result;
59
+ }
60
+ }
61
+ export function asWorkRpc(transport) {
62
+ return {
63
+ call: transport.call.bind(transport),
64
+ finalizedContext: () => transport.call("minijam_getFinalizedContext"),
65
+ genesisHash: () => transport.call("chain_getBlockHash", [0]),
66
+ serviceStorageAt: (blockHash, serviceId, key) => transport.call("minijam_getServiceStorageAt", [blockHash, serviceId, key]),
67
+ managedStateAt: (serviceId, stateRoot, keyBase64) => transport.call("minijam_getManagedStateV1", { serviceId, stateRoot, keyBase64 }),
68
+ submitWork: (request) => transport.call("minijam_submitWorkV1", request),
69
+ workStatus: (packageHash, serviceId) => transport.call("minijam_getWorkStatusV1", serviceId === undefined ? { packageHash } : { packageHash, serviceId }),
70
+ submitTransaction: (request) => transport.call("jamscript_submitTransactionV1", request),
71
+ transactionStatus: async (transactionId) => {
72
+ const result = await transport.call("jamscript_getTransactionStatusV1", { transactionId });
73
+ return {
74
+ ...result,
75
+ packageHash: result.packageHash ?? null,
76
+ itemIndex: result.itemIndex ?? null,
77
+ actionIndex: result.actionIndex ?? null,
78
+ executionReceipt: result.executionReceipt ?? result.receipt ?? null,
79
+ error: result.error ?? null,
80
+ };
81
+ },
82
+ };
83
+ }
@@ -0,0 +1,53 @@
1
+ export declare const RUNTIME_REFINEMENT_VERSION = 1;
2
+ export declare const MAX_RUNTIME_ACTIONS = 1024;
3
+ export declare const MAX_RUNTIME_ACTION_BYTES: number;
4
+ export declare const MAX_RUNTIME_ACTION_TOTAL_BYTES: number;
5
+ export declare const MAX_RECOVERY_BYTES: number;
6
+ export declare const MAX_RECOVERY_CHANGES = 4096;
7
+ export declare const MAX_STATE_KEY_BYTES = 4096;
8
+ export declare const MAX_STATE_VALUE_BYTES: number;
9
+ export declare const MAX_EXTERNAL_STATE_WITNESSES = 64;
10
+ export declare const MAX_EXTERNAL_WITNESS_TOTAL_BYTES: number;
11
+ export type ActionReceiptV1 = {
12
+ actionHash: Uint8Array;
13
+ status: 0 | 1 | 2;
14
+ errorCode: number | null;
15
+ };
16
+ export type RuntimeRefineOutputV1 = {
17
+ version: 1;
18
+ parentRoot: Uint8Array;
19
+ newRoot: Uint8Array;
20
+ externalDependencies: ExternalStateDependencyV1[];
21
+ transitionValidUntil: bigint | null;
22
+ recoveryCommitment: Uint8Array;
23
+ receipts: ActionReceiptV1[];
24
+ recoveryPayload: Uint8Array;
25
+ };
26
+ export type ExternalStateDependencyV1 = {
27
+ serviceId: number;
28
+ stateRoot: Uint8Array;
29
+ };
30
+ export type StateAccessPlanV1 = {
31
+ version: 1;
32
+ keys: Uint8Array[];
33
+ };
34
+ export type ManagedStateWitnessV1 = {
35
+ version: 1;
36
+ parentRoot: Uint8Array;
37
+ accessPlan: StateAccessPlanV1;
38
+ storageProof: Uint8Array[];
39
+ };
40
+ export type ExternalStateWitnessV1 = {
41
+ serviceId: number;
42
+ managedState: ManagedStateWitnessV1;
43
+ };
44
+ export type RuntimeRefineInputV1 = {
45
+ version: 1;
46
+ managedState: ManagedStateWitnessV1;
47
+ externalState: ExternalStateWitnessV1[];
48
+ actions: Uint8Array[];
49
+ };
50
+ export declare function encodeRuntimeRefineOutputV1(output: RuntimeRefineOutputV1): Uint8Array;
51
+ export declare function decodeRuntimeRefineOutputV1(bytes: Uint8Array): RuntimeRefineOutputV1;
52
+ export declare function encodeRuntimeRefineInputV1(input: RuntimeRefineInputV1): Uint8Array;
53
+ export declare function decodeRuntimeRefineInputV1(bytes: Uint8Array): RuntimeRefineInputV1;
@@ -0,0 +1,405 @@
1
+ import { blake2AsU8a } from "@polkadot/util-crypto";
2
+ export const RUNTIME_REFINEMENT_VERSION = 1;
3
+ export const MAX_RUNTIME_ACTIONS = 1024;
4
+ export const MAX_RUNTIME_ACTION_BYTES = 1024 * 1024;
5
+ export const MAX_RUNTIME_ACTION_TOTAL_BYTES = 4 * 1024 * 1024;
6
+ export const MAX_RECOVERY_BYTES = 1024 * 1024;
7
+ export const MAX_RECOVERY_CHANGES = 4096;
8
+ export const MAX_STATE_KEY_BYTES = 4096;
9
+ export const MAX_STATE_VALUE_BYTES = 64 * 1024;
10
+ export const MAX_EXTERNAL_STATE_WITNESSES = 64;
11
+ export const MAX_EXTERNAL_WITNESS_TOTAL_BYTES = 2 * 1024 * 1024;
12
+ const MAX_WITNESS_NODES = 4096;
13
+ const MAX_WITNESS_NODE_BYTES = 64 * 1024;
14
+ const MAX_WITNESS_BYTES = 1024 * 1024;
15
+ const MAX_STATE_VIEW_BYTES = 1024 * 1024;
16
+ function concat(...parts) {
17
+ const output = new Uint8Array(parts.reduce((size, part) => size + part.length, 0));
18
+ let offset = 0;
19
+ for (const part of parts) {
20
+ output.set(part, offset);
21
+ offset += part.length;
22
+ }
23
+ return output;
24
+ }
25
+ function u32(value) {
26
+ const output = new Uint8Array(4);
27
+ new DataView(output.buffer).setUint32(0, value, true);
28
+ return output;
29
+ }
30
+ function u64(value) {
31
+ const output = new Uint8Array(8);
32
+ new DataView(output.buffer).setBigUint64(0, value, true);
33
+ return output;
34
+ }
35
+ function ensureBytes(value, length, name) {
36
+ if (value.length !== length)
37
+ throw new Error(`${name} must be ${length} bytes`);
38
+ }
39
+ function validateRecoveryPayload(bytes) {
40
+ if (bytes.length > MAX_RECOVERY_BYTES)
41
+ throw new Error("recovery payload is too large");
42
+ let offset = 0;
43
+ const take = (length) => {
44
+ const end = offset + length;
45
+ if (end > bytes.length)
46
+ throw new Error("truncated recovery payload");
47
+ const value = bytes.slice(offset, end);
48
+ offset = end;
49
+ return value;
50
+ };
51
+ const readU8 = () => take(1)[0];
52
+ const readU32 = () => new DataView(take(4).buffer).getUint32(0, true);
53
+ const diffVersion = readU8();
54
+ if (diffVersion !== 1)
55
+ throw new Error("unsupported recovery version");
56
+ const diffLength = readU32();
57
+ const diff = take(diffLength);
58
+ let diffOffset = 0;
59
+ const diffTake = (length) => {
60
+ const end = diffOffset + length;
61
+ if (end > diff.length)
62
+ throw new Error("truncated state diff");
63
+ const value = diff.slice(diffOffset, end);
64
+ diffOffset = end;
65
+ return value;
66
+ };
67
+ const diffU8 = () => diffTake(1)[0];
68
+ const diffU32 = () => new DataView(diffTake(4).buffer).getUint32(0, true);
69
+ if (diffU8() !== 1)
70
+ throw new Error("unsupported state diff version");
71
+ const count = diffU32();
72
+ if (count > MAX_RECOVERY_CHANGES)
73
+ throw new Error("too many state changes");
74
+ let previousKey = null;
75
+ for (let index = 0; index < count; index += 1) {
76
+ const keyLength = diffU32();
77
+ if (keyLength > MAX_STATE_KEY_BYTES)
78
+ throw new Error("state key is too large");
79
+ const key = diffTake(keyLength);
80
+ if (previousKey && compareBytes(previousKey, key) >= 0) {
81
+ throw new Error("state diff keys are not strictly sorted");
82
+ }
83
+ previousKey = key;
84
+ const valueTag = diffU8();
85
+ if (valueTag === 1) {
86
+ const valueLength = diffU32();
87
+ if (valueLength > MAX_STATE_VALUE_BYTES)
88
+ throw new Error("state value is too large");
89
+ diffTake(valueLength);
90
+ }
91
+ else if (valueTag !== 0) {
92
+ throw new Error("invalid state diff value tag");
93
+ }
94
+ }
95
+ if (diffOffset !== diff.length || offset !== bytes.length) {
96
+ throw new Error("trailing recovery payload bytes");
97
+ }
98
+ }
99
+ function compareBytes(left, right) {
100
+ const length = Math.min(left.length, right.length);
101
+ for (let index = 0; index < length; index += 1) {
102
+ if (left[index] !== right[index])
103
+ return left[index] - right[index];
104
+ }
105
+ return left.length - right.length;
106
+ }
107
+ export function encodeRuntimeRefineOutputV1(output) {
108
+ if (output.version !== RUNTIME_REFINEMENT_VERSION)
109
+ throw new Error("unsupported runtime output version");
110
+ ensureBytes(output.parentRoot, 32, "parentRoot");
111
+ ensureBytes(output.newRoot, 32, "newRoot");
112
+ ensureBytes(output.recoveryCommitment, 32, "recoveryCommitment");
113
+ const dependencies = canonicalDependencies(output.externalDependencies);
114
+ if (output.receipts.length > MAX_RUNTIME_ACTIONS)
115
+ throw new Error("too many receipts");
116
+ validateRecoveryPayload(output.recoveryPayload);
117
+ const validity = output.transitionValidUntil === null
118
+ ? Uint8Array.of(0)
119
+ : concat(Uint8Array.of(1), u64(output.transitionValidUntil));
120
+ const receipts = output.receipts.map((receipt) => {
121
+ ensureBytes(receipt.actionHash, 32, "actionHash");
122
+ if (![0, 1, 2].includes(receipt.status))
123
+ throw new Error("invalid receipt status");
124
+ return concat(receipt.actionHash, Uint8Array.of(receipt.status), receipt.errorCode === null ? Uint8Array.of(0) : concat(Uint8Array.of(1), u32(receipt.errorCode)));
125
+ });
126
+ return concat(Uint8Array.of(output.version), output.parentRoot, output.newRoot, u32(dependencies.length), ...dependencies.map((dependency) => concat(u32(dependency.serviceId), dependency.stateRoot)), validity, output.recoveryCommitment, u32(output.receipts.length), ...receipts, u32(output.recoveryPayload.length), output.recoveryPayload);
127
+ }
128
+ export function decodeRuntimeRefineOutputV1(bytes) {
129
+ let offset = 0;
130
+ const take = (length) => {
131
+ const end = offset + length;
132
+ if (end > bytes.length)
133
+ throw new Error("truncated RuntimeRefineOutputV1");
134
+ const value = bytes.slice(offset, end);
135
+ offset = end;
136
+ return value;
137
+ };
138
+ const readU8 = () => take(1)[0];
139
+ const readU32 = () => new DataView(take(4).buffer).getUint32(0, true);
140
+ const readU64 = () => new DataView(take(8).buffer).getBigUint64(0, true);
141
+ const version = readU8();
142
+ if (version !== RUNTIME_REFINEMENT_VERSION)
143
+ throw new Error("unsupported runtime output version");
144
+ const parentRoot = take(32);
145
+ const newRoot = take(32);
146
+ const dependencyCount = readU32();
147
+ if (dependencyCount > MAX_EXTERNAL_STATE_WITNESSES)
148
+ throw new Error("too many external dependencies");
149
+ const externalDependencies = [];
150
+ let previousServiceId = null;
151
+ for (let index = 0; index < dependencyCount; index += 1) {
152
+ const serviceId = readU32();
153
+ const stateRoot = take(32);
154
+ if (previousServiceId !== null && serviceId < previousServiceId) {
155
+ throw new Error("external dependencies are not sorted");
156
+ }
157
+ if (externalDependencies.at(-1)?.serviceId === serviceId) {
158
+ const previousRoot = externalDependencies.at(-1)?.stateRoot;
159
+ if (!previousRoot || compareBytes(previousRoot, stateRoot) !== 0) {
160
+ throw new Error("duplicate external dependency service ID");
161
+ }
162
+ continue;
163
+ }
164
+ previousServiceId = serviceId;
165
+ externalDependencies.push({ serviceId, stateRoot });
166
+ }
167
+ const validityTag = readU8();
168
+ const transitionValidUntil = validityTag === 0 ? null : validityTag === 1 ? readU64() : (() => { throw new Error("invalid validity tag"); })();
169
+ const recoveryCommitment = take(32);
170
+ const count = readU32();
171
+ if (count > MAX_RUNTIME_ACTIONS)
172
+ throw new Error("too many receipts");
173
+ const receipts = [];
174
+ for (let index = 0; index < count; index += 1) {
175
+ const actionHash = take(32);
176
+ const status = readU8();
177
+ if (status > 2)
178
+ throw new Error("invalid receipt status");
179
+ const errorTag = readU8();
180
+ const errorCode = errorTag === 0 ? null : errorTag === 1 ? readU32() : (() => { throw new Error("invalid receipt error tag"); })();
181
+ receipts.push({ actionHash, status: status, errorCode });
182
+ }
183
+ const recoveryLength = readU32();
184
+ if (recoveryLength > MAX_RECOVERY_BYTES)
185
+ throw new Error("recovery payload is too large");
186
+ const recoveryPayload = take(recoveryLength);
187
+ if (offset !== bytes.length)
188
+ throw new Error("trailing RuntimeRefineOutputV1 bytes");
189
+ validateRecoveryPayload(recoveryPayload);
190
+ const expectedCommitment = blake2AsU8a(recoveryPayload, 256);
191
+ if (compareBytes(expectedCommitment, recoveryCommitment) !== 0)
192
+ throw new Error("recovery commitment mismatch");
193
+ return { version: 1, parentRoot, newRoot, externalDependencies, transitionValidUntil, recoveryCommitment, receipts, recoveryPayload };
194
+ }
195
+ export function encodeRuntimeRefineInputV1(input) {
196
+ if (input.version !== RUNTIME_REFINEMENT_VERSION)
197
+ throw new Error("unsupported runtime input version");
198
+ const managed = encodeManagedStateWitnessV1(input.managedState);
199
+ const external = encodeExternalStateWitnessesV1(input.externalState);
200
+ if (input.actions.length > MAX_RUNTIME_ACTIONS)
201
+ throw new Error("too many actions");
202
+ const actionBytes = input.actions.reduce((total, action) => total + action.length, 0);
203
+ if (actionBytes > MAX_RUNTIME_ACTION_TOTAL_BYTES)
204
+ throw new Error("actions are too large");
205
+ return concat(Uint8Array.of(input.version), encodeBytes(managed, MAX_WITNESS_ENCODED_BYTES), encodeBytes(external, MAX_EXTERNAL_WITNESS_TOTAL_BYTES), u32(input.actions.length), ...input.actions.map((action) => encodeBytes(action, MAX_RUNTIME_ACTION_BYTES)));
206
+ }
207
+ export function decodeRuntimeRefineInputV1(bytes) {
208
+ const reader = new RuntimeReader(bytes);
209
+ const version = reader.u8();
210
+ if (version !== RUNTIME_REFINEMENT_VERSION)
211
+ throw new Error("unsupported runtime input version");
212
+ const managedState = decodeManagedStateWitnessV1(reader.bytes(MAX_WITNESS_ENCODED_BYTES));
213
+ const externalState = decodeExternalStateWitnessesV1(reader.bytes(MAX_EXTERNAL_WITNESS_TOTAL_BYTES));
214
+ const count = reader.u32();
215
+ if (count > MAX_RUNTIME_ACTIONS)
216
+ throw new Error("too many actions");
217
+ const actions = [];
218
+ let actionBytes = 0;
219
+ for (let index = 0; index < count; index += 1) {
220
+ const action = reader.bytes(MAX_RUNTIME_ACTION_BYTES);
221
+ actionBytes += action.length;
222
+ if (actionBytes > MAX_RUNTIME_ACTION_TOTAL_BYTES)
223
+ throw new Error("actions are too large");
224
+ actions.push(action);
225
+ }
226
+ if (reader.remaining() !== 0)
227
+ throw new Error("trailing RuntimeRefineInputV1 bytes");
228
+ return { version: 1, managedState, externalState, actions };
229
+ }
230
+ const MAX_WITNESS_ENCODED_BYTES = 1 + 32 + 4 + MAX_STATE_VIEW_BYTES + 4 + (MAX_WITNESS_NODES * 4) + MAX_WITNESS_BYTES;
231
+ function encodeBytes(value, maximum) {
232
+ if (value.length > maximum)
233
+ throw new Error("runtime value is too large");
234
+ return concat(u32(value.length), value);
235
+ }
236
+ function encodeAccessPlanV1(plan) {
237
+ if (plan.version !== 1 || plan.keys.length > MAX_RECOVERY_CHANGES)
238
+ throw new Error("invalid state access plan");
239
+ for (let index = 1; index < plan.keys.length; index += 1) {
240
+ if (compareBytes(plan.keys[index - 1], plan.keys[index]) >= 0)
241
+ throw new Error("state access plan is not canonical");
242
+ }
243
+ return concat(Uint8Array.of(plan.version), u32(plan.keys.length), ...plan.keys.map((key) => encodeBytes(key, MAX_STATE_KEY_BYTES)));
244
+ }
245
+ function encodeManagedStateWitnessV1(witness) {
246
+ if (witness.version !== 1)
247
+ throw new Error("invalid managed state witness version");
248
+ ensureBytes(witness.parentRoot, 32, "parentRoot");
249
+ if (witness.storageProof.length > MAX_WITNESS_NODES)
250
+ throw new Error("too many witness nodes");
251
+ let total = 0;
252
+ const nodes = witness.storageProof.map((node) => {
253
+ total += node.length;
254
+ if (node.length > MAX_WITNESS_NODE_BYTES || total > MAX_WITNESS_BYTES)
255
+ throw new Error("witness is too large");
256
+ return encodeBytes(node, MAX_WITNESS_NODE_BYTES);
257
+ });
258
+ return concat(Uint8Array.of(witness.version), witness.parentRoot, encodeBytes(encodeAccessPlanV1(witness.accessPlan), MAX_ACCESS_PLAN_ENCODED_BYTES), u32(nodes.length), ...nodes);
259
+ }
260
+ function encodeExternalStateWitnessesV1(witnesses) {
261
+ if (witnesses.length > MAX_EXTERNAL_STATE_WITNESSES)
262
+ throw new Error("too many external witnesses");
263
+ const ordered = [...witnesses].sort((left, right) => left.serviceId - right.serviceId);
264
+ const result = [u32(ordered.length)];
265
+ let previous = null;
266
+ let total = 0;
267
+ for (const witness of ordered) {
268
+ if (previous === witness.serviceId)
269
+ throw new Error("duplicate external witness service ID");
270
+ if (!Number.isSafeInteger(witness.serviceId) || witness.serviceId < 0 || witness.serviceId > 0xffffffff)
271
+ throw new Error("invalid external witness service ID");
272
+ previous = witness.serviceId;
273
+ const managed = encodeManagedStateWitnessV1(witness.managedState);
274
+ total += managed.length;
275
+ if (total > MAX_EXTERNAL_WITNESS_TOTAL_BYTES)
276
+ throw new Error("external witnesses are too large");
277
+ result.push(u32(witness.serviceId), encodeBytes(managed, MAX_WITNESS_ENCODED_BYTES));
278
+ }
279
+ const encoded = concat(...result);
280
+ if (encoded.length > MAX_EXTERNAL_WITNESS_TOTAL_BYTES)
281
+ throw new Error("external witnesses are too large");
282
+ return encoded;
283
+ }
284
+ function decodeAccessPlanV1(bytes) {
285
+ const reader = new RuntimeReader(bytes);
286
+ const version = reader.u8();
287
+ if (version !== 1)
288
+ throw new Error("invalid state access plan version");
289
+ const count = reader.u32();
290
+ if (count > MAX_RECOVERY_CHANGES)
291
+ throw new Error("too many state access keys");
292
+ const keys = [];
293
+ for (let index = 0; index < count; index += 1) {
294
+ const key = reader.bytes(MAX_STATE_KEY_BYTES);
295
+ if (keys.length > 0 && compareBytes(keys[keys.length - 1], key) >= 0)
296
+ throw new Error("state access plan is not canonical");
297
+ keys.push(key);
298
+ }
299
+ if (reader.remaining() !== 0)
300
+ throw new Error("trailing state access plan bytes");
301
+ return { version: 1, keys };
302
+ }
303
+ function decodeManagedStateWitnessV1(bytes) {
304
+ const reader = new RuntimeReader(bytes);
305
+ const version = reader.u8();
306
+ if (version !== 1)
307
+ throw new Error("invalid managed state witness version");
308
+ const parentRoot = reader.take(32);
309
+ const accessPlan = decodeAccessPlanV1(reader.bytes(MAX_ACCESS_PLAN_ENCODED_BYTES));
310
+ const count = reader.u32();
311
+ if (count > MAX_WITNESS_NODES)
312
+ throw new Error("too many witness nodes");
313
+ const storageProof = [];
314
+ let total = 0;
315
+ for (let index = 0; index < count; index += 1) {
316
+ const node = reader.bytes(MAX_WITNESS_NODE_BYTES);
317
+ total += node.length;
318
+ if (total > MAX_WITNESS_BYTES)
319
+ throw new Error("witness is too large");
320
+ storageProof.push(node);
321
+ }
322
+ if (reader.remaining() !== 0)
323
+ throw new Error("trailing managed state witness bytes");
324
+ return { version: 1, parentRoot, accessPlan, storageProof };
325
+ }
326
+ function decodeExternalStateWitnessesV1(bytes) {
327
+ const reader = new RuntimeReader(bytes);
328
+ const count = reader.u32();
329
+ if (count > MAX_EXTERNAL_STATE_WITNESSES)
330
+ throw new Error("too many external witnesses");
331
+ const witnesses = [];
332
+ let previous = null;
333
+ let total = 0;
334
+ for (let index = 0; index < count; index += 1) {
335
+ const serviceId = reader.u32();
336
+ if (previous !== null && serviceId <= previous)
337
+ throw new Error("external witnesses are not sorted");
338
+ previous = serviceId;
339
+ const managedBytes = reader.bytes(MAX_WITNESS_ENCODED_BYTES);
340
+ total += managedBytes.length;
341
+ if (total > MAX_EXTERNAL_WITNESS_TOTAL_BYTES)
342
+ throw new Error("external witnesses are too large");
343
+ witnesses.push({ serviceId, managedState: decodeManagedStateWitnessV1(managedBytes) });
344
+ }
345
+ if (reader.remaining() !== 0)
346
+ throw new Error("trailing external witness bytes");
347
+ return witnesses;
348
+ }
349
+ const MAX_ACCESS_PLAN_ENCODED_BYTES = MAX_STATE_VIEW_BYTES;
350
+ class RuntimeReader {
351
+ bytesValue;
352
+ offset = 0;
353
+ constructor(bytesValue) {
354
+ this.bytesValue = bytesValue;
355
+ }
356
+ take(length) {
357
+ const end = this.offset + length;
358
+ if (end > this.bytesValue.length)
359
+ throw new Error("truncated runtime wire value");
360
+ const value = this.bytesValue.slice(this.offset, end);
361
+ this.offset = end;
362
+ return value;
363
+ }
364
+ u8() {
365
+ return this.take(1)[0];
366
+ }
367
+ u32() {
368
+ return new DataView(this.take(4).buffer).getUint32(0, true);
369
+ }
370
+ bytes(maximum) {
371
+ const length = this.u32();
372
+ if (length > maximum)
373
+ throw new Error("runtime wire value is too large");
374
+ return this.take(length);
375
+ }
376
+ remaining() {
377
+ return this.bytesValue.length - this.offset;
378
+ }
379
+ }
380
+ function canonicalDependencies(dependencies) {
381
+ if (dependencies.length > MAX_EXTERNAL_STATE_WITNESSES) {
382
+ throw new Error("too many external dependencies");
383
+ }
384
+ const ordered = [...dependencies]
385
+ .map((dependency) => {
386
+ ensureBytes(dependency.stateRoot, 32, "external dependency stateRoot");
387
+ if (!Number.isSafeInteger(dependency.serviceId) || dependency.serviceId < 0 || dependency.serviceId > 0xffffffff) {
388
+ throw new Error("invalid external dependency serviceId");
389
+ }
390
+ return dependency;
391
+ })
392
+ .sort((left, right) => left.serviceId - right.serviceId);
393
+ const result = [];
394
+ for (const dependency of ordered) {
395
+ const previous = result.at(-1);
396
+ if (previous?.serviceId === dependency.serviceId) {
397
+ if (compareBytes(previous.stateRoot, dependency.stateRoot) !== 0) {
398
+ throw new Error("duplicate external dependency service ID");
399
+ }
400
+ continue;
401
+ }
402
+ result.push(dependency);
403
+ }
404
+ return result;
405
+ }
@@ -0,0 +1,27 @@
1
+ export interface JamSigner {
2
+ readonly publicKey: Uint8Array;
3
+ signRaw(message: Uint8Array): Promise<Uint8Array>;
4
+ }
5
+ export type JamScriptOwnershipSignRequest = Omit<import("./crypto.js").SignedActionV2, "authorizationProof"> & {
6
+ message: Uint8Array;
7
+ };
8
+ export interface OwnershipSigner {
9
+ getController(): Promise<import("./crypto.js").Ownership>;
10
+ signJamScriptAction(request: JamScriptOwnershipSignRequest): Promise<Uint8Array>;
11
+ }
12
+ export type InjectedSigner = {
13
+ signRaw(input: {
14
+ address: string;
15
+ data: string;
16
+ type: "bytes";
17
+ }): Promise<{
18
+ signature: string;
19
+ }>;
20
+ };
21
+ export declare class PolkadotExtensionSigner implements JamSigner {
22
+ readonly publicKey: Uint8Array;
23
+ private readonly address;
24
+ private readonly injector;
25
+ constructor(publicKey: Uint8Array, address: string, injector: InjectedSigner);
26
+ signRaw(message: Uint8Array): Promise<Uint8Array>;
27
+ }
package/dist/signer.js ADDED
@@ -0,0 +1,33 @@
1
+ export class PolkadotExtensionSigner {
2
+ publicKey;
3
+ address;
4
+ injector;
5
+ constructor(publicKey, address, injector) {
6
+ this.publicKey = publicKey;
7
+ this.address = address;
8
+ this.injector = injector;
9
+ if (publicKey.length !== 32)
10
+ throw new Error("sr25519 public key must be 32 bytes");
11
+ }
12
+ async signRaw(message) {
13
+ const result = await this.injector.signRaw({
14
+ address: this.address,
15
+ data: bytesToHex(message),
16
+ type: "bytes",
17
+ });
18
+ return hexToBytes(result.signature);
19
+ }
20
+ }
21
+ function bytesToHex(bytes) {
22
+ return "0x" + Array.from(bytes, (value) => value.toString(16).padStart(2, "0")).join("");
23
+ }
24
+ function hexToBytes(value) {
25
+ const hex = value.startsWith("0x") ? value.slice(2) : value;
26
+ if (hex.length % 2 !== 0)
27
+ throw new Error("signature hex has odd length");
28
+ const output = new Uint8Array(hex.length / 2);
29
+ for (let index = 0; index < output.length; index += 1) {
30
+ output[index] = Number.parseInt(hex.slice(index * 2, index * 2 + 2), 16);
31
+ }
32
+ return output;
33
+ }
@@ -0,0 +1,51 @@
1
+ import type { RpcTransport } from "./rpc.js";
2
+ export type StateProviderRequest = {
3
+ serviceId: number;
4
+ serviceKey: string;
5
+ stateRoot: string;
6
+ key: Uint8Array;
7
+ };
8
+ export type StateProviderResponse = {
9
+ serviceId: number;
10
+ stateRoot: string;
11
+ key: Uint8Array;
12
+ value: Uint8Array | null;
13
+ proof: Uint8Array[];
14
+ };
15
+ export type StateProviderFailureKind = "Unavailable" | "RootUnavailable" | "MalformedResponse" | "InvalidProof" | "InconsistentResponse";
16
+ export declare class StateProviderError extends Error {
17
+ readonly kind: StateProviderFailureKind;
18
+ readonly cause?: unknown | undefined;
19
+ constructor(kind: StateProviderFailureKind, message: string, cause?: unknown | undefined);
20
+ }
21
+ export interface StateProvider {
22
+ get(request: StateProviderRequest): Promise<StateProviderResponse>;
23
+ }
24
+ /** Legacy explicit-root proof provider retained for compatibility. */
25
+ export declare class RpcStateProvider implements StateProvider {
26
+ private readonly transport;
27
+ constructor(transport: RpcTransport);
28
+ get(request: StateProviderRequest): Promise<StateProviderResponse>;
29
+ }
30
+ /**
31
+ * The v0.1 default provider. The backend owns canonical-root discovery and
32
+ * returns only the trusted value; proof validation remains available through
33
+ * RpcStateProvider when an application explicitly opts into proof mode.
34
+ */
35
+ export declare class TrustedStateProvider implements StateProvider {
36
+ private readonly transport;
37
+ constructor(transport: RpcTransport);
38
+ get(request: StateProviderRequest): Promise<StateProviderResponse>;
39
+ }
40
+ /** Requests the v1 proof endpoint and leaves verification to the client. */
41
+ export declare class ProofStateProvider implements StateProvider {
42
+ private readonly transport;
43
+ constructor(transport: RpcTransport);
44
+ get(request: StateProviderRequest): Promise<StateProviderResponse>;
45
+ }
46
+ /** Tries providers in order and only accepts a response with a valid proof. */
47
+ export declare class FallbackStateProvider implements StateProvider {
48
+ private readonly providers;
49
+ constructor(providers: readonly StateProvider[]);
50
+ get(request: StateProviderRequest): Promise<StateProviderResponse>;
51
+ }