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.
Overview
Section titled “Overview”Fetching the Candid source and compiling it to JavaScript are two separate steps, each with its own fallback chain.
Fetching the source:
- Canister metadata (preferred) — reads the canister’s
candidmetadata path __get_candid_interface_tmp_hack(fallback) — query method on the canister
Compiling to JavaScript:
- Local parser (preferred) — the
@ic-reactor/parserWASM module, a dependency of this package, loaded on first use - didjs canister (fallback) — remote
did_to_jscall
The didjs canister runs upstream candid_parser without the local parser’s
fixes. For an interface with a record field or variant tag named like a
numeric id, such as _0_, it prints the key _0_, which @icp-sdk/core sends
as field 0, where the local parser prints _4735054_ (see
Field names that look like numeric ids).
import { CandidAdapter } from "@ic-reactor/candid"
const adapter = new CandidAdapter({ clientManager })
// Fetch and parse in one callconst { idlFactory } = await adapter.getCandidDefinition( "ryjl3-tyaaa-aaaaa-aaaba-cai")Import
Section titled “Import”import { CandidAdapter } from "@ic-reactor/candid"Constructor
Section titled “Constructor”new CandidAdapter(config: CandidAdapterParameters)Parameters
Section titled “Parameters”| 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)
The default is read from clientManager.isLocal each time the adapter needs
it, and an ID passed here or assigned to didjsCanisterId wins. The adapter
keeps no subscription on the client manager, so an adapter, or a candid
reactor that created one, needs no cleanup when it is dropped. The deprecated
unsubscribe() is a no-op.
Methods
Section titled “Methods”getCandidDefinition(canisterId)
Section titled “getCandidDefinition(canisterId)”Fetch and parse Candid for a canister in one call.
import { IDL } from "@icp-sdk/core/candid"
const { idlFactory, init } = await adapter.getCandidDefinition( "ryjl3-tyaaa-aaaaa-aaaba-cai")
// Use the idlFactoryconst service = idlFactory({ IDL })console.log(service._fields) // Method definitionsReturns
Section titled “Returns”interface CandidDefinition { idlFactory: IDL.InterfaceFactory init?: (args: { IDL: typeof IDL }) => IDL.Type<unknown>[]}fetchCandidSource(canisterId)
Section titled “fetchCandidSource(canisterId)”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; ... }parseCandidSource(candidSource)
Section titled “parseCandidSource(candidSource)”Parse Candid source text into an IDL factory.
const candidSource = ` service : { greet : (text) -> (text) query; }`
const { idlFactory } = await adapter.parseCandidSource(candidSource)This method:
- Tries the local parser first, loading
@ic-reactor/parseron first use - Falls back to remote compilation via the didjs canister
It is the only method that returns a CandidDefinition — compileLocal and
compileRemote return the compiled JavaScript source instead.
loadParser()
Section titled “loadParser()”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 worksconst jsSource = adapter.compileLocal(candidSource)compileLocal(candidSource)
Section titled “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() firstconst jsSource: string = adapter.compileLocal(candidSource)Throws if the parser hasn’t been loaded.
compileRemote(candidSource)
Section titled “compileRemote(candidSource)”Compile Candid using the didjs canister. Also returns the compiled JavaScript source, wrapped in a promise.
const jsSource = await adapter.compileRemote(candidSource) // string | undefinedThis is the fallback method when local parsing isn’t available.
validateCandid(candidSource)
Section titled “validateCandid(candidSource)”Validate Candid syntax without compiling.
await adapter.loadParser()
const isValid = adapter.validateCandid(` service : { greet : (text) -> (text) query; }`)console.log(isValid) // trueRequires the parser to be loaded.
Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
clientManager |
CandidClientManager |
The client manager instance |
didjsCanisterId |
CanisterId |
The didjs canister ID in use: the one given or assigned, otherwise the default for the current network |
Examples
Section titled “Examples”Basic Usage
Section titled “Basic Usage”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 definitionconst { idlFactory } = await adapter.getCandidDefinition( "ryjl3-tyaaa-aaaaa-aaaba-cai")
// List methodsconst service = idlFactory({ IDL })for (const [name, func] of service._fields) { console.log(`${name}: ${func.toString()}`)}With Local Parser
Section titled “With Local Parser”import { CandidAdapter } from "@ic-reactor/candid"
const adapter = new CandidAdapter({ clientManager })
// Load parser once at startupawait adapter.loadParser()
// All subsequent compilations are local (fast!)const candidSource = await adapter.fetchCandidSource(canisterId)const jsSource = adapter.compileLocal(candidSource)Error Handling
Section titled “Error Handling”try { const definition = await adapter.getCandidDefinition(canisterId)} catch (error) { const message = error instanceof Error ? error.message : String(error) if (message.includes("Failed to compile Candid")) { console.log("Invalid Candid syntax") } else { // "Failed to retrieve Candid source by any method: …" comes from a canister // that exposes no Candid and from failed requests (network, missing local // root key) alike. The rest of the message says what stopped each attempt, // so pass it on rather than report "no Candid". throw error }}Method Signature Parsing
Section titled “Method Signature Parsing”For dynamic calls, you can parse individual method signatures:
import { IDL } from "@icp-sdk/core/candid"
// Parse a method signature wrapped in a serviceconst 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 informationconst funcField = service._fields.find(([name]) => name === "my_method")if (!funcField) throw new Error("my_method is not in the service")const func = funcField[1]
console.log("Arg types:", func.argTypes)console.log("Return types:", func.retTypes)console.log("Is query:", func.annotations.includes("query"))Best Practices
Section titled “Best Practices”See Also
Section titled “See Also”- CandidReactor — High-level dynamic Reactor
- @ic-reactor/candid Overview — Package overview
- @ic-reactor/parser — Local WASM parser reference
- Reactor — Base Reactor class