From b048263a056191bca474e6a8b9de5d8b0c6ed2a9 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 22 Sep 2026 10:49:34 +0200 Subject: [PATCH 1/8] fix(icp-cli): move to @icp-sdk/core ^6 and bindgen >= 0.4.0 Pin core to ^6 in both install paths and state why: auth, signer, canisters (>= 4) and @dfinity/utils (>= 5) all peer ^6, so a project on another major fails with ERESOLVE, and --legacy-peer-deps papers over it by installing two copies of core. Also corrects the claim that @icp-sdk/bindgen depends on @icp-sdk/core. Its only dependency is commander; the generated code imports core, which the project installs itself. --- skills/icp-cli/SKILL.md | 2 +- skills/icp-cli/references/binding-generation.md | 10 +++++----- skills/icp-cli/references/dfx-migration.md | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/skills/icp-cli/SKILL.md b/skills/icp-cli/SKILL.md index ad222867..87059a19 100644 --- a/skills/icp-cli/SKILL.md +++ b/skills/icp-cli/SKILL.md @@ -112,7 +112,7 @@ npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm 11. **Expecting `output_env_file` or `.env` with canister IDs.** dfx writes canister IDs to a `.env` file (`CANISTER_ID_BACKEND=...`) via `output_env_file`. icp-cli does not generate `.env` files. Instead, it injects canister IDs as environment variables (`PUBLIC_CANISTER_ID:`) directly into canisters during `icp deploy`. Frontends read these from the `ic_env` cookie set by the frontend canister (static-site or the legacy asset canister). Remove `output_env_file` from your config and any code that reads `CANISTER_ID_*` from `.env` — frontends use the `ic_env` cookie, and canister code reads the same variables at runtime (see Canister Environment Variables below and Pitfall 22). -12. **Expecting `dfx generate` for TypeScript bindings.** icp-cli does not have a `dfx generate` equivalent. Use `@icp-sdk/bindgen` (>= 0.3.0) with `@icp-sdk/core` (>= 5.0.0 — there is no 0.x or 1.x release) to generate TypeScript bindings from `.did` files at build time. Use `outDir: "./src/bindings"` so imports are clean (e.g., `./bindings/backend`). The `.did` file must exist on disk — either commit it to the repo, or generate it with `icp build` first (recipes auto-generate it when `candid` is not specified). See `references/binding-generation.md` for the full Vite plugin setup. +12. **Expecting `dfx generate` for TypeScript bindings.** icp-cli does not have a `dfx generate` equivalent. Use `@icp-sdk/bindgen` (>= 0.4.0) with `@icp-sdk/core` pinned to `^6` (there is no 0.x or 1.x release; the rest of the SDK peers `^6`, so a different major fails to install with `ERESOLVE`) to generate TypeScript bindings from `.did` files at build time. Use `outDir: "./src/bindings"` so imports are clean (e.g., `./bindings/backend`). The `.did` file must exist on disk — either commit it to the repo, or generate it with `icp build` first (recipes auto-generate it when `candid` is not specified). See `references/binding-generation.md` for the full Vite plugin setup. 13. **Passing `{ agent }` to `createActor` from `@icp-sdk/bindgen`.** The old `@dfinity/agent` pattern was `createActor(canisterId, { agent })`. The `@icp-sdk/bindgen` pattern is `createActor(canisterId, { agentOptions: { host, rootKey } })` — the binding creates the agent internally. Passing `{ agent }` to the new API **silently creates an anonymous identity** — no error is thrown, but calls return empty data or access denied. See `references/binding-generation.md` for the correct pattern. diff --git a/skills/icp-cli/references/binding-generation.md b/skills/icp-cli/references/binding-generation.md index 438138b8..c4635f5c 100644 --- a/skills/icp-cli/references/binding-generation.md +++ b/skills/icp-cli/references/binding-generation.md @@ -1,6 +1,6 @@ # Binding Generation -icp-cli does not have a built-in `dfx generate` command. Use `@icp-sdk/bindgen` (>= 0.3.0) to generate TypeScript bindings from `.did` files. It depends on `@icp-sdk/core` (>= 5.0.0). +icp-cli does not have a built-in `dfx generate` command. Use `@icp-sdk/bindgen` (>= 0.4.0) to generate TypeScript bindings from `.did` files. The generated code imports `@icp-sdk/core`, which the project installs itself — `@icp-sdk/bindgen` does not depend on it. ## Vite plugin (recommended) @@ -82,11 +82,11 @@ if (result !== null) { name = result; } Install both packages in the frontend project (note the minimum versions): ```bash -npm install @icp-sdk/core@^5.0.0 -npm install -D @icp-sdk/bindgen@^0.3.0 +npm install @icp-sdk/core@^6 +npm install -D @icp-sdk/bindgen@^0.4.0 ``` -**Important:** `@icp-sdk/core` starts at version 5.x — there is no 0.x or 1.x release. Do not guess a lower version. +**Pin `@icp-sdk/core` to `^6`; do not take `latest` or guess a lower major.** There is no 0.x or 1.x release — core starts at 5.x, and 6.x is current. `@icp-sdk/auth`, `@icp-sdk/signer`, `@icp-sdk/canisters` (>= 4) and `@dfinity/utils` (>= 5) all peer `@icp-sdk/core@^6`, so a project that lands on a different major fails to install with `ERESOLVE`. Do not use `--legacy-peer-deps` to get past that — it installs two copies of core in one tree, which degrades silently instead of failing. - The `.did` file must exist on disk before the frontend builds. The recommended workflow: generate the `.did` file once (see SKILL.md pitfall #16), commit it to the repo, and specify `candid:` in the recipe config. If `candid` is omitted, the recipe auto-generates the `.did` into the build cache at a non-deterministic path that bindgen cannot reference — so always commit the `.did` and set `candid:` when using bindgen. -- `@icp-sdk/bindgen` (>= 0.3.0) generates code that depends on `@icp-sdk/core` (>= 5.0.0). Projects using `@dfinity/agent` must upgrade to `@icp-sdk/core` + `@icp-sdk/bindgen`. This is not optional — there is no way to generate TypeScript bindings with icp-cli while staying on `@dfinity/agent`. +- `@icp-sdk/bindgen` (>= 0.4.0) emits code that imports `@icp-sdk/core`; bindgen itself depends only on `commander`, so the project installs core separately (see the pin above). Projects using `@dfinity/agent` must upgrade to `@icp-sdk/core` + `@icp-sdk/bindgen`. This is not optional — there is no way to generate TypeScript bindings with icp-cli while staying on `@dfinity/agent`. diff --git a/skills/icp-cli/references/dfx-migration.md b/skills/icp-cli/references/dfx-migration.md index cc01b383..32d51db8 100644 --- a/skills/icp-cli/references/dfx-migration.md +++ b/skills/icp-cli/references/dfx-migration.md @@ -60,7 +60,7 @@ createActor(canisterEnv?.["PUBLIC_CANISTER_ID:backend"], { Steps: 1. `npm uninstall @dfinity/agent @dfinity/candid @dfinity/principal vite-plugin-environment` -2. `npm install @icp-sdk/core@^5.0.0 @icp-sdk/bindgen@^0.3.0` +2. `npm install @icp-sdk/core@^6 @icp-sdk/bindgen@^0.4.0` — pin core explicitly; the rest of the SDK peers `^6` (see `references/binding-generation.md`) 3. Delete `src/declarations/` (dfx-generated bindings) 4. Add `**/src/bindings/` to `.gitignore` 5. Commit the `.did` file(s) used by bindgen From 83ce413340d776354c77f97d44e758996b10a507 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 22 Sep 2026 10:51:23 +0200 Subject: [PATCH 2/8] fix(internet-identity): pin @icp-sdk/auth ^10 with @icp-sdk/core ^6 The Prerequisites line paired an open-ended "auth >= 9.0.0" with "core >= 5.3.0". auth 10 peers @icp-sdk/core@^6, so that floor resolves to a combination that does not install: npm error Found: @icp-sdk/core@5.4.0 npm error peer @icp-sdk/core@"^6" from @icp-sdk/auth@10.0.0 Pin both majors together and add a pitfall for the mismatch. auth 10's API is identical to 9's -- only the peer moved -- so the flow above is unchanged and the Older API notes now record 9.x as the core-5 option. --- skills/internet-identity/SKILL.md | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/skills/internet-identity/SKILL.md b/skills/internet-identity/SKILL.md index 32652916..896bd118 100644 --- a/skills/internet-identity/SKILL.md +++ b/skills/internet-identity/SKILL.md @@ -16,7 +16,7 @@ Internet Identity (II) is the Internet Computer's native authentication system. ## Prerequisites -- `@icp-sdk/auth` (>= 9.0.0), `@icp-sdk/core` (>= 5.3.0) (`AttributesIdentity` was added in core v5.3.0) +- `@icp-sdk/auth@^10` with `@icp-sdk/core@^6` — **pin both majors together.** auth 10 peers `@icp-sdk/core@^6` and auth 9 peers `^5`, so an open-ended floor such as "auth >= 9" resolves to 10 and then fails with `ERESOLVE` against a core 5 install. - For the Motoko backend example: `mo:identity-attributes` >= 0.4.0 (mops) — the mixin that injects the two sign-in methods and verifies the bundle for you. It pulls in `mo:core` >= 2.5.0 and requires `moc` >= 1.6.0 for the `include` mixin. ## Canister IDs @@ -30,9 +30,9 @@ Internet Identity (II) is the Internet Computer's native authentication system. 1. **Using the wrong II URL for the environment.** `authorizeUrl` must point to the **frontend** canister (`uqzsh-gqaaa-aaaaq-qaada-cai`), not the backend. Mainnet uses `https://id.ai/authorize`. Local-only II (when `ii: true` is set in `icp.yaml`) uses `http://id.ai.localhost:8000/authorize`. Both canister IDs are well-known and identical on mainnet and local replicas — hardcode them rather than doing a dynamic lookup. -2. **Passing `identityProvider` as a URL string, or naming only half of it.** In 9.x it is an object — `{ authorizeUrl, canisterId }` — and both fields are required together: the page a ceremony renders at and the canister that mints delegations are separate facts, and neither is derived from the other. A string or a `URL` throws a `TypeError`. Omit the option entirely to get mainnet Internet Identity, which is what most apps want. The URL is used verbatim, so include the `/authorize` path: `https://id.ai` opens the II home page and never returns a delegation. +2. **Passing `identityProvider` as a URL string, or naming only half of it.** In 9.x and later it is an object — `{ authorizeUrl, canisterId }` — and both fields are required together: the page a ceremony renders at and the canister that mints delegations are separate facts, and neither is derived from the other. A string or a `URL` throws a `TypeError`. Omit the option entirely to get mainnet Internet Identity, which is what most apps want. The URL is used verbatim, so include the `/authorize` path: `https://id.ai` opens the II home page and never returns a delegation. -3. **Treating `maxTimeToLive` as the lifetime of the key the frontend signs with.** In 9.x it bounds the **session** at Internet Identity, and `maxTimeToIdle` ends a session nobody has used; the delegation your calls are signed with is short-lived and replaced for you. Leave both unset unless the app has a policy of its own — the provider applies seven days of idleness and thirty days in total. Bound them where the data is sensitive, not to keep key material fresh. +3. **Treating `maxTimeToLive` as the lifetime of the key the frontend signs with.** In 9.x and later it bounds the **session** at Internet Identity, and `maxTimeToIdle` ends a session nobody has used; the delegation your calls are signed with is short-lived and replaced for you. Leave both unset unless the app has a policy of its own — the provider applies seven days of idleness and thirty days in total. Bound them where the data is sensitive, not to keep key material fresh. 4. **Not awaiting `signIn()` or skipping the `try`/`catch`.** `authClient.signIn()` returns a promise that rejects when the user closes the popup or authentication fails. Without `await` and a `catch`, those failures are silently swallowed. @@ -65,6 +65,16 @@ Internet Identity (II) is the Internet Computer's native authentication system. 16. **Assuming a bad field in `ii-app-metadata` is just dropped, or confusing a rejected logo with a rejected document.** One field that fails validation invalidates the **whole document**: none of your metadata is applied, not just the offending field (II then falls back to its curated entry if it ships one for your app, and to your origin alone otherwise). `name` is capped at 40 Unicode code points and `description` at 120, counted on the value as served. `logo` straddles the two failure modes — a URL that is not on the **same origin** as the document fails document validation and takes the whole document down with it, and that includes your own canister on a sibling gateway domain, since II may fetch the document from any of `ic0.app`, `icp0.io`, or `icp.net` (write the URL relative) — while an SVG (`image/svg+xml` is not accepted; serve a raster copy), an oversized image, or one that cannot be fetched or decoded costs you the logo alone. +17. **Installing `@icp-sdk/auth` and `@icp-sdk/core` at majors that do not pair.** auth 10 peers `@icp-sdk/core@^6`; auth 9 peers `^5`. Pinning core to `^5` out of habit while `@icp-sdk/auth` resolves to `latest` gives you auth 10 on core 5, which does not install: + + ```text + npm error ERESOLVE unable to resolve dependency tree + npm error Found: @icp-sdk/core@5.4.0 + npm error peer @icp-sdk/core@"^6" from @icp-sdk/auth@10.0.0 + ``` + + Do not clear it with `--legacy-peer-deps` — that installs two copies of core rather than fixing the pair. Pin `@icp-sdk/auth@^10` with `@icp-sdk/core@^6`, or stay on `@icp-sdk/auth@^9` if something else holds you on core 5. + ## Using II during local development **Default: use mainnet II from your local network.** Starting with `icp-cli >= 0.2.4`, the local network (pocket-ic, launched by `icp-cli-network-launcher`) is configured to trust the mainnet subnet's BLS signatures. Delegations signed by `https://id.ai` are accepted by your local replica, so both the sign-in flow *and* authenticated calls to a locally-deployed backend just work — no extra config in `icp.yaml`, no local II canister to manage, and the UI is the real one your users will see. @@ -713,7 +723,9 @@ Backend access control (anonymous principal rejection, role guards, caller bindi ## Older API notes -Everything above targets `@icp-sdk/auth` 9.x. On an older major the same flow differs: +Everything above targets `@icp-sdk/auth` 10.x. On an older major the same flow differs: + +**9.x** — the same API. 10.x changed only its peer, from `@icp-sdk/core@^5` to `^6`, so 9.x is what you use if you are held on core 5. One behavioural gain comes with it: core 6 carries delegation permissions, so a read-only session signs in instead of throwing *"this session is read-only, which `@icp-sdk/auth` cannot act for yet"*. Nothing in your code changes. **8.x** — what 9.x changed: @@ -735,4 +747,4 @@ Everything above targets `@icp-sdk/auth` 9.x. On an older major the same flow di - 5.x auto-appends `/authorize` to the `identityProvider` URL, so you can pass just `https://id.ai`. - No `requestAttributes` / `AttributesIdentity` support — the identity-attributes flow above requires 7.x or later. -Upgrade when you can: the promise-based API is harder to misuse, the callback variant has been removed, and 9.x re-mints the delegation your calls are signed with instead of leaving one key alive for the whole session. +Upgrade when you can: the promise-based API is harder to misuse, the callback variant has been removed, and 9.x and later re-mint the delegation your calls are signed with instead of leaving one key alive for the whole session. From fa05faf4760410a01f04896f595d39b8fc5d61c9 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 22 Sep 2026 10:52:43 +0200 Subject: [PATCH 3/8] fix(vetkeys,encrypted-maps): require @icp-sdk/vetkeys >= 0.7 0.5 and 0.6 declared @icp-sdk/core as a plain dependency, so they could nest a second copy of core beside the app's own rather than failing. 0.7 peers it as ^5 || ^6. The type surface is identical across 0.5-0.7, so no code sample changes. Also moves the vetkeys frontend core requirement from ^5.4 to ^6: the ^5.4 floor came from 0.5.0's dependency pin and no longer applies. --- skills/encrypted-maps/SKILL.md | 4 ++-- skills/vetkeys/SKILL.md | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/skills/encrypted-maps/SKILL.md b/skills/encrypted-maps/SKILL.md index 64de4d33..bc1eeb00 100644 --- a/skills/encrypted-maps/SKILL.md +++ b/skills/encrypted-maps/SKILL.md @@ -16,10 +16,10 @@ Use the **`vetkeys` skill** instead when you need lower-level primitives: identi | Layer | Rust | Motoko | Frontend | |-------|------|--------|----------| -| Package | `ic-vetkeys` **0.9** | `ic-vetkeys` **0.6** (moc ≥ 1.13.0, core ≥ 2.6.1) | `@icp-sdk/vetkeys` **0.5** | +| Package | `ic-vetkeys` **0.9** | `ic-vetkeys` **0.6** (moc ≥ 1.13.0, core ≥ 2.6.1) | `@icp-sdk/vetkeys` **0.7** | | Backend | `export_encrypted_maps_canister!` macro | `EncryptedMapsCanister` mixin | `@icp-sdk/vetkeys/encrypted_maps` | -> Use `@icp-sdk/vetkeys` (≥0.5), not the legacy `@dfinity/vetkeys` (frozen at 0.4). Frontend agent/identity come from `@icp-sdk/core`, not `@dfinity/agent`. +> Use `@icp-sdk/vetkeys` (≥0.7), not the legacy `@dfinity/vetkeys` (frozen at 0.4). 0.5 and 0.6 carried `@icp-sdk/core` as a plain dependency and could nest a second copy of it; 0.7 peers it as `^5 || ^6`, with an identical API. Frontend agent/identity come from `@icp-sdk/core`, not `@dfinity/agent`. ## Concepts diff --git a/skills/vetkeys/SKILL.md b/skills/vetkeys/SKILL.md index 53e6ea8e..5e41cb6b 100644 --- a/skills/vetkeys/SKILL.md +++ b/skills/vetkeys/SKILL.md @@ -16,13 +16,15 @@ Build on the maintained libraries — do not hand-roll the cryptography or the C | Layer | Rust | Motoko | Frontend | |-------|------|--------|----------| -| Package | `ic-vetkeys` **0.9** ([crates.io](https://crates.io/crates/ic-vetkeys)) | `ic-vetkeys` **0.6** ([mops](https://mops.one/ic-vetkeys)) | `@icp-sdk/vetkeys` **0.5** ([npm](https://www.npmjs.com/package/@icp-sdk/vetkeys)) | +| Package | `ic-vetkeys` **0.9** ([crates.io](https://crates.io/crates/ic-vetkeys)) | `ic-vetkeys` **0.6** ([mops](https://mops.one/ic-vetkeys)) | `@icp-sdk/vetkeys` **0.7** ([npm](https://www.npmjs.com/package/@icp-sdk/vetkeys)) | | Management API | `ic-cdk-management-canister`, `ic_vetkeys::management_canister` | `mo:ic-vetkeys/ManagementCanister` | — | | Low-level primitives | crate root (`ic_vetkeys::…`) | — (**not available**, see below) | package root (`@icp-sdk/vetkeys`) | > **`@dfinity/vetkeys` is legacy** (frozen at 0.4.0). The package was renamed to `@icp-sdk/vetkeys` at 0.5.0. Frontend agent/identity types come from `@icp-sdk/core` (`@icp-sdk/core/agent`, `@icp-sdk/core/principal`), **not** `@dfinity/agent`/`@dfinity/principal`. +> +> Use **0.7 or later**. 0.5 and 0.6 declared `@icp-sdk/core` as a plain dependency, so they could nest a second copy of core beside the one the app installs instead of failing; 0.7 peers it as `^5 || ^6`. The API is identical across all three. -Also required: Rust `ic-cdk = "0.20"` + `ic-cdk-management-canister = "0.1"` (and `ic-dummy-getrandom-for-wasm` for IBE); Motoko `ic-vetkeys` 0.6 needs `moc ≥ 1.13.0` / `core ≥ 2.6.1`; frontend also `@icp-sdk/core ^5.4`. +Also required: Rust `ic-cdk = "0.20"` + `ic-cdk-management-canister = "0.1"` (and `ic-dummy-getrandom-for-wasm` for IBE); Motoko `ic-vetkeys` 0.6 needs `moc ≥ 1.13.0` / `core ≥ 2.6.1`; frontend also `@icp-sdk/core ^6`. ## Which skill / which feature @@ -198,7 +200,7 @@ A vetKey can be turned into **verifiable randomness**: a Rust canister calls `ic ## Pitfalls -1. **Wrong package / imports.** Use `@icp-sdk/vetkeys` (≥0.5), not `@dfinity/vetkeys` (frozen at 0.4). Import agent/identity from `@icp-sdk/core` (`@icp-sdk/core/agent`, `@icp-sdk/core/principal`), and build the agent with `await HttpAgent.create({ identity, host, rootKey })` — the client classes take a ready `HttpAgent`, not options. Get `rootKey` from `safeGetCanisterEnv()` (`@icp-sdk/core/agent/canister-env`); never call `fetchRootKey()` in shipped code (see the `icp-cli` skill). +1. **Wrong package / imports.** Use `@icp-sdk/vetkeys` (≥0.7), not `@dfinity/vetkeys` (frozen at 0.4). Import agent/identity from `@icp-sdk/core` (`@icp-sdk/core/agent`, `@icp-sdk/core/principal`), and build the agent with `await HttpAgent.create({ identity, host, rootKey })` — the client classes take a ready `HttpAgent`, not options. Get `rootKey` from `safeGetCanisterEnv()` (`@icp-sdk/core/agent/canister-env`); never call `fetchRootKey()` in shipped code (see the `icp-cli` skill). 2. **`toDerivedKeyMaterial()` does not exist.** For symmetric encryption: `const dkm = await vetKey.asDerivedKeyMaterial()`, then `await dkm.encryptMessage(msg, domainSep, associatedData)` / `await dkm.decryptMessage(ct, domainSep, associatedData)` (all async). Never use the raw decrypted vetKey bytes directly as an AES key. From 4acfde70a94a530e5753471260f60eca83cd1065 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 22 Sep 2026 10:54:49 +0200 Subject: [PATCH 4/8] fix(static-site): move the legacy AssetManager path to canisters ^4 / core ^6 @icp-sdk/canisters 4.0.0 and @dfinity/utils 5.0.0 peer @icp-sdk/core@^6, so the legacy asset-canister upload path no longer has to stay on core 5. Of the hand-written API surface, canisters 4 changed only the NNS governance converters -- AssetManager is untouched, and the sample here typechecks unchanged against core 6.1.0. Records the core-5 fallback (canisters ^3.6) for projects not yet moved. --- skills/static-site/references/legacy-asset-canister.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/static-site/references/legacy-asset-canister.md b/skills/static-site/references/legacy-asset-canister.md index 2d82bbcf..1f94784b 100644 --- a/skills/static-site/references/legacy-asset-canister.md +++ b/skills/static-site/references/legacy-asset-canister.md @@ -82,7 +82,7 @@ If the standard security policy blocks the app, override the default security he ## Programmatic Uploads with `@icp-sdk/canisters` (legacy only) -`AssetManager` works **only** against the legacy asset canister — it uses the `store`/`create_batch`/`commit_batch` API. It does **not** work against the certified-assets (static-site) canister. Requires `@icp-sdk/canisters` (>= 3.5.0) and `@icp-sdk/core` (>= 5.0.0). +`AssetManager` works **only** against the legacy asset canister — it uses the `store`/`create_batch`/`commit_batch` API. It does **not** work against the certified-assets (static-site) canister. Requires `@icp-sdk/canisters@^4` with `@icp-sdk/core@^6` (npm pulls in canisters' `@dfinity/utils` peer automatically). A project still held on `@icp-sdk/core@^5` needs `@icp-sdk/canisters@^3.6` instead — canisters 4 peers core `^6` and will not install against 5. ```javascript import { AssetManager } from "@icp-sdk/canisters/assets"; From d37d86b91b2fb6ecf1647260bc3e499ed616ac09 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 22 Sep 2026 11:06:04 +0200 Subject: [PATCH 5/8] test: retarget the version evals to core ^6 and cover the auth/core pair icp-cli 6 and 15: bindgen floor -> >= 0.4.0, core -> an explicit ^6 pin. The core expectation now asserts "pinned" rather than a floor, so it tests the durable behaviour instead of a version snapshot. internet-identity 26 (new): a project pinned to core ^5 asking to add sign-in. @icp-sdk/auth@latest is 10.x and peers core ^6, so the install fails; the case checks the conflict is named and resolved without --legacy-peer-deps. --- evaluations/icp-cli.json | 8 ++++---- evaluations/internet-identity.json | 10 ++++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/evaluations/icp-cli.json b/evaluations/icp-cli.json index e02ac969..6e572e4c 100644 --- a/evaluations/icp-cli.json +++ b/evaluations/icp-cli.json @@ -64,8 +64,8 @@ "prompt": "How do I generate TypeScript bindings for my backend canister so I can call it from my React frontend?", "expected_behaviors": [ "Does NOT suggest 'dfx generate' — it does not exist in icp-cli", - "Recommends @icp-sdk/bindgen with a version constraint (>= 0.3.0) or references references/binding-generation.md which contains the version", - "Mentions @icp-sdk/core with a version constraint (>= 5.0.0) or explicitly warns that there is no 0.x/1.x release", + "Recommends @icp-sdk/bindgen with a version constraint (>= 0.4.0) or references references/binding-generation.md which contains the version", + "Mentions @icp-sdk/core with an explicit major pin (^6) — does NOT leave core unpinned, and does NOT suggest a 0.x or 1.x version", "Shows npm install commands for both packages (@icp-sdk/core and @icp-sdk/bindgen)", "Mentions that the .did file must exist on disk before the frontend builds", "Shows a Vite plugin setup using icpBindgen from @icp-sdk/bindgen, or references references/binding-generation.md for the full setup" @@ -161,8 +161,8 @@ "icp.yaml has Motoko backend with @dfinity/motoko@v5.0.0 (no recipe.configuration block) and a version-pinned asset canister for the frontend", "mops.toml has a [toolchain] section (concrete moc version) and a [canisters.backend] section with a main field", "Uses 'mops generate candid backend' (or 'mops generate candid') as the command that produces the committed .did file — NOT the older 'mops build' + 'cp .mops/.build/backend.did' two-step", - "Uses @icp-sdk/bindgen (>= 0.3.0) Vite plugin with a didFile path pointing to the committed .did file", - "Uses @icp-sdk/core (>= 5.0.0) if a version is referenced — does NOT use a 0.x or 1.x version", + "Uses @icp-sdk/bindgen (>= 0.4.0) Vite plugin with a didFile path pointing to the committed .did file", + "Uses @icp-sdk/core pinned to ^6 if a version is referenced — does NOT use a 0.x or 1.x version", "Does NOT use dfx commands, dfx.json, .env files, or process.env for canister IDs" ] }, diff --git a/evaluations/internet-identity.json b/evaluations/internet-identity.json index 47c016a9..7424eb4c 100644 --- a/evaluations/internet-identity.json +++ b/evaluations/internet-identity.json @@ -254,6 +254,16 @@ "Says an already-open page should prompt the user, for example a banner or dialog whose button runs the redirect", "Keeps the automatic redirect for the check a page runs on load, rather than calling the whole redirect approach wrong" ] + }, + { + "name": "Adversarial: @icp-sdk/auth major must pair with the core major", + "prompt": "My dapp's package.json already pins \"@icp-sdk/core\": \"^5\". Give me the npm install command to add Internet Identity sign-in. No integration code.", + "expected_behaviors": [ + "Does NOT give a bare unpinned `npm install @icp-sdk/auth` as the answer", + "States that the current @icp-sdk/auth major (10.x) peers @icp-sdk/core@^6 and therefore cannot install against a core ^5 pin", + "Offers a concrete resolution: either move core to ^6 alongside @icp-sdk/auth@^10, or stay on @icp-sdk/auth@^9 which peers core ^5", + "Does NOT recommend --legacy-peer-deps or --force to get past the peer conflict" + ] } ], "trigger_evals": { From a3ffa710a2458eca2f8b47743cdc5f73758b89fe Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 22 Sep 2026 11:46:42 +0200 Subject: [PATCH 6/8] fix: correct the core release history and what --legacy-peer-deps does Both claims were wrong, per Copilot review on #400: - "@icp-sdk/core starts at 5.x; there is no 0.x or 1.x release" is false. The registry has 12 stable 4.x releases (4.0.0-4.2.3) plus 1.x betas, which is also why @dfinity/oisy-wallet-signer@6 peers core ^4. The claim is dropped rather than corrected: an agent does not need core's version history, only the reason to pin ^6. - "--legacy-peer-deps installs two copies of core" is wrong for a PEER conflict. Verified: core@^5 + auth@^10 under that flag installs ONE core (5.4.0) beside auth 10.0.0 -- an incompatible pair, not a duplicate. It skips the peer check, so the mismatch surfaces at runtime instead of at install time. The nested-second-copy wording stays where it is accurate: vetkeys 0.5/0.6 carried core as a plain dependency, and an app on core 6 does end up with 6.1.0 at the root and 5.4.0 nested under vetkeys. Evals re-run after dropping the vestigial 0.x/1.x expectation: icp-cli 6 WITH 6/6 | WITHOUT 0/6, icp-cli 15 WITH 6/6 | WITHOUT 4/6. --- evaluations/icp-cli.json | 4 ++-- skills/icp-cli/SKILL.md | 2 +- skills/icp-cli/references/binding-generation.md | 2 +- skills/internet-identity/SKILL.md | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/evaluations/icp-cli.json b/evaluations/icp-cli.json index 6e572e4c..9500dd48 100644 --- a/evaluations/icp-cli.json +++ b/evaluations/icp-cli.json @@ -65,7 +65,7 @@ "expected_behaviors": [ "Does NOT suggest 'dfx generate' — it does not exist in icp-cli", "Recommends @icp-sdk/bindgen with a version constraint (>= 0.4.0) or references references/binding-generation.md which contains the version", - "Mentions @icp-sdk/core with an explicit major pin (^6) — does NOT leave core unpinned, and does NOT suggest a 0.x or 1.x version", + "Mentions @icp-sdk/core with an explicit major pin (^6) — does NOT leave core unpinned", "Shows npm install commands for both packages (@icp-sdk/core and @icp-sdk/bindgen)", "Mentions that the .did file must exist on disk before the frontend builds", "Shows a Vite plugin setup using icpBindgen from @icp-sdk/bindgen, or references references/binding-generation.md for the full setup" @@ -162,7 +162,7 @@ "mops.toml has a [toolchain] section (concrete moc version) and a [canisters.backend] section with a main field", "Uses 'mops generate candid backend' (or 'mops generate candid') as the command that produces the committed .did file — NOT the older 'mops build' + 'cp .mops/.build/backend.did' two-step", "Uses @icp-sdk/bindgen (>= 0.4.0) Vite plugin with a didFile path pointing to the committed .did file", - "Uses @icp-sdk/core pinned to ^6 if a version is referenced — does NOT use a 0.x or 1.x version", + "Uses @icp-sdk/core pinned to ^6 if a version is referenced", "Does NOT use dfx commands, dfx.json, .env files, or process.env for canister IDs" ] }, diff --git a/skills/icp-cli/SKILL.md b/skills/icp-cli/SKILL.md index 87059a19..79deb6f0 100644 --- a/skills/icp-cli/SKILL.md +++ b/skills/icp-cli/SKILL.md @@ -112,7 +112,7 @@ npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm 11. **Expecting `output_env_file` or `.env` with canister IDs.** dfx writes canister IDs to a `.env` file (`CANISTER_ID_BACKEND=...`) via `output_env_file`. icp-cli does not generate `.env` files. Instead, it injects canister IDs as environment variables (`PUBLIC_CANISTER_ID:`) directly into canisters during `icp deploy`. Frontends read these from the `ic_env` cookie set by the frontend canister (static-site or the legacy asset canister). Remove `output_env_file` from your config and any code that reads `CANISTER_ID_*` from `.env` — frontends use the `ic_env` cookie, and canister code reads the same variables at runtime (see Canister Environment Variables below and Pitfall 22). -12. **Expecting `dfx generate` for TypeScript bindings.** icp-cli does not have a `dfx generate` equivalent. Use `@icp-sdk/bindgen` (>= 0.4.0) with `@icp-sdk/core` pinned to `^6` (there is no 0.x or 1.x release; the rest of the SDK peers `^6`, so a different major fails to install with `ERESOLVE`) to generate TypeScript bindings from `.did` files at build time. Use `outDir: "./src/bindings"` so imports are clean (e.g., `./bindings/backend`). The `.did` file must exist on disk — either commit it to the repo, or generate it with `icp build` first (recipes auto-generate it when `candid` is not specified). See `references/binding-generation.md` for the full Vite plugin setup. +12. **Expecting `dfx generate` for TypeScript bindings.** icp-cli does not have a `dfx generate` equivalent. Use `@icp-sdk/bindgen` (>= 0.4.0) with `@icp-sdk/core` pinned to `^6` (the rest of the SDK peers `^6`, so a different major fails to install with `ERESOLVE`) to generate TypeScript bindings from `.did` files at build time. Use `outDir: "./src/bindings"` so imports are clean (e.g., `./bindings/backend`). The `.did` file must exist on disk — either commit it to the repo, or generate it with `icp build` first (recipes auto-generate it when `candid` is not specified). See `references/binding-generation.md` for the full Vite plugin setup. 13. **Passing `{ agent }` to `createActor` from `@icp-sdk/bindgen`.** The old `@dfinity/agent` pattern was `createActor(canisterId, { agent })`. The `@icp-sdk/bindgen` pattern is `createActor(canisterId, { agentOptions: { host, rootKey } })` — the binding creates the agent internally. Passing `{ agent }` to the new API **silently creates an anonymous identity** — no error is thrown, but calls return empty data or access denied. See `references/binding-generation.md` for the correct pattern. diff --git a/skills/icp-cli/references/binding-generation.md b/skills/icp-cli/references/binding-generation.md index c4635f5c..4cb77284 100644 --- a/skills/icp-cli/references/binding-generation.md +++ b/skills/icp-cli/references/binding-generation.md @@ -86,7 +86,7 @@ npm install @icp-sdk/core@^6 npm install -D @icp-sdk/bindgen@^0.4.0 ``` -**Pin `@icp-sdk/core` to `^6`; do not take `latest` or guess a lower major.** There is no 0.x or 1.x release — core starts at 5.x, and 6.x is current. `@icp-sdk/auth`, `@icp-sdk/signer`, `@icp-sdk/canisters` (>= 4) and `@dfinity/utils` (>= 5) all peer `@icp-sdk/core@^6`, so a project that lands on a different major fails to install with `ERESOLVE`. Do not use `--legacy-peer-deps` to get past that — it installs two copies of core in one tree, which degrades silently instead of failing. +**Pin `@icp-sdk/core` to `^6`; do not take `latest`.** `@icp-sdk/auth`, `@icp-sdk/signer`, `@icp-sdk/canisters` (>= 4) and `@dfinity/utils` (>= 5) all peer `@icp-sdk/core@^6`, so a project that lands on a different major fails to install with `ERESOLVE`. Do not use `--legacy-peer-deps` to get past that — it skips the peer check and installs the mismatched pair anyway, so the incompatibility surfaces at runtime instead of at install time. - The `.did` file must exist on disk before the frontend builds. The recommended workflow: generate the `.did` file once (see SKILL.md pitfall #16), commit it to the repo, and specify `candid:` in the recipe config. If `candid` is omitted, the recipe auto-generates the `.did` into the build cache at a non-deterministic path that bindgen cannot reference — so always commit the `.did` and set `candid:` when using bindgen. - `@icp-sdk/bindgen` (>= 0.4.0) emits code that imports `@icp-sdk/core`; bindgen itself depends only on `commander`, so the project installs core separately (see the pin above). Projects using `@dfinity/agent` must upgrade to `@icp-sdk/core` + `@icp-sdk/bindgen`. This is not optional — there is no way to generate TypeScript bindings with icp-cli while staying on `@dfinity/agent`. diff --git a/skills/internet-identity/SKILL.md b/skills/internet-identity/SKILL.md index 896bd118..41e5b809 100644 --- a/skills/internet-identity/SKILL.md +++ b/skills/internet-identity/SKILL.md @@ -73,7 +73,7 @@ Internet Identity (II) is the Internet Computer's native authentication system. npm error peer @icp-sdk/core@"^6" from @icp-sdk/auth@10.0.0 ``` - Do not clear it with `--legacy-peer-deps` — that installs two copies of core rather than fixing the pair. Pin `@icp-sdk/auth@^10` with `@icp-sdk/core@^6`, or stay on `@icp-sdk/auth@^9` if something else holds you on core 5. + Do not clear it with `--legacy-peer-deps` — that skips the peer check and installs the mismatched pair anyway. Pin `@icp-sdk/auth@^10` with `@icp-sdk/core@^6`, or stay on `@icp-sdk/auth@^9` if something else holds you on core 5. ## Using II during local development From 8aae1badd65feb4a8f7175375c003726112f2c8c Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 22 Sep 2026 12:00:54 +0200 Subject: [PATCH 7/8] fix: install bindgen as a dev dependency, and stop overstating vetkeys' core floor Two findings Copilot had suppressed on the first pass of #400: - dfx-migration step 2 installed @icp-sdk/bindgen as a regular dependency while binding-generation.md uses -D. bindgen is build-time only (CLI + Vite plugin), so devDependencies is right and the two references now agree. - vetkeys listed "@icp-sdk/core ^6" under "Also required", two lines below the note that 0.7 peers core as ^5 || ^6. Reworded so ^6 reads as the repo-wide anchor rather than a vetKeys requirement. No evals cover either line, so none were re-run. --- skills/icp-cli/references/dfx-migration.md | 2 +- skills/vetkeys/SKILL.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/skills/icp-cli/references/dfx-migration.md b/skills/icp-cli/references/dfx-migration.md index 32d51db8..0bd76937 100644 --- a/skills/icp-cli/references/dfx-migration.md +++ b/skills/icp-cli/references/dfx-migration.md @@ -60,7 +60,7 @@ createActor(canisterEnv?.["PUBLIC_CANISTER_ID:backend"], { Steps: 1. `npm uninstall @dfinity/agent @dfinity/candid @dfinity/principal vite-plugin-environment` -2. `npm install @icp-sdk/core@^6 @icp-sdk/bindgen@^0.4.0` — pin core explicitly; the rest of the SDK peers `^6` (see `references/binding-generation.md`) +2. `npm install @icp-sdk/core@^6` then `npm install -D @icp-sdk/bindgen@^0.4.0` — bindgen is a build-time tool, so it belongs in `devDependencies`. Pin core explicitly; the rest of the SDK peers `^6` (see `references/binding-generation.md`) 3. Delete `src/declarations/` (dfx-generated bindings) 4. Add `**/src/bindings/` to `.gitignore` 5. Commit the `.did` file(s) used by bindgen diff --git a/skills/vetkeys/SKILL.md b/skills/vetkeys/SKILL.md index 5e41cb6b..56d76075 100644 --- a/skills/vetkeys/SKILL.md +++ b/skills/vetkeys/SKILL.md @@ -24,7 +24,7 @@ Build on the maintained libraries — do not hand-roll the cryptography or the C > > Use **0.7 or later**. 0.5 and 0.6 declared `@icp-sdk/core` as a plain dependency, so they could nest a second copy of core beside the one the app installs instead of failing; 0.7 peers it as `^5 || ^6`. The API is identical across all three. -Also required: Rust `ic-cdk = "0.20"` + `ic-cdk-management-canister = "0.1"` (and `ic-dummy-getrandom-for-wasm` for IBE); Motoko `ic-vetkeys` 0.6 needs `moc ≥ 1.13.0` / `core ≥ 2.6.1`; frontend also `@icp-sdk/core ^6`. +Also required: Rust `ic-cdk = "0.20"` + `ic-cdk-management-canister = "0.1"` (and `ic-dummy-getrandom-for-wasm` for IBE); Motoko `ic-vetkeys` 0.6 needs `moc ≥ 1.13.0` / `core ≥ 2.6.1`; frontend `@icp-sdk/core ^6` — vetKeys itself accepts `^5 || ^6`, but the rest of the SDK peers `^6`. ## Which skill / which feature From 403b7e79e65aff9d20ac7cfa2641c3d670c6aded Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 22 Sep 2026 12:30:08 +0200 Subject: [PATCH 8/8] fix(vetkeys): write the core pin as @icp-sdk/core@^6 The frontend requirement was written `@icp-sdk/core ^6` with a space, which is not a valid npm package@version spec and was the only such form left in skills/. Everywhere else in this PR uses the @ form, and an agent assembling an install command may splice the string verbatim. --- skills/vetkeys/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/vetkeys/SKILL.md b/skills/vetkeys/SKILL.md index 56d76075..7a7c4002 100644 --- a/skills/vetkeys/SKILL.md +++ b/skills/vetkeys/SKILL.md @@ -24,7 +24,7 @@ Build on the maintained libraries — do not hand-roll the cryptography or the C > > Use **0.7 or later**. 0.5 and 0.6 declared `@icp-sdk/core` as a plain dependency, so they could nest a second copy of core beside the one the app installs instead of failing; 0.7 peers it as `^5 || ^6`. The API is identical across all three. -Also required: Rust `ic-cdk = "0.20"` + `ic-cdk-management-canister = "0.1"` (and `ic-dummy-getrandom-for-wasm` for IBE); Motoko `ic-vetkeys` 0.6 needs `moc ≥ 1.13.0` / `core ≥ 2.6.1`; frontend `@icp-sdk/core ^6` — vetKeys itself accepts `^5 || ^6`, but the rest of the SDK peers `^6`. +Also required: Rust `ic-cdk = "0.20"` + `ic-cdk-management-canister = "0.1"` (and `ic-dummy-getrandom-for-wasm` for IBE); Motoko `ic-vetkeys` 0.6 needs `moc ≥ 1.13.0` / `core ≥ 2.6.1`; frontend `@icp-sdk/core@^6` — vetKeys itself accepts `^5 || ^6`, but the rest of the SDK peers `^6`. ## Which skill / which feature