@talismn/sapi 0.0.3 → 0.0.5

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.
@@ -0,0 +1,9 @@
1
+ type RpcSendFunc = <T>(method: string, params: unknown[], isCacheable?: boolean) => Promise<T>;
2
+ /**
3
+ * Fetches the highest supported version of metadata from the chain.
4
+ *
5
+ * @param rpcSend
6
+ * @returns hex-encoded metadata starting with the magic number
7
+ */
8
+ export declare const fetchBestMetadata: (rpcSend: RpcSendFunc, allowLegacyFallback?: boolean) => Promise<`0x${string}`>;
9
+ export {};
@@ -1,7 +1,6 @@
1
1
  import { ExtDef } from "@polkadot/types/extrinsic/signedExtensions/types";
2
- import { getDynamicBuilder, getLookupFn, V14, V15 } from "@talismn/scale";
2
+ import { getDynamicBuilder, getLookupFn, UnifiedMetadata } from "@talismn/scale";
3
3
  import { SapiConnector } from "./getSapiConnector";
4
- export type ScaleMetadata = V14 | V15;
5
4
  export type ScaleBuilder = ReturnType<typeof getDynamicBuilder>;
6
5
  export type ScaleLookup = ReturnType<typeof getLookupFn>;
7
6
  export type Chain = {
@@ -14,7 +13,7 @@ export type Chain = {
14
13
  hasCheckMetadataHash?: boolean;
15
14
  signedExtensions?: ExtDef;
16
15
  registryTypes?: unknown;
17
- metadata: ScaleMetadata;
16
+ metadata: UnifiedMetadata;
18
17
  lookup: ScaleLookup;
19
18
  builder: ScaleBuilder;
20
19
  };
@@ -1,2 +1,3 @@
1
1
  export * from "./types";
2
2
  export * from "./sapi";
3
+ export * from "./fetchBestMetadata";
@@ -9,6 +9,7 @@ var utils = require('@polkadot-api/utils');
9
9
  var types = require('@polkadot/types');
10
10
  var merkleizeMetadata = require('@polkadot-api/merkleize-metadata');
11
11
  var substrateBindings = require('@polkadot-api/substrate-bindings');
12
+ var scaleTs = require('scale-ts');
12
13
 
13
14
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
14
15
 
@@ -23,7 +24,7 @@ const getCallDocs = (chain, pallet, method) => {
23
24
  try {
24
25
  const typeIdCalls = chain.metadata.pallets.find(({
25
26
  name
26
- }) => name === pallet)?.calls;
27
+ }) => name === pallet)?.calls?.type;
27
28
  if (!typeIdCalls) return null;
28
29
 
29
30
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -375,7 +376,7 @@ const getPayloadWithMetadataHash = (chain, chainInfo, payload) => {
375
376
 
376
377
  // TODO do this without PJS / registry => waiting for @polkadot-api/tx-utils
377
378
  // const { extra, additionalSigned } = getSignedExtensionValues(payload, metadata)
378
- // const badExtPayload = mergeUint8(fromHex(payload.method), ...extra, ...additionalSigned)
379
+ // const badExtPayload = mergeUint8([fromHex(payload.method), ...extra, ...additionalSigned])
379
380
 
380
381
  const registry = getTypeRegistry(chain, payload);
381
382
  const extPayload = registry.createType("ExtrinsicPayload", payloadWithMetadataHash);
@@ -435,7 +436,7 @@ const getSignerPayloadJSON = async (chain, palletName, methodName, args, signerC
435
436
  codec,
436
437
  location
437
438
  } = chain.builder.buildCall(palletName, methodName);
438
- const method = polkadotApi.Binary.fromBytes(utils.mergeUint8(new Uint8Array(location), codec.enc(args)));
439
+ const method = polkadotApi.Binary.fromBytes(utils.mergeUint8([new Uint8Array(location), codec.enc(args)]));
439
440
  const blockNumber = await getStorageValue(chain, "System", "Number", []);
440
441
  if (blockNumber === null) throw new Error("Block number not found");
441
442
  const [account, genesisHash, blockHash] = await Promise.all([
@@ -485,11 +486,8 @@ const getSignerPayloadJSON = async (chain, palletName, methodName, args, signerC
485
486
  };
486
487
 
487
488
  const getScaleApi = (connector, hexMetadata, token, hasCheckMetadataHash, signedExtensions, registryTypes) => {
488
- const decoded = scale.decodeMetadata(hexMetadata);
489
- util.assert(decoded.metadata, `Missing Metadata V14+ for chain ${connector.chainId}`);
490
- const {
491
- metadata
492
- } = decoded;
489
+ const metadata = scale.unifyMetadata(scale.decAnyMetadata(hexMetadata));
490
+ util.assert(metadata, `Missing Metadata V14+ for chain ${connector.chainId}`);
493
491
  const lookup = scale.getLookupFn(metadata);
494
492
  const builder = scale.getDynamicBuilder(lookup);
495
493
  const chain = {
@@ -533,4 +531,51 @@ const getScaleApi = (connector, hexMetadata, token, hasCheckMetadataHash, signed
533
531
  };
534
532
  };
535
533
 
534
+ const MAGIC_NUMBER = 1635018093;
535
+
536
+ // it's important to set a max because some chains also return high invalid version numbers in the metadata_versions list (ex on Polkadot, related to JAM?)
537
+ const MAX_SUPPORTED_METADATA_VERSION = 16;
538
+ /**
539
+ * Fetches the highest supported version of metadata from the chain.
540
+ *
541
+ * @param rpcSend
542
+ * @returns hex-encoded metadata starting with the magic number
543
+ */
544
+ const fetchBestMetadata = async (rpcSend, allowLegacyFallback) => {
545
+ try {
546
+ // fetch available versions of metadata
547
+ const metadataVersions = await rpcSend("state_call", ["Metadata_metadata_versions", "0x"], true);
548
+ const availableVersions = scaleTs.Vector(scaleTs.u32).dec(metadataVersions);
549
+ const bestVersion = Math.max(...availableVersions.filter(v => v <= MAX_SUPPORTED_METADATA_VERSION));
550
+ const metadata = await rpcSend("state_call", ["Metadata_metadata_at_version", utils.toHex(scaleTs.u32.enc(bestVersion))], true);
551
+ return normalizeMetadata(metadata);
552
+ } catch (cause) {
553
+ // if the chain doesnt support the Metadata pallet, fallback to legacy rpc provided metadata (V14)
554
+ const message = cause?.message;
555
+ if (allowLegacyFallback || message?.includes("is not found") ||
556
+ // ex: crust standalone
557
+ message?.includes("Module doesn't have export Metadata_metadata_versions") // ex: 3DPass
558
+ ) {
559
+ return await rpcSend("state_getMetadata", [], true);
560
+ }
561
+
562
+ // otherwise throw so it can be handled by the caller
563
+ throw new Error("Failed to fetch metadata", {
564
+ cause
565
+ });
566
+ }
567
+ };
568
+
569
+ /**
570
+ * Removes everything before the magic number in the metadata.
571
+ * This ensures Opaque metadata is usable by pjs
572
+ */
573
+ const normalizeMetadata = metadata => {
574
+ const hexMagicNumber = utils.toHex(scaleTs.u32.enc(MAGIC_NUMBER)).slice(2);
575
+ const magicNumberIndex = metadata.indexOf(hexMagicNumber);
576
+ if (magicNumberIndex === -1) throw new Error("Invalid metadata format: magic number not found");
577
+ return `0x${metadata.slice(magicNumberIndex)}`;
578
+ };
579
+
580
+ exports.fetchBestMetadata = fetchBestMetadata;
536
581
  exports.getScaleApi = getScaleApi;
@@ -9,6 +9,7 @@ var utils = require('@polkadot-api/utils');
9
9
  var types = require('@polkadot/types');
10
10
  var merkleizeMetadata = require('@polkadot-api/merkleize-metadata');
11
11
  var substrateBindings = require('@polkadot-api/substrate-bindings');
12
+ var scaleTs = require('scale-ts');
12
13
 
13
14
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
14
15
 
@@ -23,7 +24,7 @@ const getCallDocs = (chain, pallet, method) => {
23
24
  try {
24
25
  const typeIdCalls = chain.metadata.pallets.find(({
25
26
  name
26
- }) => name === pallet)?.calls;
27
+ }) => name === pallet)?.calls?.type;
27
28
  if (!typeIdCalls) return null;
28
29
 
29
30
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -375,7 +376,7 @@ const getPayloadWithMetadataHash = (chain, chainInfo, payload) => {
375
376
 
376
377
  // TODO do this without PJS / registry => waiting for @polkadot-api/tx-utils
377
378
  // const { extra, additionalSigned } = getSignedExtensionValues(payload, metadata)
378
- // const badExtPayload = mergeUint8(fromHex(payload.method), ...extra, ...additionalSigned)
379
+ // const badExtPayload = mergeUint8([fromHex(payload.method), ...extra, ...additionalSigned])
379
380
 
380
381
  const registry = getTypeRegistry(chain, payload);
381
382
  const extPayload = registry.createType("ExtrinsicPayload", payloadWithMetadataHash);
@@ -435,7 +436,7 @@ const getSignerPayloadJSON = async (chain, palletName, methodName, args, signerC
435
436
  codec,
436
437
  location
437
438
  } = chain.builder.buildCall(palletName, methodName);
438
- const method = polkadotApi.Binary.fromBytes(utils.mergeUint8(new Uint8Array(location), codec.enc(args)));
439
+ const method = polkadotApi.Binary.fromBytes(utils.mergeUint8([new Uint8Array(location), codec.enc(args)]));
439
440
  const blockNumber = await getStorageValue(chain, "System", "Number", []);
440
441
  if (blockNumber === null) throw new Error("Block number not found");
441
442
  const [account, genesisHash, blockHash] = await Promise.all([
@@ -485,11 +486,8 @@ const getSignerPayloadJSON = async (chain, palletName, methodName, args, signerC
485
486
  };
486
487
 
487
488
  const getScaleApi = (connector, hexMetadata, token, hasCheckMetadataHash, signedExtensions, registryTypes) => {
488
- const decoded = scale.decodeMetadata(hexMetadata);
489
- util.assert(decoded.metadata, `Missing Metadata V14+ for chain ${connector.chainId}`);
490
- const {
491
- metadata
492
- } = decoded;
489
+ const metadata = scale.unifyMetadata(scale.decAnyMetadata(hexMetadata));
490
+ util.assert(metadata, `Missing Metadata V14+ for chain ${connector.chainId}`);
493
491
  const lookup = scale.getLookupFn(metadata);
494
492
  const builder = scale.getDynamicBuilder(lookup);
495
493
  const chain = {
@@ -533,4 +531,51 @@ const getScaleApi = (connector, hexMetadata, token, hasCheckMetadataHash, signed
533
531
  };
534
532
  };
535
533
 
534
+ const MAGIC_NUMBER = 1635018093;
535
+
536
+ // it's important to set a max because some chains also return high invalid version numbers in the metadata_versions list (ex on Polkadot, related to JAM?)
537
+ const MAX_SUPPORTED_METADATA_VERSION = 16;
538
+ /**
539
+ * Fetches the highest supported version of metadata from the chain.
540
+ *
541
+ * @param rpcSend
542
+ * @returns hex-encoded metadata starting with the magic number
543
+ */
544
+ const fetchBestMetadata = async (rpcSend, allowLegacyFallback) => {
545
+ try {
546
+ // fetch available versions of metadata
547
+ const metadataVersions = await rpcSend("state_call", ["Metadata_metadata_versions", "0x"], true);
548
+ const availableVersions = scaleTs.Vector(scaleTs.u32).dec(metadataVersions);
549
+ const bestVersion = Math.max(...availableVersions.filter(v => v <= MAX_SUPPORTED_METADATA_VERSION));
550
+ const metadata = await rpcSend("state_call", ["Metadata_metadata_at_version", utils.toHex(scaleTs.u32.enc(bestVersion))], true);
551
+ return normalizeMetadata(metadata);
552
+ } catch (cause) {
553
+ // if the chain doesnt support the Metadata pallet, fallback to legacy rpc provided metadata (V14)
554
+ const message = cause?.message;
555
+ if (allowLegacyFallback || message?.includes("is not found") ||
556
+ // ex: crust standalone
557
+ message?.includes("Module doesn't have export Metadata_metadata_versions") // ex: 3DPass
558
+ ) {
559
+ return await rpcSend("state_getMetadata", [], true);
560
+ }
561
+
562
+ // otherwise throw so it can be handled by the caller
563
+ throw new Error("Failed to fetch metadata", {
564
+ cause
565
+ });
566
+ }
567
+ };
568
+
569
+ /**
570
+ * Removes everything before the magic number in the metadata.
571
+ * This ensures Opaque metadata is usable by pjs
572
+ */
573
+ const normalizeMetadata = metadata => {
574
+ const hexMagicNumber = utils.toHex(scaleTs.u32.enc(MAGIC_NUMBER)).slice(2);
575
+ const magicNumberIndex = metadata.indexOf(hexMagicNumber);
576
+ if (magicNumberIndex === -1) throw new Error("Invalid metadata format: magic number not found");
577
+ return `0x${metadata.slice(magicNumberIndex)}`;
578
+ };
579
+
580
+ exports.fetchBestMetadata = fetchBestMetadata;
536
581
  exports.getScaleApi = getScaleApi;
@@ -1,5 +1,5 @@
1
1
  import { assert, u8aConcatStrict } from '@polkadot/util';
2
- import { decodeMetadata, getLookupFn as getLookupFn$1, getDynamicBuilder } from '@talismn/scale';
2
+ import { unifyMetadata, decAnyMetadata, getLookupFn as getLookupFn$1, getDynamicBuilder } from '@talismn/scale';
3
3
  import anylogger from 'anylogger';
4
4
  import { Enum, Binary } from 'polkadot-api';
5
5
  import { getLookupFn } from '@polkadot-api/metadata-builders';
@@ -7,6 +7,7 @@ import { toHex, mergeUint8 } from '@polkadot-api/utils';
7
7
  import { TypeRegistry, Metadata } from '@polkadot/types';
8
8
  import { merkleizeMetadata } from '@polkadot-api/merkleize-metadata';
9
9
  import { enhanceEncoder, Bytes, u16 } from '@polkadot-api/substrate-bindings';
10
+ import { Vector, u32 } from 'scale-ts';
10
11
 
11
12
  var packageJson = {
12
13
  name: "@talismn/sapi"};
@@ -17,7 +18,7 @@ const getCallDocs = (chain, pallet, method) => {
17
18
  try {
18
19
  const typeIdCalls = chain.metadata.pallets.find(({
19
20
  name
20
- }) => name === pallet)?.calls;
21
+ }) => name === pallet)?.calls?.type;
21
22
  if (!typeIdCalls) return null;
22
23
 
23
24
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -369,7 +370,7 @@ const getPayloadWithMetadataHash = (chain, chainInfo, payload) => {
369
370
 
370
371
  // TODO do this without PJS / registry => waiting for @polkadot-api/tx-utils
371
372
  // const { extra, additionalSigned } = getSignedExtensionValues(payload, metadata)
372
- // const badExtPayload = mergeUint8(fromHex(payload.method), ...extra, ...additionalSigned)
373
+ // const badExtPayload = mergeUint8([fromHex(payload.method), ...extra, ...additionalSigned])
373
374
 
374
375
  const registry = getTypeRegistry(chain, payload);
375
376
  const extPayload = registry.createType("ExtrinsicPayload", payloadWithMetadataHash);
@@ -429,7 +430,7 @@ const getSignerPayloadJSON = async (chain, palletName, methodName, args, signerC
429
430
  codec,
430
431
  location
431
432
  } = chain.builder.buildCall(palletName, methodName);
432
- const method = Binary.fromBytes(mergeUint8(new Uint8Array(location), codec.enc(args)));
433
+ const method = Binary.fromBytes(mergeUint8([new Uint8Array(location), codec.enc(args)]));
433
434
  const blockNumber = await getStorageValue(chain, "System", "Number", []);
434
435
  if (blockNumber === null) throw new Error("Block number not found");
435
436
  const [account, genesisHash, blockHash] = await Promise.all([
@@ -479,11 +480,8 @@ const getSignerPayloadJSON = async (chain, palletName, methodName, args, signerC
479
480
  };
480
481
 
481
482
  const getScaleApi = (connector, hexMetadata, token, hasCheckMetadataHash, signedExtensions, registryTypes) => {
482
- const decoded = decodeMetadata(hexMetadata);
483
- assert(decoded.metadata, `Missing Metadata V14+ for chain ${connector.chainId}`);
484
- const {
485
- metadata
486
- } = decoded;
483
+ const metadata = unifyMetadata(decAnyMetadata(hexMetadata));
484
+ assert(metadata, `Missing Metadata V14+ for chain ${connector.chainId}`);
487
485
  const lookup = getLookupFn$1(metadata);
488
486
  const builder = getDynamicBuilder(lookup);
489
487
  const chain = {
@@ -527,4 +525,50 @@ const getScaleApi = (connector, hexMetadata, token, hasCheckMetadataHash, signed
527
525
  };
528
526
  };
529
527
 
530
- export { getScaleApi };
528
+ const MAGIC_NUMBER = 1635018093;
529
+
530
+ // it's important to set a max because some chains also return high invalid version numbers in the metadata_versions list (ex on Polkadot, related to JAM?)
531
+ const MAX_SUPPORTED_METADATA_VERSION = 16;
532
+ /**
533
+ * Fetches the highest supported version of metadata from the chain.
534
+ *
535
+ * @param rpcSend
536
+ * @returns hex-encoded metadata starting with the magic number
537
+ */
538
+ const fetchBestMetadata = async (rpcSend, allowLegacyFallback) => {
539
+ try {
540
+ // fetch available versions of metadata
541
+ const metadataVersions = await rpcSend("state_call", ["Metadata_metadata_versions", "0x"], true);
542
+ const availableVersions = Vector(u32).dec(metadataVersions);
543
+ const bestVersion = Math.max(...availableVersions.filter(v => v <= MAX_SUPPORTED_METADATA_VERSION));
544
+ const metadata = await rpcSend("state_call", ["Metadata_metadata_at_version", toHex(u32.enc(bestVersion))], true);
545
+ return normalizeMetadata(metadata);
546
+ } catch (cause) {
547
+ // if the chain doesnt support the Metadata pallet, fallback to legacy rpc provided metadata (V14)
548
+ const message = cause?.message;
549
+ if (allowLegacyFallback || message?.includes("is not found") ||
550
+ // ex: crust standalone
551
+ message?.includes("Module doesn't have export Metadata_metadata_versions") // ex: 3DPass
552
+ ) {
553
+ return await rpcSend("state_getMetadata", [], true);
554
+ }
555
+
556
+ // otherwise throw so it can be handled by the caller
557
+ throw new Error("Failed to fetch metadata", {
558
+ cause
559
+ });
560
+ }
561
+ };
562
+
563
+ /**
564
+ * Removes everything before the magic number in the metadata.
565
+ * This ensures Opaque metadata is usable by pjs
566
+ */
567
+ const normalizeMetadata = metadata => {
568
+ const hexMagicNumber = toHex(u32.enc(MAGIC_NUMBER)).slice(2);
569
+ const magicNumberIndex = metadata.indexOf(hexMagicNumber);
570
+ if (magicNumberIndex === -1) throw new Error("Invalid metadata format: magic number not found");
571
+ return `0x${metadata.slice(magicNumberIndex)}`;
572
+ };
573
+
574
+ export { fetchBestMetadata, getScaleApi };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@talismn/sapi",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "author": "Talisman",
5
5
  "homepage": "https://talisman.xyz",
6
6
  "license": "GPL-3.0-or-later",
@@ -21,16 +21,17 @@
21
21
  "node": ">=18"
22
22
  },
23
23
  "dependencies": {
24
- "@polkadot-api/merkleize-metadata": "1.1.14",
25
- "@polkadot-api/metadata-builders": "0.10.2",
26
- "@polkadot-api/substrate-bindings": "0.11.1",
27
- "@polkadot-api/utils": "0.1.2",
28
- "@polkadot/types": "15.8.1",
29
- "@polkadot/types-codec": "15.8.1",
30
- "@polkadot/util": "13.4.3",
24
+ "@polkadot-api/merkleize-metadata": "1.1.18",
25
+ "@polkadot-api/metadata-builders": "0.12.2",
26
+ "@polkadot-api/substrate-bindings": "0.14.0",
27
+ "@polkadot-api/utils": "0.2.0",
28
+ "@polkadot/types": "16.1.2",
29
+ "@polkadot/types-codec": "16.1.2",
30
+ "@polkadot/util": "13.5.1",
31
31
  "anylogger": "^1.0.11",
32
- "polkadot-api": "1.7.6",
33
- "@talismn/scale": "0.1.1"
32
+ "polkadot-api": "1.13.1",
33
+ "scale-ts": "^1.6.1",
34
+ "@talismn/scale": "0.1.2"
34
35
  },
35
36
  "devDependencies": {
36
37
  "@types/jest": "^29.5.14",
@@ -38,8 +39,8 @@
38
39
  "jest": "^29.7",
39
40
  "ts-jest": "^29.2.5",
40
41
  "typescript": "^5.6.3",
41
- "@talismn/eslint-config": "0.0.3",
42
- "@talismn/tsconfig": "0.0.2"
42
+ "@talismn/tsconfig": "0.0.2",
43
+ "@talismn/eslint-config": "0.0.3"
43
44
  },
44
45
  "eslintConfig": {
45
46
  "root": true,