From f0ff0dc6003134a734f0cf0071d97c5f27a6d15d Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 08:41:57 +0000 Subject: [PATCH] fix(deploy): record production's canister ids, and never create on mainnet `.gitignore` left out all of `.icp/`, so the mainnet canister ids lived only on the machine that deployed. From any other clone, `icp deploy -e production` finds no ids and creates new canisters. So does `-e ic`, icp-cli's built-in mainnet environment, which is what happened on 2026-10-27: it stopped only because the cycles balance was below the 2 T a creation asks for. A new frontend is a new origin, which gives every user a new principal. - Commit `.icp/data/mappings/production.ids.json`, written by `icp canister link` (icp-cli 1.6.0), and ignore only `.icp/cache/`, as icp-cli's docs advise and b3pay-hub does. - Deploy with `--no-create` in the README, `icp.yaml`'s header and `pnpm deploy:production`, so a missing id stops the deploy, and say why not to use `-e ic`. - `tests/production.test.ts` checks the recorded ids against `origins.ts`. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017cSwEz3Q2o3DpuJ8qSQzY5 --- .gitignore | 5 +++-- .icp/data/mappings/production.ids.json | 4 ++++ README.md | 10 +++++++++- frontend/tests/production.test.ts | 24 ++++++++++++++++++++++++ icp.yaml | 3 ++- package.json | 2 +- 6 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 .icp/data/mappings/production.ids.json create mode 100644 frontend/tests/production.test.ts diff --git a/.gitignore b/.gitignore index faf60ac..2fc1ebf 100755 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,9 @@ dist/ *.tsbuildinfo coverage/ -# icp-cli / dfx state -.icp/ +# icp-cli / dfx state. `.icp/data/` holds the mainnet canister ids and is +# committed, as icp-cli advises: without it, a deploy creates new canisters. +.icp/cache/ .dfx/ # Environment diff --git a/.icp/data/mappings/production.ids.json b/.icp/data/mappings/production.ids.json new file mode 100644 index 0000000..0883d7b --- /dev/null +++ b/.icp/data/mappings/production.ids.json @@ -0,0 +1,4 @@ +{ + "backend": "xeka7-ryaaa-aaaal-qb57a-cai", + "frontend": "4lidq-zqaaa-aaaap-abkbq-cai" +} \ No newline at end of file diff --git a/README.md b/README.md index 0990f09..bbabe07 100755 --- a/README.md +++ b/README.md @@ -156,9 +156,17 @@ backend uses it automatically. ## Deploying to mainnet ```bash -icp deploy -e production +icp deploy -e production --no-create # both canisters +icp deploy frontend -e production --no-create # the frontend only ``` +The deploy upgrades the canisters recorded in `.icp/data/mappings/production.ids.json`: backend +`xeka7-ryaaa-aaaal-qb57a-cai` and frontend `4lidq-zqaaa-aaaap-abkbq-cai`. `--no-create` makes it stop +rather than create a canister that is not recorded there. Do not deploy with `-e ic`: that is icp-cli's +built-in mainnet environment, which records none of these ids, so it creates new canisters. A new +frontend is a new origin, which gives every user a new principal, and the custom domain would still +point at the old one. + The `production` environment uses the vetKD key `key_1`, keeps the AI assistant off and sets strict limits (`icp.yaml`). The frontend reads the backend's id from the asset canister's `ic_env` cookie on `*.icp0.io`, and from `frontend/src/lib/origins.ts` on a custom domain ([Custom domain](#custom-domain)). diff --git a/frontend/tests/production.test.ts b/frontend/tests/production.test.ts new file mode 100644 index 0000000..b8810f6 --- /dev/null +++ b/frontend/tests/production.test.ts @@ -0,0 +1,24 @@ +/** + * B3Note's mainnet canisters (README, "Deploying to mainnet"). icp-cli deploys + * the `production` environment to the ids in `.icp/data/mappings`, and creates + * a new canister for any it cannot find there. A new frontend is a new origin, + * which gives every user a new principal, so the ids recorded there must stay + * the ones the app signs in for and calls. + */ +import { readFileSync } from "node:fs" +import { dirname, join } from "node:path" +import { fileURLToPath } from "node:url" +import { describe, expect, it } from "vitest" +import { PRODUCTION } from "../src/lib/origins" + +const root = join(dirname(fileURLToPath(import.meta.url)), "../..") + +describe("production's canisters", () => { + it("are recorded for icp-cli as the ones the app signs in for and calls", () => { + const ids = JSON.parse(readFileSync(join(root, ".icp/data/mappings/production.ids.json"), "utf8")) + expect(ids).toEqual({ + backend: PRODUCTION.backend, + frontend: new URL(PRODUCTION.origin).hostname.replace(/\.icp0\.io$/, ""), + }) + }) +}) diff --git a/icp.yaml b/icp.yaml index 3ee053e..6a5fddd 100644 --- a/icp.yaml +++ b/icp.yaml @@ -1,7 +1,8 @@ # yaml-language-server: $schema=https://github.com/dfinity/icp-cli/raw/refs/tags/v1.6.0/docs/schemas/icp-yaml-schema.json # # B3Note: `icp network start -d && icp deploy` for a local deployment, -# `icp deploy -e production` for mainnet. +# `icp deploy -e production --no-create` for mainnet, never `-e ic` +# (README, "Deploying to mainnet"). canisters: - name: backend diff --git a/package.json b/package.json index b8cc1df..04f5d6a 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "dev": "pnpm --dir frontend dev", "dev:pocketic": "pnpm --dir frontend dev:pocketic", "deploy": "icp deploy", - "deploy:production": "icp deploy -e production" + "deploy:production": "icp deploy -e production --no-create" }, "packageManager": "pnpm@10.28.0" }