@talismn/sapi 0.0.4 → 0.0.6
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 {};
|
|
@@ -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
|
|
|
@@ -430,27 +431,26 @@ function trailingZeroes(n) {
|
|
|
430
431
|
return i;
|
|
431
432
|
}
|
|
432
433
|
|
|
434
|
+
const PERIOD = 64; // validity period in blocks, used for mortal era
|
|
435
|
+
|
|
433
436
|
const getSignerPayloadJSON = async (chain, palletName, methodName, args, signerConfig, chainInfo) => {
|
|
434
437
|
const {
|
|
435
438
|
codec,
|
|
436
439
|
location
|
|
437
440
|
} = chain.builder.buildCall(palletName, methodName);
|
|
438
441
|
const method = polkadotApi.Binary.fromBytes(utils.mergeUint8([new Uint8Array(location), codec.enc(args)]));
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
getStorageValue(chain, "System", "Account", [signerConfig.address]), getStorageValue(chain, "System", "BlockHash", [0]), getSendRequestResult(chain, "chain_getBlockHash", [blockNumber], false) // TODO find the right way to fetch this with new RPC api, this is not available in storage yet
|
|
445
|
-
]);
|
|
442
|
+
|
|
443
|
+
// on unstable networks with lots of forks (ex: westend asset hub as of june 2025),
|
|
444
|
+
// using a finalized block as reference for mortality is necessary for txs to get through
|
|
445
|
+
const blockHash = await getSendRequestResult(chain, "chain_getFinalizedHead", [], false);
|
|
446
|
+
const [nonce, genesisHash, blockNumber] = await Promise.all([getSendRequestResult(chain, "system_accountNextIndex", [signerConfig.address], false), getStorageValue(chain, "System", "BlockHash", [0]), getStorageValue(chain, "System", "Number", [], blockHash)]);
|
|
446
447
|
if (!genesisHash) throw new Error("Genesis hash not found");
|
|
447
448
|
if (!blockHash) throw new Error("Block hash not found");
|
|
448
|
-
const nonce = account ? account.nonce : 0;
|
|
449
449
|
const era = mortal({
|
|
450
|
-
period:
|
|
451
|
-
phase: blockNumber %
|
|
450
|
+
period: PERIOD,
|
|
451
|
+
phase: blockNumber % PERIOD
|
|
452
452
|
});
|
|
453
|
-
const signedExtensions = chain.metadata.extrinsic.signedExtensions.map(ext => ext.identifier
|
|
453
|
+
const signedExtensions = chain.metadata.extrinsic.signedExtensions.map(ext => ext.identifier);
|
|
454
454
|
const basePayload = {
|
|
455
455
|
address: signerConfig.address,
|
|
456
456
|
genesisHash: genesisHash.asHex(),
|
|
@@ -530,4 +530,51 @@ const getScaleApi = (connector, hexMetadata, token, hasCheckMetadataHash, signed
|
|
|
530
530
|
};
|
|
531
531
|
};
|
|
532
532
|
|
|
533
|
+
const MAGIC_NUMBER = 1635018093;
|
|
534
|
+
|
|
535
|
+
// 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?)
|
|
536
|
+
const MAX_SUPPORTED_METADATA_VERSION = 16;
|
|
537
|
+
/**
|
|
538
|
+
* Fetches the highest supported version of metadata from the chain.
|
|
539
|
+
*
|
|
540
|
+
* @param rpcSend
|
|
541
|
+
* @returns hex-encoded metadata starting with the magic number
|
|
542
|
+
*/
|
|
543
|
+
const fetchBestMetadata = async (rpcSend, allowLegacyFallback) => {
|
|
544
|
+
try {
|
|
545
|
+
// fetch available versions of metadata
|
|
546
|
+
const metadataVersions = await rpcSend("state_call", ["Metadata_metadata_versions", "0x"], true);
|
|
547
|
+
const availableVersions = scaleTs.Vector(scaleTs.u32).dec(metadataVersions);
|
|
548
|
+
const bestVersion = Math.max(...availableVersions.filter(v => v <= MAX_SUPPORTED_METADATA_VERSION));
|
|
549
|
+
const metadata = await rpcSend("state_call", ["Metadata_metadata_at_version", utils.toHex(scaleTs.u32.enc(bestVersion))], true);
|
|
550
|
+
return normalizeMetadata(metadata);
|
|
551
|
+
} catch (cause) {
|
|
552
|
+
// if the chain doesnt support the Metadata pallet, fallback to legacy rpc provided metadata (V14)
|
|
553
|
+
const message = cause?.message;
|
|
554
|
+
if (allowLegacyFallback || message?.includes("is not found") ||
|
|
555
|
+
// ex: crust standalone
|
|
556
|
+
message?.includes("Module doesn't have export Metadata_metadata_versions") // ex: 3DPass
|
|
557
|
+
) {
|
|
558
|
+
return await rpcSend("state_getMetadata", [], true);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// otherwise throw so it can be handled by the caller
|
|
562
|
+
throw new Error("Failed to fetch metadata", {
|
|
563
|
+
cause
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Removes everything before the magic number in the metadata.
|
|
570
|
+
* This ensures Opaque metadata is usable by pjs
|
|
571
|
+
*/
|
|
572
|
+
const normalizeMetadata = metadata => {
|
|
573
|
+
const hexMagicNumber = utils.toHex(scaleTs.u32.enc(MAGIC_NUMBER)).slice(2);
|
|
574
|
+
const magicNumberIndex = metadata.indexOf(hexMagicNumber);
|
|
575
|
+
if (magicNumberIndex === -1) throw new Error("Invalid metadata format: magic number not found");
|
|
576
|
+
return `0x${metadata.slice(magicNumberIndex)}`;
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
exports.fetchBestMetadata = fetchBestMetadata;
|
|
533
580
|
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
|
|
|
@@ -430,27 +431,26 @@ function trailingZeroes(n) {
|
|
|
430
431
|
return i;
|
|
431
432
|
}
|
|
432
433
|
|
|
434
|
+
const PERIOD = 64; // validity period in blocks, used for mortal era
|
|
435
|
+
|
|
433
436
|
const getSignerPayloadJSON = async (chain, palletName, methodName, args, signerConfig, chainInfo) => {
|
|
434
437
|
const {
|
|
435
438
|
codec,
|
|
436
439
|
location
|
|
437
440
|
} = chain.builder.buildCall(palletName, methodName);
|
|
438
441
|
const method = polkadotApi.Binary.fromBytes(utils.mergeUint8([new Uint8Array(location), codec.enc(args)]));
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
getStorageValue(chain, "System", "Account", [signerConfig.address]), getStorageValue(chain, "System", "BlockHash", [0]), getSendRequestResult(chain, "chain_getBlockHash", [blockNumber], false) // TODO find the right way to fetch this with new RPC api, this is not available in storage yet
|
|
445
|
-
]);
|
|
442
|
+
|
|
443
|
+
// on unstable networks with lots of forks (ex: westend asset hub as of june 2025),
|
|
444
|
+
// using a finalized block as reference for mortality is necessary for txs to get through
|
|
445
|
+
const blockHash = await getSendRequestResult(chain, "chain_getFinalizedHead", [], false);
|
|
446
|
+
const [nonce, genesisHash, blockNumber] = await Promise.all([getSendRequestResult(chain, "system_accountNextIndex", [signerConfig.address], false), getStorageValue(chain, "System", "BlockHash", [0]), getStorageValue(chain, "System", "Number", [], blockHash)]);
|
|
446
447
|
if (!genesisHash) throw new Error("Genesis hash not found");
|
|
447
448
|
if (!blockHash) throw new Error("Block hash not found");
|
|
448
|
-
const nonce = account ? account.nonce : 0;
|
|
449
449
|
const era = mortal({
|
|
450
|
-
period:
|
|
451
|
-
phase: blockNumber %
|
|
450
|
+
period: PERIOD,
|
|
451
|
+
phase: blockNumber % PERIOD
|
|
452
452
|
});
|
|
453
|
-
const signedExtensions = chain.metadata.extrinsic.signedExtensions.map(ext => ext.identifier
|
|
453
|
+
const signedExtensions = chain.metadata.extrinsic.signedExtensions.map(ext => ext.identifier);
|
|
454
454
|
const basePayload = {
|
|
455
455
|
address: signerConfig.address,
|
|
456
456
|
genesisHash: genesisHash.asHex(),
|
|
@@ -530,4 +530,51 @@ const getScaleApi = (connector, hexMetadata, token, hasCheckMetadataHash, signed
|
|
|
530
530
|
};
|
|
531
531
|
};
|
|
532
532
|
|
|
533
|
+
const MAGIC_NUMBER = 1635018093;
|
|
534
|
+
|
|
535
|
+
// 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?)
|
|
536
|
+
const MAX_SUPPORTED_METADATA_VERSION = 16;
|
|
537
|
+
/**
|
|
538
|
+
* Fetches the highest supported version of metadata from the chain.
|
|
539
|
+
*
|
|
540
|
+
* @param rpcSend
|
|
541
|
+
* @returns hex-encoded metadata starting with the magic number
|
|
542
|
+
*/
|
|
543
|
+
const fetchBestMetadata = async (rpcSend, allowLegacyFallback) => {
|
|
544
|
+
try {
|
|
545
|
+
// fetch available versions of metadata
|
|
546
|
+
const metadataVersions = await rpcSend("state_call", ["Metadata_metadata_versions", "0x"], true);
|
|
547
|
+
const availableVersions = scaleTs.Vector(scaleTs.u32).dec(metadataVersions);
|
|
548
|
+
const bestVersion = Math.max(...availableVersions.filter(v => v <= MAX_SUPPORTED_METADATA_VERSION));
|
|
549
|
+
const metadata = await rpcSend("state_call", ["Metadata_metadata_at_version", utils.toHex(scaleTs.u32.enc(bestVersion))], true);
|
|
550
|
+
return normalizeMetadata(metadata);
|
|
551
|
+
} catch (cause) {
|
|
552
|
+
// if the chain doesnt support the Metadata pallet, fallback to legacy rpc provided metadata (V14)
|
|
553
|
+
const message = cause?.message;
|
|
554
|
+
if (allowLegacyFallback || message?.includes("is not found") ||
|
|
555
|
+
// ex: crust standalone
|
|
556
|
+
message?.includes("Module doesn't have export Metadata_metadata_versions") // ex: 3DPass
|
|
557
|
+
) {
|
|
558
|
+
return await rpcSend("state_getMetadata", [], true);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// otherwise throw so it can be handled by the caller
|
|
562
|
+
throw new Error("Failed to fetch metadata", {
|
|
563
|
+
cause
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Removes everything before the magic number in the metadata.
|
|
570
|
+
* This ensures Opaque metadata is usable by pjs
|
|
571
|
+
*/
|
|
572
|
+
const normalizeMetadata = metadata => {
|
|
573
|
+
const hexMagicNumber = utils.toHex(scaleTs.u32.enc(MAGIC_NUMBER)).slice(2);
|
|
574
|
+
const magicNumberIndex = metadata.indexOf(hexMagicNumber);
|
|
575
|
+
if (magicNumberIndex === -1) throw new Error("Invalid metadata format: magic number not found");
|
|
576
|
+
return `0x${metadata.slice(magicNumberIndex)}`;
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
exports.fetchBestMetadata = fetchBestMetadata;
|
|
533
580
|
exports.getScaleApi = getScaleApi;
|
package/dist/talismn-sapi.esm.js
CHANGED
|
@@ -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"};
|
|
@@ -424,27 +425,26 @@ function trailingZeroes(n) {
|
|
|
424
425
|
return i;
|
|
425
426
|
}
|
|
426
427
|
|
|
428
|
+
const PERIOD = 64; // validity period in blocks, used for mortal era
|
|
429
|
+
|
|
427
430
|
const getSignerPayloadJSON = async (chain, palletName, methodName, args, signerConfig, chainInfo) => {
|
|
428
431
|
const {
|
|
429
432
|
codec,
|
|
430
433
|
location
|
|
431
434
|
} = chain.builder.buildCall(palletName, methodName);
|
|
432
435
|
const method = Binary.fromBytes(mergeUint8([new Uint8Array(location), codec.enc(args)]));
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
getStorageValue(chain, "System", "Account", [signerConfig.address]), getStorageValue(chain, "System", "BlockHash", [0]), getSendRequestResult(chain, "chain_getBlockHash", [blockNumber], false) // TODO find the right way to fetch this with new RPC api, this is not available in storage yet
|
|
439
|
-
]);
|
|
436
|
+
|
|
437
|
+
// on unstable networks with lots of forks (ex: westend asset hub as of june 2025),
|
|
438
|
+
// using a finalized block as reference for mortality is necessary for txs to get through
|
|
439
|
+
const blockHash = await getSendRequestResult(chain, "chain_getFinalizedHead", [], false);
|
|
440
|
+
const [nonce, genesisHash, blockNumber] = await Promise.all([getSendRequestResult(chain, "system_accountNextIndex", [signerConfig.address], false), getStorageValue(chain, "System", "BlockHash", [0]), getStorageValue(chain, "System", "Number", [], blockHash)]);
|
|
440
441
|
if (!genesisHash) throw new Error("Genesis hash not found");
|
|
441
442
|
if (!blockHash) throw new Error("Block hash not found");
|
|
442
|
-
const nonce = account ? account.nonce : 0;
|
|
443
443
|
const era = mortal({
|
|
444
|
-
period:
|
|
445
|
-
phase: blockNumber %
|
|
444
|
+
period: PERIOD,
|
|
445
|
+
phase: blockNumber % PERIOD
|
|
446
446
|
});
|
|
447
|
-
const signedExtensions = chain.metadata.extrinsic.signedExtensions.map(ext => ext.identifier
|
|
447
|
+
const signedExtensions = chain.metadata.extrinsic.signedExtensions.map(ext => ext.identifier);
|
|
448
448
|
const basePayload = {
|
|
449
449
|
address: signerConfig.address,
|
|
450
450
|
genesisHash: genesisHash.asHex(),
|
|
@@ -524,4 +524,50 @@ const getScaleApi = (connector, hexMetadata, token, hasCheckMetadataHash, signed
|
|
|
524
524
|
};
|
|
525
525
|
};
|
|
526
526
|
|
|
527
|
-
|
|
527
|
+
const MAGIC_NUMBER = 1635018093;
|
|
528
|
+
|
|
529
|
+
// 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?)
|
|
530
|
+
const MAX_SUPPORTED_METADATA_VERSION = 16;
|
|
531
|
+
/**
|
|
532
|
+
* Fetches the highest supported version of metadata from the chain.
|
|
533
|
+
*
|
|
534
|
+
* @param rpcSend
|
|
535
|
+
* @returns hex-encoded metadata starting with the magic number
|
|
536
|
+
*/
|
|
537
|
+
const fetchBestMetadata = async (rpcSend, allowLegacyFallback) => {
|
|
538
|
+
try {
|
|
539
|
+
// fetch available versions of metadata
|
|
540
|
+
const metadataVersions = await rpcSend("state_call", ["Metadata_metadata_versions", "0x"], true);
|
|
541
|
+
const availableVersions = Vector(u32).dec(metadataVersions);
|
|
542
|
+
const bestVersion = Math.max(...availableVersions.filter(v => v <= MAX_SUPPORTED_METADATA_VERSION));
|
|
543
|
+
const metadata = await rpcSend("state_call", ["Metadata_metadata_at_version", toHex(u32.enc(bestVersion))], true);
|
|
544
|
+
return normalizeMetadata(metadata);
|
|
545
|
+
} catch (cause) {
|
|
546
|
+
// if the chain doesnt support the Metadata pallet, fallback to legacy rpc provided metadata (V14)
|
|
547
|
+
const message = cause?.message;
|
|
548
|
+
if (allowLegacyFallback || message?.includes("is not found") ||
|
|
549
|
+
// ex: crust standalone
|
|
550
|
+
message?.includes("Module doesn't have export Metadata_metadata_versions") // ex: 3DPass
|
|
551
|
+
) {
|
|
552
|
+
return await rpcSend("state_getMetadata", [], true);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// otherwise throw so it can be handled by the caller
|
|
556
|
+
throw new Error("Failed to fetch metadata", {
|
|
557
|
+
cause
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Removes everything before the magic number in the metadata.
|
|
564
|
+
* This ensures Opaque metadata is usable by pjs
|
|
565
|
+
*/
|
|
566
|
+
const normalizeMetadata = metadata => {
|
|
567
|
+
const hexMagicNumber = toHex(u32.enc(MAGIC_NUMBER)).slice(2);
|
|
568
|
+
const magicNumberIndex = metadata.indexOf(hexMagicNumber);
|
|
569
|
+
if (magicNumberIndex === -1) throw new Error("Invalid metadata format: magic number not found");
|
|
570
|
+
return `0x${metadata.slice(magicNumberIndex)}`;
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
export { fetchBestMetadata, getScaleApi };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@talismn/sapi",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"author": "Talisman",
|
|
5
5
|
"homepage": "https://talisman.xyz",
|
|
6
6
|
"license": "GPL-3.0-or-later",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"@polkadot/util": "13.5.1",
|
|
31
31
|
"anylogger": "^1.0.11",
|
|
32
32
|
"polkadot-api": "1.13.1",
|
|
33
|
+
"scale-ts": "^1.6.1",
|
|
33
34
|
"@talismn/scale": "0.1.2"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|