Skip to content

Commit 0b38e43

Browse files
authored
feat: adopt icp.net as the mainnet HTTP gateway domain (icp-cli v1.0.0) (#226)
* feat: adopt icp.net as the mainnet HTTP gateway domain icp-cli v1.0.0 changed the default mainnet HTTP gateway from icp0.io to icp.net (browser canister access is now <id>.icp.net / raw.icp.net). Reconcile the skills with the developer-docs change. Changed (gateway / browser canister-access URLs): - asset-canister: mainnet access URL, certified/raw serving domains, browser-open URL; also fix the Node.js HttpAgent host from the legacy ic0.app access domain to the canonical API endpoint icp-api.io - custom-domains: default access URL, ic-domains fetch URLs, and the custom-domains/v1 REST API host (confirmed to resolve on icp.net) - certified-variables: mainnet curl example - deploy-to-cloud-engine: frontend canister access URL - internet-identity: illustrative frontend_origins / TRUSTED_ORIGIN Kept (NOT the gateway): - API/agent hosts stay icp-api.io (icp.net is gateway-only) - Named dapp domains (nns.ic0.app) in icp-cli and agent-web-identity - II delegation rewriting pitfall: II still rewrites icp0.io -> ic0.app at the protocol level (per the II spec, unchanged by the gateway rename), so that behavioral claim stays as-is rather than becoming a meaningless icp.net-vs-icp.net statement Evals: updated custom-domains API-endpoint and asset-canister host assertions; added an asset-canister guard that the mainnet browser URL is <id>.icp.net (not ic0.app/icp0.io/icp-api.io). Both new/updated cases pass with the skill. Closes #224 * fix(custom-domains): correct HttpAgent host guidance (Raymond's review) The pitfall and HttpAgent Configuration section claimed the agent "cannot auto-detect the IC API host on custom domains, so you must set host explicitly." That is inaccurate for recent agents. Per @icp-sdk/core determineHost(), an omitted host defaults to the local replica for localhost/127.0.0.1 origins and to https://icp-api.io for every other origin — including custom domains and icp.net. So on a custom domain you do not need to set host; it already resolves to the mainnet API boundary nodes. Reframe the pitfall around the real mistake: pointing host at the custom domain (or window.location.origin), which serves only the HTTP gateway, not /api/v2, so calls fail. Update the eval case to match. * docs(custom-domains): make HttpAgent host instructions crystal clear Rewrite the HttpAgent Configuration section with an explicit resolution table (what an omitted host resolves to per runtime) and a per-scenario "what to do" list covering all cases: - mainnet browser frontend (icp.net / custom domain / legacy gateways): leave host unset → resolves to icp-api.io - mainnet Node.js: unset or icp-api.io - local dev in the browser on *.localhost: unset (auto-detects replica) - local dev in Node.js: MUST set host to the local replica URL — no window.location to detect localhost, so omitted host hits mainnet - non-mainnet/custom network: MUST set host to that network's API endpoint — the icp-api.io default points at mainnet Clarify that host is the API endpoint, never the gateway domain the frontend is served from. Tighten pitfall 8 to point at the section. Add an eval for the Node.js-local case (omitted host silently hits mainnet), the most counterintuitive of the must-set-host scenarios. * docs(custom-domains): scope HttpAgent host guidance to mainnet only The custom-domains skill covers only the mainnet IC custom-domains service — you cannot have a custom domain on a local replica or a custom network. Drop the local-development and custom-network HttpAgent cases (and the resolution table) added in the previous commit; they were out of scope and diluted the skill. The section now states only what is relevant: a mainnet frontend served from a custom domain does not need host set (it resolves to icp-api.io), and must never point host at the gateway/custom domain. Remove the matching out-of-scope Node.js-local eval case.
1 parent a63f551 commit 0b38e43

7 files changed

Lines changed: 50 additions & 37 deletions

File tree

evaluations/asset-canister.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@
33
"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.",
44

55
"output_evals": [
6+
{
7+
"name": "Mainnet browser-access domain is icp.net",
8+
"prompt": "I deployed my asset (frontend) canister to mainnet. What URL do I open in the browser to view it? Just the URL pattern.",
9+
"expected_behaviors": [
10+
"Gives the URL as https://<canister-id>.icp.net",
11+
"Does NOT use the legacy ic0.app or icp0.io gateway domains",
12+
"Does NOT use icp-api.io (that is the API endpoint, not the browser gateway)"
13+
]
14+
},
615
{
716
"name": "Programmatic asset upload",
817
"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.",
@@ -49,7 +58,7 @@
4958
"expected_behaviors": [
5059
"Sets shouldFetchRootKey to true ONLY for local development",
5160
"Explicitly warns that shouldFetchRootKey on mainnet is a security vulnerability",
52-
"Uses different host values for local (localhost:8000) vs mainnet (ic0.app or icp-api.io)"
61+
"Uses different host values for local (localhost:8000) vs mainnet (icp-api.io)"
5362
]
5463
}
5564
],

evaluations/custom-domains.json

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
"name": "Domain registration API",
1818
"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.",
1919
"expected_behaviors": [
20-
"Uses the validate endpoint: GET https://icp0.io/custom-domains/v1/app.example.com/validate",
21-
"Uses the registration endpoint: POST https://icp0.io/custom-domains/v1/app.example.com",
22-
"Uses the status check endpoint: GET https://icp0.io/custom-domains/v1/app.example.com",
20+
"Uses the validate endpoint: GET https://icp.net/custom-domains/v1/app.example.com/validate",
21+
"Uses the registration endpoint: POST https://icp.net/custom-domains/v1/app.example.com",
22+
"Uses the status check endpoint: GET https://icp.net/custom-domains/v1/app.example.com",
2323
"Does NOT invent non-existent API endpoints or parameters"
2424
]
2525
},
@@ -53,19 +53,19 @@
5353
},
5454
{
5555
"name": "HttpAgent host configuration",
56-
"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.",
56+
"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.",
5757
"expected_behaviors": [
58-
"Identifies that HttpAgent cannot auto-detect the IC API host on custom domains",
59-
"Shows setting host to https://icp-api.io for mainnet",
60-
"Shows HttpAgent.create({ host }) or equivalent configuration"
58+
"Identifies that the custom domain serves only the HTTP gateway, not the /api/v2 API endpoint, so host must not point at it",
59+
"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",
60+
"Does NOT recommend setting host to the custom domain or window.location.origin"
6161
]
6262
},
6363
{
6464
"name": "Domain update flow",
6565
"prompt": "I want to point my existing custom domain to a different canister. What do I need to do? Just the steps.",
6666
"expected_behaviors": [
6767
"Updates the _canister-id TXT record to the new canister ID",
68-
"Uses PATCH https://icp0.io/custom-domains/v1/DOMAIN to notify the service",
68+
"Uses PATCH https://icp.net/custom-domains/v1/DOMAIN to notify the service",
6969
"Does NOT say to delete and re-register the domain"
7070
]
7171
},
@@ -74,7 +74,7 @@
7474
"prompt": "How do I remove a custom domain registration from the IC? Just the steps.",
7575
"expected_behaviors": [
7676
"Removes the _canister-id TXT and _acme-challenge CNAME DNS records",
77-
"Uses DELETE https://icp0.io/custom-domains/v1/DOMAIN to notify the service",
77+
"Uses DELETE https://icp.net/custom-domains/v1/DOMAIN to notify the service",
7878
"Does NOT suggest only removing DNS records without calling the API"
7979
]
8080
}

skills/asset-canister/SKILL.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Access patterns:
2626
| Environment | URL Pattern |
2727
|-------------|-------------|
2828
| Local | `http://<canister-id>.localhost:8000` |
29-
| Mainnet | `https://<canister-id>.ic0.app` or `https://<canister-id>.icp0.io` |
29+
| Mainnet | `https://<canister-id>.icp.net` |
3030
| Custom domain | `https://yourdomain.com` (with DNS configuration) |
3131

3232
## Mistakes That Break Your Build
@@ -45,7 +45,7 @@ Access patterns:
4545

4646
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.
4747

48-
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.
48+
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.
4949

5050
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.
5151

@@ -155,7 +155,7 @@ import { readFileSync, readdirSync } from "fs";
155155
// For browser frontends, use rootKey from safeGetCanisterEnv() instead (see
156156
// the internet-identity skill or icp-cli/references/binding-generation.md).
157157
const LOCAL_REPLICA = "http://localhost:8000";
158-
const MAINNET = "https://ic0.app";
158+
const MAINNET = "https://icp-api.io";
159159
const host = LOCAL_REPLICA; // Change to MAINNET for production
160160
161161
async function manageAssets() {
@@ -291,7 +291,7 @@ icp canister call frontend http_request '(record {
291291

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

296296
# 6. Get canister ID
297297
icp canister id frontend

skills/certified-variables/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,7 @@ icp canister call backend get '("nonexistent")'
476476
# Console should NOT show "Certificate verification failed" errors
477477

478478
# 6. For HTTP certification (custom HTTP canister):
479-
curl -v https://CANISTER_ID.ic0.app/path
479+
curl -v https://CANISTER_ID.icp.net/path
480480
# Expected: Response headers include IC-Certificate
481481
# HTTP gateway verifies the certificate before forwarding to client
482482
```

skills/custom-domains/SKILL.md

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ metadata:
1212

1313
## What This Is
1414

15-
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.
15+
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.
1616

1717
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`.
1818

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

4141
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.
4242

43-
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.
43+
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.
4444

4545
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.
4646

@@ -78,14 +78,14 @@ www.example.com
7878

7979
### Step 3: Deploy
8080

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

8383
### Step 4: Validate
8484

8585
Check DNS records and canister configuration before registering:
8686

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

9191
Success response:
@@ -116,7 +116,7 @@ If validation fails, common errors and fixes:
116116
### Step 5: Register
117117

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

122122
Success response:
@@ -137,7 +137,7 @@ Success response:
137137
Poll until `registration_status` is `registered`:
138138

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

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

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

158158
3. Check status:
159159

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

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

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

173173
3. Confirm deletion (should return 404):
174174

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

179179
## HttpAgent Configuration
180180

181-
On custom domains, the agent cannot auto-detect the IC API host. Set it explicitly:
181+
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`.
182+
183+
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.
182184

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

186-
const isProduction = process.env.NODE_ENV === "production";
187-
const host = isProduction ? "https://icp-api.io" : undefined;
188-
const agent = await HttpAgent.create({ host });
188+
// host omitted on a custom domain → resolves to https://icp-api.io
189+
const agent = await HttpAgent.create();
190+
191+
// equivalent, explicit:
192+
const agentExplicit = await HttpAgent.create({ host: "https://icp-api.io" });
189193
```
190194

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

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

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

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

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

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

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

227231
# 4. Verify the custom domain serves your canister

skills/deploy-to-cloud-engine/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ icp deploy -e ic --subnet <subnet-id>
160160
- The `icp deploy` output reports the deployed canister ids.
161161
- The canisters appear on the engine's **Applications** page in the console; each canister's detail view offers an "Open in browser" link.
162162
- 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.
163-
- A frontend (asset) canister is served at `https://<frontend-canister-id>.icp0.io`.
163+
- A frontend (asset) canister is served at `https://<frontend-canister-id>.icp.net`.
164164

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

skills/internet-identity/SKILL.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ canisters:
311311
# II backend principal (required). List your local II principal too if tests run against it.
312312
trusted_attribute_signers: "rdmx6-jaaaa-aaaaa-aaadq-cai"
313313
# Allowed frontend origins, comma-separated (required).
314-
frontend_origins: "https://your-app.icp0.io"
314+
frontend_origins: "https://your-app.icp.net"
315315
# Trusted SSO domains, comma-separated (optional; omit to reject all sso:* keys).
316316
trusted_sso_domains: "your-org.com"
317317
```
@@ -335,7 +335,7 @@ use std::cell::RefCell;
335335
use std::collections::HashSet;
336336
337337
const II_PRINCIPAL: &str = "rdmx6-jaaaa-aaaaa-aaadq-cai";
338-
const TRUSTED_ORIGIN: &str = "https://your-app.icp0.io";
338+
const TRUSTED_ORIGIN: &str = "https://your-app.icp.net";
339339
const FRESHNESS_NS: u64 = 300_000_000_000; // 5 minutes
340340
341341
thread_local! {

0 commit comments

Comments
 (0)