@d9-network/ink 0.0.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/index.cjs +10131 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +750 -0
- package/dist/index.d.mts +750 -0
- package/dist/index.mjs +10098 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +46 -0
- package/src/codec-builder.ts +573 -0
- package/src/contract.ts +571 -0
- package/src/decode.ts +176 -0
- package/src/encode.ts +128 -0
- package/src/errors.ts +220 -0
- package/src/event-types.ts +75 -0
- package/src/events.ts +258 -0
- package/src/index.ts +94 -0
- package/src/sdk.ts +116 -0
- package/src/subscriptions.ts +207 -0
- package/src/types.ts +274 -0
- package/test/events.test.ts +325 -0
- package/test/ink.test.ts +404 -0
- package/tsconfig.json +12 -0
- package/tsdown.config.ts +13 -0
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@d9-network/ink",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.mjs",
|
|
8
|
+
"types": "./dist/index.d.cts",
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"require": "./dist/index.cjs",
|
|
13
|
+
"import": "./dist/index.mjs"
|
|
14
|
+
},
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"exports": {
|
|
20
|
+
".": "./src/index.ts",
|
|
21
|
+
"./package.json": "./package.json"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"test": "bun test",
|
|
25
|
+
"build": "tsdown",
|
|
26
|
+
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@noble/hashes": "^2.0.1",
|
|
30
|
+
"@polkadot-api/ink-contracts": "^0.4.4",
|
|
31
|
+
"@polkadot-api/substrate-bindings": "^0.16.5",
|
|
32
|
+
"@polkadot-labs/hdkd-helpers": "^0.0.11",
|
|
33
|
+
"@subsquid/scale-codec": "^4.0.1",
|
|
34
|
+
"polkadot-api": "^1.23.1"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@polkadot-api/descriptors": "file:../spec/.papi/descriptors",
|
|
38
|
+
"@polkadot-api/legacy-provider": "^0.3.6",
|
|
39
|
+
"@polkadot-api/signer": "^0.2.11",
|
|
40
|
+
"@polkadot-labs/hdkd": "^0.0.11",
|
|
41
|
+
"@types/bun": "latest",
|
|
42
|
+
"@types/node": "latest",
|
|
43
|
+
"tsdown": "^0.18.1",
|
|
44
|
+
"typescript": "~5.9.3"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-build SCALE decoders from ink metadata type definitions.
|
|
3
|
+
*
|
|
4
|
+
* This module provides a way to automatically construct decoders for ink contract
|
|
5
|
+
* message return types without manually specifying codecs for each message.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
u8,
|
|
10
|
+
u16,
|
|
11
|
+
u32,
|
|
12
|
+
u64,
|
|
13
|
+
u128,
|
|
14
|
+
i8,
|
|
15
|
+
i16,
|
|
16
|
+
i32,
|
|
17
|
+
i64,
|
|
18
|
+
i128,
|
|
19
|
+
bool,
|
|
20
|
+
str,
|
|
21
|
+
Bytes,
|
|
22
|
+
Vector,
|
|
23
|
+
Tuple,
|
|
24
|
+
Struct,
|
|
25
|
+
Variant,
|
|
26
|
+
_void,
|
|
27
|
+
Option,
|
|
28
|
+
AccountId,
|
|
29
|
+
type Codec,
|
|
30
|
+
} from "@polkadot-api/substrate-bindings";
|
|
31
|
+
import type { InkMetadata } from "@polkadot-api/ink-contracts";
|
|
32
|
+
import { blake2b } from "@noble/hashes/blake2.js";
|
|
33
|
+
|
|
34
|
+
// Use 'any' for dynamic codec building to avoid TypeScript strict type issues
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
36
|
+
type AnyCodec = Codec<any>;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Type definition from ink metadata
|
|
40
|
+
*/
|
|
41
|
+
interface TypeDef {
|
|
42
|
+
primitive?: string;
|
|
43
|
+
composite?: {
|
|
44
|
+
fields: Array<{
|
|
45
|
+
name?: string;
|
|
46
|
+
type: number;
|
|
47
|
+
typeName?: string;
|
|
48
|
+
}>;
|
|
49
|
+
};
|
|
50
|
+
variant?: {
|
|
51
|
+
variants: Array<{
|
|
52
|
+
name: string;
|
|
53
|
+
index: number;
|
|
54
|
+
fields: Array<{
|
|
55
|
+
name?: string;
|
|
56
|
+
type: number;
|
|
57
|
+
typeName?: string;
|
|
58
|
+
}>;
|
|
59
|
+
}>;
|
|
60
|
+
};
|
|
61
|
+
sequence?: {
|
|
62
|
+
type: number;
|
|
63
|
+
};
|
|
64
|
+
array?: {
|
|
65
|
+
len: number;
|
|
66
|
+
type: number;
|
|
67
|
+
};
|
|
68
|
+
tuple?: number[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface TypeEntry {
|
|
72
|
+
id: number;
|
|
73
|
+
type: {
|
|
74
|
+
def: TypeDef;
|
|
75
|
+
path?: string[];
|
|
76
|
+
params?: Array<{
|
|
77
|
+
name: string;
|
|
78
|
+
type?: number;
|
|
79
|
+
}>;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Cache for built codecs to avoid rebuilding the same type
|
|
85
|
+
*/
|
|
86
|
+
type CodecCache = Map<number, AnyCodec>;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Build a SCALE codec from ink metadata type definition
|
|
90
|
+
*/
|
|
91
|
+
function buildCodecFromType(
|
|
92
|
+
typeId: number,
|
|
93
|
+
types: TypeEntry[],
|
|
94
|
+
cache: CodecCache,
|
|
95
|
+
): AnyCodec {
|
|
96
|
+
// Check cache first
|
|
97
|
+
const cached = cache.get(typeId);
|
|
98
|
+
if (cached) {
|
|
99
|
+
return cached;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const typeEntry = types.find((t) => t.id === typeId);
|
|
103
|
+
if (!typeEntry) {
|
|
104
|
+
throw new Error(`Type ${typeId} not found in metadata`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const def = typeEntry.type.def;
|
|
108
|
+
const path = typeEntry.type.path;
|
|
109
|
+
|
|
110
|
+
// Handle primitive types
|
|
111
|
+
if (def.primitive) {
|
|
112
|
+
const codec = buildPrimitiveCodec(def.primitive);
|
|
113
|
+
cache.set(typeId, codec);
|
|
114
|
+
return codec;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Handle special path types (AccountId, Hash, etc.)
|
|
118
|
+
if (path && path.length > 0) {
|
|
119
|
+
const specialCodec = buildSpecialTypeCodec(path, typeEntry, types, cache);
|
|
120
|
+
if (specialCodec) {
|
|
121
|
+
cache.set(typeId, specialCodec);
|
|
122
|
+
return specialCodec;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Handle tuple
|
|
127
|
+
if (def.tuple) {
|
|
128
|
+
const codec = buildTupleCodec(def.tuple, types, cache);
|
|
129
|
+
cache.set(typeId, codec);
|
|
130
|
+
return codec;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Handle sequence (Vec<T>)
|
|
134
|
+
if (def.sequence) {
|
|
135
|
+
const innerCodec = buildCodecFromType(def.sequence.type, types, cache);
|
|
136
|
+
const codec = Vector(innerCodec);
|
|
137
|
+
cache.set(typeId, codec);
|
|
138
|
+
return codec;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Handle array [T; N]
|
|
142
|
+
if (def.array) {
|
|
143
|
+
const innerCodec = buildCodecFromType(def.array.type, types, cache);
|
|
144
|
+
// For fixed-size byte arrays, use Bytes
|
|
145
|
+
if (def.array.type === findPrimitiveTypeId(types, "u8")) {
|
|
146
|
+
const codec = Bytes(def.array.len);
|
|
147
|
+
cache.set(typeId, codec);
|
|
148
|
+
return codec;
|
|
149
|
+
}
|
|
150
|
+
// For other arrays, use Vector with fixed length validation
|
|
151
|
+
const codec = Vector(innerCodec, def.array.len);
|
|
152
|
+
cache.set(typeId, codec);
|
|
153
|
+
return codec;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Handle composite (struct)
|
|
157
|
+
if (def.composite) {
|
|
158
|
+
const codec = buildCompositeCodec(def.composite, types, cache);
|
|
159
|
+
cache.set(typeId, codec);
|
|
160
|
+
return codec;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Handle variant (enum)
|
|
164
|
+
if (def.variant) {
|
|
165
|
+
const codec = buildVariantCodec(def.variant, path, types, cache);
|
|
166
|
+
cache.set(typeId, codec);
|
|
167
|
+
return codec;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
throw new Error(
|
|
171
|
+
`Unknown type definition for type ${typeId}: ${JSON.stringify(def)}`,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Build codec for primitive types
|
|
177
|
+
*/
|
|
178
|
+
function buildPrimitiveCodec(primitive: string): AnyCodec {
|
|
179
|
+
switch (primitive) {
|
|
180
|
+
case "u8":
|
|
181
|
+
return u8;
|
|
182
|
+
case "u16":
|
|
183
|
+
return u16;
|
|
184
|
+
case "u32":
|
|
185
|
+
return u32;
|
|
186
|
+
case "u64":
|
|
187
|
+
return u64;
|
|
188
|
+
case "u128":
|
|
189
|
+
return u128;
|
|
190
|
+
case "i8":
|
|
191
|
+
return i8;
|
|
192
|
+
case "i16":
|
|
193
|
+
return i16;
|
|
194
|
+
case "i32":
|
|
195
|
+
return i32;
|
|
196
|
+
case "i64":
|
|
197
|
+
return i64;
|
|
198
|
+
case "i128":
|
|
199
|
+
return i128;
|
|
200
|
+
case "bool":
|
|
201
|
+
return bool;
|
|
202
|
+
case "str":
|
|
203
|
+
return str;
|
|
204
|
+
default:
|
|
205
|
+
throw new Error(`Unknown primitive type: ${primitive}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Build codec for special types based on path
|
|
211
|
+
*/
|
|
212
|
+
function buildSpecialTypeCodec(
|
|
213
|
+
path: string[],
|
|
214
|
+
typeEntry: TypeEntry,
|
|
215
|
+
types: TypeEntry[],
|
|
216
|
+
cache: CodecCache,
|
|
217
|
+
): AnyCodec | null {
|
|
218
|
+
const fullPath = path.join("::");
|
|
219
|
+
|
|
220
|
+
// AccountId type
|
|
221
|
+
if (fullPath.includes("AccountId")) {
|
|
222
|
+
return AccountId();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Option type
|
|
226
|
+
if (path[0] === "Option") {
|
|
227
|
+
const params = typeEntry.type.params;
|
|
228
|
+
if (params && params.length > 0 && params[0]?.type !== undefined) {
|
|
229
|
+
const innerCodec = buildCodecFromType(params[0].type, types, cache);
|
|
230
|
+
return Option(innerCodec);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Result type - we need special handling
|
|
235
|
+
if (path[0] === "Result") {
|
|
236
|
+
const params = typeEntry.type.params;
|
|
237
|
+
if (params && params.length >= 2) {
|
|
238
|
+
const okTypeId = params[0]?.type;
|
|
239
|
+
const errTypeId = params[1]?.type;
|
|
240
|
+
if (okTypeId !== undefined && errTypeId !== undefined) {
|
|
241
|
+
const okCodec = buildCodecFromType(okTypeId, types, cache);
|
|
242
|
+
const errCodec = buildCodecFromType(errTypeId, types, cache);
|
|
243
|
+
// Build a proper Result variant
|
|
244
|
+
return Variant(
|
|
245
|
+
{
|
|
246
|
+
Ok: okCodec,
|
|
247
|
+
Err: errCodec,
|
|
248
|
+
},
|
|
249
|
+
[0, 1],
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Build codec for tuple types
|
|
260
|
+
*/
|
|
261
|
+
function buildTupleCodec(
|
|
262
|
+
tupleTypes: number[],
|
|
263
|
+
types: TypeEntry[],
|
|
264
|
+
cache: CodecCache,
|
|
265
|
+
): AnyCodec {
|
|
266
|
+
if (tupleTypes.length === 0) {
|
|
267
|
+
return _void;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const innerCodecs = tupleTypes.map((t) => buildCodecFromType(t, types, cache));
|
|
271
|
+
|
|
272
|
+
// Handle different tuple sizes
|
|
273
|
+
switch (innerCodecs.length) {
|
|
274
|
+
case 1:
|
|
275
|
+
return Tuple(innerCodecs[0]!);
|
|
276
|
+
case 2:
|
|
277
|
+
return Tuple(innerCodecs[0]!, innerCodecs[1]!);
|
|
278
|
+
case 3:
|
|
279
|
+
return Tuple(innerCodecs[0]!, innerCodecs[1]!, innerCodecs[2]!);
|
|
280
|
+
case 4:
|
|
281
|
+
return Tuple(
|
|
282
|
+
innerCodecs[0]!,
|
|
283
|
+
innerCodecs[1]!,
|
|
284
|
+
innerCodecs[2]!,
|
|
285
|
+
innerCodecs[3]!,
|
|
286
|
+
);
|
|
287
|
+
default:
|
|
288
|
+
// For larger tuples, use dynamic tuple (cast to any)
|
|
289
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
290
|
+
return (Tuple as any)(...innerCodecs);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Build codec for composite (struct) types
|
|
296
|
+
*/
|
|
297
|
+
function buildCompositeCodec(
|
|
298
|
+
composite: NonNullable<TypeDef["composite"]>,
|
|
299
|
+
types: TypeEntry[],
|
|
300
|
+
cache: CodecCache,
|
|
301
|
+
): AnyCodec {
|
|
302
|
+
const fields = composite.fields;
|
|
303
|
+
|
|
304
|
+
// Single unnamed field - unwrap it
|
|
305
|
+
if (fields.length === 1 && !fields[0]?.name) {
|
|
306
|
+
return buildCodecFromType(fields[0]!.type, types, cache);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Multiple fields - build a struct
|
|
310
|
+
const structDef: Record<string, AnyCodec> = {};
|
|
311
|
+
for (const field of fields) {
|
|
312
|
+
const fieldName = field.name || `field${fields.indexOf(field)}`;
|
|
313
|
+
structDef[fieldName] = buildCodecFromType(field.type, types, cache);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
317
|
+
return Struct(structDef as any);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Build codec for variant (enum) types
|
|
322
|
+
*/
|
|
323
|
+
function buildVariantCodec(
|
|
324
|
+
variant: NonNullable<TypeDef["variant"]>,
|
|
325
|
+
path: string[] | undefined,
|
|
326
|
+
types: TypeEntry[],
|
|
327
|
+
cache: CodecCache,
|
|
328
|
+
): AnyCodec {
|
|
329
|
+
const variants = variant.variants;
|
|
330
|
+
|
|
331
|
+
// Check if this is a LangError type (ink specific)
|
|
332
|
+
const isLangError = path?.includes("LangError");
|
|
333
|
+
|
|
334
|
+
// Build variant definition
|
|
335
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
336
|
+
const variantDef: Record<string, any> = {};
|
|
337
|
+
const indices: number[] = [];
|
|
338
|
+
|
|
339
|
+
// For LangError, add a placeholder for index 0 if missing
|
|
340
|
+
if (isLangError && !variants.some((v) => v.index === 0)) {
|
|
341
|
+
variantDef["_Placeholder"] = _void;
|
|
342
|
+
indices.push(0);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
for (const v of variants) {
|
|
346
|
+
let fieldCodec: AnyCodec;
|
|
347
|
+
|
|
348
|
+
// Handle variants with no fields or undefined fields
|
|
349
|
+
const fields = v.fields ?? [];
|
|
350
|
+
|
|
351
|
+
if (fields.length === 0) {
|
|
352
|
+
fieldCodec = _void;
|
|
353
|
+
} else if (fields.length === 1 && !fields[0]?.name) {
|
|
354
|
+
// Single unnamed field
|
|
355
|
+
fieldCodec = buildCodecFromType(fields[0]!.type, types, cache);
|
|
356
|
+
} else {
|
|
357
|
+
// Multiple or named fields - build struct
|
|
358
|
+
const structDef: Record<string, AnyCodec> = {};
|
|
359
|
+
for (const field of fields) {
|
|
360
|
+
const fieldName = field.name || `field${fields.indexOf(field)}`;
|
|
361
|
+
structDef[fieldName] = buildCodecFromType(field.type, types, cache);
|
|
362
|
+
}
|
|
363
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
364
|
+
fieldCodec = Struct(structDef as any);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
variantDef[v.name] = fieldCodec;
|
|
368
|
+
indices.push(v.index);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
372
|
+
return Variant(variantDef as any, indices as any);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Find the type ID for a primitive type
|
|
377
|
+
*/
|
|
378
|
+
function findPrimitiveTypeId(types: TypeEntry[], primitive: string): number {
|
|
379
|
+
const entry = types.find((t) => t.type.def.primitive === primitive);
|
|
380
|
+
return entry?.id ?? -1;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Extract the inner type from Result<T, LangError>
|
|
385
|
+
* Returns the type ID of T
|
|
386
|
+
*/
|
|
387
|
+
function extractResultInnerType(typeEntry: TypeEntry): number | null {
|
|
388
|
+
const path = typeEntry.type.path;
|
|
389
|
+
if (!path || path[0] !== "Result") {
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const params = typeEntry.type.params;
|
|
394
|
+
if (!params || params.length < 1) {
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return params[0]?.type ?? null;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Build a decoder for a message's return type from ink metadata.
|
|
403
|
+
* This handles the Result<T, LangError> wrapper and returns a decoder for T.
|
|
404
|
+
*
|
|
405
|
+
* @param metadata - The ink contract metadata
|
|
406
|
+
* @param messageLabel - The message label (e.g., "PSP22::balance_of")
|
|
407
|
+
* @returns A decoder function that decodes the inner value bytes
|
|
408
|
+
*/
|
|
409
|
+
export function buildMessageDecoder(
|
|
410
|
+
metadata: InkMetadata,
|
|
411
|
+
messageLabel: string,
|
|
412
|
+
): (data: Uint8Array) => unknown {
|
|
413
|
+
const types = metadata.types as TypeEntry[];
|
|
414
|
+
const message = metadata.spec.messages.find((m) => m.label === messageLabel);
|
|
415
|
+
|
|
416
|
+
if (!message) {
|
|
417
|
+
throw new Error(`Message "${messageLabel}" not found in metadata`);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const returnTypeId = message.returnType.type;
|
|
421
|
+
const returnTypeEntry = types.find((t) => t.id === returnTypeId);
|
|
422
|
+
|
|
423
|
+
if (!returnTypeEntry) {
|
|
424
|
+
throw new Error(
|
|
425
|
+
`Return type ${returnTypeId} not found for message "${messageLabel}"`,
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const cache: CodecCache = new Map();
|
|
430
|
+
|
|
431
|
+
// Check if it's a Result type and extract inner type
|
|
432
|
+
const innerTypeId = extractResultInnerType(returnTypeEntry);
|
|
433
|
+
|
|
434
|
+
if (innerTypeId !== null) {
|
|
435
|
+
// Build decoder for the inner type (T in Result<T, LangError>)
|
|
436
|
+
const innerCodec = buildCodecFromType(innerTypeId, types, cache);
|
|
437
|
+
return (data: Uint8Array) => innerCodec.dec(data);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Not a Result type, build decoder for the full return type
|
|
441
|
+
const codec = buildCodecFromType(returnTypeId, types, cache);
|
|
442
|
+
return (data: Uint8Array) => codec.dec(data);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Build decoders for all messages in the metadata.
|
|
447
|
+
* Returns a Map of message label -> decoder function.
|
|
448
|
+
*/
|
|
449
|
+
export function buildAllMessageDecoders(
|
|
450
|
+
metadata: InkMetadata,
|
|
451
|
+
): Map<string, (data: Uint8Array) => unknown> {
|
|
452
|
+
const decoders = new Map<string, (data: Uint8Array) => unknown>();
|
|
453
|
+
|
|
454
|
+
for (const message of metadata.spec.messages as Array<{ label: string }>) {
|
|
455
|
+
try {
|
|
456
|
+
const decoder = buildMessageDecoder(metadata, message.label);
|
|
457
|
+
decoders.set(message.label, decoder);
|
|
458
|
+
} catch (error) {
|
|
459
|
+
console.warn(
|
|
460
|
+
`Failed to build decoder for message "${message.label}":`,
|
|
461
|
+
error,
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return decoders;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Create a codec registry compatible with ResponseDecoder type
|
|
471
|
+
*/
|
|
472
|
+
export function createCodecRegistry(
|
|
473
|
+
metadata: InkMetadata,
|
|
474
|
+
): Map<string, { dec: (data: Uint8Array) => unknown }> {
|
|
475
|
+
const decoders = buildAllMessageDecoders(metadata);
|
|
476
|
+
const registry = new Map<string, { dec: (data: Uint8Array) => unknown }>();
|
|
477
|
+
|
|
478
|
+
for (const [label, decoder] of decoders) {
|
|
479
|
+
registry.set(label, { dec: decoder });
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return registry;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Build a SCALE decoder for a contract event from ink metadata
|
|
487
|
+
*
|
|
488
|
+
* @param metadata - The ink contract metadata
|
|
489
|
+
* @param eventLabel - The event label (e.g., "Transfer", "Approval")
|
|
490
|
+
* @returns A decoder function that decodes the event data bytes
|
|
491
|
+
*/
|
|
492
|
+
export function buildEventDecoder(
|
|
493
|
+
metadata: InkMetadata,
|
|
494
|
+
eventLabel: string,
|
|
495
|
+
): (data: Uint8Array) => unknown {
|
|
496
|
+
const types = metadata.types as TypeEntry[];
|
|
497
|
+
const events = metadata.spec.events as Array<{
|
|
498
|
+
label: string;
|
|
499
|
+
args: Array<{
|
|
500
|
+
label: string;
|
|
501
|
+
type: { type: number };
|
|
502
|
+
}>;
|
|
503
|
+
}>;
|
|
504
|
+
|
|
505
|
+
const event = events.find((e) => e.label === eventLabel);
|
|
506
|
+
|
|
507
|
+
if (!event) {
|
|
508
|
+
throw new Error(`Event "${eventLabel}" not found in metadata`);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const cache: CodecCache = new Map();
|
|
512
|
+
|
|
513
|
+
// Build struct codec from event args
|
|
514
|
+
const structDef: Record<string, AnyCodec> = {};
|
|
515
|
+
|
|
516
|
+
for (const arg of event.args) {
|
|
517
|
+
const fieldName = arg.label;
|
|
518
|
+
const fieldCodec = buildCodecFromType(arg.type.type, types, cache);
|
|
519
|
+
structDef[fieldName] = fieldCodec;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// If event has no args, return void decoder
|
|
523
|
+
if (event.args.length === 0) {
|
|
524
|
+
return () => undefined;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// If event has single unnamed arg, unwrap it
|
|
528
|
+
if (event.args.length === 1) {
|
|
529
|
+
const argCodec = structDef[event.args[0]!.label];
|
|
530
|
+
return (data: Uint8Array) => argCodec!.dec(data);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Multiple args - return struct
|
|
534
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
535
|
+
const codec = Struct(structDef as any);
|
|
536
|
+
return (data: Uint8Array) => codec.dec(data);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Build decoders for all events in the metadata
|
|
541
|
+
*
|
|
542
|
+
* @param metadata - The ink contract metadata
|
|
543
|
+
* @returns Map of event label -> decoder function
|
|
544
|
+
*/
|
|
545
|
+
export function buildAllEventDecoders(
|
|
546
|
+
metadata: InkMetadata,
|
|
547
|
+
): Map<string, (data: Uint8Array) => unknown> {
|
|
548
|
+
const decoders = new Map<string, (data: Uint8Array) => unknown>();
|
|
549
|
+
|
|
550
|
+
const events = metadata.spec.events as Array<{ label: string }>;
|
|
551
|
+
|
|
552
|
+
for (const event of events) {
|
|
553
|
+
try {
|
|
554
|
+
const decoder = buildEventDecoder(metadata, event.label);
|
|
555
|
+
decoders.set(event.label, decoder);
|
|
556
|
+
} catch (error) {
|
|
557
|
+
console.warn(`Failed to build decoder for event "${event.label}":`, error);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return decoders;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Get event signature (topic[0]) for filtering
|
|
566
|
+
* Events in ink! use blake2_256 hash of event label as topic[0]
|
|
567
|
+
*
|
|
568
|
+
* @param eventLabel - The event label (e.g., "Transfer")
|
|
569
|
+
* @returns Event signature as Uint8Array (32 bytes)
|
|
570
|
+
*/
|
|
571
|
+
export function getEventSignature(eventLabel: string): Uint8Array {
|
|
572
|
+
return blake2b(new TextEncoder().encode(eventLabel), { dkLen: 32 });
|
|
573
|
+
}
|