Skip to content
IC Reactor

CandidAdapter

CandidAdapter provides low-level utilities for fetching Candid definitions from canisters and compiling them to IDL factories. Use it when you need fine-grained control over Candid handling.

Fetching the Candid source and compiling it to JavaScript are two separate steps, each with its own fallback chain.

Fetching the source:

  1. Canister metadata (preferred) — reads the canister’s candid metadata path
  2. __get_candid_interface_tmp_hack (fallback) — query method on the canister

Compiling to JavaScript:

  1. Local parser (preferred) — the @ic-reactor/parser WASM module, a dependency of this package, loaded on first use
  2. didjs canister (fallback) — remote did_to_js call
import { CandidAdapter } from "@ic-reactor/candid"
const adapter = new CandidAdapter({ clientManager })
// Fetch and parse in one call
const { idlFactory } = await adapter.getCandidDefinition(
"ryjl3-tyaaa-aaaaa-aaaba-cai"
)
import { CandidAdapter } from "@ic-reactor/candid"
new CandidAdapter(config: CandidAdapterParameters)
Parameter Type Required Description
clientManager CandidClientManager Yes Client manager with agent access
didjsCanisterId CanisterId No Custom didjs canister ID

The didjs canister ID is auto-selected based on network:

  • IC Mainnet: a4gq6-oaaaa-aaaab-qaa4q-cai
  • Local: bd3sg-teaaa-aaaaa-qaaba-cai (must be deployed to the local replica)

Fetch and parse Candid for a canister in one call.

const { idlFactory, init } = await adapter.getCandidDefinition(
"ryjl3-tyaaa-aaaaa-aaaba-cai"
)
// Use the idlFactory
const service = idlFactory({ IDL })
console.log(service._fields) // Method definitions
interface CandidDefinition {
idlFactory: IDL.InterfaceFactory
init?: (args: { IDL: typeof IDL }) => IDL.Type<unknown>[]
}

Fetch raw Candid source text from a canister.

const candidSource = await adapter.fetchCandidSource(
"ryjl3-tyaaa-aaaaa-aaaba-cai"
)
console.log(candidSource)
// service { icrc1_name : () -> (text) query; ... }

Parse Candid source text into an IDL factory.

const candidSource = `
service : {
greet : (text) -> (text) query;
}
`
const { idlFactory } = await adapter.parseCandidSource(candidSource)

This method:

  1. Tries the local parser first, loading @ic-reactor/parser on first use
  2. Falls back to remote compilation via the didjs canister

It is the only method that returns a CandidDefinitioncompileLocal and compileRemote return the compiled JavaScript source instead.


Load the local WASM parser for offline compilation.

import { CandidAdapter } from "@ic-reactor/candid"
const adapter = new CandidAdapter({ clientManager })
// Load the parser (only needed once). It imports @ic-reactor/parser itself:
// the package is a dependency of @ic-reactor/candid, so do not import it
// from your app unless you also install it directly.
await adapter.loadParser()
// Now compileLocal works
const jsSource = adapter.compileLocal(candidSource)

Compile Candid using the local WASM parser (synchronous after loading). Returns the compiled JavaScript source string — not a CandidDefinition. Use parseCandidSource() when you need an idlFactory.

// Requires loadParser() first
const jsSource: string = adapter.compileLocal(candidSource)

Throws if the parser hasn’t been loaded.


Compile Candid using the didjs canister. Also returns the compiled JavaScript source, wrapped in a promise.

const jsSource = await adapter.compileRemote(candidSource) // string | undefined

This is the fallback method when local parsing isn’t available.


Validate Candid syntax without compiling.

await adapter.loadParser()
const isValid = adapter.validateCandid(`
service {
greet: (text) -> (text) query;
}
`)
console.log(isValid) // true

Requires the parser to be loaded.


Property Type Description
clientManager CandidClientManager The client manager instance
didjsCanisterId CanisterId The didjs canister ID in use

import { CandidAdapter } from "@ic-reactor/candid"
import { ClientManager } from "@ic-reactor/core"
import { IDL } from "@icp-sdk/core/candid"
import { QueryClient } from "@tanstack/query-core"
const clientManager = new ClientManager({ queryClient: new QueryClient() })
await clientManager.initialize()
const adapter = new CandidAdapter({ clientManager })
// Get full definition
const { idlFactory } = await adapter.getCandidDefinition(
"ryjl3-tyaaa-aaaaa-aaaba-cai"
)
// List methods
const service = idlFactory({ IDL })
for (const [name, func] of service._fields) {
console.log(`${name}: ${func.toString()}`)
}
import { CandidAdapter } from "@ic-reactor/candid"
const adapter = new CandidAdapter({ clientManager })
// Load parser once at startup
await adapter.loadParser()
// All subsequent compilations are local (fast!)
const candidSource = await adapter.fetchCandidSource(canisterId)
const jsSource = adapter.compileLocal(candidSource)
try {
const definition = await adapter.getCandidDefinition(canisterId)
} catch (error) {
if (error.message.includes("has no query method")) {
console.log("Canister doesn't expose Candid metadata")
} else if (error.message.includes("compilation failed")) {
console.log("Invalid Candid syntax")
} else {
throw error
}
}

For dynamic calls, you can parse individual method signatures:

// Parse a method signature wrapped in a service
const methodSignature = "(record { owner : principal }) -> (nat) query"
const serviceSource = `service : { my_method : ${methodSignature}; }`
const { idlFactory } = await adapter.parseCandidSource(serviceSource)
const service = idlFactory({ IDL })
// Extract the method's type information
const funcField = service._fields.find(([name]) => name === "my_method")
const func = funcField[1]
console.log("Arg types:", func.argTypes)
console.log("Return types:", func.retTypes)
console.log("Is query:", func.annotations.includes("query"))