Skip to content
IC Reactor

@ic-reactor/candid

The @ic-reactor/candid package enables dynamic canister interaction without compile-time IDL. Perfect for building tools, explorers, or any application that needs to interact with canisters discovered at runtime.

Most IC applications know which canisters they’ll interact with at build time. But some applications need to:

  • Build canister explorers — Discover and interact with any canister
  • Create wallet UIs — Support arbitrary token standards dynamically
  • Debug tools — Inspect methods on unknown canisters
  • Dynamic integrations — Add support for new canisters without redeploying

Dynamic IDL Fetching

Fetch Candid definitions from canister metadata or compile via didjs

CandidReactor

Extends Reactor with dynamic method registration and calling

CandidDisplayReactor

Dynamic Reactor with automatic display type transformations (bigint → string)

MetadataDisplayReactor

Display Reactor with visitor-based metadata for form building and result display

CandidFormVisitor

Generate form metadata from Candid IDL with schema, component, and render hints

Local Parsing

Bundled WASM parser for fast, offline Candid compilation, loaded on first use

Terminal window
npm install @ic-reactor/candid @ic-reactor/core @icp-sdk/core @tanstack/query-core

@ic-reactor/parser is a dependency, so local parsing works out of the box. The adapter imports it on the first parse and bundlers emit it as its own chunk; if it cannot be loaded at runtime, the adapter falls back to the didjs canister.

Unlike @ic-reactor/core and @ic-reactor/react (Node.js 18+), this package requires Node.js 20.19.0 or later — the floor of its @noble/hashes dependency, declared in the package’s engines field.

The easiest way to interact with canisters dynamically:

import { CandidReactor } 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()
// Create a reactor for any canister
const reactor = new CandidReactor({
name: "ledger",
canisterId: "ryjl3-tyaaa-aaaaa-aaaba-cai", // ICP Ledger
clientManager,
})
// Fetch IDL from network
await reactor.initialize()
// Now all standard Reactor methods work!
const name = await reactor.callMethod({ functionName: "icrc1_name" })
console.log(name) // "Internet Computer"

For quick one-off calls, use the convenience methods:

// Query with inline Candid signature
const balance = await reactor.queryDynamic({
functionName: "icrc1_balance_of",
candid: "(record { owner : principal }) -> (nat) query",
args: [{ owner: Principal.fromText("...") }],
})
// With TanStack Query caching
const cachedBalance = await reactor.fetchQueryDynamic({
functionName: "icrc1_balance_of",
candid: "(record { owner : principal }) -> (nat) query",
args: [{ owner }],
})

For more control over Candid fetching and parsing:

import { CandidAdapter } from "@ic-reactor/candid"
const adapter = new CandidAdapter({ clientManager })
// Fetch Candid source
const candidSource = await adapter.fetchCandidSource(
"ryjl3-tyaaa-aaaaa-aaaba-cai"
)
// Parse to IDL factory
const { idlFactory } = await adapter.parseCandidSource(candidSource)
// Or do both in one call
const definition = await adapter.getCandidDefinition(
"ryjl3-tyaaa-aaaaa-aaaba-cai"
)
Class Purpose
CandidReactor Dynamic Reactor with raw Candid types
CandidDisplayReactor Dynamic Reactor with display transformations (UI-friendly)
MetadataDisplayReactor Display Reactor with form & result metadata generation
CandidFormVisitor Generate form metadata from Candid IDL types
CandidAdapter Low-level Candid fetching and parsing utilities

Use CandidReactor when you want raw Candid types (bigint, Principal) and full Reactor experience.

Use CandidDisplayReactor when building UIs — it automatically converts bigint to string, Principal to string, and supports form validation.

Use MetadataDisplayReactor when building dynamic forms or result displays — it generates structured field metadata from Candid types.

Use CandidAdapter when you need fine-grained control over Candid fetching, or you’re building your own abstractions.

The adapter tries multiple methods to get a canister’s Candid source:

  1. Canister metadata (preferred) — reads the canister’s candid metadata path
  2. __get_candid_interface_tmp_hack (fallback) — query method on the canister
// Automatic — local parser when it can be loaded, otherwise the didjs canister.
// Returns a CandidDefinition ({ idlFactory, init? }).
const parsed = await adapter.parseCandidSource(candidSource)
// Local only — faster, offline, requires @ic-reactor/parser.
// Returns the compiled JavaScript source as a string.
await adapter.loadParser()
const jsSource = adapter.compileLocal(candidSource)