Skip to content

MetadataReactor is the candid-native metadata reactor. It extends CandidReactor and adds runtime metadata for:

  • argument forms (getInputMeta, getAllInputMeta)
  • result rendering (getOutputMeta, getAllOutputMeta)
  • hydrated initial form values from candid args hex (buildForMethod)
  • dynamic variable mapping candidates (buildMethodVariableCandidates)

Unlike MetadataDisplayReactor, this class keeps argument handling candid-native.

import { MetadataReactor } from "@ic-reactor/candid"

Use MetadataReactor when you want:

  • dynamic forms for unknown canister methods
  • zod schemas + defaults generated from Candid at runtime
  • metadata-driven output rendering
  • candid-first semantics for arguments and method registration

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 nodes
  • defaults: default form state
  • schema: zod tuple for method args
  • argCount, isEmpty, functionType

getAllInputMeta() 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:

  • metadata generation
  • hydration decoding for initial form values

Hydration status can be:

  • empty
  • hydrated
  • skipped
  • error
const 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: "{{",
})

Build Metadata for a Standalone Value Type

Section titled “Build Metadata for a Standalone Value Type”

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")
  1. Create and initialize MetadataReactor.
  2. Read method metadata with getInputMeta / getOutputMeta.
  3. Build UI state from defaults + schema.
  4. Optionally hydrate from candidArgsHex for replay/edit flows.
  5. Call the method and resolve output through MethodMeta.resolve.

Practical Pattern: Dynamic Form + Result Viewer

Section titled “Practical Pattern: Dynamic Form + Result Viewer”
const methodName = "icrc1_transfer"
const inputMeta = reactor.getInputMeta(methodName)
if (!inputMeta) throw new Error("Method metadata missing")
// 1) initialize form
const formDefaults = inputMeta.defaults
// 2) validate before submit
inputMeta.schema.parse(formDefaults)
// 3) call
const raw = await reactor.callMethod({
functionName: methodName,
args: formDefaults,
})
// 4) resolve for rendering
const outputMeta = reactor.getOutputMeta(methodName)
const rendered = outputMeta?.resolve(raw)
  • Ensure await reactor.initialize() completed.
  • Confirm method exists in reactor.getMethodNames().
  • If method is dynamic, call registerMethod(...) first.
  • Confirm candidArgsHex matches the method argument types exactly.
  • Ensure hex is plain hex (no 0x prefix).
  • You passed skipHydrationIfContains and input matched that token.
  • Remove token or skip-option if full decode is expected.