@polkadot/typegen
Version:
Type generation scripts
220 lines (219 loc) • 10 kB
JavaScript
import Handlebars from 'handlebars';
import * as defaultDefs from '@polkadot/types/interfaces/definitions';
import lookupDefinitions from '@polkadot/types-augment/lookup/definitions';
import { objectSpread, stringCamelCase } from '@polkadot/util';
import { blake2AsHex } from '@polkadot/util-crypto';
import { createImports, formatType, getSimilarTypes, initMeta, readTemplate, setImports, writeFile } from '../util/index.js';
import { getDeprecationNotice } from './types.js';
const generateCallsTypesTemplate = Handlebars.compile(readTemplate('calls'));
const aliases = {
AssetHubKusamaRuntimeRuntimeCall: 'RuntimeCall',
AssetHubPolkadotRuntimeRuntimeCall: 'RuntimeCall',
KitchensinkRuntimeRuntimeCall: 'RuntimeCall',
OpaqueValue: 'Bytes',
PolkadotParachainPrimitivesPrimitivesId: 'ParaId',
PolkadotParachainPrimitivesPrimitivesValidationCodeHash: 'ValidationCodeHash',
PolkadotPrimitivesV7SlashingOpaqueKeyOwnershipProof: 'OpaqueKeyOwnershipProof',
PolkadotPrimitivesV8SlashingOpaqueKeyOwnershipProof: 'OpaqueKeyOwnershipProof',
PolkadotRuntimeRuntimeCall: 'RuntimeCall',
PrimitiveTypesH160: 'H160',
PrimitiveTypesH256: 'H256',
PrimitiveTypesU256: 'U256',
SpConsensusBabeOpaqueKeyOwnershipProof: 'OpaqueKeyOwnershipProof',
SpConsensusSlotsSlot: 'Slot',
SpConsensusSlotsSlotDuration: 'SlotDuration',
SpCoreCryptoAccountId32: 'AccountId32',
SpCoreOpaqueMetadata: 'OpaqueMetadata',
SpRuntimeOpaqueValue: 'Bytes',
SpRuntimeUncheckedExtrinsic: 'Extrinsic',
StagingKusamaRuntimeRuntimeCall: 'RuntimeCall'
};
const getTypesViaAlias = (registry, id) => {
const typeName = registry.lookup.getName(id) || registry.lookup.getTypeDef(id).type;
if (aliases[typeName]) {
return aliases[typeName];
}
return typeName;
};
/** @internal */
function getMethods(registry, methods) {
const result = {};
methods.forEach((m) => {
const { deprecationInfo, docs, inputs, name, output } = m;
let description = docs.map((d) => d.toString()).join();
if (!deprecationInfo.isNotDeprecated) {
const deprecationNotice = getDeprecationNotice(deprecationInfo, stringCamelCase(name));
const notice = description.length ? `\n * ${deprecationNotice}` : ` * ${deprecationNotice}`;
description += notice;
}
result[name.toString()] = {
description,
params: inputs.map(({ name, type }) => {
return { name: name.toString(), type: getTypesViaAlias(registry, type) };
}),
type: getTypesViaAlias(registry, output)
};
});
return result;
}
/** @internal */
function getRuntimeDefViaMetadata(registry) {
const result = {};
const { apis } = registry.metadata;
for (let i = 0, count = apis.length; i < count; i++) {
const { methods, name } = apis[i];
result[name.toString()] = [{
methods: getMethods(registry, methods),
// We set the version to 0 here since it will not be relevant when we are grabbing the runtime apis
// from the Metadata.
version: 0
}];
}
return Object.entries(result);
}
/** @internal */
function getDefs(apis, defs, registry) {
const named = {};
const all = Object.values(defs);
const isApiInMetadata = registry.metadata.apis.length > 0;
if (isApiInMetadata) {
const sections = getRuntimeDefViaMetadata(registry);
for (let j = 0, jcount = sections.length; j < jcount; j++) {
const [_section, secs] = sections[j];
const sec = secs[0];
const sectionHash = blake2AsHex(_section, 64);
const section = stringCamelCase(_section);
const methods = Object.entries(sec.methods);
if (!named[section]) {
named[section] = {};
}
for (let m = 0, mcount = methods.length; m < mcount; m++) {
const [_method, def] = methods[m];
const method = stringCamelCase(_method);
named[section][method] = objectSpread({ method, name: `${_section}_${_method}`, section, sectionHash }, def);
}
}
}
else {
for (let j = 0, jcount = all.length; j < jcount; j++) {
const set = all[j].runtime;
if (set) {
const sections = Object.entries(set);
for (let i = 0, scount = sections.length; i < scount; i++) {
const [_section, sec] = sections[i];
const sectionHash = blake2AsHex(_section, 64);
const api = apis?.find(([h]) => h === sectionHash);
if (api) {
const ver = sec.find(({ version }) => version === api[1]);
if (ver) {
const methods = Object.entries(ver.methods);
const mcount = methods.length;
if (mcount) {
const section = stringCamelCase(_section);
if (!named[section]) {
named[section] = {};
}
for (let m = 0; m < mcount; m++) {
const [_method, def] = methods[m];
const method = stringCamelCase(_method);
named[section][method] = objectSpread({ method, name: `${_section}_${method}`, section, sectionHash, version: ver.version }, def);
}
}
}
else {
console.warn(`Unable to find matching version for runtime ${_section}, expected ${api[1]}`);
}
}
}
}
}
}
return named;
}
/** @internal */
export function generateCallTypes(registry, meta, dest, extraTypes, isStrict, customLookupDefinitions) {
writeFile(dest, () => {
const allTypes = {
'@polkadot/types-augment': {
lookup: {
...lookupDefinitions,
...customLookupDefinitions
}
},
'@polkadot/types/interfaces': defaultDefs,
...extraTypes
};
const imports = createImports(allTypes);
// find the system.Version in metadata
let apis = null;
const sysp = meta.asLatest.pallets.find(({ name }) => name.eq('System'));
if (sysp) {
const verc = sysp.constants.find(({ name }) => name.eq('Version'));
if (verc) {
apis = registry.createType('RuntimeVersion', verc.value).apis.map(([k, v]) => [k.toHex(), v.toNumber()]);
}
else {
console.error('Unable to find System.Version pallet, skipping API extraction');
}
}
else {
console.error('Unable to find System pallet, skipping API extraction');
}
const allDefs = Object.entries(allTypes).reduce((defs, [path, obj]) => {
return Object.entries(obj).reduce((defs, [key, value]) => ({ ...defs, [`${path}/${key}`]: value }), defs);
}, {});
const definitions = getDefs(apis, imports.definitions, registry);
const callKeys = Object.keys(definitions);
const modules = callKeys.map((section) => {
const calls = definitions[section];
const allMethods = Object.keys(calls).sort().map((methodName) => {
const def = calls[methodName];
setImports(allDefs, imports, [def.type]);
const args = def.params.map((param) => {
const similarTypes = getSimilarTypes(registry, imports.definitions, param.type, imports);
setImports(allDefs, imports, [param.type, ...similarTypes]);
return `${param.name}: ${similarTypes.join(' | ')}`;
});
return {
args: args.join(', '),
docs: [def.description],
name: methodName,
sectionHash: def.sectionHash,
sectionName: def.section,
sectionVersion: def.version,
type: formatType(registry, allDefs, def.type, imports)
};
}).sort((a, b) => a.name.localeCompare(b.name));
return {
items: allMethods,
name: section || 'unknown',
sectionHash: allMethods.length && allMethods[0].sectionHash,
sectionName: allMethods.length && allMethods[0].sectionName,
sectionVersion: allMethods.length && allMethods[0].sectionVersion
};
}).filter(({ items }) => items.length).sort((a, b) => a.name.localeCompare(b.name));
if (modules.length) {
imports.typesTypes['Observable'] = true;
}
return generateCallsTypesTemplate({
headerType: 'chain',
imports,
isStrict,
modules,
types: [
...Object.keys(imports.localTypes).sort().map((packagePath) => ({
file: packagePath.replace('@polkadot/types-augment', '@polkadot/types'),
types: Object.keys(imports.localTypes[packagePath])
})),
{
file: '@polkadot/api-base/types',
types: ['ApiTypes', 'AugmentedCall', 'DecoratedCallBase']
}
]
});
});
}
export function generateDefaultRuntime(dest, data, extraTypes = {}, isStrict = false, customLookupDefinitions) {
const { metadata, registry } = initMeta(data, extraTypes);
generateCallTypes(registry, metadata, dest, extraTypes, isStrict, customLookupDefinitions);
}