Skip to content
Open
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
11 changes: 10 additions & 1 deletion evaluations/asset-canister.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
"description": "Evaluation cases for the asset-canister skill. Tests whether agents produce correct asset canister configuration, avoid top-level await in programmatic upload code, and handle SPA routing.",

"output_evals": [
{
"name": "Mainnet browser-access domain is icp.net",
"prompt": "I deployed my asset (frontend) canister to mainnet. What URL do I open in the browser to view it? Just the URL pattern.",
"expected_behaviors": [
"Gives the URL as https://<canister-id>.icp.net",
"Does NOT use the legacy ic0.app or icp0.io gateway domains",
"Does NOT use icp-api.io (that is the API endpoint, not the browser gateway)"
]
},
{
"name": "Programmatic asset upload",
"prompt": "Show me a Node.js script to create an HttpAgent and AssetManager, then upload a single file to my asset canister on the local replica. Keep it minimal — no icp.yaml, no deploy steps.",
Expand Down Expand Up @@ -49,7 +58,7 @@
"expected_behaviors": [
"Sets shouldFetchRootKey to true ONLY for local development",
"Explicitly warns that shouldFetchRootKey on mainnet is a security vulnerability",
"Uses different host values for local (localhost:8000) vs mainnet (ic0.app or icp-api.io)"
"Uses different host values for local (localhost:8000) vs mainnet (icp-api.io)"
]
}
],
Expand Down
18 changes: 9 additions & 9 deletions evaluations/custom-domains.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
"name": "Domain registration API",
"prompt": "I've set up my DNS records and deployed my canister with the ic-domains file. What curl commands do I run to register my custom domain app.example.com? Just the commands.",
"expected_behaviors": [
"Uses the validate endpoint: GET https://icp0.io/custom-domains/v1/app.example.com/validate",
"Uses the registration endpoint: POST https://icp0.io/custom-domains/v1/app.example.com",
"Uses the status check endpoint: GET https://icp0.io/custom-domains/v1/app.example.com",
"Uses the validate endpoint: GET https://icp.net/custom-domains/v1/app.example.com/validate",
"Uses the registration endpoint: POST https://icp.net/custom-domains/v1/app.example.com",
"Uses the status check endpoint: GET https://icp.net/custom-domains/v1/app.example.com",
"Does NOT invent non-existent API endpoints or parameters"
]
},
Expand Down Expand Up @@ -53,19 +53,19 @@
},
{
"name": "HttpAgent host configuration",
"prompt": "My IC frontend works fine on icp0.io but API calls fail when I access it through my custom domain. What's wrong? Just the fix.",
"prompt": "My IC frontend's canister calls fail when it's served from my custom domain, though they work on icp.net. I set the HttpAgent host to my custom domain. What's wrong? Just the fix.",
"expected_behaviors": [
"Identifies that HttpAgent cannot auto-detect the IC API host on custom domains",
"Shows setting host to https://icp-api.io for mainnet",
"Shows HttpAgent.create({ host }) or equivalent configuration"
"Identifies that the custom domain serves only the HTTP gateway, not the /api/v2 API endpoint, so host must not point at it",
"Says to leave host unset (a recent agent defaults it to https://icp-api.io on a custom domain) or set it explicitly to https://icp-api.io",
"Does NOT recommend setting host to the custom domain or window.location.origin"
]
},
{
"name": "Domain update flow",
"prompt": "I want to point my existing custom domain to a different canister. What do I need to do? Just the steps.",
"expected_behaviors": [
"Updates the _canister-id TXT record to the new canister ID",
"Uses PATCH https://icp0.io/custom-domains/v1/DOMAIN to notify the service",
"Uses PATCH https://icp.net/custom-domains/v1/DOMAIN to notify the service",
"Does NOT say to delete and re-register the domain"
]
},
Expand All @@ -74,7 +74,7 @@
"prompt": "How do I remove a custom domain registration from the IC? Just the steps.",
"expected_behaviors": [
"Removes the _canister-id TXT and _acme-challenge CNAME DNS records",
"Uses DELETE https://icp0.io/custom-domains/v1/DOMAIN to notify the service",
"Uses DELETE https://icp.net/custom-domains/v1/DOMAIN to notify the service",
"Does NOT suggest only removing DNS records without calling the API"
]
}
Expand Down
8 changes: 4 additions & 4 deletions skills/asset-canister/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Access patterns:
| Environment | URL Pattern |
|-------------|-------------|
| Local | `http://<canister-id>.localhost:8000` |
| Mainnet | `https://<canister-id>.ic0.app` or `https://<canister-id>.icp0.io` |
| Mainnet | `https://<canister-id>.icp.net` |
| Custom domain | `https://yourdomain.com` (with DNS configuration) |

## Mistakes That Break Your Build
Expand All @@ -45,7 +45,7 @@ Access patterns:

7. **Pinning the asset canister Wasm version below `0.30.2`.** The `ic_env` cookie (used by `safeGetCanisterEnv()` from `@icp-sdk/core` to read canister IDs and the root key at runtime) is only served by asset canister Wasm versions >= `0.30.2`. The Wasm version is set via `configuration.version` in the recipe, independently of the recipe version itself. If you pin an older Wasm version, the cookie is silently missing and frontend code relying on `ic_env` will fail. Either omit `configuration.version` (latest is used) or pin to `0.30.2` or later.

8. **Not configuring `allow_raw_access` correctly.** The asset canister has two serving modes: certified (via `ic0.app` / `icp0.io`, where HTTP gateways verify response integrity) and raw (via `raw.ic0.app` / `raw.icp0.io`, where no verification occurs). By default, `allow_raw_access` is `true`, meaning assets are also available on the raw domain. On the raw domain, boundary nodes or a network-level attacker can tamper with response content undetected. Set `"allow_raw_access": false` in `.ic-assets.json5` for any sensitive assets. Only enable raw access when strictly needed.
8. **Not configuring `allow_raw_access` correctly.** The asset canister has two serving modes: certified (via `icp.net`, where HTTP gateways verify response integrity) and raw (via `raw.icp.net`, where no verification occurs). By default, `allow_raw_access` is `true`, meaning assets are also available on the raw domain. On the raw domain, boundary nodes or a network-level attacker can tamper with response content undetected. Set `"allow_raw_access": false` in `.ic-assets.json5` for any sensitive assets. Only enable raw access when strictly needed.

9. **Downgrading the asset canister WASM version.** Upgrading a canister to an older WASM version can fail with "Cannot parse header" panics if the stable memory format changed between versions. Prefer the `@dfinity/asset-canister` recipe over `type: pre-built` with a manually specified WASM URL — the recipe loads the latest asset canister version automatically if not explicitly specified in `configuration.version`. If you must pin a version, ensure it matches or exceeds the version currently deployed on-chain. If a downgrade is intentional, use reinstall mode (`icp deploy --mode reinstall`) instead of upgrade — this wipes stable memory and all uploaded assets.

Expand Down Expand Up @@ -155,7 +155,7 @@ import { readFileSync, readdirSync } from "fs";
// For browser frontends, use rootKey from safeGetCanisterEnv() instead (see
// the internet-identity skill or icp-cli/references/binding-generation.md).
const LOCAL_REPLICA = "http://localhost:8000";
const MAINNET = "https://ic0.app";
const MAINNET = "https://icp-api.io";
const host = LOCAL_REPLICA; // Change to MAINNET for production

async function manageAssets() {
Expand Down Expand Up @@ -291,7 +291,7 @@ icp canister call frontend http_request '(record {

# 5. Open in browser
# Local: http://<frontend-canister-id>.localhost:8000
# Mainnet: https://<frontend-canister-id>.ic0.app
# Mainnet: https://<frontend-canister-id>.icp.net

# 6. Get canister ID
icp canister id frontend
Expand Down
2 changes: 1 addition & 1 deletion skills/certified-variables/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ icp canister call backend get '("nonexistent")'
# Console should NOT show "Certificate verification failed" errors

# 6. For HTTP certification (custom HTTP canister):
curl -v https://CANISTER_ID.ic0.app/path
curl -v https://CANISTER_ID.icp.net/path
# Expected: Response headers include IC-Certificate
# HTTP gateway verifies the certificate before forwarding to client
```
42 changes: 23 additions & 19 deletions skills/custom-domains/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ metadata:

## What This Is

By default, canisters are accessible at `<canister-id>.icp0.io`. The custom domains service lets you serve any canister under your own domain (e.g., `yourdomain.com`). You configure DNS, deploy a domain ownership file to your canister, and register via a REST API. The HTTP gateways then handle TLS certificate provisioning, renewal, and routing automatically.
By default, canisters are accessible at `<canister-id>.icp.net`. The custom domains service lets you serve any canister under your own domain (e.g., `yourdomain.com`). You configure DNS, deploy a domain ownership file to your canister, and register via a REST API. The HTTP gateways then handle TLS certificate provisioning, renewal, and routing automatically.

Custom domains work at the boundary node level — they map a domain to any canister ID via DNS. This works with any canister that can serve `/.well-known/ic-domains` over HTTP, not just asset canisters. That includes asset canisters, Juno satellites, and custom canisters implementing `http_request`.

Expand Down Expand Up @@ -40,7 +40,7 @@ Custom domains work at the boundary node level — they map a domain to any cani

7. **Not explicitly registering the domain.** DNS configuration alone is not enough. You must call `POST /custom-domains/v1/CUSTOM_DOMAIN` to start registration. It is not automatic.

8. **Not setting `host` in HttpAgent on custom domains.** When serving from a custom domain, the `HttpAgent` cannot automatically infer the IC API host like it can on `icp0.io`. You must set `host: "https://icp-api.io"` explicitly for mainnet.
8. **Setting `HttpAgent`'s `host` to your custom domain.** `host` is the **API endpoint** canister calls go to, not the domain your frontend is served from. Your custom domain is the HTTP gateway — it does not serve `/api/v2`, so pointing `host` at it (or at `window.location.origin`) makes calls fail. You do not need to set `host`: a recent `@icp-sdk/core` `HttpAgent` resolves an omitted `host` to `https://icp-api.io` (the mainnet API boundary nodes) on a custom domain. Leave it unset, or set it explicitly to `https://icp-api.io` — never the gateway domain.

9. **Forgetting alternative origins for Internet Identity.** II principals depend on the origin domain. Switching from a canister URL to a custom domain changes principals. Configure `.well-known/ii-alternative-origins` to keep the same principals. See the `internet-identity` skill.

Expand Down Expand Up @@ -78,14 +78,14 @@ www.example.com

### Step 3: Deploy

Deploy your canister so that `/.well-known/ic-domains` is accessible at `https://<canister-id>.icp0.io/.well-known/ic-domains`.
Deploy your canister so that `/.well-known/ic-domains` is accessible at `https://<canister-id>.icp.net/.well-known/ic-domains`.

### Step 4: Validate

Check DNS records and canister configuration before registering:

```bash
curl -sL -X GET "https://icp0.io/custom-domains/v1/CUSTOM_DOMAIN/validate" | jq
curl -sL -X GET "https://icp.net/custom-domains/v1/CUSTOM_DOMAIN/validate" | jq
```

Success response:
Expand Down Expand Up @@ -116,7 +116,7 @@ If validation fails, common errors and fixes:
### Step 5: Register

```bash
curl -sL -X POST "https://icp0.io/custom-domains/v1/CUSTOM_DOMAIN" | jq
curl -sL -X POST "https://icp.net/custom-domains/v1/CUSTOM_DOMAIN" | jq
```

Success response:
Expand All @@ -137,7 +137,7 @@ Success response:
Poll until `registration_status` is `registered`:

```bash
curl -sL -X GET "https://icp0.io/custom-domains/v1/CUSTOM_DOMAIN" | jq
curl -sL -X GET "https://icp.net/custom-domains/v1/CUSTOM_DOMAIN" | jq
```

Status values: `registering` → `registered` (success), or `failed` (check error message).
Expand All @@ -152,13 +152,13 @@ To point an existing custom domain at a different canister:
2. Notify the service:

```bash
curl -sL -X PATCH "https://icp0.io/custom-domains/v1/CUSTOM_DOMAIN" | jq
curl -sL -X PATCH "https://icp.net/custom-domains/v1/CUSTOM_DOMAIN" | jq
```

3. Check status:

```bash
curl -sL -X GET "https://icp0.io/custom-domains/v1/CUSTOM_DOMAIN" | jq
curl -sL -X GET "https://icp.net/custom-domains/v1/CUSTOM_DOMAIN" | jq
```

## Removing a Custom Domain
Expand All @@ -167,25 +167,29 @@ curl -sL -X GET "https://icp0.io/custom-domains/v1/CUSTOM_DOMAIN" | jq
2. Notify the service:

```bash
curl -sL -X DELETE "https://icp0.io/custom-domains/v1/CUSTOM_DOMAIN" | jq
curl -sL -X DELETE "https://icp.net/custom-domains/v1/CUSTOM_DOMAIN" | jq
```

3. Confirm deletion (should return 404):

```bash
curl -sL -X GET "https://icp0.io/custom-domains/v1/CUSTOM_DOMAIN" | jq
curl -sL -X GET "https://icp.net/custom-domains/v1/CUSTOM_DOMAIN" | jq
```

## HttpAgent Configuration

On custom domains, the agent cannot auto-detect the IC API host. Set it explicitly:
A frontend served from your custom domain still makes its canister calls through the mainnet **API boundary nodes** (`https://icp-api.io`), not through the domain it is served from. `HttpAgent`'s `host` is that API endpoint — *not* your frontend's origin. The custom domain is the HTTP gateway and does not serve `/api/v2`.

You do not need to set `host`. When it is omitted, a recent `@icp-sdk/core` `HttpAgent` resolves to `https://icp-api.io` on a custom domain (and on `icp.net`). Do **not** point `host` at your custom domain or `window.location.origin` — that is the gateway, so calls would fail.

```typescript
import { HttpAgent } from "@icp-sdk/core/agent";

const isProduction = process.env.NODE_ENV === "production";
const host = isProduction ? "https://icp-api.io" : undefined;
const agent = await HttpAgent.create({ host });
// host omitted on a custom domain → resolves to https://icp-api.io
const agent = await HttpAgent.create();

// equivalent, explicit:
const agentExplicit = await HttpAgent.create({ host: "https://icp-api.io" });
```

## Deploy & Test
Expand All @@ -194,13 +198,13 @@ const agent = await HttpAgent.create({ host });
# 1. Deploy your canister with the ic-domains file served at /.well-known/ic-domains

# 2. Validate DNS + canister config
curl -sL -X GET "https://icp0.io/custom-domains/v1/yourdomain.com/validate" | jq
curl -sL -X GET "https://icp.net/custom-domains/v1/yourdomain.com/validate" | jq

# 3. Register
curl -sL -X POST "https://icp0.io/custom-domains/v1/yourdomain.com" | jq
curl -sL -X POST "https://icp.net/custom-domains/v1/yourdomain.com" | jq

# 4. Poll until registered
curl -sL -X GET "https://icp0.io/custom-domains/v1/yourdomain.com" | jq
curl -sL -X GET "https://icp.net/custom-domains/v1/yourdomain.com" | jq
```

## Verify It Works
Expand All @@ -217,11 +221,11 @@ dig CNAME _acme-challenge.yourdomain.com
# Expected: _acme-challenge.yourdomain.com. CNAME _acme-challenge.yourdomain.com.icp2.io.

# 2. Verify ic-domains file is served by the canister
curl -sL "https://<canister-id>.icp0.io/.well-known/ic-domains"
curl -sL "https://<canister-id>.icp.net/.well-known/ic-domains"
# Expected: your domain listed

# 3. Verify registration status is "registered"
curl -sL -X GET "https://icp0.io/custom-domains/v1/yourdomain.com" | jq '.data.registration_status'
curl -sL -X GET "https://icp.net/custom-domains/v1/yourdomain.com" | jq '.data.registration_status'
# Expected: "registered"

# 4. Verify the custom domain serves your canister
Expand Down
2 changes: 1 addition & 1 deletion skills/deploy-to-cloud-engine/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ icp deploy -e ic --subnet <subnet-id>
- The `icp deploy` output reports the deployed canister ids.
- The canisters appear on the engine's **Applications** page in the console; each canister's detail view offers an "Open in browser" link.
- If you set the metadata in Step 2, the canisters are grouped under your `__META_PROJECT` name with their `__META_NAME` labels, and the main canister shows an "Open" button — instead of bare principal rows.
- A frontend (asset) canister is served at `https://<frontend-canister-id>.icp0.io`.
- A frontend (asset) canister is served at `https://<frontend-canister-id>.icp.net`.

Report the deployed canister ids (and the frontend URL, if any) back to the user.

Expand Down
4 changes: 2 additions & 2 deletions skills/internet-identity/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ canisters:
# II backend principal (required). List your local II principal too if tests run against it.
trusted_attribute_signers: "rdmx6-jaaaa-aaaaa-aaadq-cai"
# Allowed frontend origins, comma-separated (required).
frontend_origins: "https://your-app.icp0.io"
frontend_origins: "https://your-app.icp.net"
# Trusted SSO domains, comma-separated (optional; omit to reject all sso:* keys).
trusted_sso_domains: "your-org.com"
```
Expand All @@ -335,7 +335,7 @@ use std::cell::RefCell;
use std::collections::HashSet;

const II_PRINCIPAL: &str = "rdmx6-jaaaa-aaaaa-aaadq-cai";
const TRUSTED_ORIGIN: &str = "https://your-app.icp0.io";
const TRUSTED_ORIGIN: &str = "https://your-app.icp.net";
const FRESHNESS_NS: u64 = 300_000_000_000; // 5 minutes

thread_local! {
Expand Down
Loading