Dynamic IDL Fetching
Fetch Candid definitions from canister metadata or compile via didjs
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:
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)
Local Parsing
Optional WASM parser for fast, offline Candid compilation
npm install @ic-reactor/candid @ic-reactor/coreFor local parsing (recommended for production):
npm install @ic-reactor/parserThe easiest way to interact with canisters dynamically:
import { CandidReactor } from "@ic-reactor/candid"import { ClientManager } from "@ic-reactor/core"
const clientManager = new ClientManager()await clientManager.initialize()
// Create a reactor for any canisterconst reactor = new CandidReactor({ canisterId: "ryjl3-tyaaa-aaaaa-aaaba-cai", // ICP Ledger clientManager,})
// Fetch IDL from networkawait 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 signatureconst balance = await reactor.queryDynamic({ functionName: "icrc1_balance_of", candid: "(record { owner : principal }) -> (nat) query", args: [{ owner: Principal.fromText("...") }],})
// With TanStack Query cachingconst 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 sourceconst candidSource = await adapter.fetchCandidSource( "ryjl3-tyaaa-aaaaa-aaaba-cai")
// Parse to IDL factoryconst { idlFactory } = await adapter.parseCandidSource(candidSource)
// Or do both in one callconst 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) |
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 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:
__get_candid_interface_tmp_hack// Remote parsing (default) — Uses didjs canisterconst definition = await adapter.parseCandidSource(candidSource)
// Local parsing — Faster, offline, requires @ic-reactor/parserawait adapter.loadParser()const definition = adapter.compileLocal(candidSource)