Files
pezkuwi-api/packages/metadata-builders/upstream-src/dynamic-builder.ts
T
SatoshiQaziMuhammed e3c72817fb chore(papi): commit the sources behind the four polkadot-api packages (#15)
* chore(papi): commit the sources behind the four polkadot-api packages

papi-utils, bizinikiwi-bindings, metadata-builders and merkleize-metadata
held only dist/ builds, and no repository held their source. Every
source map in dist/ embeds the TypeScript it was compiled from; that is
now committed as src/ (59 files), and it matches upstream polkadot-api
(MIT) exactly once @polkadot-api/* imports are renamed:

  papi-utils           @polkadot-api/utils 0.2.0
  bizinikiwi-bindings  @polkadot-api/substrate-bindings 0.16.6
  metadata-builders    @polkadot-api/metadata-builders 0.13.0-0.13.2
  merkleize-metadata   @polkadot-api/merkleize-metadata 1.1.21-1.1.23

scripts/check-papi-sources.mjs, run in the lint job, fails unless the
source embedded in dist/ is exactly the committed src/. tsconfig.json
excludes these src/ trees; ESLint already skipped the packages. Each
README now states the upstream package and version.

* chore(papi): keep the upstream sources in upstream-src/

The pezkuwi-dev build treats any package with a src/ directory as its
own: it requires our license header on every file and builds it. These
are upstream files that must stay byte-identical to the source embedded
in dist/, so they live in upstream-src/ instead, which neither the build
nor the root tsconfig picks up.
2026-10-19 22:50:59 +04:00

192 lines
5.5 KiB
TypeScript

import type { Codec, StringRecord } from "@pezkuwi/bizinikiwi-bindings"
import * as scale from "@pezkuwi/bizinikiwi-bindings"
import { mapObject } from "@pezkuwi/papi-utils"
import type { EnumVar, MetadataLookup } from "./lookups"
import { getLookupCodecBuilder } from "./lookup-codec-builder"
const nullCodec = scale.enhanceCodec(
scale._void,
() => undefined,
() => null,
)
export const getDynamicBuilder = (getLookupEntryDef: MetadataLookup) => {
const { metadata } = getLookupEntryDef
let buildDefinition = getLookupCodecBuilder(getLookupEntryDef)
const prefix = metadata.pallets
.find((x) => x.name === "System")
?.constants.find((x) => x.name === "SS58Prefix")
let ss58Prefix: number | undefined
if (prefix) {
try {
const prefixVal = buildDefinition(prefix.type).dec(prefix.value)
if (typeof prefixVal === "number") {
ss58Prefix = prefixVal
buildDefinition = getLookupCodecBuilder(
getLookupEntryDef,
scale.AccountId(prefixVal),
)
}
} catch (_) {}
}
const storagePallets = new Map<string, ReturnType<typeof scale.Storage>>()
const buildStorage = (pallet: string, entry: string) => {
let storagePallet = storagePallets.get(pallet)
if (!storagePallet)
storagePallets.set(pallet, (storagePallet = scale.Storage(pallet)))
const storageEntry = metadata.pallets
.find((x) => x.name === pallet)!
.storage!.items.find((s) => s.name === entry)!
// if val is `void` it decodes to `undefined`, making it impossible
// to differentiate from a non-existant key
// therefore, if the key exists => null, if it doesn't => undefined
const withNullVoid = (codec: Codec<any>) =>
codec === scale._void ? nullCodec : codec
const storageWithFallback = (
len: number,
value: Codec<any>,
...args: Parameters<ReturnType<typeof scale.Storage>>
) => {
const keys = storagePallet!(...args)
const [, ...encodersWithHash] = args
return {
args: scale.Tuple(...encodersWithHash.map(([codec]) => codec)),
keys,
value,
len,
fallback:
storageEntry.modifier === 1
? value.dec(storageEntry.fallback)
: undefined,
}
}
if (storageEntry.type.tag === "plain")
return storageWithFallback(
0,
withNullVoid(buildDefinition(storageEntry.type.value)),
entry,
)
const { key, value, hashers } = storageEntry.type.value
const val = withNullVoid(buildDefinition(value))
const hashes = hashers.map((x) => scale[x.tag])
const hashArgs: scale.EncoderWithHash<unknown>[] = (() => {
if (hashes.length === 1) {
return [[buildDefinition(key), hashes[0]]]
}
const keyDef = getLookupEntryDef(key)
switch (keyDef.type) {
case "array":
return hashes.map((hash) => [buildDefinition(keyDef.value.id), hash])
case "tuple":
return keyDef.value.map((x, idx) => [
buildDefinition(x.id),
hashes[idx],
])
default:
throw new Error("Invalid key type")
}
})()
return storageWithFallback(hashes.length, val, entry, ...hashArgs)
}
const buildEnumEntry = (
entry: EnumVar["value"][keyof EnumVar["value"]],
): Codec<any> => {
switch (entry.type) {
case "void":
return scale._void
case "lookupEntry":
return buildDefinition(entry.value.id)
case "tuple":
return scale.Tuple(
...Object.values(entry.value).map((l) => buildDefinition(l.id)),
)
case "struct":
return scale.Struct(
mapObject(entry.value, (x) => buildDefinition(x.id)) as StringRecord<
Codec<any>
>,
)
case "array":
return scale.Vector(buildDefinition(entry.value.id), entry.len)
}
}
const buildConstant = (pallet: string, constantName: string) => {
const storageEntry = metadata.pallets
.find((x) => x.name === pallet)!
.constants!.find((s) => s.name === constantName)!
return buildDefinition(storageEntry.type as number)
}
const buildVariant =
(type: "errors" | "events" | "calls") =>
(
pallet: string,
name: string,
): {
codec: Codec<any>
location: [number, number]
} => {
const palletEntry = metadata.pallets.find((x) => x.name === pallet)!
const lookup = getLookupEntryDef(palletEntry[type]!.type)
if (lookup.type !== "enum") throw null
const entry = lookup.value[name]
return {
location: [palletEntry.index, entry.idx],
codec: buildEnumEntry(lookup.value[name]),
}
}
const buildViewFn = (pallet: string, entry: string) => {
const fn = metadata.pallets
.find((x) => x.name === pallet)
?.viewFns.find((x) => x.name === entry)
if (!fn) throw null
return {
args: scale.Tuple(...fn.inputs.map((x) => buildDefinition(x.type))),
value: buildDefinition(fn.output),
}
}
const buildRuntimeCall = (api: string, method: string) => {
const entry = metadata.apis
.find((x) => x.name === api)
?.methods.find((x) => x.name === method)
if (!entry) throw null
return {
args: scale.Tuple(...entry.inputs.map((x) => buildDefinition(x.type))),
value: buildDefinition(entry.output),
}
}
return {
buildDefinition,
buildStorage,
buildEvent: buildVariant("events"),
buildError: buildVariant("errors"),
buildViewFn,
buildRuntimeCall,
buildCall: buildVariant("calls"),
buildConstant,
ss58Prefix,
}
}