diff --git a/.docs-plan/decisions.md b/.docs-plan/decisions.md index 17c655ea..8608e17e 100644 --- a/.docs-plan/decisions.md +++ b/.docs-plan/decisions.md @@ -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/` 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) diff --git a/.docs-plan/migration-plan.md b/.docs-plan/migration-plan.md index 51d19575..1995ba48 100644 --- a/.docs-plan/migration-plan.md +++ b/.docs-plan/migration-plan.md @@ -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 | @@ -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 diff --git a/docs/guides/canister-calls/binding-generation.md b/docs/guides/canister-calls/binding-generation.md deleted file mode 100644 index 2f611786..00000000 --- a/docs/guides/canister-calls/binding-generation.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: "Binding Generation" -description: "Generate type-safe JS/TS and Rust bindings from Candid interface files" -sidebar: - order: 2 -icskills: [multi-canister] ---- - -TODO: Write content for this page. - - -Generate type-safe bindings from Candid files for JS/TS (@icp-sdk/bindgen), Rust (didc), and icp-cli automatic generation. Cover Vite plugin integration for frontend projects, CLI generation for backend projects, and custom generation configuration. Show the hello-world template as the reference implementation. - - -- icp-cli: concepts/binding-generation.md -- icskills: multi-canister -- JS SDK: @icp-sdk/bindgen (https://js.icp.build/bindgen) -- Template: hello-world (shows Vite plugin) - - -- guides/canister-calls/candid -- Candid as input -- getting-started/project-structure -- bindgen in project setup -- icp-cli docs: https://dfinity.github.io/icp-cli/ diff --git a/docs/guides/canister-calls/candid.md b/docs/guides/canister-calls/candid.md index 054317a0..24dcf46a 100644 --- a/docs/guides/canister-calls/candid.md +++ b/docs/guides/canister-calls/candid.md @@ -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 @@ -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 @@ -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: @@ -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. + + +### 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:` variables that `icp deploy` injects (see [canister discovery](onchain-calls.md#canister-discovery)). For canisters with fixed IDs, use `.static_callee(principal)` instead. + + +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 ` | Decode binary Candid back to text | | `didc subtype new.did old.did` | Check that `new` is a safe upgrade from `old` | @@ -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 - + diff --git a/docs/guides/canister-calls/offchain-calls.md b/docs/guides/canister-calls/offchain-calls.md index 1afad835..7c75b2ba 100644 --- a/docs/guides/canister-calls/offchain-calls.md +++ b/docs/guides/canister-calls/offchain-calls.md @@ -30,7 +30,7 @@ Call canister functions from outside the IC using agent libraries. Explain the I - 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 diff --git a/docs/guides/canister-calls/onchain-calls.md b/docs/guides/canister-calls/onchain-calls.md index dd9d3084..4d289948 100644 --- a/docs/guides/canister-calls/onchain-calls.md +++ b/docs/guides/canister-calls/onchain-calls.md @@ -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: