import type { AnyString, Codec, CodecClass, IU8a, LookupString } from 'https://deno.land/x/polkadot@0.2.33/types-codec/types/index.ts';import type { CreateOptions, TypeDef } from 'https://deno.land/x/polkadot@0.2.33/types-create/types/index.ts';import type { ExtDef } from '../extrinsic/signedExtensions/types.ts';import type { ChainProperties, DispatchErrorModule, DispatchErrorModuleU8, DispatchErrorModuleU8a, EventMetadataLatest, Hash, MetadataLatest, SiField, SiLookupTypeId, SiVariant, WeightV1, WeightV2 } from '../interfaces/types.ts';import type { CallFunction, CodecHasher, Definitions, DetectCodec, RegisteredTypes, Registry, RegistryError, RegistryTypes } from '../types/index.ts';
import { DoNotConstruct, Json, Raw } from 'https://deno.land/x/polkadot@0.2.33/types-codec/mod.ts';import { constructTypeClass, createClassUnsafe, createTypeUnsafe } from 'https://deno.land/x/polkadot@0.2.33/types-create/mod.ts';import { assertReturn, BN_ZERO, formatBalance, isBn, isFunction, isNumber, isString, isU8a, lazyMethod, logger, objectSpread, stringCamelCase, stringify } from 'https://deno.land/x/polkadot@0.2.33/util/mod.ts';import { blake2AsU8a } from 'https://deno.land/x/polkadot@0.2.33/util-crypto/mod.ts';
import { expandExtensionTypes, fallbackExtensions, findUnknownExtensions } from '../extrinsic/signedExtensions/index.ts';import { GenericEventData } from '../generic/Event.ts';import * as baseTypes from '../index.types.ts';import * as definitions from '../interfaces/definitions.ts';import { createCallFunction } from '../metadata/decorate/extrinsics/index.ts';import { decorateConstants, filterCallsSome, filterEventsSome } from '../metadata/decorate/index.ts';import { Metadata } from '../metadata/Metadata.ts';import { PortableRegistry } from '../metadata/PortableRegistry/index.ts';import { lazyVariants } from './lazy.ts';
const DEFAULT_FIRST_CALL_IDX = new Uint8Array(2);
const l = logger('registry');
function sortDecimalStrings (a: string, b: string): number { return parseInt(a, 10) - parseInt(b, 10);}
function valueToString (v: { toString: () => string }): string { return v.toString();}
function getFieldArgs (lookup: PortableRegistry, fields: SiField[]): string[] { const args = new Array<string>(fields.length);
for (let i = 0; i < fields.length; i++) { args[i] = lookup.getTypeDef(fields[i].type).type; }
return args;}
function clearRecord (record: Record<string, unknown>): void { const keys = Object.keys(record);
for (let i = 0; i < keys.length; i++) { delete record[keys[i]]; }}
function getVariantStringIdx ({ index }: SiVariant): string { return index.toString();}
function injectErrors (_: TypeRegistry, { lookup, pallets }: MetadataLatest, version: number, result: Record<string, Record<string, RegistryError>>): void { clearRecord(result);
for (let i = 0; i < pallets.length; i++) { const { errors, index, name } = pallets[i];
if (errors.isSome) { const sectionName = stringCamelCase(name);
lazyMethod(result, version >= 12 ? index.toNumber() : i, () => lazyVariants(lookup, errors.unwrap(), getVariantStringIdx, ({ docs, fields, index, name }: SiVariant): RegistryError => ({ args: getFieldArgs(lookup, fields), docs: docs.map(valueToString), fields, index: index.toNumber(), method: name.toString(), name: name.toString(), section: sectionName })) ); } }}
function injectEvents (registry: TypeRegistry, { lookup, pallets }: MetadataLatest, version: number, result: Record<string, Record<string, CodecClass<GenericEventData>>>): void { const filtered = pallets.filter(filterEventsSome);
clearRecord(result);
for (let i = 0; i < filtered.length; i++) { const { events, index, name } = filtered[i];
lazyMethod(result, version >= 12 ? index.toNumber() : i, () => lazyVariants(lookup, events.unwrap(), getVariantStringIdx, (variant: SiVariant): CodecClass<GenericEventData> => { const meta = registry.createType<EventMetadataLatest>('EventMetadataLatest', objectSpread({}, variant, { args: getFieldArgs(lookup, variant.fields) }));
return class extends GenericEventData { constructor (registry: Registry, value: Uint8Array) { super(registry, value, meta, stringCamelCase(name), variant.name.toString()); } }; }) ); }}
function injectExtrinsics (registry: TypeRegistry, { lookup, pallets }: MetadataLatest, version: number, result: Record<string, Record<string, CallFunction>>, mapping: Record<string, string[]>): void { const filtered = pallets.filter(filterCallsSome);
clearRecord(result); clearRecord(mapping);
for (let i = 0; i < filtered.length; i++) { const { calls, index, name } = filtered[i]; const sectionIndex = version >= 12 ? index.toNumber() : i; const sectionName = stringCamelCase(name); const allCalls = calls.unwrap();
lazyMethod(result, sectionIndex, () => lazyVariants(lookup, allCalls, getVariantStringIdx, (variant: SiVariant) => createCallFunction(registry, lookup, variant, sectionName, sectionIndex) ) );
const { path } = registry.lookup.getSiType(allCalls.type);
const palletIdx = path.findIndex((v) => v.eq('pallet'));
if (palletIdx !== -1) { const name = stringCamelCase( path .slice(0, palletIdx) .map((p, i) => i === 0 ? p.replace(/^(frame|pallet)_/, '') : p ) .join(' ') );
if (!mapping[name]) { mapping[name] = [sectionName]; } else { mapping[name].push(sectionName); } } }}
function extractProperties (registry: TypeRegistry, metadata: Metadata): ChainProperties | undefined { const original = registry.getChainProperties(); const constants = decorateConstants(registry, metadata.asLatest, metadata.version); const ss58Format = constants.system && (constants.system.sS58Prefix || constants.system.ss58Prefix);
if (!ss58Format) { return original; }
const { tokenDecimals, tokenSymbol } = original || {};
return registry.createTypeUnsafe<ChainProperties>('ChainProperties', [{ ss58Format, tokenDecimals, tokenSymbol }]);}
export class TypeRegistry implements Registry { #chainProperties?: ChainProperties; #classes = new Map<string, CodecClass>(); #definitions = new Map<string, string>(); #firstCallIndex: Uint8Array | null = null; #hasher: (data: Uint8Array) => Uint8Array = blake2AsU8a; #knownTypes: RegisteredTypes = {}; #lookup?: PortableRegistry; #metadata?: MetadataLatest; #metadataVersion = 0; #signedExtensions: string[] = fallbackExtensions; #unknownTypes = new Map<string, boolean>(); #userExtensions?: ExtDef;
readonly #knownDefaults: Record<string, CodecClass>; readonly #knownDefinitions: Record<string, Definitions>; readonly #metadataCalls: Record<string, Record<string, CallFunction>> = {}; readonly #metadataErrors: Record<string, Record<string, RegistryError>> = {}; readonly #metadataEvents: Record<string, Record<string, CodecClass<GenericEventData>>> = {}; readonly #moduleMap: Record<string, string[]> = {};
public createdAtHash?: Hash;
constructor (createdAtHash?: Hash | Uint8Array | string) { this.#knownDefaults = objectSpread({ Json, Metadata, PortableRegistry, Raw }, baseTypes); this.#knownDefinitions = definitions;
const allKnown = Object.values(this.#knownDefinitions);
for (let i = 0; i < allKnown.length; i++) { this.register(allKnown[i].types as unknown as RegistryTypes); }
if (createdAtHash) { this.createdAtHash = this.createType('BlockHash', createdAtHash); } }
public get chainDecimals (): number[] { if (this.#chainProperties?.tokenDecimals.isSome) { const allDecimals = this.#chainProperties.tokenDecimals.unwrap();
if (allDecimals.length) { return allDecimals.map((b) => b.toNumber()); } }
return [12]; }
public get chainSS58 (): number | undefined { return this.#chainProperties?.ss58Format.isSome ? this.#chainProperties.ss58Format.unwrap().toNumber() : undefined; }
public get chainTokens (): string[] { if (this.#chainProperties?.tokenSymbol.isSome) { const allTokens = this.#chainProperties.tokenSymbol.unwrap();
if (allTokens.length) { return allTokens.map(valueToString); } }
return [formatBalance.getDefaults().unit]; }
public get firstCallIndex (): Uint8Array { return this.#firstCallIndex || DEFAULT_FIRST_CALL_IDX; }
public isLookupType (value: string): value is LookupString { return /Lookup\d+$/.test(value); }
public createLookupType (lookupId: SiLookupTypeId | number): LookupString { return `Lookup${typeof lookupId === 'number' ? lookupId : lookupId.toNumber()}`; }
public get knownTypes (): RegisteredTypes { return this.#knownTypes; }
public get lookup (): PortableRegistry { return assertReturn(this.#lookup, 'PortableRegistry has not been set on this registry'); }
public get metadata (): MetadataLatest { return assertReturn(this.#metadata, 'Metadata has not been set on this registry'); }
public get unknownTypes (): string[] { return [...this.#unknownTypes.keys()]; }
public get signedExtensions (): string[] { return this.#signedExtensions; }
public clearCache (): void { this.#classes = new Map(); }
public createClass <T extends Codec = Codec, K extends string = string> (type: K): CodecClass<DetectCodec<T, K>> { return createClassUnsafe<DetectCodec<T, K>>(this, type); }
public createClassUnsafe <T extends Codec = Codec, K extends string = string> (type: K): CodecClass<T> { return createClassUnsafe(this, type); }
public createType <T extends Codec = Codec, K extends string = string> (type: K, ...params: unknown[]): DetectCodec<T, K> { return createTypeUnsafe(this, type, params); }
public createTypeUnsafe <T extends Codec = Codec, K extends string = string> (type: K, params: unknown[], options?: CreateOptions): T { return createTypeUnsafe(this, type, params, options); }
public findMetaCall (callIndex: Uint8Array): CallFunction { const [section, method] = [callIndex[0], callIndex[1]];
return assertReturn( this.#metadataCalls[`${section}`] && this.#metadataCalls[`${section}`][`${method}`], () => `findMetaCall: Unable to find Call with index [${section}, ${method}]/[${callIndex.toString()}]` ); }
public findMetaError (errorIndex: Uint8Array | DispatchErrorModule | DispatchErrorModuleU8 | DispatchErrorModuleU8a): RegistryError { const [section, method] = isU8a(errorIndex) ? [errorIndex[0], errorIndex[1]] : [ errorIndex.index.toNumber(), isU8a(errorIndex.error) ? errorIndex.error[0] : errorIndex.error.toNumber() ];
return assertReturn( this.#metadataErrors[`${section}`] && this.#metadataErrors[`${section}`][`${method}`], () => `findMetaError: Unable to find Error with index [${section}, ${method}]/[${errorIndex.toString()}]` ); }
public findMetaEvent (eventIndex: Uint8Array): CodecClass<GenericEventData> { const [section, method] = [eventIndex[0], eventIndex[1]];
return assertReturn( this.#metadataEvents[`${section}`] && this.#metadataEvents[`${section}`][`${method}`], () => `findMetaEvent: Unable to find Event with index [${section}, ${method}]/[${eventIndex.toString()}]` ); }
public get <T extends Codec = Codec, K extends string = string> (name: K, withUnknown?: boolean, knownTypeDef?: TypeDef): CodecClass<T> | undefined { return this.getUnsafe(name, withUnknown, knownTypeDef) as CodecClass<T>; }
public getUnsafe <T extends Codec = Codec, K extends string = string> (name: K, withUnknown?: boolean, knownTypeDef?: TypeDef): CodecClass<T> | undefined { let Type = this.#classes.get(name) || this.#knownDefaults[name];
if (!Type) { const definition = this.#definitions.get(name); let BaseType: CodecClass | undefined;
if (definition) { BaseType = createClassUnsafe(this, definition); } else if (knownTypeDef) { BaseType = constructTypeClass(this, knownTypeDef); } else if (withUnknown) { l.warn(`Unable to resolve type ${name}, it will fail on construction`);
this.#unknownTypes.set(name, true);
BaseType = DoNotConstruct.with(name); }
if (BaseType) { Type = class extends BaseType {};
this.#classes.set(name, Type);
if (knownTypeDef && isNumber(knownTypeDef.lookupIndex)) { this.#classes.set(this.createLookupType(knownTypeDef.lookupIndex), Type); } } }
return Type as unknown as CodecClass<T>; }
public getChainProperties (): ChainProperties | undefined { return this.#chainProperties; }
public getClassName (Type: CodecClass): string | undefined { const names: string[] = [];
for (const [name, Clazz] of Object.entries(this.#knownDefaults)) { if (Type === Clazz) { names.push(name); } }
for (const [name, Clazz] of this.#classes.entries()) { if (Type === Clazz) { names.push(name); } }
names.sort().reverse();
return names.length ? names[0] : undefined; }
public getDefinition (typeName: string): string | undefined { return this.#definitions.get(typeName); }
public getModuleInstances (specName: AnyString, moduleName: string): string[] | undefined { return this.#knownTypes?.typesBundle?.spec?.[specName.toString()]?.instances?.[moduleName] || this.#moduleMap[moduleName]; }
public getOrThrow <T extends Codec = Codec, K extends string = string, R = DetectCodec<T, K>> (name: K): CodecClass<R> { const Clazz = this.get<T, K>(name);
if (!Clazz) { throw new Error(`type ${name} not found`); }
return Clazz as unknown as CodecClass<R>; }
public getOrUnknown <T extends Codec = Codec, K extends string = string, R = DetectCodec<T, K>> (name: K): CodecClass<R> { return this.get<T, K>(name, true) as unknown as CodecClass<R>; }
public getSignedExtensionExtra (): Record<string, string> { return expandExtensionTypes(this.#signedExtensions, 'payload', this.#userExtensions); }
public getSignedExtensionTypes (): Record<string, string> { return expandExtensionTypes(this.#signedExtensions, 'extrinsic', this.#userExtensions); }
public hasClass (name: string): boolean { return this.#classes.has(name) || !!this.#knownDefaults[name]; }
public hasDef (name: string): boolean { return this.#definitions.has(name); }
public hasType (name: string): boolean { return !this.#unknownTypes.get(name) && (this.hasClass(name) || this.hasDef(name)); }
public hash (data: Uint8Array): IU8a { return this.createType('CodecHash', this.#hasher(data)); }
public register (type: CodecClass | RegistryTypes): void;
public register (name: string, type: CodecClass): void;
public register (arg1: string | CodecClass | RegistryTypes, arg2?: CodecClass): void { if (isFunction(arg1)) { this.#classes.set(arg1.name, arg1); } else if (isString(arg1)) { if (!isFunction(arg2)) { throw new Error(`Expected class definition passed to '${arg1}' registration`); } else if (arg1 === arg2.toString()) { throw new Error(`Unable to register circular ${arg1} === ${arg1}`); }
this.#classes.set(arg1, arg2); } else { this.#registerObject(arg1); } }
#registerObject = (obj: RegistryTypes): void => { const entries = Object.entries(obj);
for (let e = 0; e < entries.length; e++) { const [name, type] = entries[e];
if (isFunction(type)) { this.#classes.set(name, type); } else { const def = isString(type) ? type : stringify(type);
if (name === def) { throw new Error(`Unable to register circular ${name} === ${def}`); }
if (this.#classes.has(name)) { this.#classes.delete(name); }
this.#definitions.set(name, def); } } };
public setChainProperties (properties?: ChainProperties): void { if (properties) { this.#chainProperties = properties; } }
setHasher (hasher?: CodecHasher | null): void { this.#hasher = hasher || blake2AsU8a; }
setKnownTypes (knownTypes: RegisteredTypes): void { this.#knownTypes = knownTypes; }
setLookup (lookup: PortableRegistry): void { this.#lookup = lookup;
lookup.register(); }
#registerLookup = (lookup: PortableRegistry): void => { this.setLookup(lookup);
let Weight: string | null = null;
if (this.hasType('SpWeightsWeightV2Weight')) { const weightv2 = this.createType<WeightV2>('SpWeightsWeightV2Weight');
Weight = weightv2.refTime && weightv2.proofSize ? 'SpWeightsWeightV2Weight' : 'WeightV1'; } else if (!isBn(this.createType<WeightV1>('Weight'))) { Weight = 'WeightV1'; }
if (Weight) { this.register({ Weight }); } };
public setMetadata (metadata: Metadata, signedExtensions?: string[], userExtensions?: ExtDef): void { this.#metadata = metadata.asLatest; this.#metadataVersion = metadata.version; this.#firstCallIndex = null;
this.#registerLookup(this.#metadata.lookup);
injectExtrinsics(this, this.#metadata, this.#metadataVersion, this.#metadataCalls, this.#moduleMap); injectErrors(this, this.#metadata, this.#metadataVersion, this.#metadataErrors); injectEvents(this, this.#metadata, this.#metadataVersion, this.#metadataEvents);
const [defSection] = Object .keys(this.#metadataCalls) .sort(sortDecimalStrings);
if (defSection) { const [defMethod] = Object .keys(this.#metadataCalls[defSection]) .sort(sortDecimalStrings);
if (defMethod) { this.#firstCallIndex = new Uint8Array([parseInt(defSection, 10), parseInt(defMethod, 10)]); } }
this.setSignedExtensions( signedExtensions || ( this.#metadata.extrinsic.version.gt(BN_ZERO) ? this.#metadata.extrinsic.signedExtensions.map(({ identifier }) => identifier.toString()) : fallbackExtensions ), userExtensions );
this.setChainProperties( extractProperties(this, metadata) ); }
setSignedExtensions (signedExtensions: string[] = fallbackExtensions, userExtensions?: ExtDef): void { this.#signedExtensions = signedExtensions; this.#userExtensions = userExtensions;
const unknown = findUnknownExtensions(this.#signedExtensions, this.#userExtensions);
if (unknown.length) { l.warn(`Unknown signed extensions ${unknown.join(', ')} found, treating them as no-effect`); } }}