Skip to content

@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)

Local Parsing

Optional WASM parser for fast, offline Candid compilation

Terminal window
npm install @ic-reactor/candid @ic-reactor/core

For local parsing (recommended for production):

Terminal window
npm install @ic-reactor/parser

The 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 canister
const reactor = new CandidReactor({
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"
)
ClassPurpose
CandidReactorDynamic Reactor with raw Candid types
CandidDisplayReactorDynamic Reactor with display transformations (UI-friendly)
CandidAdapterLow-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:

  1. Canister Metadata (preferred) — Queries __get_candid_interface_tmp_hack
  2. didjs Canister (fallback) — Uses the didjs canister to compile Candid to JS
// Remote parsing (default) — Uses didjs canister
const definition = await adapter.parseCandidSource(candidSource)
// Local parsing — Faster, offline, requires @ic-reactor/parser
await adapter.loadParser()
const definition = adapter.compileLocal(candidSource)