Files
pezkuwi-api/packages/metadata-builders/upstream-src/lookups.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

457 lines
11 KiB
TypeScript

import type {
StringRecord,
UnifiedMetadata,
V14Lookup,
} from "@pezkuwi/bizinikiwi-bindings"
export type SignedPrimitive = "i8" | "i16" | "i32" | "i64" | "i128" | "i256"
export type UnsignedPrimitive = "u8" | "u16" | "u32" | "u64" | "u128" | "u256"
export type MetadataPrimitives =
| "bool"
| "char"
| "str"
| SignedPrimitive
| UnsignedPrimitive
export type PrimitiveVar = {
type: "primitive"
value: MetadataPrimitives
}
export type VoidVar = { type: "void" }
export type CompactVar = {
type: "compact"
isBig: boolean
size: UnsignedPrimitive
}
export type BitSequenceVar = {
type: "bitSequence"
isLSB: boolean
}
export type AccountId32 = { type: "AccountId32" }
export type AccountId20 = { type: "AccountId20" }
export type TerminalVar =
| PrimitiveVar
| VoidVar
| CompactVar
| BitSequenceVar
| AccountId32
| AccountId20
/* Array-like vars:
* - TupleVar: Mixed types, fixed length
* - Sequence: One type, arbitrary length
* - Array: One type, fixed length
*/
export type TupleVar = {
type: "tuple"
value: LookupEntry[]
innerDocs: Array<string[]>
}
export type StructVar = {
type: "struct"
value: StringRecord<LookupEntry>
innerDocs: StringRecord<string[]>
}
export type EnumVar = {
type: "enum"
value: StringRecord<
(
| { type: "lookupEntry"; value: LookupEntry }
| VoidVar
| TupleVar
| StructVar
| ArrayVar
) & { idx: number }
>
innerDocs: StringRecord<string[]>
byteLength?: number
}
export type OptionVar = {
type: "option"
value: LookupEntry
}
export type ResultVar = {
type: "result"
value: { ok: LookupEntry; ko: LookupEntry }
}
export type SequenceVar = {
type: "sequence"
value: LookupEntry
}
export type ArrayVar = {
type: "array"
value: LookupEntry
len: number
}
export type ComposedVar =
| TupleVar
| StructVar
| SequenceVar
| ArrayVar
| OptionVar
| ResultVar
| EnumVar
export type Var = TerminalVar | ComposedVar
export type LookupEntry = {
id: number
} & Var
const isBytes = (value: LookupEntry, nBytes: number) =>
value.type === "array" &&
value.len === nBytes &&
value.value.type === "primitive" &&
value.value.value === "u8"
const _void: VoidVar = { type: "void" }
export interface MetadataLookup {
(id: number): LookupEntry
metadata: UnifiedMetadata
call: number | null
}
const _denormalizeLookup = (
lookupData: V14Lookup,
customMap: (value: V14Lookup[number]) => Var | null = () => null,
): ((id: number) => LookupEntry) => {
const lookups = new Map<number, LookupEntry>()
const from = new Set<number>()
const withCache = (
fn: (id: number) => Var,
): ((id: number) => LookupEntry) => {
return (id) => {
let entry = lookups.get(id)
if (entry) return entry
if (from.has(id)) {
const entry = {
id,
} as LookupEntry
lookups.set(id, entry)
return entry
}
from.add(id)
const value = fn(id)
entry = lookups.get(id)
if (entry) {
Object.assign(entry, value)
} else {
entry = {
id,
...value,
}
lookups.set(id, entry!)
}
from.delete(id)
return entry
}
}
let isAccountId32SearchOn = true
let isAccountId20SearchOn = true
const getLookupEntryDef = withCache((id): Var => {
const custom = customMap(lookupData[id])
if (custom) return custom
const { def, path, params } = lookupData[id]
if (def.tag === "composite") {
if (def.value.length === 0) return _void
// used to be a "pointer"
if (def.value.length === 1) {
const inner = getLookupEntryDef(def.value[0].type as number)
if (
isAccountId32SearchOn &&
path.at(-1) === "AccountId32" &&
isBytes(inner, 32)
) {
isAccountId32SearchOn = false
return { type: "AccountId32" }
}
if (
isAccountId20SearchOn &&
path.at(-1) === "AccountId20" &&
isBytes(inner, 20)
) {
isAccountId20SearchOn = false
return { type: "AccountId20" }
}
return inner
}
return getComplexVar(def.value)
}
if (def.tag === "variant") {
if (
path.length === 1 &&
path[0] === "Option" &&
params.length === 1 &&
params[0].name === "T"
) {
const value = getLookupEntryDef(params[0].type!)
return value.type === "void"
? // Option<void> would return a Codec<undefined> which makes no sense
// Therefore, we better treat it as a bool
{ type: "primitive", value: "bool" }
: {
type: "option",
value,
}
}
if (
path.length === 1 &&
path[0] === "Result" &&
params.length === 2 &&
params[0].name === "T" &&
params[1].name === "E"
) {
return {
type: "result",
value: {
ok: getLookupEntryDef(params[0].type as number),
ko: getLookupEntryDef(params[1].type as number),
},
}
}
if (def.value.length === 0) return _void
const enumValue: StringRecord<EnumVar["value"][keyof EnumVar["value"]]> =
{}
const enumDocs: StringRecord<string[]> = {}
def.value.forEach((x) => {
const key = x.name
enumDocs[key] = x.docs
if (x.fields.length === 0) {
enumValue[key] = { ..._void, idx: x.index }
return
}
if (x.fields.length === 1 && !x.fields[0].name) {
enumValue[key] = {
type: "lookupEntry",
value: getLookupEntryDef(x.fields[0].type),
idx: x.index,
}
return
}
enumValue[key] = { ...getComplexVar(x.fields), idx: x.index }
})
return {
type: "enum",
value: enumValue,
innerDocs: enumDocs,
}
}
if (def.tag === "sequence")
return {
type: "sequence",
value: getLookupEntryDef(def.value as number),
}
if (def.tag === "array") {
const { len } = def.value
const value = getLookupEntryDef(def.value.type)
return !len || value.type === "void"
? _void
: len > 1
? {
type: "array",
value,
len: def.value.len,
}
: value
}
if (def.tag === "tuple") {
if (def.value.length === 0) return _void
return def.value.length > 1
? getArrayOrTuple(
def.value.map((x) => getLookupEntryDef(x as number)),
def.value.map((x) => lookupData[x].docs),
)
: getLookupEntryDef(def.value[0] as number) // use to be a "pointer"
}
if (def.tag === "primitive") {
return {
type: "primitive",
value: def.value.tag,
}
}
if (def.tag === "compact") {
const translated = getLookupEntryDef(def.value) as PrimitiveVar | VoidVar
if (translated.type === "void") return _void
const isBig = Number(translated.value.slice(1)) > 32
return {
type: "compact",
isBig,
size: translated.value as UnsignedPrimitive,
}
}
// bitSequence
return {
type: def.tag,
isLSB: (lookupData[def.value.bitOrderType].path.at(-1) ?? "LSB")
.toUpperCase()
.startsWith("LSB"),
}
})
const getComplexVar = (
input: Array<{ type: number; name?: string; docs: string[] }>,
): TupleVar | StructVar | ArrayVar | VoidVar => {
let allKey = true
const values: Record<string | number, LookupEntry> = {}
const innerDocs: Record<string | number, string[]> = {}
input.forEach((x, idx) => {
allKey = allKey && !!x.name
const key = x.name || idx
const value = getLookupEntryDef(x.type as number)
if (value.type !== "void") {
values[key] = value
innerDocs[key] = x.docs
}
})
return allKey
? {
type: "struct",
value: values as StringRecord<LookupEntry>,
innerDocs: innerDocs as StringRecord<string[]>,
}
: getArrayOrTuple(Object.values(values), Object.values(innerDocs))
}
const getArrayOrTuple = (
values: Array<LookupEntry>,
innerDocs: Array<string[]>,
): TupleVar | ArrayVar | VoidVar => {
if (
values.every((v) => v.id === values[0].id) &&
innerDocs.every((doc) => !doc.length)
) {
const [value] = values
return value.type === "void"
? _void
: {
type: "array",
value: values[0],
len: values.length,
}
}
return {
type: "tuple",
value: values,
innerDocs: innerDocs,
}
}
return getLookupEntryDef
}
export const denormalizeLookup = (lookupData: V14Lookup) =>
_denormalizeLookup(lookupData)
export const getLookupFn = (metadata: UnifiedMetadata): MetadataLookup => {
const getLookupEntryDef = _denormalizeLookup(metadata.lookup, ({ def }) => {
if (def.tag === "composite") {
const moduleErrorLength = getModuleErrorLength(def)
if (moduleErrorLength) {
return {
type: "enum",
innerDocs: {},
value: Object.fromEntries(
metadata.pallets.map((p) => [
p.name,
p.errors == null
? { ..._void, idx: p.index }
: {
type: "lookupEntry" as const,
value: getLookupEntryDef(p.errors.type),
idx: p.index,
},
]),
) as StringRecord<
(
| VoidVar
| {
type: "lookupEntry"
value: LookupEntry
}
) & { idx: number }
>,
byteLength: moduleErrorLength,
}
}
}
return null
})
function getModuleErrorLength(def: {
tag: "composite"
value: {
name: string | undefined
type: number
typeName: string | undefined
docs: string[]
}[]
}) {
const preChecks =
def.value.length === 2 &&
def.value[0].name === "index" &&
def.value[1].name === "error"
if (!preChecks) return null
const index = getLookupEntryDef(def.value[0].type)
const error = getLookupEntryDef(def.value[1].type)
return index.type === "primitive" &&
index.value === "u8" &&
error.type === "array" &&
error.value.type === "primitive" &&
error.value.value === "u8"
? 1 + error.len
: null
}
const getCall = () => {
if ("call" in metadata.extrinsic) {
return metadata.extrinsic.call
}
const extrinsic = metadata.lookup[metadata.extrinsic.type]
const call = extrinsic?.params.find((p) => p.name === "Call")
return call?.type ?? null
}
return Object.assign(getLookupEntryDef, { metadata, call: getCall() })
}