polkadot-js/api

View on GitHub
packages/typegen/src/generate/runtime.ts

Summary

Maintainability
D
2 days
Test Coverage
// Copyright 2017-2024 @polkadot/typegen authors & contributors
// SPDX-License-Identifier: Apache-2.0

import type { Metadata } from '@polkadot/types/metadata/Metadata';
import type { DefinitionCallNamed, Definitions, Registry } from '@polkadot/types/types';
import type { HexString } from '@polkadot/util/types';
import type { ExtraTypes } from './types.js';

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';

type Apis = [HexString, number][];

const generateCallsTypesTemplate = Handlebars.compile(readTemplate('calls'));

/** @internal */
function getDefs (apis: Apis | null, defs: Record<string, Definitions>): Record<string, Record<string, DefinitionCallNamed>> {
  const named: Record<string, Record<string, DefinitionCallNamed>> = {};
  const all = Object.values(defs);

  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: Registry, meta: Metadata, dest: string, extraTypes: ExtraTypes, isStrict: boolean, customLookupDefinitions?: Definitions): void {
  writeFile(dest, (): string => {
    const allTypes: ExtraTypes = {
      '@polkadot/types-augment': {
        lookup: {
          ...lookupDefinitions,
          ...customLookupDefinitions
        }
      },
      '@polkadot/types/interfaces': defaultDefs,
      ...extraTypes
    };
    const imports = createImports(allTypes);

    // find the system.Version in metadata
    let apis: Apis | null = 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]): [HexString, number] => [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 as Record<string, Definitions>);
    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: string; types: string[] } => ({
          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: string, data: HexString, extraTypes: ExtraTypes = {}, isStrict = false, customLookupDefinitions?: Definitions): void {
  const { metadata, registry } = initMeta(data, extraTypes);

  generateCallTypes(
    registry,
    metadata,
    dest,
    extraTypes,
    isStrict,
    customLookupDefinitions
  );
}