@d9-network/ink 0.0.3 → 0.0.4
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 +1274 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +750 -0
- package/package.json +7 -5
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1274 @@
|
|
|
1
|
+
let polkadot_api = require("polkadot-api");
|
|
2
|
+
let polkadot_api_utils = require("polkadot-api/utils");
|
|
3
|
+
let _polkadot_api_ink_contracts = require("@polkadot-api/ink-contracts");
|
|
4
|
+
let _polkadot_labs_hdkd_helpers = require("@polkadot-labs/hdkd-helpers");
|
|
5
|
+
let _subsquid_scale_codec = require("@subsquid/scale-codec");
|
|
6
|
+
let _polkadot_api_substrate_bindings = require("@polkadot-api/substrate-bindings");
|
|
7
|
+
let _noble_hashes_blake2_js = require("@noble/hashes/blake2.js");
|
|
8
|
+
let rxjs = require("rxjs");
|
|
9
|
+
|
|
10
|
+
//#region src/encode.ts
|
|
11
|
+
/**
|
|
12
|
+
* Encode a contract call for ContractsApi_call state_call.
|
|
13
|
+
*
|
|
14
|
+
* The encoded format matches the ContractsApi::call runtime API:
|
|
15
|
+
* - origin: AccountId (32 bytes)
|
|
16
|
+
* - dest: AccountId (32 bytes)
|
|
17
|
+
* - value: Balance (u128)
|
|
18
|
+
* - gas_limit: Option<Weight> (1 byte for None)
|
|
19
|
+
* - storage_deposit_limit: Option<Balance> (1 byte for None)
|
|
20
|
+
* - input_data: Vec<u8> (compact length + bytes)
|
|
21
|
+
*
|
|
22
|
+
* @param origin - The origin account (as Uint8Array or hex string)
|
|
23
|
+
* @param dest - The contract address (as Uint8Array or hex string)
|
|
24
|
+
* @param input - The encoded call data (selector + arguments)
|
|
25
|
+
* @param value - Optional value to transfer (default: 0)
|
|
26
|
+
* @returns Hex-encoded bytes for state_call
|
|
27
|
+
*/
|
|
28
|
+
function encodeContractCall(origin, dest, input, value = 0n) {
|
|
29
|
+
const sink = new _subsquid_scale_codec.HexSink();
|
|
30
|
+
const originBytes = typeof origin === "string" ? (0, polkadot_api_utils.fromHex)(origin) : origin;
|
|
31
|
+
const destBytes = typeof dest === "string" ? (0, polkadot_api_utils.fromHex)(dest) : dest;
|
|
32
|
+
const inputBytes = "asBytes" in input ? input.asBytes() : input;
|
|
33
|
+
sink.bytes(originBytes);
|
|
34
|
+
sink.bytes(destBytes);
|
|
35
|
+
sink.u128(value);
|
|
36
|
+
sink.u8(0);
|
|
37
|
+
sink.u8(0);
|
|
38
|
+
sink.compact(inputBytes.length);
|
|
39
|
+
sink.bytes(inputBytes);
|
|
40
|
+
return sink.toHex();
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Encode a contract call using the same address for origin and dest.
|
|
44
|
+
* This is a simplified version for query operations where the origin
|
|
45
|
+
* doesn't matter much.
|
|
46
|
+
*
|
|
47
|
+
* @param address - The contract address
|
|
48
|
+
* @param input - The encoded call data
|
|
49
|
+
* @param value - Optional value to transfer
|
|
50
|
+
* @returns Hex-encoded bytes for state_call
|
|
51
|
+
*/
|
|
52
|
+
function encodeCall(address, input, value = 0n) {
|
|
53
|
+
return encodeContractCall(address, address, input, value);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Encode a contract call with specific gas limit and storage deposit limit.
|
|
57
|
+
*
|
|
58
|
+
* @param origin - The origin account
|
|
59
|
+
* @param dest - The contract address
|
|
60
|
+
* @param input - The encoded call data
|
|
61
|
+
* @param options - Call options including value, gas limit, storage deposit limit
|
|
62
|
+
* @returns Hex-encoded bytes for state_call
|
|
63
|
+
*/
|
|
64
|
+
function encodeContractCallWithLimits(origin, dest, input, options = {}) {
|
|
65
|
+
const sink = new _subsquid_scale_codec.HexSink();
|
|
66
|
+
const originBytes = typeof origin === "string" ? (0, polkadot_api_utils.fromHex)(origin) : origin;
|
|
67
|
+
const destBytes = typeof dest === "string" ? (0, polkadot_api_utils.fromHex)(dest) : dest;
|
|
68
|
+
const inputBytes = "asBytes" in input ? input.asBytes() : input;
|
|
69
|
+
sink.bytes(originBytes);
|
|
70
|
+
sink.bytes(destBytes);
|
|
71
|
+
sink.u128(options.value ?? 0n);
|
|
72
|
+
if (options.gasLimit) {
|
|
73
|
+
sink.u8(1);
|
|
74
|
+
sink.compact(options.gasLimit.refTime);
|
|
75
|
+
sink.compact(options.gasLimit.proofSize);
|
|
76
|
+
} else sink.u8(0);
|
|
77
|
+
if (options.storageDepositLimit !== void 0) {
|
|
78
|
+
sink.u8(1);
|
|
79
|
+
sink.u128(options.storageDepositLimit);
|
|
80
|
+
} else sink.u8(0);
|
|
81
|
+
sink.compact(inputBytes.length);
|
|
82
|
+
sink.bytes(inputBytes);
|
|
83
|
+
return sink.toHex();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region src/decode.ts
|
|
88
|
+
/**
|
|
89
|
+
* Decoding utilities for ContractsApi_call response
|
|
90
|
+
*/
|
|
91
|
+
/**
|
|
92
|
+
* Decode the raw ContractsApi_call response.
|
|
93
|
+
*
|
|
94
|
+
* The response format is:
|
|
95
|
+
* - gasConsumed: Weight { ref_time: Compact<u64>, proof_size: Compact<u64> }
|
|
96
|
+
* - gasRequired: Weight
|
|
97
|
+
* - storageDeposit: StorageDeposit { variant: u8, amount: u128 }
|
|
98
|
+
* - debugMessage: String
|
|
99
|
+
* - result: Result<ExecReturnValue, DispatchError>
|
|
100
|
+
* - ExecReturnValue: { flags: u32, data: Vec<u8> }
|
|
101
|
+
*
|
|
102
|
+
* @param result - The raw response bytes from state_call ContractsApi_call
|
|
103
|
+
* @returns Decoded contract call result
|
|
104
|
+
*/
|
|
105
|
+
function decodeContractCallResult(result) {
|
|
106
|
+
const src = new _subsquid_scale_codec.Src(result);
|
|
107
|
+
const gasConsumedRefTime = BigInt(src.compact());
|
|
108
|
+
const gasConsumedProofSize = BigInt(src.compact());
|
|
109
|
+
const gasRequiredRefTime = BigInt(src.compact());
|
|
110
|
+
const gasRequiredProofSize = BigInt(src.compact());
|
|
111
|
+
const storageDepositVariant = src.u8();
|
|
112
|
+
const storageDepositAmount = src.u128();
|
|
113
|
+
const debugMessage = src.str();
|
|
114
|
+
const success = src.u8() === 0;
|
|
115
|
+
const flags = src.u32();
|
|
116
|
+
const data = src.bytes(src.compactLength());
|
|
117
|
+
return {
|
|
118
|
+
gas: {
|
|
119
|
+
gasConsumed: {
|
|
120
|
+
refTime: gasConsumedRefTime,
|
|
121
|
+
proofSize: gasConsumedProofSize
|
|
122
|
+
},
|
|
123
|
+
gasRequired: {
|
|
124
|
+
refTime: gasRequiredRefTime,
|
|
125
|
+
proofSize: gasRequiredProofSize
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
storageDeposit: {
|
|
129
|
+
type: storageDepositVariant === 0 ? "Refund" : "Charge",
|
|
130
|
+
amount: storageDepositAmount
|
|
131
|
+
},
|
|
132
|
+
debugMessage,
|
|
133
|
+
success,
|
|
134
|
+
flags,
|
|
135
|
+
data
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Legacy function - decode and return just the exec result data.
|
|
140
|
+
* @deprecated Use decodeContractCallResult for full information
|
|
141
|
+
*/
|
|
142
|
+
function decodeResult(result) {
|
|
143
|
+
return decodeContractCallResult(result).data;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Unwrap the inner value from Result<T, LangError> by checking the variant byte.
|
|
147
|
+
*
|
|
148
|
+
* Ink contracts wrap their return values in Result<T, LangError>.
|
|
149
|
+
* This function handles the unwrapping and throws an error for LangError.
|
|
150
|
+
*
|
|
151
|
+
* @param data - The exec result bytes (Result<T, LangError> encoded)
|
|
152
|
+
* @returns The inner value bytes (T encoded)
|
|
153
|
+
* @throws Error if the result is Err variant (LangError)
|
|
154
|
+
*/
|
|
155
|
+
function unwrapInkResult(data) {
|
|
156
|
+
if (data.length === 0) throw new Error("Empty result data");
|
|
157
|
+
const variant = data[0];
|
|
158
|
+
if (variant === 0) return data.slice(1);
|
|
159
|
+
else if (variant === 1) throw new Error("Contract call returned LangError");
|
|
160
|
+
else throw new Error(`Unknown result variant: ${variant}`);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Check if the result indicates a LangError
|
|
164
|
+
*
|
|
165
|
+
* @param data - The exec result bytes
|
|
166
|
+
* @returns True if it's a LangError (Err variant)
|
|
167
|
+
*/
|
|
168
|
+
function isLangError(data) {
|
|
169
|
+
return data.length > 0 && data[0] === 1;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Decode ink contract message result using a custom SCALE codec.
|
|
173
|
+
* This bypasses polkadot-api's ink decoder which has issues with LangError type.
|
|
174
|
+
*
|
|
175
|
+
* @param data - The exec result bytes (Result<T, LangError> encoded)
|
|
176
|
+
* @param codec - The SCALE codec for the inner value type T
|
|
177
|
+
* @returns The decoded value
|
|
178
|
+
*/
|
|
179
|
+
function decodeInkValue(data, codec) {
|
|
180
|
+
const innerData = unwrapInkResult(data);
|
|
181
|
+
return codec.dec(innerData);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Pre-defined codecs for common types
|
|
185
|
+
*/
|
|
186
|
+
const InkCodecs = {
|
|
187
|
+
u128: _polkadot_api_substrate_bindings.u128,
|
|
188
|
+
balancePair: (0, _polkadot_api_substrate_bindings.Tuple)(_polkadot_api_substrate_bindings.u128, _polkadot_api_substrate_bindings.u128)
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
//#endregion
|
|
192
|
+
//#region src/codec-builder.ts
|
|
193
|
+
/**
|
|
194
|
+
* Auto-build SCALE decoders from ink metadata type definitions.
|
|
195
|
+
*
|
|
196
|
+
* This module provides a way to automatically construct decoders for ink contract
|
|
197
|
+
* message return types without manually specifying codecs for each message.
|
|
198
|
+
*/
|
|
199
|
+
/**
|
|
200
|
+
* Build a SCALE codec from ink metadata type definition
|
|
201
|
+
*/
|
|
202
|
+
function buildCodecFromType(typeId, types, cache) {
|
|
203
|
+
const cached = cache.get(typeId);
|
|
204
|
+
if (cached) return cached;
|
|
205
|
+
const typeEntry = types.find((t) => t.id === typeId);
|
|
206
|
+
if (!typeEntry) throw new Error(`Type ${typeId} not found in metadata`);
|
|
207
|
+
const def = typeEntry.type.def;
|
|
208
|
+
const path = typeEntry.type.path;
|
|
209
|
+
if (def.primitive) {
|
|
210
|
+
const codec = buildPrimitiveCodec(def.primitive);
|
|
211
|
+
cache.set(typeId, codec);
|
|
212
|
+
return codec;
|
|
213
|
+
}
|
|
214
|
+
if (path && path.length > 0) {
|
|
215
|
+
const specialCodec = buildSpecialTypeCodec(path, typeEntry, types, cache);
|
|
216
|
+
if (specialCodec) {
|
|
217
|
+
cache.set(typeId, specialCodec);
|
|
218
|
+
return specialCodec;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (def.tuple) {
|
|
222
|
+
const codec = buildTupleCodec(def.tuple, types, cache);
|
|
223
|
+
cache.set(typeId, codec);
|
|
224
|
+
return codec;
|
|
225
|
+
}
|
|
226
|
+
if (def.sequence) {
|
|
227
|
+
const codec = (0, _polkadot_api_substrate_bindings.Vector)(buildCodecFromType(def.sequence.type, types, cache));
|
|
228
|
+
cache.set(typeId, codec);
|
|
229
|
+
return codec;
|
|
230
|
+
}
|
|
231
|
+
if (def.array) {
|
|
232
|
+
const innerCodec = buildCodecFromType(def.array.type, types, cache);
|
|
233
|
+
if (def.array.type === findPrimitiveTypeId(types, "u8")) {
|
|
234
|
+
const codec$1 = (0, _polkadot_api_substrate_bindings.Bytes)(def.array.len);
|
|
235
|
+
cache.set(typeId, codec$1);
|
|
236
|
+
return codec$1;
|
|
237
|
+
}
|
|
238
|
+
const codec = (0, _polkadot_api_substrate_bindings.Vector)(innerCodec, def.array.len);
|
|
239
|
+
cache.set(typeId, codec);
|
|
240
|
+
return codec;
|
|
241
|
+
}
|
|
242
|
+
if (def.composite) {
|
|
243
|
+
const codec = buildCompositeCodec(def.composite, types, cache);
|
|
244
|
+
cache.set(typeId, codec);
|
|
245
|
+
return codec;
|
|
246
|
+
}
|
|
247
|
+
if (def.variant) {
|
|
248
|
+
const codec = buildVariantCodec(def.variant, path, types, cache);
|
|
249
|
+
cache.set(typeId, codec);
|
|
250
|
+
return codec;
|
|
251
|
+
}
|
|
252
|
+
throw new Error(`Unknown type definition for type ${typeId}: ${JSON.stringify(def)}`);
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Build codec for primitive types
|
|
256
|
+
*/
|
|
257
|
+
function buildPrimitiveCodec(primitive) {
|
|
258
|
+
switch (primitive) {
|
|
259
|
+
case "u8": return _polkadot_api_substrate_bindings.u8;
|
|
260
|
+
case "u16": return _polkadot_api_substrate_bindings.u16;
|
|
261
|
+
case "u32": return _polkadot_api_substrate_bindings.u32;
|
|
262
|
+
case "u64": return _polkadot_api_substrate_bindings.u64;
|
|
263
|
+
case "u128": return _polkadot_api_substrate_bindings.u128;
|
|
264
|
+
case "i8": return _polkadot_api_substrate_bindings.i8;
|
|
265
|
+
case "i16": return _polkadot_api_substrate_bindings.i16;
|
|
266
|
+
case "i32": return _polkadot_api_substrate_bindings.i32;
|
|
267
|
+
case "i64": return _polkadot_api_substrate_bindings.i64;
|
|
268
|
+
case "i128": return _polkadot_api_substrate_bindings.i128;
|
|
269
|
+
case "bool": return _polkadot_api_substrate_bindings.bool;
|
|
270
|
+
case "str": return _polkadot_api_substrate_bindings.str;
|
|
271
|
+
default: throw new Error(`Unknown primitive type: ${primitive}`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Build codec for special types based on path
|
|
276
|
+
*/
|
|
277
|
+
function buildSpecialTypeCodec(path, typeEntry, types, cache) {
|
|
278
|
+
if (path.join("::").includes("AccountId")) return (0, _polkadot_api_substrate_bindings.AccountId)();
|
|
279
|
+
if (path[0] === "Option") {
|
|
280
|
+
const params = typeEntry.type.params;
|
|
281
|
+
if (params && params.length > 0 && params[0]?.type !== void 0) return (0, _polkadot_api_substrate_bindings.Option)(buildCodecFromType(params[0].type, types, cache));
|
|
282
|
+
}
|
|
283
|
+
if (path[0] === "Result") {
|
|
284
|
+
const params = typeEntry.type.params;
|
|
285
|
+
if (params && params.length >= 2) {
|
|
286
|
+
const okTypeId = params[0]?.type;
|
|
287
|
+
const errTypeId = params[1]?.type;
|
|
288
|
+
if (okTypeId !== void 0 && errTypeId !== void 0) return (0, _polkadot_api_substrate_bindings.Variant)({
|
|
289
|
+
Ok: buildCodecFromType(okTypeId, types, cache),
|
|
290
|
+
Err: buildCodecFromType(errTypeId, types, cache)
|
|
291
|
+
}, [0, 1]);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Build codec for tuple types
|
|
298
|
+
*/
|
|
299
|
+
function buildTupleCodec(tupleTypes, types, cache) {
|
|
300
|
+
if (tupleTypes.length === 0) return _polkadot_api_substrate_bindings._void;
|
|
301
|
+
const innerCodecs = tupleTypes.map((t) => buildCodecFromType(t, types, cache));
|
|
302
|
+
switch (innerCodecs.length) {
|
|
303
|
+
case 1: return (0, _polkadot_api_substrate_bindings.Tuple)(innerCodecs[0]);
|
|
304
|
+
case 2: return (0, _polkadot_api_substrate_bindings.Tuple)(innerCodecs[0], innerCodecs[1]);
|
|
305
|
+
case 3: return (0, _polkadot_api_substrate_bindings.Tuple)(innerCodecs[0], innerCodecs[1], innerCodecs[2]);
|
|
306
|
+
case 4: return (0, _polkadot_api_substrate_bindings.Tuple)(innerCodecs[0], innerCodecs[1], innerCodecs[2], innerCodecs[3]);
|
|
307
|
+
default: return (0, _polkadot_api_substrate_bindings.Tuple)(...innerCodecs);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Build codec for composite (struct) types
|
|
312
|
+
*/
|
|
313
|
+
function buildCompositeCodec(composite, types, cache) {
|
|
314
|
+
const fields = composite.fields;
|
|
315
|
+
if (fields.length === 1 && !fields[0]?.name) return buildCodecFromType(fields[0].type, types, cache);
|
|
316
|
+
const structDef = {};
|
|
317
|
+
for (const field of fields) {
|
|
318
|
+
const fieldName = field.name || `field${fields.indexOf(field)}`;
|
|
319
|
+
structDef[fieldName] = buildCodecFromType(field.type, types, cache);
|
|
320
|
+
}
|
|
321
|
+
return (0, _polkadot_api_substrate_bindings.Struct)(structDef);
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Build codec for variant (enum) types
|
|
325
|
+
*/
|
|
326
|
+
function buildVariantCodec(variant, path, types, cache) {
|
|
327
|
+
const variants = variant.variants;
|
|
328
|
+
const isLangError$1 = path?.includes("LangError");
|
|
329
|
+
const variantDef = {};
|
|
330
|
+
const indices = [];
|
|
331
|
+
if (isLangError$1 && !variants.some((v) => v.index === 0)) {
|
|
332
|
+
variantDef["_Placeholder"] = _polkadot_api_substrate_bindings._void;
|
|
333
|
+
indices.push(0);
|
|
334
|
+
}
|
|
335
|
+
for (const v of variants) {
|
|
336
|
+
let fieldCodec;
|
|
337
|
+
const fields = v.fields ?? [];
|
|
338
|
+
if (fields.length === 0) fieldCodec = _polkadot_api_substrate_bindings._void;
|
|
339
|
+
else if (fields.length === 1 && !fields[0]?.name) fieldCodec = buildCodecFromType(fields[0].type, types, cache);
|
|
340
|
+
else {
|
|
341
|
+
const structDef = {};
|
|
342
|
+
for (const field of fields) {
|
|
343
|
+
const fieldName = field.name || `field${fields.indexOf(field)}`;
|
|
344
|
+
structDef[fieldName] = buildCodecFromType(field.type, types, cache);
|
|
345
|
+
}
|
|
346
|
+
fieldCodec = (0, _polkadot_api_substrate_bindings.Struct)(structDef);
|
|
347
|
+
}
|
|
348
|
+
variantDef[v.name] = fieldCodec;
|
|
349
|
+
indices.push(v.index);
|
|
350
|
+
}
|
|
351
|
+
return (0, _polkadot_api_substrate_bindings.Variant)(variantDef, indices);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Find the type ID for a primitive type
|
|
355
|
+
*/
|
|
356
|
+
function findPrimitiveTypeId(types, primitive) {
|
|
357
|
+
return types.find((t) => t.type.def.primitive === primitive)?.id ?? -1;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Extract the inner type from Result<T, LangError>
|
|
361
|
+
* Returns the type ID of T
|
|
362
|
+
*/
|
|
363
|
+
function extractResultInnerType(typeEntry) {
|
|
364
|
+
const path = typeEntry.type.path;
|
|
365
|
+
if (!path || path[0] !== "Result") return null;
|
|
366
|
+
const params = typeEntry.type.params;
|
|
367
|
+
if (!params || params.length < 1) return null;
|
|
368
|
+
return params[0]?.type ?? null;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Build a decoder for a message's return type from ink metadata.
|
|
372
|
+
* This handles the Result<T, LangError> wrapper and returns a decoder for T.
|
|
373
|
+
*
|
|
374
|
+
* @param metadata - The ink contract metadata
|
|
375
|
+
* @param messageLabel - The message label (e.g., "PSP22::balance_of")
|
|
376
|
+
* @returns A decoder function that decodes the inner value bytes
|
|
377
|
+
*/
|
|
378
|
+
function buildMessageDecoder(metadata, messageLabel) {
|
|
379
|
+
const types = metadata.types;
|
|
380
|
+
const message = metadata.spec.messages.find((m) => m.label === messageLabel);
|
|
381
|
+
if (!message) throw new Error(`Message "${messageLabel}" not found in metadata`);
|
|
382
|
+
const returnTypeId = message.returnType.type;
|
|
383
|
+
const returnTypeEntry = types.find((t) => t.id === returnTypeId);
|
|
384
|
+
if (!returnTypeEntry) throw new Error(`Return type ${returnTypeId} not found for message "${messageLabel}"`);
|
|
385
|
+
const cache = /* @__PURE__ */ new Map();
|
|
386
|
+
const innerTypeId = extractResultInnerType(returnTypeEntry);
|
|
387
|
+
if (innerTypeId !== null) {
|
|
388
|
+
const innerCodec = buildCodecFromType(innerTypeId, types, cache);
|
|
389
|
+
return (data) => innerCodec.dec(data);
|
|
390
|
+
}
|
|
391
|
+
const codec = buildCodecFromType(returnTypeId, types, cache);
|
|
392
|
+
return (data) => codec.dec(data);
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Build decoders for all messages in the metadata.
|
|
396
|
+
* Returns a Map of message label -> decoder function.
|
|
397
|
+
*/
|
|
398
|
+
function buildAllMessageDecoders(metadata) {
|
|
399
|
+
const decoders = /* @__PURE__ */ new Map();
|
|
400
|
+
for (const message of metadata.spec.messages) try {
|
|
401
|
+
const decoder = buildMessageDecoder(metadata, message.label);
|
|
402
|
+
decoders.set(message.label, decoder);
|
|
403
|
+
} catch (error) {
|
|
404
|
+
console.warn(`Failed to build decoder for message "${message.label}":`, error);
|
|
405
|
+
}
|
|
406
|
+
return decoders;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Create a codec registry compatible with ResponseDecoder type
|
|
410
|
+
*/
|
|
411
|
+
function createCodecRegistry(metadata) {
|
|
412
|
+
const decoders = buildAllMessageDecoders(metadata);
|
|
413
|
+
const registry = /* @__PURE__ */ new Map();
|
|
414
|
+
for (const [label, decoder] of decoders) registry.set(label, { dec: decoder });
|
|
415
|
+
return registry;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Build a SCALE decoder for a contract event from ink metadata
|
|
419
|
+
*
|
|
420
|
+
* @param metadata - The ink contract metadata
|
|
421
|
+
* @param eventLabel - The event label (e.g., "Transfer", "Approval")
|
|
422
|
+
* @returns A decoder function that decodes the event data bytes
|
|
423
|
+
*/
|
|
424
|
+
function buildEventDecoder(metadata, eventLabel) {
|
|
425
|
+
const types = metadata.types;
|
|
426
|
+
const event = metadata.spec.events.find((e) => e.label === eventLabel);
|
|
427
|
+
if (!event) throw new Error(`Event "${eventLabel}" not found in metadata`);
|
|
428
|
+
const cache = /* @__PURE__ */ new Map();
|
|
429
|
+
const structDef = {};
|
|
430
|
+
for (const arg of event.args) {
|
|
431
|
+
const fieldName = arg.label;
|
|
432
|
+
structDef[fieldName] = buildCodecFromType(arg.type.type, types, cache);
|
|
433
|
+
}
|
|
434
|
+
if (event.args.length === 0) return () => void 0;
|
|
435
|
+
if (event.args.length === 1) {
|
|
436
|
+
const argCodec = structDef[event.args[0].label];
|
|
437
|
+
return (data) => argCodec.dec(data);
|
|
438
|
+
}
|
|
439
|
+
const codec = (0, _polkadot_api_substrate_bindings.Struct)(structDef);
|
|
440
|
+
return (data) => codec.dec(data);
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Build decoders for all events in the metadata
|
|
444
|
+
*
|
|
445
|
+
* @param metadata - The ink contract metadata
|
|
446
|
+
* @returns Map of event label -> decoder function
|
|
447
|
+
*/
|
|
448
|
+
function buildAllEventDecoders(metadata) {
|
|
449
|
+
const decoders = /* @__PURE__ */ new Map();
|
|
450
|
+
const events = metadata.spec.events;
|
|
451
|
+
for (const event of events) try {
|
|
452
|
+
const decoder = buildEventDecoder(metadata, event.label);
|
|
453
|
+
decoders.set(event.label, decoder);
|
|
454
|
+
} catch (error) {
|
|
455
|
+
console.warn(`Failed to build decoder for event "${event.label}":`, error);
|
|
456
|
+
}
|
|
457
|
+
return decoders;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Get event signature (topic[0]) for filtering
|
|
461
|
+
* Events in ink! use blake2_256 hash of event label as topic[0]
|
|
462
|
+
*
|
|
463
|
+
* @param eventLabel - The event label (e.g., "Transfer")
|
|
464
|
+
* @returns Event signature as Uint8Array (32 bytes)
|
|
465
|
+
*/
|
|
466
|
+
function getEventSignature(eventLabel) {
|
|
467
|
+
return (0, _noble_hashes_blake2_js.blake2b)(new TextEncoder().encode(eventLabel), { dkLen: 32 });
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
//#endregion
|
|
471
|
+
//#region src/events.ts
|
|
472
|
+
/**
|
|
473
|
+
* Event parser for a specific contract
|
|
474
|
+
*/
|
|
475
|
+
var ContractEventParser = class {
|
|
476
|
+
eventDecoders;
|
|
477
|
+
eventSignatures;
|
|
478
|
+
contractAddressBytes;
|
|
479
|
+
contractAddress;
|
|
480
|
+
metadata;
|
|
481
|
+
constructor(metadata, contractAddress) {
|
|
482
|
+
this.metadata = metadata;
|
|
483
|
+
this.contractAddress = contractAddress;
|
|
484
|
+
this.eventDecoders = buildAllEventDecoders(metadata);
|
|
485
|
+
this.contractAddressBytes = (0, _polkadot_labs_hdkd_helpers.ss58Decode)(contractAddress)[0];
|
|
486
|
+
this.eventSignatures = /* @__PURE__ */ new Map();
|
|
487
|
+
const events = metadata.spec.events;
|
|
488
|
+
for (const event of events) {
|
|
489
|
+
const sig = getEventSignature(event.label);
|
|
490
|
+
this.eventSignatures.set(event.label, sig);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Parse a raw chain event into a contract event (if it matches)
|
|
495
|
+
* Returns null if the event is not from this contract or cannot be parsed
|
|
496
|
+
*/
|
|
497
|
+
parseEvent(chainEvent) {
|
|
498
|
+
const extracted = this.extractContractEmittedEvent(chainEvent);
|
|
499
|
+
if (!extracted) return null;
|
|
500
|
+
const { contract, data, topics, blockNumber, blockHash, eventIndex } = extracted;
|
|
501
|
+
if (!this.bytesEqual(contract, this.contractAddressBytes)) return null;
|
|
502
|
+
if (topics.length === 0) return null;
|
|
503
|
+
const signature = topics[0];
|
|
504
|
+
let eventLabel = null;
|
|
505
|
+
for (const [label, sig] of this.eventSignatures) if (this.bytesEqual(signature, sig)) {
|
|
506
|
+
eventLabel = label;
|
|
507
|
+
break;
|
|
508
|
+
}
|
|
509
|
+
if (!eventLabel) {
|
|
510
|
+
console.warn("Unknown event signature:", signature);
|
|
511
|
+
return null;
|
|
512
|
+
}
|
|
513
|
+
const decoder = this.eventDecoders.get(eventLabel);
|
|
514
|
+
if (!decoder) {
|
|
515
|
+
console.warn(`No decoder for event ${eventLabel}`);
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
try {
|
|
519
|
+
const decodedData = decoder(data);
|
|
520
|
+
return {
|
|
521
|
+
label: eventLabel,
|
|
522
|
+
data: decodedData,
|
|
523
|
+
raw: {
|
|
524
|
+
blockNumber,
|
|
525
|
+
blockHash,
|
|
526
|
+
eventIndex,
|
|
527
|
+
contractAddress: this.getContractAddress(),
|
|
528
|
+
data,
|
|
529
|
+
topics
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
} catch (error) {
|
|
533
|
+
console.warn(`Failed to decode event ${eventLabel}:`, error);
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Filter a batch of events
|
|
539
|
+
*/
|
|
540
|
+
filterEvents(chainEvents, options) {
|
|
541
|
+
const results = [];
|
|
542
|
+
for (const chainEvent of chainEvents) {
|
|
543
|
+
const parsed = this.parseEvent(chainEvent);
|
|
544
|
+
if (!parsed) continue;
|
|
545
|
+
if (options?.eventLabels && !options.eventLabels.includes(parsed.label)) continue;
|
|
546
|
+
if (options?.fromBlock && parsed.raw.blockNumber < options.fromBlock) continue;
|
|
547
|
+
if (options?.toBlock && parsed.raw.blockNumber > options.toBlock) continue;
|
|
548
|
+
results.push(parsed);
|
|
549
|
+
}
|
|
550
|
+
return results;
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Get the contract address as SS58 string
|
|
554
|
+
*/
|
|
555
|
+
getContractAddress() {
|
|
556
|
+
return this.contractAddress;
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Extract ContractEmitted event from chain event structure
|
|
560
|
+
* Based on polkadot-api event format
|
|
561
|
+
*/
|
|
562
|
+
extractContractEmittedEvent(chainEvent) {
|
|
563
|
+
if (!chainEvent || typeof chainEvent !== "object") return null;
|
|
564
|
+
const record = chainEvent;
|
|
565
|
+
const event = record.event;
|
|
566
|
+
if (!event || typeof event !== "object") return null;
|
|
567
|
+
if (event.type !== "Contracts") return null;
|
|
568
|
+
const eventValue = event.value;
|
|
569
|
+
if (!eventValue || typeof eventValue !== "object") return null;
|
|
570
|
+
if (eventValue.type !== "ContractEmitted") return null;
|
|
571
|
+
const contractEmittedData = eventValue.value;
|
|
572
|
+
if (!contractEmittedData || typeof contractEmittedData !== "object") return null;
|
|
573
|
+
const contract = contractEmittedData.contract;
|
|
574
|
+
const data = contractEmittedData.data;
|
|
575
|
+
if (!(contract instanceof Uint8Array) || !(data instanceof Uint8Array)) return null;
|
|
576
|
+
const topics = [];
|
|
577
|
+
if (record.topics && Array.isArray(record.topics)) {
|
|
578
|
+
for (const topic of record.topics) if (topic instanceof Uint8Array) topics.push(topic);
|
|
579
|
+
}
|
|
580
|
+
return {
|
|
581
|
+
contract,
|
|
582
|
+
data,
|
|
583
|
+
topics,
|
|
584
|
+
blockNumber: typeof record.blockNumber === "number" ? record.blockNumber : 0,
|
|
585
|
+
blockHash: typeof record.blockHash === "string" ? record.blockHash : "",
|
|
586
|
+
eventIndex: typeof record.eventIndex === "number" ? record.eventIndex : 0
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Compare two Uint8Arrays for equality
|
|
591
|
+
*/
|
|
592
|
+
bytesEqual(a, b) {
|
|
593
|
+
if (a.length !== b.length) return false;
|
|
594
|
+
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
595
|
+
return true;
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Build event signature map from metadata
|
|
599
|
+
*/
|
|
600
|
+
static buildEventSignatureMap(metadata) {
|
|
601
|
+
const signatures = /* @__PURE__ */ new Map();
|
|
602
|
+
const events = metadata.spec.events;
|
|
603
|
+
for (const event of events) {
|
|
604
|
+
const sig = getEventSignature(event.label);
|
|
605
|
+
signatures.set(event.label, sig);
|
|
606
|
+
}
|
|
607
|
+
return signatures;
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
//#endregion
|
|
612
|
+
//#region src/subscriptions.ts
|
|
613
|
+
/**
|
|
614
|
+
* Event subscriptions using RxJS
|
|
615
|
+
*/
|
|
616
|
+
/**
|
|
617
|
+
* Create an observable stream of contract events
|
|
618
|
+
*
|
|
619
|
+
* @param client - Polkadot API client
|
|
620
|
+
* @param metadata - Contract metadata
|
|
621
|
+
* @param options - Subscription options
|
|
622
|
+
* @returns Observable stream of decoded contract events
|
|
623
|
+
*/
|
|
624
|
+
function createContractEventStream(client, metadata, options) {
|
|
625
|
+
const parser = new ContractEventParser(metadata, options.contractAddress);
|
|
626
|
+
return client.finalizedBlock$.pipe((0, rxjs.mergeMap)(async (block) => {
|
|
627
|
+
try {
|
|
628
|
+
return {
|
|
629
|
+
block,
|
|
630
|
+
events: await options.getEvents(block.hash)
|
|
631
|
+
};
|
|
632
|
+
} catch (error) {
|
|
633
|
+
console.error("Error fetching events at block", block.number, ":", error instanceof Error ? error.message : String(error));
|
|
634
|
+
return {
|
|
635
|
+
block,
|
|
636
|
+
events: []
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
}), (0, rxjs.map)(({ block, events }) => {
|
|
640
|
+
return events.map((event, index) => {
|
|
641
|
+
const eventWithMeta = {
|
|
642
|
+
...event,
|
|
643
|
+
blockNumber: block.number,
|
|
644
|
+
blockHash: block.hash,
|
|
645
|
+
eventIndex: index
|
|
646
|
+
};
|
|
647
|
+
return parser.parseEvent(eventWithMeta);
|
|
648
|
+
}).filter((e) => e !== null);
|
|
649
|
+
}), (0, rxjs.mergeMap)((events) => (0, rxjs.from)(events)), (0, rxjs.filter)((event) => {
|
|
650
|
+
if (!options.eventLabels) return true;
|
|
651
|
+
return options.eventLabels.includes(event.label);
|
|
652
|
+
}), (0, rxjs.catchError)((error) => {
|
|
653
|
+
console.error("Error in contract event stream:", error);
|
|
654
|
+
return (0, rxjs.of)();
|
|
655
|
+
}), (0, rxjs.share)());
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Convenience helper to create a Transfer event stream for PSP22 tokens
|
|
659
|
+
*
|
|
660
|
+
* @param client - Polkadot API client
|
|
661
|
+
* @param metadata - PSP22 contract metadata
|
|
662
|
+
* @param contractAddress - PSP22 contract address
|
|
663
|
+
* @param getEvents - Function to fetch System.Events at a block hash
|
|
664
|
+
* @param watchAddress - Optional address to filter transfers (only events involving this address)
|
|
665
|
+
* @returns Observable stream of Transfer events
|
|
666
|
+
*/
|
|
667
|
+
function createPSP22TransferStream(client, metadata, contractAddress, getEvents, watchAddress) {
|
|
668
|
+
return createContractEventStream(client, metadata, {
|
|
669
|
+
contractAddress,
|
|
670
|
+
eventLabels: ["Transfer"],
|
|
671
|
+
getEvents
|
|
672
|
+
}).pipe((0, rxjs.filter)((event) => {
|
|
673
|
+
if (!watchAddress) return true;
|
|
674
|
+
const data = event.data;
|
|
675
|
+
return data.from === watchAddress || data.to === watchAddress;
|
|
676
|
+
}));
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Create a native token (D9) transfer event stream
|
|
680
|
+
*
|
|
681
|
+
* This monitors System.Transfer events instead of contract events
|
|
682
|
+
*
|
|
683
|
+
* @param client - Polkadot API client
|
|
684
|
+
* @param getEvents - Function to fetch System.Events at a block hash
|
|
685
|
+
* @param watchAddress - Address to monitor for transfers
|
|
686
|
+
* @returns Observable stream of native transfer events
|
|
687
|
+
*/
|
|
688
|
+
function createNativeTransferStream(client, getEvents, watchAddress) {
|
|
689
|
+
return client.finalizedBlock$.pipe((0, rxjs.mergeMap)(async (block) => {
|
|
690
|
+
try {
|
|
691
|
+
return {
|
|
692
|
+
block,
|
|
693
|
+
events: await getEvents(block.hash)
|
|
694
|
+
};
|
|
695
|
+
} catch (error) {
|
|
696
|
+
console.error("Error fetching events for native transfers:", error instanceof Error ? error.message : String(error));
|
|
697
|
+
return {
|
|
698
|
+
block,
|
|
699
|
+
events: []
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
}), (0, rxjs.map)(({ block, events }) => {
|
|
703
|
+
return events.map((record) => {
|
|
704
|
+
if (record.event?.type !== "Balances") return null;
|
|
705
|
+
if (record.event?.value?.type !== "Transfer") return null;
|
|
706
|
+
const { from: from$1, to, amount } = record.event.value.value;
|
|
707
|
+
if (from$1 !== watchAddress && to !== watchAddress) return null;
|
|
708
|
+
return {
|
|
709
|
+
from: from$1,
|
|
710
|
+
to,
|
|
711
|
+
amount,
|
|
712
|
+
blockNumber: block.number,
|
|
713
|
+
blockHash: block.hash
|
|
714
|
+
};
|
|
715
|
+
}).filter((t) => t !== null);
|
|
716
|
+
}), (0, rxjs.mergeMap)((transfers) => (0, rxjs.from)(transfers)), (0, rxjs.catchError)((error) => {
|
|
717
|
+
console.error("Error in native transfer stream:", error);
|
|
718
|
+
return (0, rxjs.of)();
|
|
719
|
+
}), (0, rxjs.share)());
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
//#endregion
|
|
723
|
+
//#region src/errors.ts
|
|
724
|
+
/**
|
|
725
|
+
* Base error class for all contract-related errors
|
|
726
|
+
*/
|
|
727
|
+
var ContractError = class ContractError extends Error {
|
|
728
|
+
timestamp;
|
|
729
|
+
cause;
|
|
730
|
+
constructor(message, type, label, details, cause) {
|
|
731
|
+
super(message, { cause });
|
|
732
|
+
this.type = type;
|
|
733
|
+
this.label = label;
|
|
734
|
+
this.details = details;
|
|
735
|
+
this.name = "ContractError";
|
|
736
|
+
this.timestamp = /* @__PURE__ */ new Date();
|
|
737
|
+
if (Error.captureStackTrace) Error.captureStackTrace(this, ContractError);
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Create a formatted error message for logging
|
|
741
|
+
*/
|
|
742
|
+
toLogString() {
|
|
743
|
+
const parts = [
|
|
744
|
+
`[${this.type}]`,
|
|
745
|
+
this.label ? `${this.label}:` : "",
|
|
746
|
+
this.message
|
|
747
|
+
].filter(Boolean);
|
|
748
|
+
if (this.details) parts.push(`Details: ${JSON.stringify(this.details)}`);
|
|
749
|
+
return parts.join(" ");
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Convert to a plain object for serialization
|
|
753
|
+
*/
|
|
754
|
+
toJSON() {
|
|
755
|
+
return {
|
|
756
|
+
name: this.name,
|
|
757
|
+
type: this.type,
|
|
758
|
+
message: this.message,
|
|
759
|
+
label: this.label,
|
|
760
|
+
details: this.details,
|
|
761
|
+
timestamp: this.timestamp.toISOString(),
|
|
762
|
+
stack: this.stack
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
/**
|
|
767
|
+
* Error thrown when contract metadata is missing or invalid
|
|
768
|
+
*/
|
|
769
|
+
var MetadataError = class extends ContractError {
|
|
770
|
+
constructor(message, details) {
|
|
771
|
+
super(message, "METADATA_ERROR", void 0, details);
|
|
772
|
+
this.name = "MetadataError";
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
/**
|
|
776
|
+
* Error thrown when encoding call data fails
|
|
777
|
+
*/
|
|
778
|
+
var EncodeError = class extends ContractError {
|
|
779
|
+
constructor(label, message, details) {
|
|
780
|
+
super(message, "ENCODE_ERROR", label, details);
|
|
781
|
+
this.name = "EncodeError";
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
/**
|
|
785
|
+
* Error thrown when decoding response fails
|
|
786
|
+
*/
|
|
787
|
+
var DecodeError = class extends ContractError {
|
|
788
|
+
constructor(label, message, details) {
|
|
789
|
+
super(message, "DECODE_ERROR", label, details);
|
|
790
|
+
this.name = "DecodeError";
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
/**
|
|
794
|
+
* Error thrown for network/RPC errors
|
|
795
|
+
*/
|
|
796
|
+
var NetworkError = class extends ContractError {
|
|
797
|
+
constructor(message, cause, details) {
|
|
798
|
+
super(message, "NETWORK_ERROR", void 0, details, cause);
|
|
799
|
+
this.name = "NetworkError";
|
|
800
|
+
}
|
|
801
|
+
};
|
|
802
|
+
/**
|
|
803
|
+
* Error thrown when contract returns an error result
|
|
804
|
+
*/
|
|
805
|
+
var ContractExecutionError = class extends ContractError {
|
|
806
|
+
constructor(label, errorValue) {
|
|
807
|
+
super(`Contract execution failed: ${JSON.stringify(errorValue)}`, "CONTRACT_ERROR", label, errorValue);
|
|
808
|
+
this.errorValue = errorValue;
|
|
809
|
+
this.name = "ContractExecutionError";
|
|
810
|
+
}
|
|
811
|
+
};
|
|
812
|
+
/**
|
|
813
|
+
* Error thrown for Ink LangError
|
|
814
|
+
*/
|
|
815
|
+
var LangError = class extends ContractError {
|
|
816
|
+
constructor(label, variant) {
|
|
817
|
+
const variantName = variant === 1 ? "CouldNotReadInput" : `Unknown(${variant})`;
|
|
818
|
+
super(`Ink LangError: ${variantName}`, "LANG_ERROR", label, {
|
|
819
|
+
variant,
|
|
820
|
+
variantName
|
|
821
|
+
});
|
|
822
|
+
this.variant = variant;
|
|
823
|
+
this.name = "LangError";
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
/**
|
|
827
|
+
* Error thrown when request times out
|
|
828
|
+
*/
|
|
829
|
+
var TimeoutError = class extends ContractError {
|
|
830
|
+
constructor(label, timeoutMs) {
|
|
831
|
+
super(`Request timed out after ${timeoutMs}ms`, "TIMEOUT_ERROR", label, { timeoutMs });
|
|
832
|
+
this.timeoutMs = timeoutMs;
|
|
833
|
+
this.name = "TimeoutError";
|
|
834
|
+
}
|
|
835
|
+
};
|
|
836
|
+
/**
|
|
837
|
+
* Error thrown when request is aborted
|
|
838
|
+
*/
|
|
839
|
+
var AbortedError = class extends ContractError {
|
|
840
|
+
constructor(label, reason) {
|
|
841
|
+
super(reason || "Request was aborted", "ABORTED", label, { reason });
|
|
842
|
+
this.name = "AbortedError";
|
|
843
|
+
}
|
|
844
|
+
};
|
|
845
|
+
/**
|
|
846
|
+
* Error thrown for signer-related issues
|
|
847
|
+
*/
|
|
848
|
+
var SignerError = class extends ContractError {
|
|
849
|
+
constructor(message, details) {
|
|
850
|
+
super(message, "SIGNER_ERROR", void 0, details);
|
|
851
|
+
this.name = "SignerError";
|
|
852
|
+
}
|
|
853
|
+
};
|
|
854
|
+
/**
|
|
855
|
+
* Error thrown when transaction submission fails
|
|
856
|
+
*/
|
|
857
|
+
var TransactionError = class extends ContractError {
|
|
858
|
+
constructor(label, message, txHash, details) {
|
|
859
|
+
super(message, "TX_ERROR", label, {
|
|
860
|
+
...details,
|
|
861
|
+
txHash
|
|
862
|
+
});
|
|
863
|
+
this.txHash = txHash;
|
|
864
|
+
this.name = "TransactionError";
|
|
865
|
+
}
|
|
866
|
+
};
|
|
867
|
+
/**
|
|
868
|
+
* Type guard to check if an error is a ContractError
|
|
869
|
+
*/
|
|
870
|
+
function isContractError(error) {
|
|
871
|
+
return error instanceof ContractError;
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Type guard to check for specific error types
|
|
875
|
+
*/
|
|
876
|
+
function isErrorType(error, type) {
|
|
877
|
+
return isContractError(error) && error.type === type;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
//#endregion
|
|
881
|
+
//#region src/contract.ts
|
|
882
|
+
/**
|
|
883
|
+
* Patch LangError type in ink metadata to fix the missing index 0 variant issue.
|
|
884
|
+
* Uses structuredClone for efficient deep cloning.
|
|
885
|
+
*/
|
|
886
|
+
function patchLangErrorInMetadata(metadata) {
|
|
887
|
+
const patched = structuredClone(metadata);
|
|
888
|
+
for (const typeEntry of patched.types) {
|
|
889
|
+
const path = typeEntry.type?.path;
|
|
890
|
+
const def = typeEntry.type?.def;
|
|
891
|
+
if (path && Array.isArray(path) && path.includes("LangError") && def?.variant) {
|
|
892
|
+
const variants = def.variant.variants;
|
|
893
|
+
if (Array.isArray(variants)) {
|
|
894
|
+
if (!variants.some((v) => v.index === 0)) variants.unshift({
|
|
895
|
+
index: 0,
|
|
896
|
+
name: "_Placeholder",
|
|
897
|
+
fields: [],
|
|
898
|
+
docs: []
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
return patched;
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* Convert SS58 address to bytes
|
|
907
|
+
*/
|
|
908
|
+
function ss58ToBytes(address) {
|
|
909
|
+
const [publicKey] = (0, _polkadot_labs_hdkd_helpers.ss58Decode)(address);
|
|
910
|
+
return publicKey;
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Create a promise that rejects after a timeout
|
|
914
|
+
*/
|
|
915
|
+
function createTimeout(ms, label) {
|
|
916
|
+
let timeoutId;
|
|
917
|
+
return {
|
|
918
|
+
promise: new Promise((_, reject) => {
|
|
919
|
+
timeoutId = setTimeout(() => {
|
|
920
|
+
reject(new TimeoutError(label, ms));
|
|
921
|
+
}, ms);
|
|
922
|
+
}),
|
|
923
|
+
clear: () => clearTimeout(timeoutId)
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* Check if AbortSignal is aborted and throw if so
|
|
928
|
+
*/
|
|
929
|
+
function checkAborted(signal, label) {
|
|
930
|
+
if (signal?.aborted) throw new AbortedError(label, signal.reason);
|
|
931
|
+
}
|
|
932
|
+
/**
|
|
933
|
+
* Create a D9 Ink Contract instance
|
|
934
|
+
*/
|
|
935
|
+
function createD9InkContract(descriptor, address, options) {
|
|
936
|
+
const { client, typedApi, defaultQueryOptions = {}, defaultSendOptions = {} } = options;
|
|
937
|
+
if (!descriptor.metadata) throw new MetadataError("Contract descriptor must include metadata");
|
|
938
|
+
const patchedMetadata = patchLangErrorInMetadata(descriptor.metadata);
|
|
939
|
+
const builder = (0, _polkadot_api_ink_contracts.getInkDynamicBuilder)((0, _polkadot_api_ink_contracts.getInkLookup)(patchedMetadata));
|
|
940
|
+
let codecRegistry;
|
|
941
|
+
try {
|
|
942
|
+
codecRegistry = createCodecRegistry(patchedMetadata);
|
|
943
|
+
} catch (error) {
|
|
944
|
+
console.warn("Failed to auto-generate codecs from metadata:", error);
|
|
945
|
+
codecRegistry = /* @__PURE__ */ new Map();
|
|
946
|
+
}
|
|
947
|
+
const addressBytes = ss58ToBytes(address);
|
|
948
|
+
const messageCodecCache = /* @__PURE__ */ new Map();
|
|
949
|
+
function getMessageCodec(label) {
|
|
950
|
+
const cached = messageCodecCache.get(label);
|
|
951
|
+
if (cached) return cached;
|
|
952
|
+
const codec = builder.buildMessage(label);
|
|
953
|
+
messageCodecCache.set(label, codec);
|
|
954
|
+
return codec;
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* Get the decoder for a message
|
|
958
|
+
*/
|
|
959
|
+
function getDecoder(label) {
|
|
960
|
+
return codecRegistry.get(label) ?? null;
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
963
|
+
* Execute a query (dry-run)
|
|
964
|
+
*/
|
|
965
|
+
async function executeQuery(method, queryOptions) {
|
|
966
|
+
const { origin, args, value = 0n, signal, timeout, at } = {
|
|
967
|
+
...defaultQueryOptions,
|
|
968
|
+
...queryOptions
|
|
969
|
+
};
|
|
970
|
+
try {
|
|
971
|
+
checkAborted(signal, method);
|
|
972
|
+
const originBytes = ss58ToBytes(origin);
|
|
973
|
+
const codec = getMessageCodec(method);
|
|
974
|
+
const message = encodeContractCall(originBytes, addressBytes, codec.call.enc(args ?? {}), value);
|
|
975
|
+
const blockHash = at ?? (await client.getFinalizedBlock()).hash;
|
|
976
|
+
checkAborted(signal, method);
|
|
977
|
+
const executeCall = async () => {
|
|
978
|
+
return (0, polkadot_api_utils.fromHex)(await client._request("state_call", [
|
|
979
|
+
"ContractsApi_call",
|
|
980
|
+
message,
|
|
981
|
+
blockHash
|
|
982
|
+
]));
|
|
983
|
+
};
|
|
984
|
+
let rawResponse;
|
|
985
|
+
if (timeout) {
|
|
986
|
+
const { promise: timeoutPromise, clear } = createTimeout(timeout, method);
|
|
987
|
+
try {
|
|
988
|
+
rawResponse = await Promise.race([executeCall(), timeoutPromise]);
|
|
989
|
+
} finally {
|
|
990
|
+
clear();
|
|
991
|
+
}
|
|
992
|
+
} else rawResponse = await executeCall();
|
|
993
|
+
checkAborted(signal, method);
|
|
994
|
+
const callResult = decodeContractCallResult(rawResponse);
|
|
995
|
+
if (!callResult.success) return {
|
|
996
|
+
success: false,
|
|
997
|
+
error: new ContractError(`Contract execution failed: ${callResult.debugMessage}`, "CONTRACT_ERROR", method)
|
|
998
|
+
};
|
|
999
|
+
if (isLangError(callResult.data)) return {
|
|
1000
|
+
success: false,
|
|
1001
|
+
error: new LangError(method, callResult.data[1] ?? 1)
|
|
1002
|
+
};
|
|
1003
|
+
const innerData = unwrapInkResult(callResult.data);
|
|
1004
|
+
let decodedResponse;
|
|
1005
|
+
const decoder = getDecoder(method);
|
|
1006
|
+
if (decoder) try {
|
|
1007
|
+
decodedResponse = decoder.dec(innerData);
|
|
1008
|
+
} catch (decodeError) {
|
|
1009
|
+
console.warn("D9InkContract: Failed to decode response:", decodeError);
|
|
1010
|
+
const fullResult = new Uint8Array(1 + innerData.length);
|
|
1011
|
+
fullResult[0] = 0;
|
|
1012
|
+
fullResult.set(innerData, 1);
|
|
1013
|
+
try {
|
|
1014
|
+
const papiResult = codec.value.dec(fullResult);
|
|
1015
|
+
if (papiResult !== null && typeof papiResult === "object" && "success" in papiResult && "value" in papiResult) if (papiResult.success) decodedResponse = papiResult.value;
|
|
1016
|
+
else return {
|
|
1017
|
+
success: false,
|
|
1018
|
+
error: new DecodeError(method, `Contract returned error: ${JSON.stringify(papiResult.value)}`, papiResult.value)
|
|
1019
|
+
};
|
|
1020
|
+
else decodedResponse = papiResult;
|
|
1021
|
+
} catch (error) {
|
|
1022
|
+
return {
|
|
1023
|
+
success: false,
|
|
1024
|
+
error: new DecodeError(method, `Failed to decode response: ${error instanceof Error ? error.message : String(error)}`, { error })
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
else {
|
|
1029
|
+
const fullResult = new Uint8Array(1 + innerData.length);
|
|
1030
|
+
fullResult[0] = 0;
|
|
1031
|
+
fullResult.set(innerData, 1);
|
|
1032
|
+
const papiResult = codec.value.dec(fullResult);
|
|
1033
|
+
if (papiResult !== null && typeof papiResult === "object" && "success" in papiResult && "value" in papiResult) if (papiResult.success) decodedResponse = papiResult.value;
|
|
1034
|
+
else return {
|
|
1035
|
+
success: false,
|
|
1036
|
+
error: new DecodeError(method, `Contract returned error: ${JSON.stringify(papiResult.value)}`, papiResult.value)
|
|
1037
|
+
};
|
|
1038
|
+
else decodedResponse = papiResult;
|
|
1039
|
+
}
|
|
1040
|
+
return {
|
|
1041
|
+
success: true,
|
|
1042
|
+
value: decodedResponse,
|
|
1043
|
+
events: [],
|
|
1044
|
+
gasConsumed: callResult.gas.gasConsumed,
|
|
1045
|
+
gasRequired: callResult.gas.gasRequired,
|
|
1046
|
+
storageDeposit: callResult.storageDeposit.amount,
|
|
1047
|
+
send: () => createSendableTransaction(method, {
|
|
1048
|
+
origin,
|
|
1049
|
+
args,
|
|
1050
|
+
value,
|
|
1051
|
+
gasLimit: callResult.gas.gasRequired
|
|
1052
|
+
})
|
|
1053
|
+
};
|
|
1054
|
+
} catch (error) {
|
|
1055
|
+
if (error instanceof ContractError) return {
|
|
1056
|
+
success: false,
|
|
1057
|
+
error
|
|
1058
|
+
};
|
|
1059
|
+
return {
|
|
1060
|
+
success: false,
|
|
1061
|
+
error: new ContractError(error instanceof Error ? error.message : String(error), "NETWORK_ERROR", method)
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Create a sendable transaction
|
|
1067
|
+
*/
|
|
1068
|
+
function createSendableTransaction(method, sendOptions) {
|
|
1069
|
+
const { origin, args, value = 0n, gasLimit, storageDepositLimit } = {
|
|
1070
|
+
...defaultSendOptions,
|
|
1071
|
+
...sendOptions
|
|
1072
|
+
};
|
|
1073
|
+
const originBytes = ss58ToBytes(origin);
|
|
1074
|
+
const callData = getMessageCodec(method).call.enc(args ?? {});
|
|
1075
|
+
return {
|
|
1076
|
+
getEncodedData: () => callData,
|
|
1077
|
+
async signAndSubmit(signer) {
|
|
1078
|
+
if (!typedApi) throw new TransactionError(method, "typedApi is required for transaction submission. Pass typedApi in SDK options.");
|
|
1079
|
+
try {
|
|
1080
|
+
let gas = gasLimit;
|
|
1081
|
+
if (!gas) {
|
|
1082
|
+
const message = encodeContractCall(originBytes, addressBytes, callData, value);
|
|
1083
|
+
const blockHash = (await client.getFinalizedBlock()).hash;
|
|
1084
|
+
gas = decodeContractCallResult((0, polkadot_api_utils.fromHex)(await client._request("state_call", [
|
|
1085
|
+
"ContractsApi_call",
|
|
1086
|
+
message,
|
|
1087
|
+
blockHash
|
|
1088
|
+
]))).gas.gasRequired;
|
|
1089
|
+
}
|
|
1090
|
+
const txResult = await typedApi.tx.Contracts.call({
|
|
1091
|
+
dest: {
|
|
1092
|
+
type: "Id",
|
|
1093
|
+
value: address
|
|
1094
|
+
},
|
|
1095
|
+
value,
|
|
1096
|
+
gas_limit: {
|
|
1097
|
+
ref_time: gas.refTime,
|
|
1098
|
+
proof_size: gas.proofSize
|
|
1099
|
+
},
|
|
1100
|
+
storage_deposit_limit: storageDepositLimit,
|
|
1101
|
+
data: polkadot_api.Binary.fromBytes(callData)
|
|
1102
|
+
}).signAndSubmit(signer, { at: "finalized" });
|
|
1103
|
+
return {
|
|
1104
|
+
ok: true,
|
|
1105
|
+
txHash: txResult.txHash,
|
|
1106
|
+
block: txResult.block,
|
|
1107
|
+
events: txResult.events ?? []
|
|
1108
|
+
};
|
|
1109
|
+
} catch (error) {
|
|
1110
|
+
return {
|
|
1111
|
+
ok: false,
|
|
1112
|
+
txHash: "",
|
|
1113
|
+
block: {
|
|
1114
|
+
hash: "",
|
|
1115
|
+
number: 0
|
|
1116
|
+
},
|
|
1117
|
+
events: [],
|
|
1118
|
+
dispatchError: error
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
/**
|
|
1125
|
+
* Create send method that returns a sendable transaction
|
|
1126
|
+
*/
|
|
1127
|
+
function send(method, sendOptions) {
|
|
1128
|
+
return createSendableTransaction(method, sendOptions);
|
|
1129
|
+
}
|
|
1130
|
+
/**
|
|
1131
|
+
* Create storage query interface
|
|
1132
|
+
*/
|
|
1133
|
+
function getStorage() {
|
|
1134
|
+
return {
|
|
1135
|
+
async getRoot() {
|
|
1136
|
+
console.warn("D9InkContract: getRoot not implemented");
|
|
1137
|
+
return {
|
|
1138
|
+
success: false,
|
|
1139
|
+
value: new ContractError("Storage queries not yet implemented", "METADATA_ERROR")
|
|
1140
|
+
};
|
|
1141
|
+
},
|
|
1142
|
+
async getNested(path, ..._keys) {
|
|
1143
|
+
console.warn("D9InkContract: getNested not implemented");
|
|
1144
|
+
return {
|
|
1145
|
+
success: false,
|
|
1146
|
+
value: new ContractError(`Storage query for "${path}" not yet implemented`, "METADATA_ERROR")
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
/**
|
|
1152
|
+
* Filter events for this contract
|
|
1153
|
+
*/
|
|
1154
|
+
function filterEvents(events) {
|
|
1155
|
+
return new ContractEventParser(patchedMetadata, address).filterEvents(events);
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Subscribe to contract events as an RxJS Observable
|
|
1159
|
+
*
|
|
1160
|
+
* @param options - Subscription options (contractAddress is automatically set)
|
|
1161
|
+
* @param options.getEvents - Function to fetch System.Events at a block hash
|
|
1162
|
+
* @param options.eventLabels - Optional filter for specific event names
|
|
1163
|
+
* @param options.fromBlock - Optional starting block number
|
|
1164
|
+
*/
|
|
1165
|
+
function subscribeToEvents(options$1) {
|
|
1166
|
+
return createContractEventStream(client, patchedMetadata, {
|
|
1167
|
+
...options$1,
|
|
1168
|
+
contractAddress: address
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
return {
|
|
1172
|
+
address,
|
|
1173
|
+
metadata: patchedMetadata,
|
|
1174
|
+
query: executeQuery,
|
|
1175
|
+
send,
|
|
1176
|
+
getStorage,
|
|
1177
|
+
filterEvents,
|
|
1178
|
+
subscribeToEvents
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
//#endregion
|
|
1183
|
+
//#region src/sdk.ts
|
|
1184
|
+
/**
|
|
1185
|
+
* Create a D9 Ink SDK instance.
|
|
1186
|
+
*
|
|
1187
|
+
* This SDK provides a similar API to the official @polkadot-api/sdk-ink,
|
|
1188
|
+
* but uses state_call + ContractsApi_call instead of ReviveApi.
|
|
1189
|
+
*
|
|
1190
|
+
* @example
|
|
1191
|
+
* ```ts
|
|
1192
|
+
* import { createD9InkSdk } from "@d9-network/ink";
|
|
1193
|
+
* import { contracts } from "@polkadot-api/descriptors";
|
|
1194
|
+
*
|
|
1195
|
+
* const sdk = createD9InkSdk(client);
|
|
1196
|
+
* const usdtContract = sdk.getContract(
|
|
1197
|
+
* contracts.d9_usdt,
|
|
1198
|
+
* "uLj9DRUujbpCyK7USZY5ebGbxdtKoWvdRvGyyUsoLWDsNng"
|
|
1199
|
+
* );
|
|
1200
|
+
*
|
|
1201
|
+
* // Query balance
|
|
1202
|
+
* const result = await usdtContract.query("PSP22::balance_of", {
|
|
1203
|
+
* origin: aliceAddress,
|
|
1204
|
+
* args: { owner: aliceAddress }
|
|
1205
|
+
* });
|
|
1206
|
+
*
|
|
1207
|
+
* if (result.success) {
|
|
1208
|
+
* console.log("Balance:", result.value.response);
|
|
1209
|
+
*
|
|
1210
|
+
* // Send transaction from the query result
|
|
1211
|
+
* const txResult = await result.value.send().signAndSubmit(aliceSigner);
|
|
1212
|
+
* }
|
|
1213
|
+
*
|
|
1214
|
+
* // Or send directly
|
|
1215
|
+
* const txResult = await usdtContract
|
|
1216
|
+
* .send("PSP22::transfer", {
|
|
1217
|
+
* origin: aliceAddress,
|
|
1218
|
+
* args: { to: bobAddress, value: 1000n, data: [] }
|
|
1219
|
+
* })
|
|
1220
|
+
* .signAndSubmit(aliceSigner);
|
|
1221
|
+
* ```
|
|
1222
|
+
*
|
|
1223
|
+
* @param client - The PolkadotClient instance
|
|
1224
|
+
* @param options - Optional SDK configuration
|
|
1225
|
+
* @returns D9 Ink SDK instance
|
|
1226
|
+
*/
|
|
1227
|
+
function createD9InkSdk(client, options = {}) {
|
|
1228
|
+
const { typedApi, defaultQueryOptions, defaultSendOptions } = options;
|
|
1229
|
+
return { getContract(descriptor, address) {
|
|
1230
|
+
return createD9InkContract(descriptor, address, {
|
|
1231
|
+
client,
|
|
1232
|
+
typedApi,
|
|
1233
|
+
defaultQueryOptions,
|
|
1234
|
+
defaultSendOptions
|
|
1235
|
+
});
|
|
1236
|
+
} };
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
//#endregion
|
|
1240
|
+
exports.AbortedError = AbortedError;
|
|
1241
|
+
exports.ContractError = ContractError;
|
|
1242
|
+
exports.ContractEventParser = ContractEventParser;
|
|
1243
|
+
exports.ContractExecutionError = ContractExecutionError;
|
|
1244
|
+
exports.DecodeError = DecodeError;
|
|
1245
|
+
exports.EncodeError = EncodeError;
|
|
1246
|
+
exports.InkCodecs = InkCodecs;
|
|
1247
|
+
exports.LangError = LangError;
|
|
1248
|
+
exports.MetadataError = MetadataError;
|
|
1249
|
+
exports.NetworkError = NetworkError;
|
|
1250
|
+
exports.SignerError = SignerError;
|
|
1251
|
+
exports.TimeoutError = TimeoutError;
|
|
1252
|
+
exports.TransactionError = TransactionError;
|
|
1253
|
+
exports.buildAllEventDecoders = buildAllEventDecoders;
|
|
1254
|
+
exports.buildAllMessageDecoders = buildAllMessageDecoders;
|
|
1255
|
+
exports.buildEventDecoder = buildEventDecoder;
|
|
1256
|
+
exports.buildMessageDecoder = buildMessageDecoder;
|
|
1257
|
+
exports.createCodecRegistry = createCodecRegistry;
|
|
1258
|
+
exports.createContractEventStream = createContractEventStream;
|
|
1259
|
+
exports.createD9InkContract = createD9InkContract;
|
|
1260
|
+
exports.createD9InkSdk = createD9InkSdk;
|
|
1261
|
+
exports.createNativeTransferStream = createNativeTransferStream;
|
|
1262
|
+
exports.createPSP22TransferStream = createPSP22TransferStream;
|
|
1263
|
+
exports.decodeContractCallResult = decodeContractCallResult;
|
|
1264
|
+
exports.decodeInkValue = decodeInkValue;
|
|
1265
|
+
exports.decodeResult = decodeResult;
|
|
1266
|
+
exports.encodeCall = encodeCall;
|
|
1267
|
+
exports.encodeContractCall = encodeContractCall;
|
|
1268
|
+
exports.encodeContractCallWithLimits = encodeContractCallWithLimits;
|
|
1269
|
+
exports.getEventSignature = getEventSignature;
|
|
1270
|
+
exports.isContractError = isContractError;
|
|
1271
|
+
exports.isErrorType = isErrorType;
|
|
1272
|
+
exports.isLangError = isLangError;
|
|
1273
|
+
exports.unwrapInkResult = unwrapInkResult;
|
|
1274
|
+
//# sourceMappingURL=index.cjs.map
|