Skip to content

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.

The adapter supports multiple methods for obtaining Candid:

  1. Canister Metadata — Query the canister’s __get_candid_interface_tmp_hack method
  2. didjs Canister — Use the didjs canister to compile Candid to JavaScript
  3. Local Parser — Use the optional @ic-reactor/parser for offline compilation
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)
ParameterTypeRequiredDescription
clientManagerCandidClientManagerYesClient manager with agent access
didjsCanisterIdCanisterIdNoCustom didjs canister ID

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

  • IC Mainnet: a4gq6-oaaaa-aaaab-qaa4q-cai
  • Local: a4gq6-oaaaa-aaaab-qaa4q-cai (same, but requires local deployment)

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 local parsing if the parser is loaded
  2. Falls back to remote compilation via didjs canister

Load the local WASM parser for offline compilation.

import { CandidAdapter } from "@ic-reactor/candid"
import "@ic-reactor/parser" // Required peer dependency
const adapter = new CandidAdapter({ clientManager })
// Load the parser (only needed once)
await adapter.loadParser()
// Now compileLocal works
const definition = adapter.compileLocal(candidSource)

Compile Candid using the local WASM parser (synchronous after loading).

// Requires loadParser() first
const { idlFactory } = adapter.compileLocal(candidSource)

Throws if the parser hasn’t been loaded.


Compile Candid using the didjs canister.

const { idlFactory } = await adapter.compileRemote(candidSource)

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.


PropertyTypeDescription
clientManagerCandidClientManagerThe client manager instance
didjsCanisterIdCanisterIdThe didjs canister ID in use

import { CandidAdapter } from "@ic-reactor/candid"
import { ClientManager } from "@ic-reactor/core"
const clientManager = new ClientManager()
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"
import "@ic-reactor/parser"
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 { idlFactory } = 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"))