@d9-network/ink 0.0.5 → 0.0.7
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 +805 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +862 -53
- package/dist/index.d.mts +862 -53
- package/dist/index.mjs +795 -68
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -207,19 +207,19 @@ function buildCodecFromType(typeId, types, cache) {
|
|
|
207
207
|
const def = typeEntry.type.def;
|
|
208
208
|
const path = typeEntry.type.path;
|
|
209
209
|
if (def.primitive) {
|
|
210
|
-
const codec = buildPrimitiveCodec(def.primitive);
|
|
210
|
+
const codec = buildPrimitiveCodec$1(def.primitive);
|
|
211
211
|
cache.set(typeId, codec);
|
|
212
212
|
return codec;
|
|
213
213
|
}
|
|
214
214
|
if (path && path.length > 0) {
|
|
215
|
-
const specialCodec = buildSpecialTypeCodec(path, typeEntry, types, cache);
|
|
215
|
+
const specialCodec = buildSpecialTypeCodec$1(path, typeEntry, types, cache);
|
|
216
216
|
if (specialCodec) {
|
|
217
217
|
cache.set(typeId, specialCodec);
|
|
218
218
|
return specialCodec;
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
221
|
if (def.tuple) {
|
|
222
|
-
const codec = buildTupleCodec(def.tuple, types, cache);
|
|
222
|
+
const codec = buildTupleCodec$1(def.tuple, types, cache);
|
|
223
223
|
cache.set(typeId, codec);
|
|
224
224
|
return codec;
|
|
225
225
|
}
|
|
@@ -230,7 +230,7 @@ function buildCodecFromType(typeId, types, cache) {
|
|
|
230
230
|
}
|
|
231
231
|
if (def.array) {
|
|
232
232
|
const innerCodec = buildCodecFromType(def.array.type, types, cache);
|
|
233
|
-
if (def.array.type === findPrimitiveTypeId(types, "u8")) {
|
|
233
|
+
if (def.array.type === findPrimitiveTypeId$1(types, "u8")) {
|
|
234
234
|
const codec$1 = (0, _polkadot_api_substrate_bindings.Bytes)(def.array.len);
|
|
235
235
|
cache.set(typeId, codec$1);
|
|
236
236
|
return codec$1;
|
|
@@ -240,12 +240,12 @@ function buildCodecFromType(typeId, types, cache) {
|
|
|
240
240
|
return codec;
|
|
241
241
|
}
|
|
242
242
|
if (def.composite) {
|
|
243
|
-
const codec = buildCompositeCodec(def.composite, types, cache);
|
|
243
|
+
const codec = buildCompositeCodec$1(def.composite, types, cache);
|
|
244
244
|
cache.set(typeId, codec);
|
|
245
245
|
return codec;
|
|
246
246
|
}
|
|
247
247
|
if (def.variant) {
|
|
248
|
-
const codec = buildVariantCodec(def.variant, path, types, cache);
|
|
248
|
+
const codec = buildVariantCodec$1(def.variant, path, types, cache);
|
|
249
249
|
cache.set(typeId, codec);
|
|
250
250
|
return codec;
|
|
251
251
|
}
|
|
@@ -254,7 +254,7 @@ function buildCodecFromType(typeId, types, cache) {
|
|
|
254
254
|
/**
|
|
255
255
|
* Build codec for primitive types
|
|
256
256
|
*/
|
|
257
|
-
function buildPrimitiveCodec(primitive) {
|
|
257
|
+
function buildPrimitiveCodec$1(primitive) {
|
|
258
258
|
switch (primitive) {
|
|
259
259
|
case "u8": return _polkadot_api_substrate_bindings.u8;
|
|
260
260
|
case "u16": return _polkadot_api_substrate_bindings.u16;
|
|
@@ -274,7 +274,7 @@ function buildPrimitiveCodec(primitive) {
|
|
|
274
274
|
/**
|
|
275
275
|
* Build codec for special types based on path
|
|
276
276
|
*/
|
|
277
|
-
function buildSpecialTypeCodec(path, typeEntry, types, cache) {
|
|
277
|
+
function buildSpecialTypeCodec$1(path, typeEntry, types, cache) {
|
|
278
278
|
if (path.join("::").includes("AccountId")) return (0, _polkadot_api_substrate_bindings.AccountId)();
|
|
279
279
|
if (path[0] === "Option") {
|
|
280
280
|
const params = typeEntry.type.params;
|
|
@@ -296,7 +296,7 @@ function buildSpecialTypeCodec(path, typeEntry, types, cache) {
|
|
|
296
296
|
/**
|
|
297
297
|
* Build codec for tuple types
|
|
298
298
|
*/
|
|
299
|
-
function buildTupleCodec(tupleTypes, types, cache) {
|
|
299
|
+
function buildTupleCodec$1(tupleTypes, types, cache) {
|
|
300
300
|
if (tupleTypes.length === 0) return _polkadot_api_substrate_bindings._void;
|
|
301
301
|
const innerCodecs = tupleTypes.map((t) => buildCodecFromType(t, types, cache));
|
|
302
302
|
switch (innerCodecs.length) {
|
|
@@ -310,7 +310,7 @@ function buildTupleCodec(tupleTypes, types, cache) {
|
|
|
310
310
|
/**
|
|
311
311
|
* Build codec for composite (struct) types
|
|
312
312
|
*/
|
|
313
|
-
function buildCompositeCodec(composite, types, cache) {
|
|
313
|
+
function buildCompositeCodec$1(composite, types, cache) {
|
|
314
314
|
const fields = composite.fields;
|
|
315
315
|
if (fields.length === 1 && !fields[0]?.name) return buildCodecFromType(fields[0].type, types, cache);
|
|
316
316
|
const structDef = {};
|
|
@@ -323,7 +323,7 @@ function buildCompositeCodec(composite, types, cache) {
|
|
|
323
323
|
/**
|
|
324
324
|
* Build codec for variant (enum) types
|
|
325
325
|
*/
|
|
326
|
-
function buildVariantCodec(variant, path, types, cache) {
|
|
326
|
+
function buildVariantCodec$1(variant, path, types, cache) {
|
|
327
327
|
const variants = variant.variants;
|
|
328
328
|
const isLangError$1 = path?.includes("LangError");
|
|
329
329
|
const variantDef = {};
|
|
@@ -353,7 +353,7 @@ function buildVariantCodec(variant, path, types, cache) {
|
|
|
353
353
|
/**
|
|
354
354
|
* Find the type ID for a primitive type
|
|
355
355
|
*/
|
|
356
|
-
function findPrimitiveTypeId(types, primitive) {
|
|
356
|
+
function findPrimitiveTypeId$1(types, primitive) {
|
|
357
357
|
return types.find((t) => t.type.def.primitive === primitive)?.id ?? -1;
|
|
358
358
|
}
|
|
359
359
|
/**
|
|
@@ -466,11 +466,37 @@ function buildAllEventDecoders(metadata) {
|
|
|
466
466
|
function getEventSignature(eventLabel) {
|
|
467
467
|
return (0, _noble_hashes_blake2_js.blake2b)(new TextEncoder().encode(eventLabel), { dkLen: 32 });
|
|
468
468
|
}
|
|
469
|
+
/**
|
|
470
|
+
* Create ASCII-encoded event topic (D9 chain format)
|
|
471
|
+
*
|
|
472
|
+
* D9 chain uses a format where:
|
|
473
|
+
* - First byte is 0x00 (null)
|
|
474
|
+
* - Remaining 31 bytes are ASCII characters of `ContractName::EventLabel`
|
|
475
|
+
* - Total is exactly 32 bytes (truncated/padded as needed)
|
|
476
|
+
*
|
|
477
|
+
* @param contractName - Contract name (e.g., "MarketMaker", "Usdt")
|
|
478
|
+
* @param eventLabel - Event label (e.g., "Transfer", "USDTToD9Conversion")
|
|
479
|
+
* @returns 32-byte topic with null prefix and ASCII-encoded path
|
|
480
|
+
*/
|
|
481
|
+
function createAsciiEventTopic(contractName, eventLabel) {
|
|
482
|
+
const topic = new Uint8Array(32);
|
|
483
|
+
topic[0] = 0;
|
|
484
|
+
const encoder = new TextEncoder();
|
|
485
|
+
const fullPath = `${contractName}::${eventLabel}`;
|
|
486
|
+
const encoded = encoder.encode(fullPath);
|
|
487
|
+
topic.set(encoded.slice(0, 31), 1);
|
|
488
|
+
return topic;
|
|
489
|
+
}
|
|
469
490
|
|
|
470
491
|
//#endregion
|
|
471
492
|
//#region src/events.ts
|
|
472
493
|
/**
|
|
473
|
-
*
|
|
494
|
+
* Type-safe event parser for a specific contract
|
|
495
|
+
*
|
|
496
|
+
* @typeParam S - The storage descriptor type
|
|
497
|
+
* @typeParam M - The messages descriptor type
|
|
498
|
+
* @typeParam C - The constructors descriptor type
|
|
499
|
+
* @typeParam E - The event type representing all possible events for this contract
|
|
474
500
|
*/
|
|
475
501
|
var ContractEventParser = class {
|
|
476
502
|
eventDecoders;
|
|
@@ -478,21 +504,31 @@ var ContractEventParser = class {
|
|
|
478
504
|
contractAddressBytes;
|
|
479
505
|
contractAddress;
|
|
480
506
|
metadata;
|
|
481
|
-
constructor(
|
|
482
|
-
|
|
507
|
+
constructor(descriptor, contractAddress) {
|
|
508
|
+
if (!descriptor.metadata) throw new Error("Contract descriptor must include metadata");
|
|
509
|
+
this.metadata = descriptor.metadata;
|
|
483
510
|
this.contractAddress = contractAddress;
|
|
484
|
-
this.eventDecoders = buildAllEventDecoders(metadata);
|
|
511
|
+
this.eventDecoders = buildAllEventDecoders(this.metadata);
|
|
485
512
|
this.contractAddressBytes = (0, _polkadot_labs_hdkd_helpers.ss58Decode)(contractAddress)[0];
|
|
486
513
|
this.eventSignatures = /* @__PURE__ */ new Map();
|
|
487
|
-
const events = metadata.spec.events;
|
|
514
|
+
const events = this.metadata.spec.events;
|
|
488
515
|
for (const event of events) {
|
|
489
516
|
const sig = getEventSignature(event.label);
|
|
490
517
|
this.eventSignatures.set(event.label, sig);
|
|
491
518
|
}
|
|
492
519
|
}
|
|
493
520
|
/**
|
|
494
|
-
* Parse a raw chain event into a contract event (if it matches)
|
|
521
|
+
* Parse a raw chain event into a type-safe contract event (if it matches)
|
|
495
522
|
* Returns null if the event is not from this contract or cannot be parsed
|
|
523
|
+
*
|
|
524
|
+
* @example
|
|
525
|
+
* ```ts
|
|
526
|
+
* const event = parser.parseEvent(chainEvent);
|
|
527
|
+
* if (event?.type === "Transfer") {
|
|
528
|
+
* // event.value is now typed as { from: SS58String; to: SS58String; value: bigint }
|
|
529
|
+
* console.log(event.value.from);
|
|
530
|
+
* }
|
|
531
|
+
* ```
|
|
496
532
|
*/
|
|
497
533
|
parseEvent(chainEvent) {
|
|
498
534
|
const extracted = this.extractContractEmittedEvent(chainEvent);
|
|
@@ -502,9 +538,13 @@ var ContractEventParser = class {
|
|
|
502
538
|
if (topics.length === 0) return null;
|
|
503
539
|
const signature = topics[0];
|
|
504
540
|
let eventLabel = null;
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
541
|
+
eventLabel = this.extractEventLabelFromAsciiTopic(signature);
|
|
542
|
+
if (eventLabel && !this.eventDecoders.has(eventLabel)) eventLabel = null;
|
|
543
|
+
if (!eventLabel) {
|
|
544
|
+
for (const [label, sig] of this.eventSignatures) if (this.bytesEqual(signature, sig)) {
|
|
545
|
+
eventLabel = label;
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
508
548
|
}
|
|
509
549
|
if (!eventLabel) {
|
|
510
550
|
console.warn("Unknown event signature:", signature);
|
|
@@ -518,8 +558,8 @@ var ContractEventParser = class {
|
|
|
518
558
|
try {
|
|
519
559
|
const decodedData = decoder(data);
|
|
520
560
|
return {
|
|
521
|
-
|
|
522
|
-
|
|
561
|
+
type: eventLabel,
|
|
562
|
+
value: decodedData,
|
|
523
563
|
raw: {
|
|
524
564
|
blockNumber,
|
|
525
565
|
blockHash,
|
|
@@ -535,14 +575,37 @@ var ContractEventParser = class {
|
|
|
535
575
|
}
|
|
536
576
|
}
|
|
537
577
|
/**
|
|
538
|
-
*
|
|
578
|
+
* Extract event label from ASCII-encoded topic (D9 chain format)
|
|
579
|
+
*
|
|
580
|
+
* D9 chain uses a format where:
|
|
581
|
+
* - First byte is 0x00 (null)
|
|
582
|
+
* - Remaining 31 bytes are ASCII characters of `ContractName::EventLabel`
|
|
583
|
+
* - Total is exactly 32 bytes (truncated/padded as needed)
|
|
584
|
+
*
|
|
585
|
+
* @param topic - The 32-byte topic from the event
|
|
586
|
+
* @returns The extracted event label, or null if not in ASCII format
|
|
587
|
+
*/
|
|
588
|
+
extractEventLabelFromAsciiTopic(topic) {
|
|
589
|
+
if (topic.length !== 32) return null;
|
|
590
|
+
if (topic[0] !== 0) return null;
|
|
591
|
+
const ascii = new TextDecoder().decode(topic.slice(1)).replace(/\0+$/, "");
|
|
592
|
+
const colonIndex = ascii.lastIndexOf("::");
|
|
593
|
+
if (colonIndex !== -1) return ascii.slice(colonIndex + 2);
|
|
594
|
+
return ascii || null;
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Filter a batch of events and return type-safe results
|
|
598
|
+
*
|
|
599
|
+
* @param chainEvents - Array of chain events to filter
|
|
600
|
+
* @param options - Optional filter criteria
|
|
601
|
+
* @returns Array of type-safe contract events
|
|
539
602
|
*/
|
|
540
603
|
filterEvents(chainEvents, options) {
|
|
541
604
|
const results = [];
|
|
542
605
|
for (const chainEvent of chainEvents) {
|
|
543
606
|
const parsed = this.parseEvent(chainEvent);
|
|
544
607
|
if (!parsed) continue;
|
|
545
|
-
if (options?.eventLabels && !options.eventLabels.includes(parsed.
|
|
608
|
+
if (options?.eventLabels && !options.eventLabels.includes(parsed.type)) continue;
|
|
546
609
|
if (options?.fromBlock && parsed.raw.blockNumber < options.fromBlock) continue;
|
|
547
610
|
if (options?.toBlock && parsed.raw.blockNumber > options.toBlock) continue;
|
|
548
611
|
results.push(parsed);
|
|
@@ -550,6 +613,29 @@ var ContractEventParser = class {
|
|
|
550
613
|
return results;
|
|
551
614
|
}
|
|
552
615
|
/**
|
|
616
|
+
* Filter events by specific type with proper type narrowing
|
|
617
|
+
*
|
|
618
|
+
* This method provides better type safety than filterEvents with eventLabels
|
|
619
|
+
* because TypeScript can narrow the return type to only the specified event type.
|
|
620
|
+
*
|
|
621
|
+
* @param chainEvents - Array of chain events to filter
|
|
622
|
+
* @param label - The event label to filter by
|
|
623
|
+
* @returns Array of events narrowed to the specific type
|
|
624
|
+
*
|
|
625
|
+
* @example
|
|
626
|
+
* ```ts
|
|
627
|
+
* const transfers = parser.filterByType(events, "Transfer");
|
|
628
|
+
*
|
|
629
|
+
* for (const t of transfers) {
|
|
630
|
+
* // t.value is fully typed as Transfer event data
|
|
631
|
+
* console.log(t.value.from, "->", t.value.to, ":", t.value.value);
|
|
632
|
+
* }
|
|
633
|
+
* ```
|
|
634
|
+
*/
|
|
635
|
+
filterByType(chainEvents, label) {
|
|
636
|
+
return this.filterEvents(chainEvents).filter((e) => e.type === label);
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
553
639
|
* Get the contract address as SS58 string
|
|
554
640
|
*/
|
|
555
641
|
getContractAddress() {
|
|
@@ -570,20 +656,31 @@ var ContractEventParser = class {
|
|
|
570
656
|
if (eventValue.type !== "ContractEmitted") return null;
|
|
571
657
|
const contractEmittedData = eventValue.value;
|
|
572
658
|
if (!contractEmittedData || typeof contractEmittedData !== "object") return null;
|
|
573
|
-
const
|
|
574
|
-
const
|
|
575
|
-
|
|
659
|
+
const contractRaw = contractEmittedData.contract;
|
|
660
|
+
const dataRaw = contractEmittedData.data;
|
|
661
|
+
let contract;
|
|
662
|
+
if (contractRaw instanceof Uint8Array) contract = contractRaw;
|
|
663
|
+
else if (typeof contractRaw === "string") contract = (0, _polkadot_labs_hdkd_helpers.ss58Decode)(contractRaw)[0];
|
|
664
|
+
else return null;
|
|
665
|
+
let data;
|
|
666
|
+
if (dataRaw instanceof Uint8Array) data = dataRaw;
|
|
667
|
+
else if (dataRaw && typeof dataRaw === "object" && "asBytes" in dataRaw && typeof dataRaw.asBytes === "function") data = dataRaw.asBytes();
|
|
668
|
+
else return null;
|
|
576
669
|
const topics = [];
|
|
577
670
|
if (record.topics && Array.isArray(record.topics)) {
|
|
578
671
|
for (const topic of record.topics) if (topic instanceof Uint8Array) topics.push(topic);
|
|
672
|
+
else if (topic && typeof topic === "object" && "asBytes" in topic && typeof topic.asBytes === "function") topics.push(topic.asBytes());
|
|
579
673
|
}
|
|
674
|
+
const blockNumber = typeof record.blockNumber === "number" ? record.blockNumber : 0;
|
|
675
|
+
const blockHash = typeof record.blockHash === "string" ? record.blockHash : "";
|
|
676
|
+
const eventIndex = typeof record.eventIndex === "number" ? record.eventIndex : 0;
|
|
580
677
|
return {
|
|
581
678
|
contract,
|
|
582
679
|
data,
|
|
583
680
|
topics,
|
|
584
|
-
blockNumber
|
|
585
|
-
blockHash
|
|
586
|
-
eventIndex
|
|
681
|
+
blockNumber,
|
|
682
|
+
blockHash,
|
|
683
|
+
eventIndex
|
|
587
684
|
};
|
|
588
685
|
}
|
|
589
686
|
/**
|
|
@@ -594,19 +691,35 @@ var ContractEventParser = class {
|
|
|
594
691
|
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
595
692
|
return true;
|
|
596
693
|
}
|
|
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
694
|
};
|
|
695
|
+
/**
|
|
696
|
+
* Type guard for narrowing event types
|
|
697
|
+
*
|
|
698
|
+
* Use this function when you have a `TypedContractEvent` and need to
|
|
699
|
+
* narrow it to a specific event type. This is useful when the type
|
|
700
|
+
* information is lost (e.g., after serialization or in generic functions).
|
|
701
|
+
*
|
|
702
|
+
* @typeParam E - The Enum type representing all possible events
|
|
703
|
+
* @typeParam L - The specific event label to check
|
|
704
|
+
* @param event - The event to check
|
|
705
|
+
* @param label - The event label to match
|
|
706
|
+
* @returns True if the event type matches the label
|
|
707
|
+
*
|
|
708
|
+
* @example
|
|
709
|
+
* ```ts
|
|
710
|
+
* const event = parser.parseEvent(chainEvent);
|
|
711
|
+
*
|
|
712
|
+
* if (event && isEventType(event, "Transfer")) {
|
|
713
|
+
* // TypeScript knows event.value is Transfer event data
|
|
714
|
+
* console.log(event.value.from);
|
|
715
|
+
* console.log(event.value.to);
|
|
716
|
+
* console.log(event.value.value);
|
|
717
|
+
* }
|
|
718
|
+
* ```
|
|
719
|
+
*/
|
|
720
|
+
function isEventType(event, label) {
|
|
721
|
+
return event.type === label;
|
|
722
|
+
}
|
|
610
723
|
|
|
611
724
|
//#endregion
|
|
612
725
|
//#region src/subscriptions.ts
|
|
@@ -617,12 +730,12 @@ var ContractEventParser = class {
|
|
|
617
730
|
* Create an observable stream of contract events
|
|
618
731
|
*
|
|
619
732
|
* @param client - Polkadot API client
|
|
620
|
-
* @param
|
|
733
|
+
* @param descriptor - Contract descriptor
|
|
621
734
|
* @param options - Subscription options
|
|
622
|
-
* @returns Observable stream of
|
|
735
|
+
* @returns Observable stream of type-safe contract events
|
|
623
736
|
*/
|
|
624
|
-
function createContractEventStream(client,
|
|
625
|
-
const parser = new ContractEventParser(
|
|
737
|
+
function createContractEventStream(client, descriptor, options) {
|
|
738
|
+
const parser = new ContractEventParser(descriptor, options.contractAddress);
|
|
626
739
|
return client.finalizedBlock$.pipe((0, rxjs.mergeMap)(async (block) => {
|
|
627
740
|
try {
|
|
628
741
|
return {
|
|
@@ -648,7 +761,7 @@ function createContractEventStream(client, metadata, options) {
|
|
|
648
761
|
}).filter((e) => e !== null);
|
|
649
762
|
}), (0, rxjs.mergeMap)((events) => (0, rxjs.from)(events)), (0, rxjs.filter)((event) => {
|
|
650
763
|
if (!options.eventLabels) return true;
|
|
651
|
-
return options.eventLabels.includes(event.
|
|
764
|
+
return options.eventLabels.includes(event.type);
|
|
652
765
|
}), (0, rxjs.catchError)((error) => {
|
|
653
766
|
console.error("Error in contract event stream:", error);
|
|
654
767
|
return (0, rxjs.of)();
|
|
@@ -658,21 +771,21 @@ function createContractEventStream(client, metadata, options) {
|
|
|
658
771
|
* Convenience helper to create a Transfer event stream for PSP22 tokens
|
|
659
772
|
*
|
|
660
773
|
* @param client - Polkadot API client
|
|
661
|
-
* @param
|
|
774
|
+
* @param descriptor - PSP22 contract descriptor
|
|
662
775
|
* @param contractAddress - PSP22 contract address
|
|
663
776
|
* @param getEvents - Function to fetch System.Events at a block hash
|
|
664
777
|
* @param watchAddress - Optional address to filter transfers (only events involving this address)
|
|
665
778
|
* @returns Observable stream of Transfer events
|
|
666
779
|
*/
|
|
667
|
-
function createPSP22TransferStream(client,
|
|
668
|
-
return createContractEventStream(client,
|
|
780
|
+
function createPSP22TransferStream(client, descriptor, contractAddress, getEvents, watchAddress) {
|
|
781
|
+
return createContractEventStream(client, descriptor, {
|
|
669
782
|
contractAddress,
|
|
670
783
|
eventLabels: ["Transfer"],
|
|
671
784
|
getEvents
|
|
672
785
|
}).pipe((0, rxjs.filter)((event) => {
|
|
673
786
|
if (!watchAddress) return true;
|
|
674
|
-
const
|
|
675
|
-
return
|
|
787
|
+
const value = event.value;
|
|
788
|
+
return value.from === watchAddress || value.to === watchAddress;
|
|
676
789
|
}));
|
|
677
790
|
}
|
|
678
791
|
/**
|
|
@@ -719,6 +832,91 @@ function createNativeTransferStream(client, getEvents, watchAddress) {
|
|
|
719
832
|
}), (0, rxjs.share)());
|
|
720
833
|
}
|
|
721
834
|
|
|
835
|
+
//#endregion
|
|
836
|
+
//#region src/message-builder.ts
|
|
837
|
+
/**
|
|
838
|
+
* Create a type-safe message builder from a contract descriptor
|
|
839
|
+
*
|
|
840
|
+
* @typeParam S - Storage descriptor type
|
|
841
|
+
* @typeParam M - Messages descriptor type
|
|
842
|
+
* @typeParam C - Constructors descriptor type
|
|
843
|
+
* @typeParam E - Events Enum type
|
|
844
|
+
* @param descriptor - The ink! contract descriptor containing metadata
|
|
845
|
+
* @returns A ContractMessageBuilder instance
|
|
846
|
+
*
|
|
847
|
+
* @example
|
|
848
|
+
* ```ts
|
|
849
|
+
* import { createMessageBuilder } from '@d9-network/ink';
|
|
850
|
+
* import { contracts } from '@polkadot-api/descriptors';
|
|
851
|
+
*
|
|
852
|
+
* const builder = createMessageBuilder(contracts.usdt);
|
|
853
|
+
*
|
|
854
|
+
* // Get a typed message interface
|
|
855
|
+
* const transfer = builder.message("PSP22::transfer");
|
|
856
|
+
*
|
|
857
|
+
* // Encode with full type checking on args
|
|
858
|
+
* const encoded = transfer.encode({
|
|
859
|
+
* to: recipientAddress, // Must be SS58String
|
|
860
|
+
* value: 1000000n, // Must be bigint
|
|
861
|
+
* data: new Uint8Array(), // Must be Uint8Array
|
|
862
|
+
* });
|
|
863
|
+
*
|
|
864
|
+
* // Decode response with full type inference
|
|
865
|
+
* const response = transfer.decode(resultBytes);
|
|
866
|
+
* ```
|
|
867
|
+
*/
|
|
868
|
+
function createMessageBuilder(descriptor) {
|
|
869
|
+
if (!descriptor.metadata) throw new Error("Contract descriptor must include metadata");
|
|
870
|
+
const metadata = descriptor.metadata;
|
|
871
|
+
const builder = (0, _polkadot_api_ink_contracts.getInkDynamicBuilder)((0, _polkadot_api_ink_contracts.getInkLookup)(metadata));
|
|
872
|
+
const codecCache = /* @__PURE__ */ new Map();
|
|
873
|
+
const messagesMetadata = /* @__PURE__ */ new Map();
|
|
874
|
+
for (const msg of metadata.spec.messages) messagesMetadata.set(msg.label, msg);
|
|
875
|
+
function getMessageCodec(label) {
|
|
876
|
+
const cached = codecCache.get(label);
|
|
877
|
+
if (cached) return cached;
|
|
878
|
+
const codec = builder.buildMessage(label);
|
|
879
|
+
codecCache.set(label, codec);
|
|
880
|
+
return codec;
|
|
881
|
+
}
|
|
882
|
+
function parseSelector(selectorHex) {
|
|
883
|
+
const hex = selectorHex.startsWith("0x") ? selectorHex.slice(2) : selectorHex;
|
|
884
|
+
return new Uint8Array(hex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
|
|
885
|
+
}
|
|
886
|
+
function message(label) {
|
|
887
|
+
const msgMeta = messagesMetadata.get(label);
|
|
888
|
+
if (!msgMeta) throw new Error(`Message "${label}" not found in metadata`);
|
|
889
|
+
const codec = getMessageCodec(label);
|
|
890
|
+
const selector = parseSelector(msgMeta.selector);
|
|
891
|
+
const encode = (args) => {
|
|
892
|
+
const encoded = codec.call.enc(args ?? {});
|
|
893
|
+
return polkadot_api.Binary.fromBytes(encoded);
|
|
894
|
+
};
|
|
895
|
+
const decode = (response) => {
|
|
896
|
+
const bytes = response instanceof Uint8Array ? response : response.asBytes();
|
|
897
|
+
return codec.value.dec(bytes);
|
|
898
|
+
};
|
|
899
|
+
return {
|
|
900
|
+
encode,
|
|
901
|
+
decode,
|
|
902
|
+
attributes: {
|
|
903
|
+
mutates: msgMeta.mutates,
|
|
904
|
+
payable: msgMeta.payable,
|
|
905
|
+
default: msgMeta.default
|
|
906
|
+
},
|
|
907
|
+
selector,
|
|
908
|
+
label
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
function getMessageLabels() {
|
|
912
|
+
return Array.from(messagesMetadata.keys());
|
|
913
|
+
}
|
|
914
|
+
return {
|
|
915
|
+
message,
|
|
916
|
+
getMessageLabels
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
|
|
722
920
|
//#endregion
|
|
723
921
|
//#region src/errors.ts
|
|
724
922
|
/**
|
|
@@ -937,6 +1135,10 @@ function createD9InkContract(descriptor, address, options) {
|
|
|
937
1135
|
if (!descriptor.metadata) throw new MetadataError("Contract descriptor must include metadata");
|
|
938
1136
|
const patchedMetadata = patchLangErrorInMetadata(descriptor.metadata);
|
|
939
1137
|
const builder = (0, _polkadot_api_ink_contracts.getInkDynamicBuilder)((0, _polkadot_api_ink_contracts.getInkLookup)(patchedMetadata));
|
|
1138
|
+
const patchedDescriptor = {
|
|
1139
|
+
...descriptor,
|
|
1140
|
+
metadata: patchedMetadata
|
|
1141
|
+
};
|
|
940
1142
|
let codecRegistry;
|
|
941
1143
|
try {
|
|
942
1144
|
codecRegistry = createCodecRegistry(patchedMetadata);
|
|
@@ -971,13 +1173,13 @@ function createD9InkContract(descriptor, address, options) {
|
|
|
971
1173
|
checkAborted(signal, method);
|
|
972
1174
|
const originBytes = ss58ToBytes(origin);
|
|
973
1175
|
const codec = getMessageCodec(method);
|
|
974
|
-
const message = encodeContractCall(originBytes, addressBytes, codec.call.enc(args ?? {}), value);
|
|
1176
|
+
const message$1 = encodeContractCall(originBytes, addressBytes, codec.call.enc(args ?? {}), value);
|
|
975
1177
|
const blockHash = at ?? (await client.getFinalizedBlock()).hash;
|
|
976
1178
|
checkAborted(signal, method);
|
|
977
1179
|
const executeCall = async () => {
|
|
978
1180
|
return (0, polkadot_api_utils.fromHex)(await client._request("state_call", [
|
|
979
1181
|
"ContractsApi_call",
|
|
980
|
-
message,
|
|
1182
|
+
message$1,
|
|
981
1183
|
blockHash
|
|
982
1184
|
]));
|
|
983
1185
|
};
|
|
@@ -1079,11 +1281,11 @@ function createD9InkContract(descriptor, address, options) {
|
|
|
1079
1281
|
try {
|
|
1080
1282
|
let gas = gasLimit;
|
|
1081
1283
|
if (!gas) {
|
|
1082
|
-
const message = encodeContractCall(originBytes, addressBytes, callData, value);
|
|
1284
|
+
const message$1 = encodeContractCall(originBytes, addressBytes, callData, value);
|
|
1083
1285
|
const blockHash = (await client.getFinalizedBlock()).hash;
|
|
1084
1286
|
gas = decodeContractCallResult((0, polkadot_api_utils.fromHex)(await client._request("state_call", [
|
|
1085
1287
|
"ContractsApi_call",
|
|
1086
|
-
message,
|
|
1288
|
+
message$1,
|
|
1087
1289
|
blockHash
|
|
1088
1290
|
]))).gas.gasRequired;
|
|
1089
1291
|
}
|
|
@@ -1152,7 +1354,20 @@ function createD9InkContract(descriptor, address, options) {
|
|
|
1152
1354
|
* Filter events for this contract
|
|
1153
1355
|
*/
|
|
1154
1356
|
function filterEvents(events) {
|
|
1155
|
-
return new ContractEventParser(
|
|
1357
|
+
return new ContractEventParser(patchedDescriptor, address).filterEvents(events);
|
|
1358
|
+
}
|
|
1359
|
+
/**
|
|
1360
|
+
* Filter events by specific type with proper type narrowing
|
|
1361
|
+
*/
|
|
1362
|
+
function filterEventsByType(events, label) {
|
|
1363
|
+
return new ContractEventParser(patchedDescriptor, address).filterByType(events, label);
|
|
1364
|
+
}
|
|
1365
|
+
const messageBuilder = createMessageBuilder(descriptor);
|
|
1366
|
+
/**
|
|
1367
|
+
* Get a type-safe message interface
|
|
1368
|
+
*/
|
|
1369
|
+
function message(label) {
|
|
1370
|
+
return messageBuilder.message(label);
|
|
1156
1371
|
}
|
|
1157
1372
|
/**
|
|
1158
1373
|
* Subscribe to contract events as an RxJS Observable
|
|
@@ -1163,7 +1378,7 @@ function createD9InkContract(descriptor, address, options) {
|
|
|
1163
1378
|
* @param options.fromBlock - Optional starting block number
|
|
1164
1379
|
*/
|
|
1165
1380
|
function subscribeToEvents(options$1) {
|
|
1166
|
-
return createContractEventStream(client,
|
|
1381
|
+
return createContractEventStream(client, patchedDescriptor, {
|
|
1167
1382
|
...options$1,
|
|
1168
1383
|
contractAddress: address
|
|
1169
1384
|
});
|
|
@@ -1175,10 +1390,43 @@ function createD9InkContract(descriptor, address, options) {
|
|
|
1175
1390
|
send,
|
|
1176
1391
|
getStorage,
|
|
1177
1392
|
filterEvents,
|
|
1393
|
+
filterEventsByType,
|
|
1394
|
+
message,
|
|
1178
1395
|
subscribeToEvents
|
|
1179
1396
|
};
|
|
1180
1397
|
}
|
|
1181
1398
|
|
|
1399
|
+
//#endregion
|
|
1400
|
+
//#region src/rpc.ts
|
|
1401
|
+
/**
|
|
1402
|
+
* Create a type-safe RPC request function from a PolkadotClient
|
|
1403
|
+
*
|
|
1404
|
+
* This wraps the client's `_request` method with proper TypeScript types,
|
|
1405
|
+
* providing autocomplete for known RPC methods and type inference for
|
|
1406
|
+
* parameters and return values.
|
|
1407
|
+
*
|
|
1408
|
+
* @param client - The PolkadotClient instance
|
|
1409
|
+
* @returns A type-safe RPC request function
|
|
1410
|
+
*
|
|
1411
|
+
* @example
|
|
1412
|
+
* ```ts
|
|
1413
|
+
* const rpc = createTypedRpc(client);
|
|
1414
|
+
*
|
|
1415
|
+
* // Autocomplete for method names, typed params and return
|
|
1416
|
+
* const hash = await rpc("chain_getBlockHash", [12345]);
|
|
1417
|
+
* // hash: HexString | null
|
|
1418
|
+
*
|
|
1419
|
+
* const header = await rpc("chain_getHeader", [hash]);
|
|
1420
|
+
* // header: BlockHeader | null
|
|
1421
|
+
*
|
|
1422
|
+
* // Custom methods still work with explicit types
|
|
1423
|
+
* const custom = await rpc<MyType>("my_custom_method", [arg]);
|
|
1424
|
+
* ```
|
|
1425
|
+
*/
|
|
1426
|
+
function createTypedRpc(client) {
|
|
1427
|
+
return ((method, params) => client._request(method, params));
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1182
1430
|
//#endregion
|
|
1183
1431
|
//#region src/sdk.ts
|
|
1184
1432
|
/**
|
|
@@ -1222,22 +1470,502 @@ function createD9InkContract(descriptor, address, options) {
|
|
|
1222
1470
|
*
|
|
1223
1471
|
* @param client - The PolkadotClient instance
|
|
1224
1472
|
* @param options - Optional SDK configuration
|
|
1225
|
-
* @returns D9 Ink SDK instance
|
|
1473
|
+
* @returns D9 Ink SDK instance with typed RPC access
|
|
1226
1474
|
*/
|
|
1227
1475
|
function createD9InkSdk(client, options = {}) {
|
|
1228
1476
|
const { typedApi, defaultQueryOptions, defaultSendOptions } = options;
|
|
1229
|
-
return {
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1477
|
+
return {
|
|
1478
|
+
getContract(descriptor, address) {
|
|
1479
|
+
return createD9InkContract(descriptor, address, {
|
|
1480
|
+
client,
|
|
1481
|
+
typedApi,
|
|
1482
|
+
defaultQueryOptions,
|
|
1483
|
+
defaultSendOptions
|
|
1484
|
+
});
|
|
1485
|
+
},
|
|
1486
|
+
rpc: createTypedRpc(client)
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
//#endregion
|
|
1491
|
+
//#region src/codec-builder-internal.ts
|
|
1492
|
+
/**
|
|
1493
|
+
* Internal codec building utilities
|
|
1494
|
+
*
|
|
1495
|
+
* This module provides lower-level codec building from metadata type IDs.
|
|
1496
|
+
*/
|
|
1497
|
+
/**
|
|
1498
|
+
* Build a SCALE codec from ink metadata type definition
|
|
1499
|
+
*/
|
|
1500
|
+
function buildCodecFromTypeInternal(typeId, types, cache) {
|
|
1501
|
+
const cached = cache.get(typeId);
|
|
1502
|
+
if (cached) return cached;
|
|
1503
|
+
const typeEntry = types.find((t) => t.id === typeId);
|
|
1504
|
+
if (!typeEntry) throw new Error(`Type ${typeId} not found in metadata`);
|
|
1505
|
+
const def = typeEntry.type.def;
|
|
1506
|
+
const path = typeEntry.type.path;
|
|
1507
|
+
if (def.primitive) {
|
|
1508
|
+
const codec = buildPrimitiveCodec(def.primitive);
|
|
1509
|
+
cache.set(typeId, codec);
|
|
1510
|
+
return codec;
|
|
1511
|
+
}
|
|
1512
|
+
if (path && path.length > 0) {
|
|
1513
|
+
const specialCodec = buildSpecialTypeCodec(path, typeEntry, types, cache);
|
|
1514
|
+
if (specialCodec) {
|
|
1515
|
+
cache.set(typeId, specialCodec);
|
|
1516
|
+
return specialCodec;
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
if (def.tuple) {
|
|
1520
|
+
const codec = buildTupleCodec(def.tuple, types, cache);
|
|
1521
|
+
cache.set(typeId, codec);
|
|
1522
|
+
return codec;
|
|
1523
|
+
}
|
|
1524
|
+
if (def.sequence) {
|
|
1525
|
+
const codec = (0, _polkadot_api_substrate_bindings.Vector)(buildCodecFromTypeInternal(def.sequence.type, types, cache));
|
|
1526
|
+
cache.set(typeId, codec);
|
|
1527
|
+
return codec;
|
|
1528
|
+
}
|
|
1529
|
+
if (def.array) {
|
|
1530
|
+
const innerCodec = buildCodecFromTypeInternal(def.array.type, types, cache);
|
|
1531
|
+
if (def.array.type === findPrimitiveTypeId(types, "u8")) {
|
|
1532
|
+
const codec$1 = (0, _polkadot_api_substrate_bindings.Bytes)(def.array.len);
|
|
1533
|
+
cache.set(typeId, codec$1);
|
|
1534
|
+
return codec$1;
|
|
1535
|
+
}
|
|
1536
|
+
const codec = (0, _polkadot_api_substrate_bindings.Vector)(innerCodec, def.array.len);
|
|
1537
|
+
cache.set(typeId, codec);
|
|
1538
|
+
return codec;
|
|
1539
|
+
}
|
|
1540
|
+
if (def.composite) {
|
|
1541
|
+
const codec = buildCompositeCodec(def.composite, types, cache);
|
|
1542
|
+
cache.set(typeId, codec);
|
|
1543
|
+
return codec;
|
|
1544
|
+
}
|
|
1545
|
+
if (def.variant) {
|
|
1546
|
+
const codec = buildVariantCodec(def.variant, path, types, cache);
|
|
1547
|
+
cache.set(typeId, codec);
|
|
1548
|
+
return codec;
|
|
1549
|
+
}
|
|
1550
|
+
throw new Error(`Unknown type definition for type ${typeId}`);
|
|
1551
|
+
}
|
|
1552
|
+
function buildPrimitiveCodec(primitive) {
|
|
1553
|
+
switch (primitive) {
|
|
1554
|
+
case "u8": return _polkadot_api_substrate_bindings.u8;
|
|
1555
|
+
case "u16": return _polkadot_api_substrate_bindings.u16;
|
|
1556
|
+
case "u32": return _polkadot_api_substrate_bindings.u32;
|
|
1557
|
+
case "u64": return _polkadot_api_substrate_bindings.u64;
|
|
1558
|
+
case "u128": return _polkadot_api_substrate_bindings.u128;
|
|
1559
|
+
case "i8": return _polkadot_api_substrate_bindings.i8;
|
|
1560
|
+
case "i16": return _polkadot_api_substrate_bindings.i16;
|
|
1561
|
+
case "i32": return _polkadot_api_substrate_bindings.i32;
|
|
1562
|
+
case "i64": return _polkadot_api_substrate_bindings.i64;
|
|
1563
|
+
case "i128": return _polkadot_api_substrate_bindings.i128;
|
|
1564
|
+
case "bool": return _polkadot_api_substrate_bindings.bool;
|
|
1565
|
+
case "str": return _polkadot_api_substrate_bindings.str;
|
|
1566
|
+
default: throw new Error(`Unknown primitive type: ${primitive}`);
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
function buildSpecialTypeCodec(path, typeEntry, types, cache) {
|
|
1570
|
+
if (path.join("::").includes("AccountId")) return (0, _polkadot_api_substrate_bindings.AccountId)();
|
|
1571
|
+
if (path[0] === "Option") {
|
|
1572
|
+
const params = typeEntry.type.params;
|
|
1573
|
+
if (params && params.length > 0 && params[0]?.type !== void 0) return (0, _polkadot_api_substrate_bindings.Option)(buildCodecFromTypeInternal(params[0].type, types, cache));
|
|
1574
|
+
}
|
|
1575
|
+
if (path[0] === "Result") {
|
|
1576
|
+
const params = typeEntry.type.params;
|
|
1577
|
+
if (params && params.length >= 2) {
|
|
1578
|
+
const okTypeId = params[0]?.type;
|
|
1579
|
+
const errTypeId = params[1]?.type;
|
|
1580
|
+
if (okTypeId !== void 0 && errTypeId !== void 0) return (0, _polkadot_api_substrate_bindings.Variant)({
|
|
1581
|
+
Ok: buildCodecFromTypeInternal(okTypeId, types, cache),
|
|
1582
|
+
Err: buildCodecFromTypeInternal(errTypeId, types, cache)
|
|
1583
|
+
}, [0, 1]);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
return null;
|
|
1587
|
+
}
|
|
1588
|
+
function buildTupleCodec(tupleTypes, types, cache) {
|
|
1589
|
+
if (tupleTypes.length === 0) return _polkadot_api_substrate_bindings._void;
|
|
1590
|
+
const innerCodecs = tupleTypes.map((t) => buildCodecFromTypeInternal(t, types, cache));
|
|
1591
|
+
switch (innerCodecs.length) {
|
|
1592
|
+
case 1: return (0, _polkadot_api_substrate_bindings.Tuple)(innerCodecs[0]);
|
|
1593
|
+
case 2: return (0, _polkadot_api_substrate_bindings.Tuple)(innerCodecs[0], innerCodecs[1]);
|
|
1594
|
+
case 3: return (0, _polkadot_api_substrate_bindings.Tuple)(innerCodecs[0], innerCodecs[1], innerCodecs[2]);
|
|
1595
|
+
case 4: return (0, _polkadot_api_substrate_bindings.Tuple)(innerCodecs[0], innerCodecs[1], innerCodecs[2], innerCodecs[3]);
|
|
1596
|
+
default: return (0, _polkadot_api_substrate_bindings.Tuple)(...innerCodecs);
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
function buildCompositeCodec(composite, types, cache) {
|
|
1600
|
+
const fields = composite.fields;
|
|
1601
|
+
if (fields.length === 1 && !fields[0]?.name) return buildCodecFromTypeInternal(fields[0].type, types, cache);
|
|
1602
|
+
const structDef = {};
|
|
1603
|
+
for (const field of fields) {
|
|
1604
|
+
const fieldName = field.name || `field${fields.indexOf(field)}`;
|
|
1605
|
+
structDef[fieldName] = buildCodecFromTypeInternal(field.type, types, cache);
|
|
1606
|
+
}
|
|
1607
|
+
return (0, _polkadot_api_substrate_bindings.Struct)(structDef);
|
|
1608
|
+
}
|
|
1609
|
+
function buildVariantCodec(variant, path, types, cache) {
|
|
1610
|
+
const variants = variant.variants;
|
|
1611
|
+
const isLangError$1 = path?.includes("LangError");
|
|
1612
|
+
const variantDef = {};
|
|
1613
|
+
const indices = [];
|
|
1614
|
+
if (isLangError$1 && !variants.some((v) => v.index === 0)) {
|
|
1615
|
+
variantDef["_Placeholder"] = _polkadot_api_substrate_bindings._void;
|
|
1616
|
+
indices.push(0);
|
|
1617
|
+
}
|
|
1618
|
+
for (const v of variants) {
|
|
1619
|
+
let fieldCodec;
|
|
1620
|
+
const fields = v.fields ?? [];
|
|
1621
|
+
if (fields.length === 0) fieldCodec = _polkadot_api_substrate_bindings._void;
|
|
1622
|
+
else if (fields.length === 1 && !fields[0]?.name) fieldCodec = buildCodecFromTypeInternal(fields[0].type, types, cache);
|
|
1623
|
+
else {
|
|
1624
|
+
const structDef = {};
|
|
1625
|
+
for (const field of fields) {
|
|
1626
|
+
const fieldName = field.name || `field${fields.indexOf(field)}`;
|
|
1627
|
+
structDef[fieldName] = buildCodecFromTypeInternal(field.type, types, cache);
|
|
1628
|
+
}
|
|
1629
|
+
fieldCodec = (0, _polkadot_api_substrate_bindings.Struct)(structDef);
|
|
1630
|
+
}
|
|
1631
|
+
variantDef[v.name] = fieldCodec;
|
|
1632
|
+
indices.push(v.index);
|
|
1633
|
+
}
|
|
1634
|
+
return (0, _polkadot_api_substrate_bindings.Variant)(variantDef, indices);
|
|
1635
|
+
}
|
|
1636
|
+
function findPrimitiveTypeId(types, primitive) {
|
|
1637
|
+
return types.find((t) => t.type.def.primitive === primitive)?.id ?? -1;
|
|
1638
|
+
}
|
|
1639
|
+
/**
|
|
1640
|
+
* Build a struct codec for message arguments
|
|
1641
|
+
*
|
|
1642
|
+
* @param metadata - The ink contract metadata
|
|
1643
|
+
* @param args - The argument definitions
|
|
1644
|
+
* @returns A struct codec
|
|
1645
|
+
*/
|
|
1646
|
+
function buildArgsCodec(metadata, args) {
|
|
1647
|
+
if (args.length === 0) return {
|
|
1648
|
+
enc: () => new Uint8Array(0),
|
|
1649
|
+
dec: () => ({})
|
|
1650
|
+
};
|
|
1651
|
+
const types = metadata.types;
|
|
1652
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1653
|
+
const structDef = {};
|
|
1654
|
+
for (const arg of args) structDef[arg.label] = buildCodecFromTypeInternal(arg.type.type, types, cache);
|
|
1655
|
+
const codec = (0, _polkadot_api_substrate_bindings.Struct)(structDef);
|
|
1656
|
+
return {
|
|
1657
|
+
enc: (value) => codec.enc(value),
|
|
1658
|
+
dec: (data) => codec.dec(data)
|
|
1659
|
+
};
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
//#endregion
|
|
1663
|
+
//#region src/calls.ts
|
|
1664
|
+
/**
|
|
1665
|
+
* Build argument decoders for all messages in the metadata
|
|
1666
|
+
*/
|
|
1667
|
+
function buildAllMessageDecodersFromMetadata(metadata) {
|
|
1668
|
+
const decoders = /* @__PURE__ */ new Map();
|
|
1669
|
+
const messages = metadata.spec.messages;
|
|
1670
|
+
for (const message of messages) try {
|
|
1671
|
+
const selectorHex = message.selector.startsWith("0x") ? message.selector.slice(2) : message.selector;
|
|
1672
|
+
const selector = new Uint8Array(selectorHex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
|
|
1673
|
+
const argsCodec = buildArgsCodec(metadata, message.args);
|
|
1674
|
+
const decoder = (data) => argsCodec.dec(data);
|
|
1675
|
+
decoders.set(message.label, {
|
|
1676
|
+
selector,
|
|
1677
|
+
decoder
|
|
1235
1678
|
});
|
|
1236
|
-
}
|
|
1679
|
+
} catch (error) {
|
|
1680
|
+
console.warn(`Failed to build decoder for message "${message.label}":`, error);
|
|
1681
|
+
}
|
|
1682
|
+
return decoders;
|
|
1683
|
+
}
|
|
1684
|
+
/**
|
|
1685
|
+
* Type-safe call parser for a specific contract
|
|
1686
|
+
*
|
|
1687
|
+
* @typeParam S - The storage descriptor type
|
|
1688
|
+
* @typeParam M - The InkCallableDescriptor type (message definitions)
|
|
1689
|
+
* @typeParam C - The constructors descriptor type
|
|
1690
|
+
* @typeParam E - The events Enum type
|
|
1691
|
+
*/
|
|
1692
|
+
var ContractCallParser = class {
|
|
1693
|
+
messageDecoders;
|
|
1694
|
+
selectorToLabel;
|
|
1695
|
+
metadata;
|
|
1696
|
+
constructor(descriptor) {
|
|
1697
|
+
if (!descriptor.metadata) throw new Error("Contract descriptor must include metadata");
|
|
1698
|
+
this.metadata = descriptor.metadata;
|
|
1699
|
+
this.messageDecoders = buildAllMessageDecodersFromMetadata(this.metadata);
|
|
1700
|
+
this.selectorToLabel = /* @__PURE__ */ new Map();
|
|
1701
|
+
for (const [label, { selector }] of this.messageDecoders) {
|
|
1702
|
+
const selectorHex = Array.from(selector).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1703
|
+
this.selectorToLabel.set(selectorHex, label);
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
/**
|
|
1707
|
+
* Parse raw call data into a type-safe contract call
|
|
1708
|
+
*
|
|
1709
|
+
* @param callData - The raw call data (selector + encoded args) or RawContractCall
|
|
1710
|
+
* @returns Parsed call or null if cannot parse
|
|
1711
|
+
*
|
|
1712
|
+
* @example
|
|
1713
|
+
* ```ts
|
|
1714
|
+
* const call = parser.parseCall(callData);
|
|
1715
|
+
* if (call?.type === "PSP22::transfer") {
|
|
1716
|
+
* // call.args is typed as { to: SS58String; value: bigint; data: Uint8Array }
|
|
1717
|
+
* console.log(call.args.to);
|
|
1718
|
+
* }
|
|
1719
|
+
* ```
|
|
1720
|
+
*/
|
|
1721
|
+
parseCall(callData) {
|
|
1722
|
+
const data = callData instanceof Uint8Array ? callData : callData.data;
|
|
1723
|
+
const raw = callData instanceof Uint8Array ? { data: callData } : callData;
|
|
1724
|
+
if (data.length < 4) return null;
|
|
1725
|
+
const selector = data.slice(0, 4);
|
|
1726
|
+
const selectorHex = Array.from(selector).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1727
|
+
const label = this.selectorToLabel.get(selectorHex);
|
|
1728
|
+
if (!label) return null;
|
|
1729
|
+
const messageInfo = this.messageDecoders.get(label);
|
|
1730
|
+
if (!messageInfo) return null;
|
|
1731
|
+
const argsData = data.slice(4);
|
|
1732
|
+
try {
|
|
1733
|
+
return {
|
|
1734
|
+
type: label,
|
|
1735
|
+
args: messageInfo.decoder(argsData),
|
|
1736
|
+
selector,
|
|
1737
|
+
raw
|
|
1738
|
+
};
|
|
1739
|
+
} catch (error) {
|
|
1740
|
+
console.warn(`Failed to decode call "${label}":`, error);
|
|
1741
|
+
return null;
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
/**
|
|
1745
|
+
* Parse multiple calls and optionally filter by message labels
|
|
1746
|
+
*
|
|
1747
|
+
* @param calls - Array of raw call data
|
|
1748
|
+
* @param options - Filter options
|
|
1749
|
+
* @returns Array of parsed calls
|
|
1750
|
+
*/
|
|
1751
|
+
parseCalls(calls, options) {
|
|
1752
|
+
const results = [];
|
|
1753
|
+
for (const call of calls) {
|
|
1754
|
+
const parsed = this.parseCall(call);
|
|
1755
|
+
if (!parsed) continue;
|
|
1756
|
+
if (options?.messageLabels && !options.messageLabels.includes(parsed.type)) continue;
|
|
1757
|
+
results.push(parsed);
|
|
1758
|
+
}
|
|
1759
|
+
return results;
|
|
1760
|
+
}
|
|
1761
|
+
/**
|
|
1762
|
+
* Filter calls by specific type with proper type narrowing
|
|
1763
|
+
*
|
|
1764
|
+
* This method provides better type safety than parseCalls with messageLabels
|
|
1765
|
+
* because TypeScript can narrow the return type to only the specified call type.
|
|
1766
|
+
*
|
|
1767
|
+
* @param calls - Array of raw call data
|
|
1768
|
+
* @param label - The message label to filter by
|
|
1769
|
+
* @returns Array of calls narrowed to the specific type
|
|
1770
|
+
*
|
|
1771
|
+
* @example
|
|
1772
|
+
* ```ts
|
|
1773
|
+
* const transfers = parser.filterByType(callDataArray, "PSP22::transfer");
|
|
1774
|
+
*
|
|
1775
|
+
* for (const t of transfers) {
|
|
1776
|
+
* // t.args is fully typed as PSP22::transfer args
|
|
1777
|
+
* console.log(t.args.to, t.args.value);
|
|
1778
|
+
* }
|
|
1779
|
+
* ```
|
|
1780
|
+
*/
|
|
1781
|
+
filterByType(calls, label) {
|
|
1782
|
+
return this.parseCalls(calls).filter((c) => c.type === label);
|
|
1783
|
+
}
|
|
1784
|
+
/**
|
|
1785
|
+
* Get information about a message by label
|
|
1786
|
+
*/
|
|
1787
|
+
getMessageInfo(label) {
|
|
1788
|
+
const info = this.messageDecoders.get(label);
|
|
1789
|
+
if (!info) return null;
|
|
1790
|
+
const message = this.metadata.spec.messages.find((m) => m.label === label);
|
|
1791
|
+
if (!message) return null;
|
|
1792
|
+
return {
|
|
1793
|
+
label: message.label,
|
|
1794
|
+
selector: info.selector,
|
|
1795
|
+
mutates: message.mutates,
|
|
1796
|
+
payable: message.payable,
|
|
1797
|
+
args: message.args
|
|
1798
|
+
};
|
|
1799
|
+
}
|
|
1800
|
+
/**
|
|
1801
|
+
* Get all available message labels
|
|
1802
|
+
*/
|
|
1803
|
+
getMessageLabels() {
|
|
1804
|
+
return Array.from(this.messageDecoders.keys());
|
|
1805
|
+
}
|
|
1806
|
+
/**
|
|
1807
|
+
* Check if a selector matches a specific message
|
|
1808
|
+
*/
|
|
1809
|
+
matchesMessage(selector, label) {
|
|
1810
|
+
const info = this.messageDecoders.get(label);
|
|
1811
|
+
if (!info) return false;
|
|
1812
|
+
if (selector.length !== info.selector.length) return false;
|
|
1813
|
+
for (let i = 0; i < selector.length; i++) if (selector[i] !== info.selector[i]) return false;
|
|
1814
|
+
return true;
|
|
1815
|
+
}
|
|
1816
|
+
};
|
|
1817
|
+
/**
|
|
1818
|
+
* Type guard for narrowing call types
|
|
1819
|
+
*
|
|
1820
|
+
* Use this function when you have a `TypedContractCall` and need to
|
|
1821
|
+
* narrow it to a specific call type. This is useful when the type
|
|
1822
|
+
* information is lost (e.g., after serialization or in generic functions).
|
|
1823
|
+
*
|
|
1824
|
+
* @typeParam M - The InkCallableDescriptor type (message definitions)
|
|
1825
|
+
* @typeParam L - The specific message label to check
|
|
1826
|
+
* @param call - The call to check
|
|
1827
|
+
* @param label - The message label to match
|
|
1828
|
+
* @returns True if the call type matches the label
|
|
1829
|
+
*
|
|
1830
|
+
* @example
|
|
1831
|
+
* ```ts
|
|
1832
|
+
* const call = parser.parseCall(callData);
|
|
1833
|
+
*
|
|
1834
|
+
* if (call && isCallType(call, "PSP22::transfer")) {
|
|
1835
|
+
* // TypeScript knows call.args is transfer args
|
|
1836
|
+
* console.log(call.args.to);
|
|
1837
|
+
* console.log(call.args.value);
|
|
1838
|
+
* }
|
|
1839
|
+
* ```
|
|
1840
|
+
*/
|
|
1841
|
+
function isCallType(call, label) {
|
|
1842
|
+
return call.type === label;
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
//#endregion
|
|
1846
|
+
//#region src/utils/fees.ts
|
|
1847
|
+
const WEIGHT_REF_TIME_PER_SECOND = 1000000000000n;
|
|
1848
|
+
const WEIGHT_FEE_COEFFICIENT = 1n;
|
|
1849
|
+
/**
|
|
1850
|
+
* Estimate the transaction cost from gas info and storage deposit
|
|
1851
|
+
*
|
|
1852
|
+
* Note: This is an approximation. Actual fees depend on chain configuration
|
|
1853
|
+
* and may vary slightly.
|
|
1854
|
+
*
|
|
1855
|
+
* @param gasInfo - Gas information from contract query
|
|
1856
|
+
* @param storageDeposit - Storage deposit amount (positive for charge, negative for refund)
|
|
1857
|
+
* @returns Estimated cost breakdown
|
|
1858
|
+
*
|
|
1859
|
+
* @example
|
|
1860
|
+
* ```typescript
|
|
1861
|
+
* const result = await contract.query("PSP22::transfer", { origin, args });
|
|
1862
|
+
* if (result.success) {
|
|
1863
|
+
* const cost = estimateTransactionCost(
|
|
1864
|
+
* { gasConsumed: result.gasConsumed, gasRequired: result.gasRequired },
|
|
1865
|
+
* result.storageDeposit
|
|
1866
|
+
* );
|
|
1867
|
+
* console.log(`Estimated cost: ${formatBalance(cost.total, { decimals: 12 })} D9`);
|
|
1868
|
+
* }
|
|
1869
|
+
* ```
|
|
1870
|
+
*/
|
|
1871
|
+
function estimateTransactionCost(gasInfo, storageDeposit) {
|
|
1872
|
+
const { gasRequired } = gasInfo;
|
|
1873
|
+
const gasCost = gasRequired.refTime * WEIGHT_FEE_COEFFICIENT / WEIGHT_REF_TIME_PER_SECOND + gasRequired.proofSize;
|
|
1874
|
+
const effectiveStorageDeposit = storageDeposit > 0n ? storageDeposit : 0n;
|
|
1875
|
+
return {
|
|
1876
|
+
gasCost,
|
|
1877
|
+
storageDeposit: effectiveStorageDeposit,
|
|
1878
|
+
total: gasCost + effectiveStorageDeposit
|
|
1879
|
+
};
|
|
1880
|
+
}
|
|
1881
|
+
/**
|
|
1882
|
+
* Format gas information for human-readable display
|
|
1883
|
+
*
|
|
1884
|
+
* @param gasInfo - Gas information from contract query
|
|
1885
|
+
* @returns Formatted strings for ref_time and proof_size
|
|
1886
|
+
*
|
|
1887
|
+
* @example
|
|
1888
|
+
* ```typescript
|
|
1889
|
+
* const formatted = formatGasInfo(result.gasRequired);
|
|
1890
|
+
* console.log(`Gas: refTime=${formatted.refTime}, proofSize=${formatted.proofSize}`);
|
|
1891
|
+
* ```
|
|
1892
|
+
*/
|
|
1893
|
+
function formatGasInfo(gasInfo) {
|
|
1894
|
+
return {
|
|
1895
|
+
refTime: formatWeight(gasInfo.refTime),
|
|
1896
|
+
proofSize: formatWeight(gasInfo.proofSize)
|
|
1897
|
+
};
|
|
1898
|
+
}
|
|
1899
|
+
/**
|
|
1900
|
+
* Format a single weight value with appropriate units
|
|
1901
|
+
*/
|
|
1902
|
+
function formatWeight(weight) {
|
|
1903
|
+
if (weight >= 1000000000000n) return `${(Number(weight) / 0xe8d4a51000).toFixed(2)}T`;
|
|
1904
|
+
if (weight >= 1000000000n) return `${(Number(weight) / 1e9).toFixed(2)}G`;
|
|
1905
|
+
if (weight >= 1000000n) return `${(Number(weight) / 1e6).toFixed(2)}M`;
|
|
1906
|
+
if (weight >= 1000n) return `${(Number(weight) / 1e3).toFixed(2)}K`;
|
|
1907
|
+
return weight.toString();
|
|
1908
|
+
}
|
|
1909
|
+
/**
|
|
1910
|
+
* Apply a safety margin to gas limits
|
|
1911
|
+
*
|
|
1912
|
+
* It's recommended to add a small margin to gas estimates to account for
|
|
1913
|
+
* slight variations in execution. 10% (multiplier = 1.1) is a common choice.
|
|
1914
|
+
*
|
|
1915
|
+
* @param gas - The gas weight to adjust
|
|
1916
|
+
* @param multiplier - The multiplier to apply (default: 1.1 for 10% margin)
|
|
1917
|
+
* @returns Adjusted gas weight with margin applied
|
|
1918
|
+
*
|
|
1919
|
+
* @example
|
|
1920
|
+
* ```typescript
|
|
1921
|
+
* const result = await contract.query("PSP22::transfer", { origin, args });
|
|
1922
|
+
* if (result.success) {
|
|
1923
|
+
* // Add 10% safety margin
|
|
1924
|
+
* const safeGas = applyGasMargin(result.gasRequired, 1.1);
|
|
1925
|
+
* await contract.send("PSP22::transfer", {
|
|
1926
|
+
* origin,
|
|
1927
|
+
* args,
|
|
1928
|
+
* gasLimit: safeGas,
|
|
1929
|
+
* });
|
|
1930
|
+
* }
|
|
1931
|
+
* ```
|
|
1932
|
+
*/
|
|
1933
|
+
function applyGasMargin(gas, multiplier = 1.1) {
|
|
1934
|
+
if (multiplier <= 0) throw new Error("Multiplier must be positive");
|
|
1935
|
+
const basisPoints = BigInt(Math.round(multiplier * 1e4));
|
|
1936
|
+
return {
|
|
1937
|
+
refTime: gas.refTime * basisPoints / 10000n,
|
|
1938
|
+
proofSize: gas.proofSize * basisPoints / 10000n
|
|
1939
|
+
};
|
|
1940
|
+
}
|
|
1941
|
+
/**
|
|
1942
|
+
* Compare two gas weights
|
|
1943
|
+
*
|
|
1944
|
+
* @param a - First gas weight
|
|
1945
|
+
* @param b - Second gas weight
|
|
1946
|
+
* @returns -1 if a < b, 0 if equal, 1 if a > b (compares refTime first, then proofSize)
|
|
1947
|
+
*/
|
|
1948
|
+
function compareGasWeight(a, b) {
|
|
1949
|
+
if (a.refTime < b.refTime) return -1;
|
|
1950
|
+
if (a.refTime > b.refTime) return 1;
|
|
1951
|
+
if (a.proofSize < b.proofSize) return -1;
|
|
1952
|
+
if (a.proofSize > b.proofSize) return 1;
|
|
1953
|
+
return 0;
|
|
1954
|
+
}
|
|
1955
|
+
/**
|
|
1956
|
+
* Check if gas weight exceeds a limit
|
|
1957
|
+
*
|
|
1958
|
+
* @param gas - The gas to check
|
|
1959
|
+
* @param limit - The limit to compare against
|
|
1960
|
+
* @returns True if gas exceeds the limit in either dimension
|
|
1961
|
+
*/
|
|
1962
|
+
function gasExceedsLimit(gas, limit) {
|
|
1963
|
+
return gas.refTime > limit.refTime || gas.proofSize > limit.proofSize;
|
|
1237
1964
|
}
|
|
1238
1965
|
|
|
1239
1966
|
//#endregion
|
|
1240
1967
|
exports.AbortedError = AbortedError;
|
|
1968
|
+
exports.ContractCallParser = ContractCallParser;
|
|
1241
1969
|
exports.ContractError = ContractError;
|
|
1242
1970
|
exports.ContractEventParser = ContractEventParser;
|
|
1243
1971
|
exports.ContractExecutionError = ContractExecutionError;
|
|
@@ -1250,25 +1978,35 @@ exports.NetworkError = NetworkError;
|
|
|
1250
1978
|
exports.SignerError = SignerError;
|
|
1251
1979
|
exports.TimeoutError = TimeoutError;
|
|
1252
1980
|
exports.TransactionError = TransactionError;
|
|
1981
|
+
exports.applyGasMargin = applyGasMargin;
|
|
1253
1982
|
exports.buildAllEventDecoders = buildAllEventDecoders;
|
|
1254
1983
|
exports.buildAllMessageDecoders = buildAllMessageDecoders;
|
|
1255
1984
|
exports.buildEventDecoder = buildEventDecoder;
|
|
1256
1985
|
exports.buildMessageDecoder = buildMessageDecoder;
|
|
1986
|
+
exports.compareGasWeight = compareGasWeight;
|
|
1987
|
+
exports.createAsciiEventTopic = createAsciiEventTopic;
|
|
1257
1988
|
exports.createCodecRegistry = createCodecRegistry;
|
|
1258
1989
|
exports.createContractEventStream = createContractEventStream;
|
|
1259
1990
|
exports.createD9InkContract = createD9InkContract;
|
|
1260
1991
|
exports.createD9InkSdk = createD9InkSdk;
|
|
1992
|
+
exports.createMessageBuilder = createMessageBuilder;
|
|
1261
1993
|
exports.createNativeTransferStream = createNativeTransferStream;
|
|
1262
1994
|
exports.createPSP22TransferStream = createPSP22TransferStream;
|
|
1995
|
+
exports.createTypedRpc = createTypedRpc;
|
|
1263
1996
|
exports.decodeContractCallResult = decodeContractCallResult;
|
|
1264
1997
|
exports.decodeInkValue = decodeInkValue;
|
|
1265
1998
|
exports.decodeResult = decodeResult;
|
|
1266
1999
|
exports.encodeCall = encodeCall;
|
|
1267
2000
|
exports.encodeContractCall = encodeContractCall;
|
|
1268
2001
|
exports.encodeContractCallWithLimits = encodeContractCallWithLimits;
|
|
2002
|
+
exports.estimateTransactionCost = estimateTransactionCost;
|
|
2003
|
+
exports.formatGasInfo = formatGasInfo;
|
|
2004
|
+
exports.gasExceedsLimit = gasExceedsLimit;
|
|
1269
2005
|
exports.getEventSignature = getEventSignature;
|
|
2006
|
+
exports.isCallType = isCallType;
|
|
1270
2007
|
exports.isContractError = isContractError;
|
|
1271
2008
|
exports.isErrorType = isErrorType;
|
|
2009
|
+
exports.isEventType = isEventType;
|
|
1272
2010
|
exports.isLangError = isLangError;
|
|
1273
2011
|
exports.unwrapInkResult = unwrapInkResult;
|
|
1274
2012
|
//# sourceMappingURL=index.cjs.map
|