Form Schema Generation
Generate field trees, defaults, component hints, and zod schemas directly from Candid.
MetadataReactor is the candid-native metadata reactor.
It extends CandidReactor and adds runtime metadata for:
getInputMeta, getAllInputMeta)getOutputMeta, getAllOutputMeta)buildForMethod)buildMethodVariableCandidates)Unlike MetadataDisplayReactor, this class keeps argument handling candid-native.
import { MetadataReactor } from "@ic-reactor/candid"Use MetadataReactor when you want:
Use MetadataDisplayReactor if you specifically want display-transform behavior for arguments in your app flow.
Form Schema Generation
Generate field trees, defaults, component hints, and zod schemas directly from Candid.
Output Resolution
Resolve canister return values with method-aware output metadata.
Hydration from Hex
Convert candid-encoded argument hex into UI-friendly form values.
Runtime Method Expansion
Register new methods at runtime and regenerate metadata immediately.
import { MetadataReactor } from "@ic-reactor/candid"import { ClientManager } from "@ic-reactor/core"import { QueryClient } from "@tanstack/query-core"
const clientManager = new ClientManager({ queryClient: new QueryClient() })await clientManager.initialize()
const reactor = new MetadataReactor({ name: "ledger", canisterId: "ryjl3-tyaaa-aaaaa-aaaba-cai", clientManager,})
await reactor.initialize()
const method = "icrc1_transfer"const inputMeta = reactor.getInputMeta(method)const outputMeta = reactor.getOutputMeta(method)getInputMeta(methodName) returns FormArgumentsMeta for a single method.
args: structured field nodesdefaults: default form stateschema: zod tuple for method argsargCount, isEmpty, functionTypegetAllInputMeta() returns the method-to-metadata map for the full service.
const transferInput = reactor.getInputMeta("icrc1_transfer")console.log(transferInput?.defaults)console.log(transferInput?.schema)getOutputMeta(methodName) returns MethodMeta, including a resolve(...) function.
getAllOutputMeta() returns metadata for all methods.
const transferOutput = reactor.getOutputMeta("icrc1_transfer")const raw = await reactor.callMethod({ functionName: "icrc1_transfer", args: [/* candid args */],})const resolved = transferOutput?.resolve(raw)buildForMethod(methodName, { candidArgsHex }) combines:
Hydration status can be:
emptyhydratedskippederrorconst built = await reactor.buildForMethod("icrc1_transfer", { candidArgsHex: "4449444c00017100",})
console.log(built.meta.defaults)console.log(built.hydration.status)You can skip hydration if payloads include unsupported placeholders:
const built = await reactor.buildForMethod("icrc1_transfer", { candidArgsHex, skipHydrationIfContains: "{{",})Use buildForValueType(...) when you want field/schema metadata for one type expression.
const valueMeta = await reactor.buildForValueType( "record { owner : principal; amount : nat }")console.log(valueMeta.meta.defaults)buildMethodVariableCandidates(methodName) helps build mapping UIs for workflow tools.
const candidates = reactor.buildMethodVariableCandidates("icrc1_transfer")// e.g. "$icrc1_transfer.Ok", "$icrc1_transfer.to.owner"When methods are discovered at runtime, register them and metadata is regenerated automatically.
await reactor.registerMethod({ functionName: "get_metadata", candid: "() -> (record { name : text; version : nat }) query",})
const meta = reactor.getInputMeta("get_metadata")MetadataReactor.getInputMeta / getOutputMeta.defaults + schema.candidArgsHex for replay/edit flows.MethodMeta.resolve.const methodName = "icrc1_transfer"const inputMeta = reactor.getInputMeta(methodName)if (!inputMeta) throw new Error("Method metadata missing")
// 1) initialize formconst formDefaults = inputMeta.defaults
// 2) validate before submitinputMeta.schema.parse(formDefaults)
// 3) callconst raw = await reactor.callMethod({ functionName: methodName, args: formDefaults,})
// 4) resolve for renderingconst outputMeta = reactor.getOutputMeta(methodName)const rendered = outputMeta?.resolve(raw)metadata not found for a methodawait reactor.initialize() completed.reactor.getMethodNames().registerMethod(...) first.errorcandidArgsHex matches the method argument types exactly.0x prefix).skippedskipHydrationIfContains and input matched that token.