Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .docs-plan/decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,10 @@ Record decisions that constrain future work — things an agent needs to know th
**Decision:** Enforce squash-merge as the only merge method on GitHub. Auto-delete branches after merge. Squash commit uses PR title and PR body.
**Rationale:** Keeps `git log` on `main` clean (one commit per page/feature). PR history is always available on GitHub. Auto-delete prevents stale `docs/<slug>` branches from accumulating.
**Alternatives considered:** Merge commits (noisy history), rebase merge (preserves individual commits nobody needs)

## 2026-03-17: Merge binding-generation page into Candid interface guide

**Context:** The planned `guides/canister-calls/binding-generation.md` had significant overlap with the existing Candid interface guide, which already covered `.did` generation and JS binding usage. The two dedicated binding tools are `@icp-sdk/bindgen` (JS/TS) and `ic-cdk-bindgen` (Rust) — `didc bind` should not be advertised for binding generation.
**Decision:** (1) Merged all binding-generation content into `candid.md` as a "Binding generation" section. (2) Removed the `binding-generation.md` stub. (3) Updated `didc` tool table to focus on validation/encoding (removed `didc bind` rows). (4) Documented both auto-generated and hand-written `.did` file paths for Motoko. (5) Updated `onchain-calls.md` canister discovery section with a cross-link to the new bindings section. (6) Removed Beads task and updated migration plan dependencies.
**Rationale:** The developer flow is linear: define interface → generate `.did` → generate bindings → use them. Splitting bindings into a separate page creates an artificial seam. The Candid guide is the natural home for the full flow.
**Alternatives considered:** Keep separate page (creates overlap and navigation friction), move all `.did` generation to the bindings page (splits related content)
7 changes: 3 additions & 4 deletions .docs-plan/migration-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,8 @@ These pages fill out the docs site with important secondary content.

| # | Layer | Page | Effort | Dependencies | Source Material | Skills |
|---|-------|------|--------|-------------|-----------------|--------|
| 47 | 2 | `guides/canister-calls/binding-generation.md` | M | candid | icp-cli binding-generation; JS SDK: @icp-sdk/bindgen | sync |
| 47b | 3 | `guides/canister-calls/offchain-calls.md` | M | candid, binding-generation | JS SDK: @icp-sdk/core, @icp-sdk/canisters; icp-cli canister-discovery; hello-world template | original |
| ~~47~~ | | ~~`guides/canister-calls/binding-generation.md`~~ | | | Merged into `candid.md` "Binding generation" section | — |
| 47b | 3 | `guides/canister-calls/offchain-calls.md` | M | candid | JS SDK: @icp-sdk/core, @icp-sdk/canisters; icp-cli canister-discovery; hello-world template | original |
| 48 | 5+ | `guides/canister-management/subnet-selection.md` | M | cycles-management | Portal deploy-specific-subnet.mdx; icp-cli deploying-to-specific-subnets | rewrite, sync |
| 49 | 2 | `guides/security/data-integrity.md` | L | concepts/security | icskills: vetkd, certified-variables; examples: vetkeys, vetkd | rewrite |
| 50 | 2 | `guides/security/dos-prevention.md` | M | concepts/security | icskills: canister-security | original |
Expand Down Expand Up @@ -338,8 +338,7 @@ concepts/canisters.md ← HUB: most pages depend on thi
│ │ │ └── guides/defi/chain-key-tokens.md
│ │ ├── guides/security/inter-canister-calls.md
│ │ └── guides/canister-calls/parallel-calls.md
│ └── guides/canister-calls/binding-generation.md
│ └── guides/canister-calls/offchain-calls.md
│ └── guides/canister-calls/offchain-calls.md
├── guides/backends/data-persistence.md (also needs: orthogonal-persistence)
│ └── guides/canister-management/lifecycle.md
│ ├── guides/canister-management/settings.md
Expand Down
23 changes: 0 additions & 23 deletions docs/guides/canister-calls/binding-generation.md

This file was deleted.

108 changes: 93 additions & 15 deletions docs/guides/canister-calls/candid.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,21 @@ For the complete type reference, including subtyping rules, see the [Candid spec

### Motoko

The Motoko compiler generates Candid descriptions automatically from your actor's type signature. When you build with icp-cli, the `.did` file is placed in the build output directory. You do not need to write or maintain a `.did` file by hand for Motoko canisters.
The Motoko compiler generates Candid descriptions automatically from your actor's type signature. When you build with icp-cli, the `.did` file is placed in the build output directory — no manual authoring needed.

You can also provide a hand-written `.did` file by setting the `candid` field in `icp.yaml`. This is useful when you want an explicit API contract that is versioned independently of the implementation:

```yaml
canisters:
- name: backend
recipe:
type: "@dfinity/motoko@v4.1.0"
configuration:
main: backend/app.mo
candid: backend/backend.did # Optional — overrides auto-generation
```

If `candid` is omitted, the Motoko recipe auto-generates the interface from the source.

### Rust

Expand Down Expand Up @@ -148,6 +162,8 @@ canisters:
candid: src/my_canister/my_canister.did
```

As with Motoko, the Rust recipe can also auto-extract the Candid interface from the compiled Wasm using `candid-extractor` if you omit the `candid` field. Providing an explicit file is recommended for stable APIs.

## Type mapping in practice

### Records
Expand Down Expand Up @@ -241,15 +257,7 @@ icp canister call my_canister set_address '("Alice", record { street = "123 Main

### From JavaScript

The [JS SDK](https://js.icp.build) (`@icp-sdk/core`) translates Candid types into native JavaScript values. To call a canister from JavaScript, you need typed declarations generated from the `.did` file. Generate them using [`@icp-sdk/bindgen`](https://js.icp.build/bindgen) or the `didc` CLI:

```bash
# Using @icp-sdk/bindgen (recommended for JS/TS projects)
npx @icp-sdk/bindgen --did-file my_canister.did --out-dir src/declarations/my_canister

# Or using didc directly
didc bind my_canister.did -t js
```
The [JS SDK](https://js.icp.build) (`@icp-sdk/core`) translates Candid types into native JavaScript values. To call a canister from JavaScript, you need typed declarations generated from the `.did` file — see [Binding generation](#binding-generation) below for how to set this up.

The generated declarations export a `createActor` function and an `idlFactory` that describes the interface:

Expand Down Expand Up @@ -324,15 +332,86 @@ type UserProfile = record {

Using `reserved` prevents future developers from accidentally reusing the field's hash for a different purpose.

## Binding generation

Once you have a `.did` file, generate type-safe client bindings so callers get compile-time type checking instead of working with raw Candid values.

### JavaScript / TypeScript

[`@icp-sdk/bindgen`](https://js.icp.build/bindgen) generates TypeScript declarations from `.did` files. It supports both a CLI and a Vite plugin for automatic regeneration during development.

**Vite plugin (recommended for frontend projects):**

```typescript
// vite.config.ts
import { defineConfig } from "vite";
import { icpBindgen } from "@icp-sdk/bindgen/vite";

export default defineConfig({
plugins: [
icpBindgen(),
],
});
```

The plugin reads your `icp.yaml`, finds each canister's `.did` file, and generates bindings into your source tree automatically when the dev server starts or when `.did` files change.

**CLI (for non-Vite projects or CI):**

```bash
npx @icp-sdk/bindgen
```

By default, the CLI reads `icp.yaml` and generates bindings for all canisters. See the [`@icp-sdk/bindgen` documentation](https://js.icp.build/bindgen) for configuration options.
<!-- Needs human verification: verify @icp-sdk/bindgen Vite plugin import path (icpBindgen from "@icp-sdk/bindgen/vite") and CLI invocation (npx @icp-sdk/bindgen with no subcommand) -->

### Rust

[`ic-cdk-bindgen`](https://crates.io/crates/ic-cdk-bindgen) generates Rust bindings from `.did` files at build time via a Cargo build script. This gives you typed functions for inter-canister calls.

Add it as a build dependency:

```bash
cargo add --build ic-cdk-bindgen
```

Create a `build.rs` that points to the callee's `.did` file:

```rust
// build.rs
fn main() {
ic_cdk_bindgen::Config::new("callee", "candid/callee.did")
.dynamic_callee("PUBLIC_CANISTER_ID:callee")
.generate();
}
```

Then include the generated module in your canister code:

```rust
#[allow(dead_code, unused_imports)]
mod callee {
include!(concat!(env!("OUT_DIR"), "/callee.rs"));
}

#[ic_cdk::update]
async fn invoke_callee() {
let _result = callee::some_method().await;
}
```

The `.dynamic_callee("PUBLIC_CANISTER_ID:callee")` mode reads the canister ID from a canister environment variable at runtime — the same `PUBLIC_CANISTER_ID:<name>` variables that `icp deploy` injects (see [canister discovery](onchain-calls.md#canister-discovery)). For canisters with fixed IDs, use `.static_callee(principal)` instead.
<!-- Needs human verification: the upstream ic-cdk-bindgen README uses ICP_CANISTER_ID: in its example, but icp-cli sets PUBLIC_CANISTER_ID:. We use PUBLIC_CANISTER_ID: here to match icp-cli. An issue has been filed on dfinity/cdk-rs to align the upstream README. -->

For type selector configuration and advanced options, see the [`ic-cdk-bindgen` documentation](https://crates.io/crates/ic-cdk-bindgen).

## Candid tools

**`didc`** — the Candid CLI for checking `.did` files, generating language bindings, encoding/decoding values, and testing subtype compatibility. Download from the [Candid releases page](https://github.com/dfinity/candid/releases).
**`didc`** — the Candid CLI for checking `.did` files, encoding/decoding values, and testing subtype compatibility. Download from the [Candid releases page](https://github.com/dfinity/candid/releases).

| Command | What it does |
|---------|-------------|
| `didc check service.did` | Validate a `.did` file |
| `didc bind service.did -t js` | Generate JavaScript bindings |
| `didc bind service.did -t rs` | Generate Rust bindings |
| `didc encode '(42, "hello")'` | Encode a Candid value to hex |
| `didc decode <hex>` | Decode binary Candid back to text |
| `didc subtype new.did old.did` | Check that `new` is a safe upgrade from `old` |
Expand All @@ -343,7 +422,6 @@ Using `reserved` prevents future developers from accidentally reusing the field'

- [Onchain calls](onchain-calls.md) — make inter-canister calls using Candid interfaces
- [Offchain calls](offchain-calls.md) — call canisters from JavaScript frontends and agents
- [Binding generation](binding-generation.md) — auto-generate typed clients from `.did` files
- [Candid specification](../../reference/candid-spec.md) — full type reference and subtyping rules

<!-- Upstream: informed by dfinity/portal docs/building-apps/interact-with-canisters/candid/ (3 files) and docs/building-apps/developer-tools/cdks/rust/generating-candid.mdx. Type mappings verified against .sources/candid (spec), .sources/motoko (IDL-Motoko.md), and .sources/cdk-rs. -->
<!-- Upstream: informed by dfinity/portal docs/building-apps/interact-with-canisters/candid/ (3 files), docs/building-apps/developer-tools/cdks/rust/generating-candid.mdx, icp-cli concepts/binding-generation.md, ic-cdk-bindgen README, and @icp-sdk/bindgen. Type mappings verified against .sources/candid (spec), .sources/motoko (IDL-Motoko.md), and .sources/cdk-rs. -->
2 changes: 1 addition & 1 deletion docs/guides/canister-calls/offchain-calls.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Call canister functions from outside the IC using agent libraries. Explain the I

<!-- Cross-Links -->
- guides/canister-calls/candid -- interface definitions (shared by all agents)
- guides/canister-calls/binding-generation -- generating typed clients
- guides/canister-calls/candid#binding-generation -- generating typed clients
- guides/canister-calls/onchain-calls -- canister-to-canister alternative
- guides/frontends/asset-canister -- deploying the frontend that makes these calls
- guides/authentication/internet-identity -- adding auth to calls
2 changes: 2 additions & 0 deletions docs/guides/canister-calls/onchain-calls.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ let counterId = Principal.fromText(counterIdText);

Deployment order does not matter — `icp deploy` creates all canisters first, then injects variables, then installs code. Variables are only updated for the canisters being deployed, so run `icp deploy` (without arguments) when adding new canisters to update all of them.

> **Tip:** For Rust canisters that make inter-canister calls, [`ic-cdk-bindgen`](candid.md#binding-generation) can generate type-safe call stubs from `.did` files — so you call typed functions instead of manually constructing `Call::unbounded_wait` with string method names. See [Binding generation](candid.md#binding-generation) for details.

### Alternative approaches

**Init arguments.** Accept the target `Principal` as an `#[init]` argument and store it. This avoids the environment variable lookup at call time but requires passing the ID at every deploy and upgrade:
Expand Down
Loading