@ic-reactor/mcp 0.0.0-stage → 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/bug_report.yml +108 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +77 -0
- package/.github/actions/setup-rust-wasm/action.yml +25 -0
- package/.github/dependabot.yml +107 -0
- package/.github/workflows/auth-latest.yml +67 -0
- package/.github/workflows/b3pay-site.yml +49 -0
- package/.github/workflows/ci.yml +430 -0
- package/.github/workflows/codeql.yml +41 -0
- package/.github/workflows/dependency-review.yml +68 -0
- package/.github/workflows/docs-check.yml +98 -0
- package/.github/workflows/docs.yml +265 -0
- package/.github/workflows/e2e.yml +90 -0
- package/.github/workflows/examples-published.yml +97 -0
- package/.github/workflows/release.yml +326 -0
- package/.husky/pre-commit +12 -0
- package/.prettierignore +109 -0
- package/.prettierrc +6 -0
- package/AGENTS.md +103 -0
- package/CHANGELOG.md +3268 -0
- package/CLAUDE.md +93 -0
- package/CNAME +1 -0
- package/CODE_OF_CONDUCT.md +7 -0
- package/CONTRIBUTING.md +433 -0
- package/LICENSE +21 -0
- package/README.md +2 -2
- package/SECURITY.md +74 -0
- package/b3pay-module.json +194 -0
- package/docs/.remarkrc.json +10 -0
- package/docs/README.md +64 -0
- package/docs/astro.config.mjs +205 -0
- package/docs/favicon.svg +15 -0
- package/docs/package.json +39 -0
- package/docs/public/favicon.svg +15 -0
- package/docs/public/ic-reactor-social-card.png +0 -0
- package/docs/scripts/b3pay-static.mjs +22 -0
- package/docs/scripts/check-example-docs.mjs +214 -0
- package/docs/scripts/check-links.mjs +212 -0
- package/docs/scripts/fix-dist-links.js +125 -0
- package/docs/scripts/fix-frontmatter.js +71 -0
- package/docs/scripts/remove-redundant-h1.cjs +48 -0
- package/docs/scripts/rewrite-typedoc-links.js +110 -0
- package/docs/scripts/split-index.js +69 -0
- package/docs/src/assets/ic-reactor-hero-dark.svg +23 -0
- package/docs/src/assets/ic-reactor-hero-light.svg +23 -0
- package/docs/src/assets/ic-reactor-lockup-dark.svg +15 -0
- package/docs/src/assets/ic-reactor-lockup-light.svg +15 -0
- package/docs/src/assets/ic-reactor-mark-dark.svg +14 -0
- package/docs/src/assets/ic-reactor-mark-light.svg +14 -0
- package/docs/src/assets/layers/candid-core-layer-icon-dark.svg +4 -0
- package/docs/src/assets/layers/candid-core-layer-icon-light.svg +4 -0
- package/docs/src/assets/layers/candid-profile-layer-icon-dark.svg +4 -0
- package/docs/src/assets/layers/candid-profile-layer-icon-light.svg +4 -0
- package/docs/src/assets/layers/ic-reactor-layer-icon-dark.svg +4 -0
- package/docs/src/assets/layers/ic-reactor-layer-icon-light.svg +4 -0
- package/docs/src/components/LibraryCards.astro +172 -0
- package/docs/src/components/LibraryIcon.astro +28 -0
- package/docs/src/components/SiteTitle.astro +241 -0
- package/docs/src/content/docs/404.mdx +19 -0
- package/docs/src/content/docs/build/agent-tools.mdx +593 -0
- package/docs/src/content/docs/build/icrc1-wallet.mdx +619 -0
- package/docs/src/content/docs/build/ssr-per-user.mdx +552 -0
- package/docs/src/content/docs/examples/canister-studio.mdx +95 -0
- package/docs/src/content/docs/examples/icrc-ledger.mdx +45 -0
- package/docs/src/content/docs/examples/index.mdx +38 -0
- package/docs/src/content/docs/examples/next-ssr.mdx +73 -0
- package/docs/src/content/docs/examples/node-agent-tool.mdx +80 -0
- package/docs/src/content/docs/examples/vite-wallet.mdx +71 -0
- package/docs/src/content/docs/guides/auth.mdx +1181 -0
- package/docs/src/content/docs/guides/client.mdx +551 -0
- package/docs/src/content/docs/guides/deploy.mdx +180 -0
- package/docs/src/content/docs/guides/errors.mdx +475 -0
- package/docs/src/content/docs/guides/getting-started.mdx +620 -0
- package/docs/src/content/docs/guides/reads.mdx +586 -0
- package/docs/src/content/docs/guides/ssr.mdx +529 -0
- package/docs/src/content/docs/guides/testing.mdx +401 -0
- package/docs/src/content/docs/guides/values.mdx +261 -0
- package/docs/src/content/docs/guides/vite-plugin.mdx +690 -0
- package/docs/src/content/docs/guides/writes.mdx +572 -0
- package/docs/src/content/docs/ic-reactor/index.mdx +167 -0
- package/docs/src/content/docs/index.mdx +65 -0
- package/docs/src/content/docs/migrating-from-3.mdx +535 -0
- package/docs/src/content/docs/packages/core.mdx +186 -0
- package/docs/src/content/docs/packages/mcp.mdx +163 -0
- package/docs/src/content/docs/packages/react.mdx +207 -0
- package/docs/src/content/docs/packages/vite-plugin.mdx +73 -0
- package/docs/src/content/docs/versioning.mdx +104 -0
- package/docs/src/content.config.ts +7 -0
- package/docs/src/styles/custom.css +189 -0
- package/docs/tsconfig.typedoc.json +26 -0
- package/docs/typedoc-entry.ts +17 -0
- package/docs/typedoc.json +37 -0
- package/e2e/Cargo.lock +646 -0
- package/e2e/Cargo.toml +3 -0
- package/e2e/README.md +101 -0
- package/e2e/frontends/build.mjs +94 -0
- package/e2e/frontends/index.html +10 -0
- package/e2e/global-setup.ts +65 -0
- package/e2e/icp.yaml +38 -0
- package/e2e/package.json +40 -0
- package/e2e/setup.ts +6 -0
- package/e2e/src/actor/Cargo.toml +14 -0
- package/e2e/src/actor/hello_actor.did +10 -0
- package/e2e/src/actor/src/lib.rs +73 -0
- package/e2e/src/calls.test.ts +132 -0
- package/e2e/src/declarations/hello_actor.envelope.json +175 -0
- package/e2e/src/declarations/hello_actor.ts +15 -0
- package/e2e/src/errors.test.ts +107 -0
- package/e2e/src/frontend-headers.test.ts +253 -0
- package/e2e/src/identity.test.ts +61 -0
- package/e2e/src/react.test.tsx +59 -0
- package/e2e/src/replica.ts +70 -0
- package/e2e/src/vite-env.test.ts +108 -0
- package/e2e/test.sh +47 -0
- package/e2e/tsconfig.json +20 -0
- package/e2e/tsconfig.typecheck.json +9 -0
- package/e2e/vitest.config.ts +22 -0
- package/eslint.config.mjs +100 -0
- package/evals/PREREGISTRATION.md +1346 -0
- package/evals/README.md +756 -0
- package/evals/conditions/thin/condition.json +8 -0
- package/evals/conditions/thin/docs/candid-core-cli.README.md +90 -0
- package/evals/conditions/thin/docs/candid-core-schema.README.md +339 -0
- package/evals/conditions/thin/package.json +25 -0
- package/evals/conditions/thin/starter/src/generated/icrc1.ts +57 -0
- package/evals/conditions/thin-guide/condition.json +10 -0
- package/evals/conditions/thin-guide/docs/llms.txt +292 -0
- package/evals/conditions/v3/condition.json +9 -0
- package/evals/conditions/v3/docs/ic-reactor-core.llms.txt +232 -0
- package/evals/conditions/v3/docs/ic-reactor-react.llms.txt +280 -0
- package/evals/conditions/v3/docs/llms-full.txt +1053 -0
- package/evals/conditions/v3/package.json +26 -0
- package/evals/conditions/v3/starter/src/declarations/icrc1.did.d.ts +95 -0
- package/evals/conditions/v3/starter/src/declarations/icrc1.did.js +140 -0
- package/evals/conditions/v4/PORTING.md +384 -0
- package/evals/conditions/v4/README.md +106 -0
- package/evals/conditions/v4/condition.json +5 -0
- package/evals/conditions/v4/docs/llms.txt +323 -0
- package/evals/conditions/v4/package.json +27 -0
- package/evals/conditions/v4/starter/src/generated/icrc1.ts +78 -0
- package/evals/conditions/v4-proto/condition.json +6 -0
- package/evals/conditions/v4-proto/docs/llms.txt +291 -0
- package/evals/conditions/v4-proto/lib/SHIM-NOTES.md +20 -0
- package/evals/conditions/v4-proto/lib/package.json +27 -0
- package/evals/conditions/v4-proto/lib/src/client.ts +471 -0
- package/evals/conditions/v4-proto/lib/src/errors.ts +214 -0
- package/evals/conditions/v4-proto/lib/src/index.ts +36 -0
- package/evals/conditions/v4-proto/lib/src/principal.ts +175 -0
- package/evals/conditions/v4-proto/lib/src/service.ts +69 -0
- package/evals/conditions/v4-proto/lib/src/types.ts +174 -0
- package/evals/conditions/v4-proto/lib/test/runtime.test.ts +170 -0
- package/evals/conditions/v4-proto/lib/test/tsconfig.json +10 -0
- package/evals/conditions/v4-proto/lib/test/types.test-d.ts +145 -0
- package/evals/conditions/v4-proto/lib/test/vitest.config.mjs +8 -0
- package/evals/conditions/v4-proto/lib/tsconfig.json +17 -0
- package/evals/conditions/v4-proto/package.json +26 -0
- package/evals/conditions/v4-proto/starter/src/generated/icrc1.service.ts +19 -0
- package/evals/conditions/v4-proto/starter/src/generated/icrc1.ts +57 -0
- package/evals/drive.mjs +1221 -0
- package/evals/gate.mjs +161 -0
- package/evals/harness/aggregate.mjs +534 -0
- package/evals/harness/aggregate.test.mjs +344 -0
- package/evals/harness/assemble.mjs +216 -0
- package/evals/harness/assemble.test.mjs +143 -0
- package/evals/harness/auth.mjs +91 -0
- package/evals/harness/check-docs.mjs +157 -0
- package/evals/harness/declarations/icrc1.did.d.ts +95 -0
- package/evals/harness/declarations/icrc1.did.js +140 -0
- package/evals/harness/drive-auth.test.mjs +584 -0
- package/evals/harness/drive.test.mjs +509 -0
- package/evals/harness/fake-auth.ts +67 -0
- package/evals/harness/fake-ledger.ts +178 -0
- package/evals/harness/gate-plan.mjs +104 -0
- package/evals/harness/gate-plan.test.mjs +243 -0
- package/evals/harness/icrc1.did +88 -0
- package/evals/harness/judge.mjs +63 -0
- package/evals/harness/judge.test.mjs +81 -0
- package/evals/harness/leak-scan.mjs +1373 -0
- package/evals/harness/leak-scan.test.mjs +1300 -0
- package/evals/harness/probes/amounts.ts +14 -0
- package/evals/harness/probes/node-tool.probe.ts +48 -0
- package/evals/harness/probes/probe.mjs +149 -0
- package/evals/harness/probes/react-wallet.probe.ts +96 -0
- package/evals/harness/public/support/auth.ts +38 -0
- package/evals/harness/public/support/fake-replica.d.ts +28 -0
- package/evals/harness/public/support/fake-replica.js +434 -0
- package/evals/harness/public/support/ledger.idl.d.ts +95 -0
- package/evals/harness/public/support/ledger.idl.js +140 -0
- package/evals/harness/public/support/world.ts +179 -0
- package/evals/harness/runs.mjs +139 -0
- package/evals/harness/sandbox.mjs +88 -0
- package/evals/harness/sandbox.test.mjs +134 -0
- package/evals/harness/ship.mjs +264 -0
- package/evals/harness/ship.test.mjs +552 -0
- package/evals/harness/world.ts +291 -0
- package/evals/package.json +26 -0
- package/evals/pnpm-lock.yaml +1537 -0
- package/evals/pnpm-workspace.yaml +12 -0
- package/evals/results/2026-09-30T14-22-05-924Z/pilot.json +384 -0
- package/evals/results/2026-09-30T14-22-05-924Z/pilot.rescanned.json +404 -0
- package/evals/results/2026-09-30T14-22-05-924Z/pilot.rescanned.rescored.json +404 -0
- package/evals/results/2026-09-30T14-22-05-924Z/plan.json +240 -0
- package/evals/results/2026-09-30T14-22-05-924Z/summary.json +1800 -0
- package/evals/results/2026-09-30T14-22-05-924Z/summary.rescanned.json +1843 -0
- package/evals/results/2026-09-30T14-22-05-924Z/summary.rescanned.rescored.json +2100 -0
- package/evals/results/2026-09-30T15-48-23-343Z/pilot.json +404 -0
- package/evals/results/2026-09-30T15-48-23-343Z/pilot.rescored.json +404 -0
- package/evals/results/2026-09-30T15-48-23-343Z/plan.json +242 -0
- package/evals/results/2026-09-30T15-48-23-343Z/summary.json +1843 -0
- package/evals/results/2026-09-30T15-48-23-343Z/summary.rescored.json +2195 -0
- package/evals/results/2026-10-03-beta1-gate/pilot.json +162 -0
- package/evals/results/2026-10-03-beta1-gate/plan.json +141 -0
- package/evals/results/2026-10-03-beta1-gate/summary.json +983 -0
- package/evals/results/2026-10-05-ga-gate/contaminated-run.json +55 -0
- package/evals/results/2026-10-05-ga-gate/pilot.json +162 -0
- package/evals/results/2026-10-05-ga-gate/plan.json +457 -0
- package/evals/results/2026-10-05-ga-gate/summary.json +1037 -0
- package/evals/results/2026-10-05-ga-gate/summary.rescanned.json +1037 -0
- package/evals/results/README.md +31 -0
- package/evals/runs-pilot.log +150 -0
- package/evals/runs-pilot2.log +150 -0
- package/evals/score.mjs +256 -0
- package/evals/setup.mjs +547 -0
- package/evals/tasks/node-tool/faulty/thin-accepts-exponent/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/thin-accepts-exponent/src/index.ts +121 -0
- package/evals/tasks/node-tool/faulty/thin-anonymous-transfer/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/thin-anonymous-transfer/src/index.ts +116 -0
- package/evals/tasks/node-tool/faulty/thin-every-reject-may-have-executed/meta.json +9 -0
- package/evals/tasks/node-tool/faulty/thin-every-reject-may-have-executed/src/index.ts +118 -0
- package/evals/tasks/node-tool/faulty/thin-fetches-root-key-off-local/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/thin-fetches-root-key-off-local/src/index.ts +118 -0
- package/evals/tasks/node-tool/faulty/thin-ignores-root-key/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/thin-ignores-root-key/src/index.ts +117 -0
- package/evals/tasks/node-tool/faulty/thin-misclassifies-reject-codes/meta.json +9 -0
- package/evals/tasks/node-tool/faulty/thin-misclassifies-reject-codes/src/index.ts +119 -0
- package/evals/tasks/node-tool/faulty/thin-no-nat64-cap/meta.json +9 -0
- package/evals/tasks/node-tool/faulty/thin-no-nat64-cap/src/index.ts +118 -0
- package/evals/tasks/node-tool/faulty/thin-number-amount/meta.json +10 -0
- package/evals/tasks/node-tool/faulty/thin-number-amount/src/index.ts +117 -0
- package/evals/tasks/node-tool/faulty/thin-truncates-fraction-digits/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/thin-truncates-fraction-digits/src/index.ts +120 -0
- package/evals/tasks/node-tool/faulty/thin-unvalidated-recipient/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/thin-unvalidated-recipient/src/index.ts +118 -0
- package/evals/tasks/node-tool/faulty/v3-retry-call-errors/meta.json +11 -0
- package/evals/tasks/node-tool/faulty/v3-retry-call-errors/src/index.ts +149 -0
- package/evals/tasks/node-tool/faulty/v4-anonymous-identity-sent/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/v4-anonymous-identity-sent/src/index.ts +71 -0
- package/evals/tasks/node-tool/faulty/v4-never-may-have-executed/meta.json +9 -0
- package/evals/tasks/node-tool/faulty/v4-never-may-have-executed/src/index.ts +84 -0
- package/evals/tasks/node-tool/faulty/v4-proto-never-may-have-executed/meta.json +9 -0
- package/evals/tasks/node-tool/faulty/v4-proto-never-may-have-executed/src/index.ts +72 -0
- package/evals/tasks/node-tool/faulty/v4-proto-refuses-nat64-max/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/v4-proto-refuses-nat64-max/src/index.ts +72 -0
- package/evals/tasks/node-tool/faulty/v4-refuses-nat64-max/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/v4-refuses-nat64-max/src/index.ts +84 -0
- package/evals/tasks/node-tool/hidden/node-tool.test.ts +312 -0
- package/evals/tasks/node-tool/prompt.md +55 -0
- package/evals/tasks/node-tool/prompt.minimal.md +30 -0
- package/evals/tasks/node-tool/solutions/thin/reference/src/index.ts +118 -0
- package/evals/tasks/node-tool/solutions/thin/reference-module-scope/src/index.ts +145 -0
- package/evals/tasks/node-tool/solutions/thin-guide/reference/src/index.ts +118 -0
- package/evals/tasks/node-tool/solutions/thin-guide/reference-module-scope/src/index.ts +145 -0
- package/evals/tasks/node-tool/solutions/v3/reference/src/index.ts +140 -0
- package/evals/tasks/node-tool/solutions/v3/reference-module-scope/src/index.ts +153 -0
- package/evals/tasks/node-tool/solutions/v4/reference/src/index.ts +84 -0
- package/evals/tasks/node-tool/solutions/v4/reference-module-scope/src/index.ts +105 -0
- package/evals/tasks/node-tool/solutions/v4-proto/reference/src/index.ts +72 -0
- package/evals/tasks/node-tool/solutions/v4-proto/reference-module-scope/src/index.ts +87 -0
- package/evals/tasks/node-tool/starter/src/contract.ts +31 -0
- package/evals/tasks/node-tool/starter/src/index.ts +7 -0
- package/evals/tasks/node-tool/starter/test/smoke.test.ts +22 -0
- package/evals/tasks/node-tool/starter/tsconfig.json +17 -0
- package/evals/tasks/node-tool/task.json +63 -0
- package/evals/tasks/react-wallet/faulty/thin-accepts-exponent/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/thin-accepts-exponent/src/Wallet.tsx +216 -0
- package/evals/tasks/react-wallet/faulty/thin-anonymous-transfer/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/thin-anonymous-transfer/src/Wallet.tsx +212 -0
- package/evals/tasks/react-wallet/faulty/thin-both-auth-buttons/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/thin-both-auth-buttons/src/Wallet.tsx +210 -0
- package/evals/tasks/react-wallet/faulty/thin-constant-balance-key/meta.json +8 -0
- package/evals/tasks/react-wallet/faulty/thin-constant-balance-key/src/Wallet.tsx +213 -0
- package/evals/tasks/react-wallet/faulty/thin-no-nat64-cap/meta.json +9 -0
- package/evals/tasks/react-wallet/faulty/thin-no-nat64-cap/src/Wallet.tsx +213 -0
- package/evals/tasks/react-wallet/faulty/thin-number-balance/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/thin-number-balance/src/Wallet.tsx +213 -0
- package/evals/tasks/react-wallet/faulty/thin-transfer-as-query/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/thin-transfer-as-query/src/Wallet.tsx +254 -0
- package/evals/tasks/react-wallet/faulty/thin-truncates-fraction-digits/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/thin-truncates-fraction-digits/src/Wallet.tsx +215 -0
- package/evals/tasks/react-wallet/faulty/thin-unguarded-balance-effect/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/thin-unguarded-balance-effect/src/Wallet.tsx +214 -0
- package/evals/tasks/react-wallet/faulty/v3-http-4xx-unknown/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/v3-http-4xx-unknown/src/Wallet.tsx +224 -0
- package/evals/tasks/react-wallet/faulty/v3-mutation-retry-3/meta.json +12 -0
- package/evals/tasks/react-wallet/faulty/v3-mutation-retry-3/src/Wallet.tsx +225 -0
- package/evals/tasks/react-wallet/faulty/v3-unknown-as-error/meta.json +11 -0
- package/evals/tasks/react-wallet/faulty/v3-unknown-as-error/src/Wallet.tsx +182 -0
- package/evals/tasks/react-wallet/faulty/v4-every-reject-unknown/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/v4-every-reject-unknown/src/Wallet.tsx +160 -0
- package/evals/tasks/react-wallet/faulty/v4-keep-previous-data/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/v4-keep-previous-data/src/Wallet.tsx +166 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-every-reject-unknown/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-every-reject-unknown/src/Wallet.tsx +161 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-keep-previous-data/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-keep-previous-data/src/Wallet.tsx +163 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-retry-spread/meta.json +12 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-retry-spread/src/Wallet.tsx +162 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-status-not-idle/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-status-not-idle/src/Wallet.tsx +163 -0
- package/evals/tasks/react-wallet/faulty/v4-retry-spread/meta.json +12 -0
- package/evals/tasks/react-wallet/faulty/v4-retry-spread/src/Wallet.tsx +163 -0
- package/evals/tasks/react-wallet/faulty/v4-status-not-idle/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/v4-status-not-idle/src/Wallet.tsx +162 -0
- package/evals/tasks/react-wallet/hidden/react-wallet.test.ts +536 -0
- package/evals/tasks/react-wallet/prompt.md +88 -0
- package/evals/tasks/react-wallet/prompt.minimal.md +51 -0
- package/evals/tasks/react-wallet/solutions/thin/reference/src/Wallet.tsx +213 -0
- package/evals/tasks/react-wallet/solutions/thin/reference-module-scope/src/Wallet.tsx +227 -0
- package/evals/tasks/react-wallet/solutions/thin-guide/reference/src/Wallet.tsx +213 -0
- package/evals/tasks/react-wallet/solutions/thin-guide/reference-module-scope/src/Wallet.tsx +227 -0
- package/evals/tasks/react-wallet/solutions/v3/reference/src/Wallet.tsx +224 -0
- package/evals/tasks/react-wallet/solutions/v3/reference-module-scope/src/Wallet.tsx +230 -0
- package/evals/tasks/react-wallet/solutions/v4/reference/src/Wallet.tsx +160 -0
- package/evals/tasks/react-wallet/solutions/v4/reference-module-scope/src/Wallet.tsx +178 -0
- package/evals/tasks/react-wallet/solutions/v4-proto/reference/src/Wallet.tsx +161 -0
- package/evals/tasks/react-wallet/solutions/v4-proto/reference-module-scope/src/Wallet.tsx +161 -0
- package/evals/tasks/react-wallet/starter/src/Wallet.tsx +12 -0
- package/evals/tasks/react-wallet/starter/src/auth.ts +16 -0
- package/evals/tasks/react-wallet/starter/src/config.ts +14 -0
- package/evals/tasks/react-wallet/starter/src/ii-auth.ts +43 -0
- package/evals/tasks/react-wallet/starter/src/main.tsx +15 -0
- package/evals/tasks/react-wallet/starter/test/smoke.test.ts +34 -0
- package/evals/tasks/react-wallet/starter/tsconfig.json +18 -0
- package/evals/tasks/react-wallet/task.json +84 -0
- package/examples/canister-studio/.stackblitzrc +4 -0
- package/examples/canister-studio/README.md +183 -0
- package/examples/canister-studio/did/ckbtc_minter.did +806 -0
- package/examples/canister-studio/did/icp_ledger.did +586 -0
- package/examples/canister-studio/did/icrc1.did +88 -0
- package/examples/canister-studio/package.json +45 -0
- package/examples/canister-studio/scripts/smoke.ts +188 -0
- package/examples/canister-studio/src/calls/CacheKeyPanel.tsx +104 -0
- package/examples/canister-studio/src/calls/ConfirmWrite.tsx +265 -0
- package/examples/canister-studio/src/calls/ErrorPanel.test.tsx +113 -0
- package/examples/canister-studio/src/calls/ErrorPanel.tsx +214 -0
- package/examples/canister-studio/src/calls/MethodRunner.test.tsx +354 -0
- package/examples/canister-studio/src/calls/MethodRunner.tsx +57 -0
- package/examples/canister-studio/src/calls/ReadRunner.tsx +244 -0
- package/examples/canister-studio/src/calls/ResendPanel.tsx +165 -0
- package/examples/canister-studio/src/calls/ShareLink.tsx +59 -0
- package/examples/canister-studio/src/calls/WriteRunner.test.tsx +678 -0
- package/examples/canister-studio/src/calls/WriteRunner.tsx +670 -0
- package/examples/canister-studio/src/calls/dedupe.ts +189 -0
- package/examples/canister-studio/src/calls/error-info.ts +168 -0
- package/examples/canister-studio/src/calls/history.test.ts +284 -0
- package/examples/canister-studio/src/calls/history.ts +287 -0
- package/examples/canister-studio/src/calls/kinds.ts +68 -0
- package/examples/canister-studio/src/calls/prepare.ts +32 -0
- package/examples/canister-studio/src/calls/query-options.ts +30 -0
- package/examples/canister-studio/src/calls/read.ts +47 -0
- package/examples/canister-studio/src/calls/readback.ts +100 -0
- package/examples/canister-studio/src/calls/record.ts +123 -0
- package/examples/canister-studio/src/calls/safe-calls.test.ts +299 -0
- package/examples/canister-studio/src/calls/share.ts +46 -0
- package/examples/canister-studio/src/calls/testing/doubles.tsx +108 -0
- package/examples/canister-studio/src/calls/testing/fixtures.ts +185 -0
- package/examples/canister-studio/src/calls/useCanisterHandle.ts +35 -0
- package/examples/canister-studio/src/canisters/ckbtc_minter.envelope.json +4228 -0
- package/examples/canister-studio/src/canisters/ckbtc_minter.ts +868 -0
- package/examples/canister-studio/src/canisters/icp_ledger.envelope.json +3454 -0
- package/examples/canister-studio/src/canisters/icp_ledger.ts +584 -0
- package/examples/canister-studio/src/canisters/icrc1.envelope.json +840 -0
- package/examples/canister-studio/src/canisters/icrc1.ts +78 -0
- package/examples/canister-studio/src/components/ActionButton.tsx +32 -0
- package/examples/canister-studio/src/components/AppHeader.tsx +83 -0
- package/examples/canister-studio/src/components/AppShell.tsx +29 -0
- package/examples/canister-studio/src/components/AuthButton.tsx +79 -0
- package/examples/canister-studio/src/components/Badge.tsx +79 -0
- package/examples/canister-studio/src/components/CanisterHeader.tsx +200 -0
- package/examples/canister-studio/src/components/CanisterJump.tsx +130 -0
- package/examples/canister-studio/src/components/Card.tsx +64 -0
- package/examples/canister-studio/src/components/Code.tsx +101 -0
- package/examples/canister-studio/src/components/CopyButton.tsx +126 -0
- package/examples/canister-studio/src/components/Disclosure.tsx +34 -0
- package/examples/canister-studio/src/components/EmptyState.tsx +34 -0
- package/examples/canister-studio/src/components/MethodList.test.tsx +137 -0
- package/examples/canister-studio/src/components/MethodList.tsx +244 -0
- package/examples/canister-studio/src/components/Notice.tsx +36 -0
- package/examples/canister-studio/src/components/ProvenanceBadge.tsx +44 -0
- package/examples/canister-studio/src/components/RouteError.tsx +42 -0
- package/examples/canister-studio/src/components/SectionBoundary.tsx +76 -0
- package/examples/canister-studio/src/components/Spinner.tsx +33 -0
- package/examples/canister-studio/src/components/Tabs.tsx +96 -0
- package/examples/canister-studio/src/components/canister/CanisterLoadFailure.test.tsx +56 -0
- package/examples/canister-studio/src/components/canister/CanisterLoadFailure.tsx +165 -0
- package/examples/canister-studio/src/components/canister/CanisterNotFound.tsx +59 -0
- package/examples/canister-studio/src/components/canister/CanisterPending.tsx +33 -0
- package/examples/canister-studio/src/components/canister/MethodOverview.tsx +93 -0
- package/examples/canister-studio/src/components/canister/MethodWorkspace.test.tsx +201 -0
- package/examples/canister-studio/src/components/canister/MethodWorkspace.tsx +187 -0
- package/examples/canister-studio/src/components/canister/shared-call.test.ts +186 -0
- package/examples/canister-studio/src/components/canister/shared-call.ts +133 -0
- package/examples/canister-studio/src/components/canister/shared-link-loader.test.ts +116 -0
- package/examples/canister-studio/src/components/canister/use-canister-page.ts +44 -0
- package/examples/canister-studio/src/components/canister/workspace-key.test.ts +27 -0
- package/examples/canister-studio/src/components/canister/workspace-key.ts +36 -0
- package/examples/canister-studio/src/components/canister-id-input.ts +69 -0
- package/examples/canister-studio/src/components/canister-names.ts +11 -0
- package/examples/canister-studio/src/components/download.ts +34 -0
- package/examples/canister-studio/src/components/fallbacks.tsx +33 -0
- package/examples/canister-studio/src/components/helpers.test.ts +107 -0
- package/examples/canister-studio/src/components/history/HistoryList.tsx +284 -0
- package/examples/canister-studio/src/components/home/FeaturedLedger.tsx +149 -0
- package/examples/canister-studio/src/components/home/OpenCanister.tsx +99 -0
- package/examples/canister-studio/src/components/home/featured.test.tsx +92 -0
- package/examples/canister-studio/src/components/home/featured.ts +64 -0
- package/examples/canister-studio/src/components/interface/CandidTab.tsx +38 -0
- package/examples/canister-studio/src/components/interface/CompatReport.tsx +92 -0
- package/examples/canister-studio/src/components/interface/CompatibilityTab.tsx +190 -0
- package/examples/canister-studio/src/components/interface/ProjectTab.tsx +196 -0
- package/examples/canister-studio/src/components/interface/TypeScriptTab.tsx +114 -0
- package/examples/canister-studio/src/components/interface/tools.test.tsx +169 -0
- package/examples/canister-studio/src/components/modes.ts +31 -0
- package/examples/canister-studio/src/components/playground/MethodPicker.tsx +67 -0
- package/examples/canister-studio/src/components/playground/PlaygroundForms.tsx +112 -0
- package/examples/canister-studio/src/components/playground/PlaygroundTools.tsx +219 -0
- package/examples/canister-studio/src/components/playground/sample.ts +43 -0
- package/examples/canister-studio/src/components/playground/target.test.tsx +86 -0
- package/examples/canister-studio/src/components/primitives.test.tsx +121 -0
- package/examples/canister-studio/src/components/provenance.ts +38 -0
- package/examples/canister-studio/src/components/route-render.test.tsx +71 -0
- package/examples/canister-studio/src/components/use-task.ts +56 -0
- package/examples/canister-studio/src/components/wallet/BalancePanel.tsx +110 -0
- package/examples/canister-studio/src/components/wallet/LedgerSummary.tsx +63 -0
- package/examples/canister-studio/src/components/wallet/MinterPanel.tsx +255 -0
- package/examples/canister-studio/src/components/wallet/SendPanel.test.tsx +297 -0
- package/examples/canister-studio/src/components/wallet/SendPanel.tsx +446 -0
- package/examples/canister-studio/src/components/wallet/ledgers.test.ts +36 -0
- package/examples/canister-studio/src/components/wallet/ledgers.ts +111 -0
- package/examples/canister-studio/src/forms/JsonPanel.tsx +157 -0
- package/examples/canister-studio/src/forms/MethodForm.test.tsx +369 -0
- package/examples/canister-studio/src/forms/MethodForm.tsx +368 -0
- package/examples/canister-studio/src/forms/TypedForm.tsx +55 -0
- package/examples/canister-studio/src/forms/context.ts +62 -0
- package/examples/canister-studio/src/forms/convert.ts +131 -0
- package/examples/canister-studio/src/forms/edge.test.ts +252 -0
- package/examples/canister-studio/src/forms/engine.test.ts +358 -0
- package/examples/canister-studio/src/forms/engine.ts +1492 -0
- package/examples/canister-studio/src/forms/fields/BytesField.tsx +210 -0
- package/examples/canister-studio/src/forms/fields/Field.tsx +106 -0
- package/examples/canister-studio/src/forms/fields/FieldShell.tsx +101 -0
- package/examples/canister-studio/src/forms/fields/Group.tsx +74 -0
- package/examples/canister-studio/src/forms/fields/ListField.tsx +256 -0
- package/examples/canister-studio/src/forms/fields/Segmented.tsx +78 -0
- package/examples/canister-studio/src/forms/fields/TimestampField.tsx +108 -0
- package/examples/canister-studio/src/forms/fields/UnitsField.tsx +119 -0
- package/examples/canister-studio/src/forms/fields/composites.tsx +389 -0
- package/examples/canister-studio/src/forms/fields/leaves.tsx +350 -0
- package/examples/canister-studio/src/forms/fields.test.tsx +396 -0
- package/examples/canister-studio/src/forms/helpers.test.ts +225 -0
- package/examples/canister-studio/src/forms/hints.ts +128 -0
- package/examples/canister-studio/src/forms/json.ts +119 -0
- package/examples/canister-studio/src/forms/parsing.test.ts +217 -0
- package/examples/canister-studio/src/forms/useMethodForm.ts +251 -0
- package/examples/canister-studio/src/interface/browser.test.ts +23 -0
- package/examples/canister-studio/src/interface/browser.ts +153 -0
- package/examples/canister-studio/src/interface/cache.ts +68 -0
- package/examples/canister-studio/src/interface/cli-cache.test.ts +155 -0
- package/examples/canister-studio/src/interface/cli-cache.ts +125 -0
- package/examples/canister-studio/src/interface/compile.server.test.ts +198 -0
- package/examples/canister-studio/src/interface/compile.server.ts +262 -0
- package/examples/canister-studio/src/interface/compile.test.ts +108 -0
- package/examples/canister-studio/src/interface/compile.ts +305 -0
- package/examples/canister-studio/src/interface/docs.test.ts +75 -0
- package/examples/canister-studio/src/interface/docs.ts +100 -0
- package/examples/canister-studio/src/interface/functions.test.ts +42 -0
- package/examples/canister-studio/src/interface/functions.ts +200 -0
- package/examples/canister-studio/src/interface/live-candid.server.ts +91 -0
- package/examples/canister-studio/src/interface/live-loader.test.ts +515 -0
- package/examples/canister-studio/src/interface/live-loader.ts +364 -0
- package/examples/canister-studio/src/interface/load.test.ts +328 -0
- package/examples/canister-studio/src/interface/load.ts +249 -0
- package/examples/canister-studio/src/interface/read-candid.test.ts +185 -0
- package/examples/canister-studio/src/interface/read-candid.ts +233 -0
- package/examples/canister-studio/src/interface/server-result.ts +42 -0
- package/examples/canister-studio/src/interface/typed.ts +68 -0
- package/examples/canister-studio/src/interface/types.ts +121 -0
- package/examples/canister-studio/src/lib/format.test.ts +107 -0
- package/examples/canister-studio/src/lib/format.ts +139 -0
- package/examples/canister-studio/src/lib/hex.test.ts +43 -0
- package/examples/canister-studio/src/lib/hex.ts +33 -0
- package/examples/canister-studio/src/lib/ids.test.ts +70 -0
- package/examples/canister-studio/src/lib/ids.ts +93 -0
- package/examples/canister-studio/src/lib/reactor.test.ts +55 -0
- package/examples/canister-studio/src/lib/reactor.ts +55 -0
- package/examples/canister-studio/src/lib/registry.test.ts +74 -0
- package/examples/canister-studio/src/lib/registry.ts +238 -0
- package/examples/canister-studio/src/lib/schema.ts +148 -0
- package/examples/canister-studio/src/routeTree.gen.ts +227 -0
- package/examples/canister-studio/src/router.test.ts +290 -0
- package/examples/canister-studio/src/router.tsx +127 -0
- package/examples/canister-studio/src/routes/__root.tsx +58 -0
- package/examples/canister-studio/src/routes/canister.$canisterId.index.tsx +172 -0
- package/examples/canister-studio/src/routes/canister.$canisterId.interface.tsx +98 -0
- package/examples/canister-studio/src/routes/canister.$canisterId.tsx +91 -0
- package/examples/canister-studio/src/routes/history.tsx +55 -0
- package/examples/canister-studio/src/routes/index.tsx +169 -0
- package/examples/canister-studio/src/routes/playground.tsx +286 -0
- package/examples/canister-studio/src/routes/safety.tsx +57 -0
- package/examples/canister-studio/src/routes/wallet.tsx +213 -0
- package/examples/canister-studio/src/safety/LabTransferForm.tsx +84 -0
- package/examples/canister-studio/src/safety/RequestLog.tsx +79 -0
- package/examples/canister-studio/src/safety/SafetyLab.test.tsx +89 -0
- package/examples/canister-studio/src/safety/SafetyLab.tsx +140 -0
- package/examples/canister-studio/src/safety/ScenarioCard.tsx +262 -0
- package/examples/canister-studio/src/safety/lab-run.ts +283 -0
- package/examples/canister-studio/src/safety/mock-ledger.test.ts +99 -0
- package/examples/canister-studio/src/safety/mock-ledger.ts +263 -0
- package/examples/canister-studio/src/safety/scenarios.test.ts +157 -0
- package/examples/canister-studio/src/safety/scenarios.ts +924 -0
- package/examples/canister-studio/src/styles.css +3453 -0
- package/examples/canister-studio/src/styles.test.ts +46 -0
- package/examples/canister-studio/src/values/CallPreview.test.tsx +86 -0
- package/examples/canister-studio/src/values/CallPreview.tsx +148 -0
- package/examples/canister-studio/src/values/ErrValueView.tsx +66 -0
- package/examples/canister-studio/src/values/Inspector.tsx +133 -0
- package/examples/canister-studio/src/values/ReplyView.test.tsx +341 -0
- package/examples/canister-studio/src/values/ReplyView.tsx +164 -0
- package/examples/canister-studio/src/values/candid-text.test.ts +516 -0
- package/examples/canister-studio/src/values/candid-text.ts +479 -0
- package/examples/canister-studio/src/values/hints.ts +58 -0
- package/examples/canister-studio/src/values/json.test.ts +66 -0
- package/examples/canister-studio/src/values/json.ts +31 -0
- package/examples/canister-studio/src/values/leaves.tsx +309 -0
- package/examples/canister-studio/src/values/schema.test.ts +123 -0
- package/examples/canister-studio/src/values/schema.ts +78 -0
- package/examples/canister-studio/src/values/test-router.tsx +21 -0
- package/examples/canister-studio/src/values/tree.tsx +670 -0
- package/examples/canister-studio/src/values/use-sha256.ts +29 -0
- package/examples/canister-studio/tsconfig.json +20 -0
- package/examples/canister-studio/vite.config.ts +50 -0
- package/examples/canister-studio/vitest.config.ts +20 -0
- package/examples/icrc-ledger/.stackblitzrc +4 -0
- package/examples/icrc-ledger/README.md +100 -0
- package/examples/icrc-ledger/ckbtc_minter.did +12 -0
- package/examples/icrc-ledger/icp_ledger.did +147 -0
- package/examples/icrc-ledger/icrc1.did +88 -0
- package/examples/icrc-ledger/index.html +13 -0
- package/examples/icrc-ledger/package.json +40 -0
- package/examples/icrc-ledger/scripts/smoke.ts +151 -0
- package/examples/icrc-ledger/src/App.tsx +71 -0
- package/examples/icrc-ledger/src/ErrorPanel.tsx +105 -0
- package/examples/icrc-ledger/src/MainnetTab.tsx +450 -0
- package/examples/icrc-ledger/src/SandboxTab.test.tsx +379 -0
- package/examples/icrc-ledger/src/SandboxTab.tsx +761 -0
- package/examples/icrc-ledger/src/blocks.test.ts +247 -0
- package/examples/icrc-ledger/src/blocks.ts +140 -0
- package/examples/icrc-ledger/src/canisters/ckbtc_minter.envelope.json +96 -0
- package/examples/icrc-ledger/src/canisters/ckbtc_minter.ts +16 -0
- package/examples/icrc-ledger/src/canisters/icp_ledger.envelope.json +690 -0
- package/examples/icrc-ledger/src/canisters/icp_ledger.ts +223 -0
- package/examples/icrc-ledger/src/canisters/icrc1.envelope.json +840 -0
- package/examples/icrc-ledger/src/canisters/icrc1.ts +78 -0
- package/examples/icrc-ledger/src/format.test.ts +41 -0
- package/examples/icrc-ledger/src/format.ts +68 -0
- package/examples/icrc-ledger/src/ledger.ts +15 -0
- package/examples/icrc-ledger/src/main.tsx +10 -0
- package/examples/icrc-ledger/src/minter.ts +16 -0
- package/examples/icrc-ledger/src/sandbox.test.ts +464 -0
- package/examples/icrc-ledger/src/sandbox.ts +281 -0
- package/examples/icrc-ledger/src/styles.css +391 -0
- package/examples/icrc-ledger/src/transfer-form.test.ts +62 -0
- package/examples/icrc-ledger/src/transfer-form.ts +97 -0
- package/examples/icrc-ledger/tsconfig.json +19 -0
- package/examples/icrc-ledger/vite.config.ts +44 -0
- package/examples/icrc-ledger/vitest.config.ts +12 -0
- package/examples/next-ssr/.stackblitzrc +4 -0
- package/examples/next-ssr/README.md +112 -0
- package/examples/next-ssr/icrc1.did +88 -0
- package/examples/next-ssr/next.config.ts +10 -0
- package/examples/next-ssr/package.json +41 -0
- package/examples/next-ssr/scripts/smoke.ts +169 -0
- package/examples/next-ssr/src/app/account/CertifiedBalances.tsx +58 -0
- package/examples/next-ssr/src/app/account/account.test.tsx +165 -0
- package/examples/next-ssr/src/app/account/page.tsx +82 -0
- package/examples/next-ssr/src/app/api/balance/[ledger]/[principal]/route.ts +10 -0
- package/examples/next-ssr/src/app/globals.css +278 -0
- package/examples/next-ssr/src/app/layout.tsx +34 -0
- package/examples/next-ssr/src/app/page.tsx +46 -0
- package/examples/next-ssr/src/app/providers.browser.test.tsx +127 -0
- package/examples/next-ssr/src/app/providers.test.tsx +73 -0
- package/examples/next-ssr/src/app/providers.tsx +60 -0
- package/examples/next-ssr/src/canisters/icrc1.envelope.json +840 -0
- package/examples/next-ssr/src/canisters/icrc1.ts +78 -0
- package/examples/next-ssr/src/components/BalanceTable.tsx +43 -0
- package/examples/next-ssr/src/components/LedgerCard.test.tsx +96 -0
- package/examples/next-ssr/src/components/LedgerCard.tsx +230 -0
- package/examples/next-ssr/src/components/LedgerError.tsx +32 -0
- package/examples/next-ssr/src/components/LedgerSections.tsx +22 -0
- package/examples/next-ssr/src/components/MyBalances.test.tsx +253 -0
- package/examples/next-ssr/src/components/MyBalances.tsx +129 -0
- package/examples/next-ssr/src/components/SessionBadge.tsx +18 -0
- package/examples/next-ssr/src/components/hydration.test.tsx +259 -0
- package/examples/next-ssr/src/components/server-data.ts +10 -0
- package/examples/next-ssr/src/format.ts +46 -0
- package/examples/next-ssr/src/ledgers.ts +38 -0
- package/examples/next-ssr/src/server/balance-route.test.ts +151 -0
- package/examples/next-ssr/src/server/balance-route.ts +157 -0
- package/examples/next-ssr/src/server/error-summary.test.ts +34 -0
- package/examples/next-ssr/src/server/error-summary.ts +37 -0
- package/examples/next-ssr/src/server/parse-owner.test.ts +39 -0
- package/examples/next-ssr/src/server/parse-owner.ts +34 -0
- package/examples/next-ssr/src/server/prefetch-ledgers.test.ts +137 -0
- package/examples/next-ssr/src/server/prefetch-ledgers.ts +70 -0
- package/examples/next-ssr/src/server/read-balances.test.ts +103 -0
- package/examples/next-ssr/src/server/read-balances.ts +78 -0
- package/examples/next-ssr/src/server/request-client.rsc.test.tsx +110 -0
- package/examples/next-ssr/src/server/request-client.ts +24 -0
- package/examples/next-ssr/src/testing/mock-ledgers.ts +115 -0
- package/examples/next-ssr/src/testing/react-server-dom-turbopack.d.ts +15 -0
- package/examples/next-ssr/src/testing/setup.ts +10 -0
- package/examples/next-ssr/tsconfig.json +31 -0
- package/examples/next-ssr/vitest.config.ts +59 -0
- package/examples/node-agent-tool/.stackblitzrc +4 -0
- package/examples/node-agent-tool/README.md +165 -0
- package/examples/node-agent-tool/icp.yaml +11 -0
- package/examples/node-agent-tool/icrc1.did +88 -0
- package/examples/node-agent-tool/package.json +32 -0
- package/examples/node-agent-tool/src/canisters/icrc1.envelope.json +840 -0
- package/examples/node-agent-tool/src/canisters/icrc1.ts +78 -0
- package/examples/node-agent-tool/src/cli.test.ts +64 -0
- package/examples/node-agent-tool/src/cli.ts +23 -0
- package/examples/node-agent-tool/src/command-line.ts +159 -0
- package/examples/node-agent-tool/src/commands/balance.ts +73 -0
- package/examples/node-agent-tool/src/commands/demo.test.ts +51 -0
- package/examples/node-agent-tool/src/commands/demo.ts +359 -0
- package/examples/node-agent-tool/src/commands/info.ts +69 -0
- package/examples/node-agent-tool/src/commands/reads.test.ts +133 -0
- package/examples/node-agent-tool/src/commands/resend.test.ts +363 -0
- package/examples/node-agent-tool/src/commands/transfer.test.ts +313 -0
- package/examples/node-agent-tool/src/commands/transfer.ts +415 -0
- package/examples/node-agent-tool/src/commands/watch.test.ts +159 -0
- package/examples/node-agent-tool/src/commands/watch.ts +135 -0
- package/examples/node-agent-tool/src/commands/whoami.ts +36 -0
- package/examples/node-agent-tool/src/context.ts +20 -0
- package/examples/node-agent-tool/src/dispatch.ts +70 -0
- package/examples/node-agent-tool/src/failure.test.ts +59 -0
- package/examples/node-agent-tool/src/failure.ts +176 -0
- package/examples/node-agent-tool/src/identity.test.ts +242 -0
- package/examples/node-agent-tool/src/identity.ts +228 -0
- package/examples/node-agent-tool/src/input.ts +110 -0
- package/examples/node-agent-tool/src/ledgers.test.ts +74 -0
- package/examples/node-agent-tool/src/ledgers.ts +62 -0
- package/examples/node-agent-tool/src/mock-ledger.ts +211 -0
- package/examples/node-agent-tool/src/network.test.ts +128 -0
- package/examples/node-agent-tool/src/network.ts +101 -0
- package/examples/node-agent-tool/src/output.test.ts +208 -0
- package/examples/node-agent-tool/src/output.ts +163 -0
- package/examples/node-agent-tool/src/run.ts +73 -0
- package/examples/node-agent-tool/src/test-kit.ts +167 -0
- package/examples/node-agent-tool/tsconfig.json +18 -0
- package/examples/node-agent-tool/vitest.config.ts +8 -0
- package/examples/vite-wallet/.stackblitzrc +4 -0
- package/examples/vite-wallet/Cargo.lock +646 -0
- package/examples/vite-wallet/Cargo.toml +5 -0
- package/examples/vite-wallet/README.md +107 -0
- package/examples/vite-wallet/backend/Cargo.toml +12 -0
- package/examples/vite-wallet/backend/backend.did +37 -0
- package/examples/vite-wallet/backend/src/lib.rs +133 -0
- package/examples/vite-wallet/icp.yaml +44 -0
- package/examples/vite-wallet/index.html +13 -0
- package/examples/vite-wallet/ledger.did +54 -0
- package/examples/vite-wallet/package.json +46 -0
- package/examples/vite-wallet/public/.ic-assets.json5 +26 -0
- package/examples/vite-wallet/scripts/faucet-args.test.ts +30 -0
- package/examples/vite-wallet/scripts/faucet-args.ts +41 -0
- package/examples/vite-wallet/scripts/faucet.ts +41 -0
- package/examples/vite-wallet/src/AddressBook.test.tsx +191 -0
- package/examples/vite-wallet/src/AddressBook.tsx +147 -0
- package/examples/vite-wallet/src/App.tsx +53 -0
- package/examples/vite-wallet/src/Balance.test.tsx +57 -0
- package/examples/vite-wallet/src/Balance.tsx +58 -0
- package/examples/vite-wallet/src/EnvironmentPanel.test.tsx +83 -0
- package/examples/vite-wallet/src/EnvironmentPanel.tsx +103 -0
- package/examples/vite-wallet/src/ErrorNote.tsx +70 -0
- package/examples/vite-wallet/src/Profile.test.tsx +145 -0
- package/examples/vite-wallet/src/Profile.tsx +92 -0
- package/examples/vite-wallet/src/SendIcp.test.tsx +231 -0
- package/examples/vite-wallet/src/SendIcp.tsx +244 -0
- package/examples/vite-wallet/src/SendToContact.test.tsx +113 -0
- package/examples/vite-wallet/src/SendToContact.tsx +102 -0
- package/examples/vite-wallet/src/SignIn.test.tsx +106 -0
- package/examples/vite-wallet/src/SignIn.tsx +120 -0
- package/examples/vite-wallet/src/Wallet.test.tsx +89 -0
- package/examples/vite-wallet/src/account-switch.test.tsx +108 -0
- package/examples/vite-wallet/src/auth/dev-accounts.test.ts +113 -0
- package/examples/vite-wallet/src/auth/dev-accounts.ts +216 -0
- package/examples/vite-wallet/src/auth/local-page.test.ts +28 -0
- package/examples/vite-wallet/src/auth/local-page.ts +20 -0
- package/examples/vite-wallet/src/auth/wallet-auth.test.ts +159 -0
- package/examples/vite-wallet/src/auth/wallet-auth.ts +131 -0
- package/examples/vite-wallet/src/canisters/backend.envelope.json +275 -0
- package/examples/vite-wallet/src/canisters/backend.ts +58 -0
- package/examples/vite-wallet/src/canisters/ledger.envelope.json +451 -0
- package/examples/vite-wallet/src/canisters/ledger.ts +56 -0
- package/examples/vite-wallet/src/client.ts +45 -0
- package/examples/vite-wallet/src/main.tsx +18 -0
- package/examples/vite-wallet/src/optimistic-contacts.ts +113 -0
- package/examples/vite-wallet/src/refusal.ts +17 -0
- package/examples/vite-wallet/src/styles.css +282 -0
- package/examples/vite-wallet/src/test/test-wallet.tsx +322 -0
- package/examples/vite-wallet/src/token.ts +50 -0
- package/examples/vite-wallet/src/transfer.test.ts +178 -0
- package/examples/vite-wallet/src/transfer.ts +110 -0
- package/examples/vite-wallet/src/use-canisters.ts +49 -0
- package/examples/vite-wallet/tsconfig.json +19 -0
- package/examples/vite-wallet/vite.config.ts +47 -0
- package/examples/vite-wallet/vitest.config.ts +17 -0
- package/package.json +9 -4
- package/packages/core/.size-limit.js +47 -0
- package/packages/core/LICENSE +21 -0
- package/packages/core/README.md +84 -0
- package/packages/core/llms.txt +610 -0
- package/packages/core/package.json +89 -0
- package/packages/core/src/call.ts +961 -0
- package/packages/core/src/canister.ts +259 -0
- package/packages/core/src/client.ts +2061 -0
- package/packages/core/src/errors.ts +1079 -0
- package/packages/core/src/hydration.ts +239 -0
- package/packages/core/src/index.ts +23 -0
- package/packages/core/src/keys.ts +108 -0
- package/packages/core/src/management.ts +138 -0
- package/packages/core/src/network.ts +1020 -0
- package/packages/core/src/options.ts +1000 -0
- package/packages/core/src/runtime.ts +50 -0
- package/packages/core/src/testing/fake-replica.ts +1166 -0
- package/packages/core/src/testing/index.ts +16 -0
- package/packages/core/src/testing/test-auth.ts +340 -0
- package/packages/core/src/testing/test-client.ts +559 -0
- package/packages/core/src/types.ts +541 -0
- package/packages/core/src/units.ts +299 -0
- package/packages/core/src/version.ts +4 -0
- package/packages/core/tests/canister-helpers.ts +187 -0
- package/packages/core/tests/canister-id.test.ts +158 -0
- package/packages/core/tests/canister-unwrap.test.ts +94 -0
- package/packages/core/tests/canister.test-d.ts +406 -0
- package/packages/core/tests/canister.test.ts +1921 -0
- package/packages/core/tests/client-account-change.test.ts +131 -0
- package/packages/core/tests/client-agents.test.ts +402 -0
- package/packages/core/tests/client-auth-network.test.ts +719 -0
- package/packages/core/tests/client-disposed.test.ts +403 -0
- package/packages/core/tests/client-helpers.ts +136 -0
- package/packages/core/tests/client-hydration.test.ts +166 -0
- package/packages/core/tests/client-pinned.test.ts +626 -0
- package/packages/core/tests/client-session.test.ts +348 -0
- package/packages/core/tests/client.test-d.ts +181 -0
- package/packages/core/tests/client.test.ts +1162 -0
- package/packages/core/tests/encode.test.ts +171 -0
- package/packages/core/tests/env-canister-id.test.ts +666 -0
- package/packages/core/tests/env-cookie-copies.test.ts +182 -0
- package/packages/core/tests/error-guard.test.ts +222 -0
- package/packages/core/tests/errors.test-d.ts +280 -0
- package/packages/core/tests/errors.test.ts +1359 -0
- package/packages/core/tests/fixtures/README.md +28 -0
- package/packages/core/tests/fixtures/accounts.did +21 -0
- package/packages/core/tests/fixtures/accounts.envelope.json +367 -0
- package/packages/core/tests/fixtures/accounts.ts +26 -0
- package/packages/core/tests/fixtures/archive.did +31 -0
- package/packages/core/tests/fixtures/archive.envelope.json +259 -0
- package/packages/core/tests/fixtures/archive.ts +55 -0
- package/packages/core/tests/fixtures/icrc1.did +88 -0
- package/packages/core/tests/fixtures/icrc1.envelope.json +840 -0
- package/packages/core/tests/fixtures/icrc1.ts +78 -0
- package/packages/core/tests/fixtures/management.did +724 -0
- package/packages/core/tests/fixtures/management.envelope.json +4455 -0
- package/packages/core/tests/fixtures/management.ts +486 -0
- package/packages/core/tests/fixtures/shapes.did +33 -0
- package/packages/core/tests/fixtures/shapes.envelope.json +440 -0
- package/packages/core/tests/fixtures/shapes.ts +56 -0
- package/packages/core/tests/fixtures/skippable.did +15 -0
- package/packages/core/tests/fixtures/skippable.envelope.json +182 -0
- package/packages/core/tests/fixtures/skippable.ts +33 -0
- package/packages/core/tests/func.test.ts +154 -0
- package/packages/core/tests/identity-attributes.test.ts +214 -0
- package/packages/core/tests/keys.test.ts +289 -0
- package/packages/core/tests/management.test.ts +206 -0
- package/packages/core/tests/network-helpers.ts +121 -0
- package/packages/core/tests/network.test-d.ts +78 -0
- package/packages/core/tests/network.test.ts +1051 -0
- package/packages/core/tests/options.test.ts +1594 -0
- package/packages/core/tests/redirect-sign-in.test.ts +393 -0
- package/packages/core/tests/resend.test-d.ts +81 -0
- package/packages/core/tests/resend.test.ts +723 -0
- package/packages/core/tests/skip.test-d.ts +125 -0
- package/packages/core/tests/testing-entry.test.ts +73 -0
- package/packages/core/tests/testing-fake-replica.test.ts +1242 -0
- package/packages/core/tests/testing-test-auth.test-d.ts +84 -0
- package/packages/core/tests/testing-test-auth.test.ts +483 -0
- package/packages/core/tests/testing-test-client.test-d.ts +184 -0
- package/packages/core/tests/testing-test-client.test.ts +1272 -0
- package/packages/core/tests/time-realm.test.ts +63 -0
- package/packages/core/tests/time.test.ts +238 -0
- package/packages/core/tests/traps/README.md +39 -0
- package/packages/core/tests/traps/anonymous-write.test.ts +145 -0
- package/packages/core/tests/traps/classification-table.test.ts +256 -0
- package/packages/core/tests/traps/err-arm.test.ts +144 -0
- package/packages/core/tests/traps/fetch-spy.ts +39 -0
- package/packages/core/tests/traps/invalidation-after-write.test.ts +178 -0
- package/packages/core/tests/traps/ledger.ts +248 -0
- package/packages/core/tests/traps/root-key-off-local.test.ts +130 -0
- package/packages/core/tests/traps/stale-data-across-users.test.ts +173 -0
- package/packages/core/tests/traps/unresolved-name.test.ts +155 -0
- package/packages/core/tests/traps/update-as-query.test.ts +244 -0
- package/packages/core/tests/traps/update-not-resent.test.ts +126 -0
- package/packages/core/tests/traps/update-resent-when-never-delivered.test.ts +159 -0
- package/packages/core/tests/traps.test-d.ts +476 -0
- package/packages/core/tests/units.test-d.ts +118 -0
- package/packages/core/tests/units.test.ts +634 -0
- package/packages/core/tsconfig.json +19 -0
- package/packages/core/tsconfig.typecheck.json +16 -0
- package/packages/core/vitest.config.ts +13 -0
- package/packages/mcp/LICENSE +21 -0
- package/packages/mcp/README.md +189 -0
- package/packages/mcp/package.json +62 -0
- package/packages/mcp/src/candid/candid-text.test.ts +455 -0
- package/packages/mcp/src/candid/candid-text.ts +501 -0
- package/packages/mcp/src/candid/contract.test.ts +398 -0
- package/packages/mcp/src/candid/formats.ts +155 -0
- package/packages/mcp/src/candid/hex.ts +29 -0
- package/packages/mcp/src/candid/json-schema.test.ts +758 -0
- package/packages/mcp/src/candid/json-schema.ts +454 -0
- package/packages/mcp/src/candid/json.test.ts +1207 -0
- package/packages/mcp/src/candid/json.ts +1053 -0
- package/packages/mcp/src/candid/schema-walk.ts +189 -0
- package/packages/mcp/src/candid/type-text.test.ts +179 -0
- package/packages/mcp/src/candid/type-text.ts +238 -0
- package/packages/mcp/src/cli.ts +134 -0
- package/packages/mcp/src/identity.ts +188 -0
- package/packages/mcp/src/interface/cache.test.ts +48 -0
- package/packages/mcp/src/interface/cache.ts +68 -0
- package/packages/mcp/src/interface/cli-cache.test.ts +150 -0
- package/packages/mcp/src/interface/cli-cache.ts +122 -0
- package/packages/mcp/src/interface/compile-worker.test.ts +246 -0
- package/packages/mcp/src/interface/compile-worker.ts +290 -0
- package/packages/mcp/src/interface/compile.test.ts +155 -0
- package/packages/mcp/src/interface/compile.ts +281 -0
- package/packages/mcp/src/interface/docs.test.ts +105 -0
- package/packages/mcp/src/interface/docs.ts +86 -0
- package/packages/mcp/src/interface/fixtures/icrc1.did +88 -0
- package/packages/mcp/src/interface/hex.ts +8 -0
- package/packages/mcp/src/interface/ids.test.ts +71 -0
- package/packages/mcp/src/interface/ids.ts +87 -0
- package/packages/mcp/src/interface/loader.test.ts +804 -0
- package/packages/mcp/src/interface/loader.ts +519 -0
- package/packages/mcp/src/interface/read-candid.test.ts +300 -0
- package/packages/mcp/src/interface/read-candid.ts +259 -0
- package/packages/mcp/src/interface/types.ts +113 -0
- package/packages/mcp/src/network.ts +122 -0
- package/packages/mcp/src/options.ts +149 -0
- package/packages/mcp/src/server/approval.ts +159 -0
- package/packages/mcp/src/server/naming.ts +43 -0
- package/packages/mcp/src/server/results.ts +102 -0
- package/packages/mcp/src/server/server.ts +797 -0
- package/packages/mcp/src/server/tools.ts +116 -0
- package/packages/mcp/src/usage.ts +40 -0
- package/packages/mcp/tests/approval.test.ts +60 -0
- package/packages/mcp/tests/identity.test.ts +234 -0
- package/packages/mcp/tests/naming.test.ts +62 -0
- package/packages/mcp/tests/options.test.ts +140 -0
- package/packages/mcp/tests/server.test.ts +997 -0
- package/packages/mcp/tsconfig.json +19 -0
- package/packages/mcp/tsconfig.typecheck.json +10 -0
- package/packages/mcp/vitest.config.ts +10 -0
- package/packages/react/LICENSE +21 -0
- package/packages/react/README.md +328 -0
- package/packages/react/llms.txt +95 -0
- package/packages/react/package.json +95 -0
- package/packages/react/src/index.tsx +678 -0
- package/packages/react/tests/abandoned-render.test.tsx +816 -0
- package/packages/react/tests/auth.test.tsx +315 -0
- package/packages/react/tests/caller-keys.test.tsx +2334 -0
- package/packages/react/tests/client-time.test.ts +40 -0
- package/packages/react/tests/entry.test.ts +52 -0
- package/packages/react/tests/helpers.tsx +291 -0
- package/packages/react/tests/hydration.test.tsx +262 -0
- package/packages/react/tests/ownership.test.tsx +260 -0
- package/packages/react/tests/provider.server.test.tsx +169 -0
- package/packages/react/tests/query-client.test.tsx +107 -0
- package/packages/react/tests/strict-mode.test.tsx +288 -0
- package/packages/react/tests/suspense.test-d.tsx +357 -0
- package/packages/react/tests/two-query-cores.test.tsx +135 -0
- package/packages/react/tests/types.test-d.tsx +63 -0
- package/packages/react/tsconfig.json +23 -0
- package/packages/react/tsconfig.test.json +8 -0
- package/packages/react/tsconfig.typecheck.json +16 -0
- package/packages/react/vitest.config.ts +39 -0
- package/packages/vite-plugin/LICENSE +21 -0
- package/packages/vite-plugin/README.md +532 -0
- package/packages/vite-plugin/package.json +76 -0
- package/packages/vite-plugin/src/describe-value.test.ts +43 -0
- package/packages/vite-plugin/src/describe-value.ts +167 -0
- package/packages/vite-plugin/src/dev-environment.test.ts +747 -0
- package/packages/vite-plugin/src/dev-environment.ts +510 -0
- package/packages/vite-plugin/src/env.ts +431 -0
- package/packages/vite-plugin/src/fetch.ts +348 -0
- package/packages/vite-plugin/src/file-identity.ts +106 -0
- package/packages/vite-plugin/src/generate.ts +691 -0
- package/packages/vite-plugin/src/index.test.ts +1243 -0
- package/packages/vite-plugin/src/index.ts +2565 -0
- package/packages/vite-plugin/src/interface.ts +339 -0
- package/packages/vite-plugin/src/local-host.ts +34 -0
- package/packages/vite-plugin/src/options.ts +641 -0
- package/packages/vite-plugin/src/principal-text.test.ts +87 -0
- package/packages/vite-plugin/src/principal-text.ts +98 -0
- package/packages/vite-plugin/tests/close.test.ts +141 -0
- package/packages/vite-plugin/tests/environment-mode.test.ts +136 -0
- package/packages/vite-plugin/tests/fake-ic.ts +214 -0
- package/packages/vite-plugin/tests/fetch-sdk.test.ts +27 -0
- package/packages/vite-plugin/tests/fetch.test.ts +1196 -0
- package/packages/vite-plugin/tests/fixtures/fake-cli/bin.js +90 -0
- package/packages/vite-plugin/tests/fixtures/fake-cli/package.json +8 -0
- package/packages/vite-plugin/tests/fixtures/icrc1.did +88 -0
- package/packages/vite-plugin/tests/generation.test.ts +1036 -0
- package/packages/vite-plugin/tests/icp-command.test.ts +194 -0
- package/packages/vite-plugin/tests/icp-timeout.test.ts +83 -0
- package/packages/vite-plugin/tests/no-process-exit.test.ts +83 -0
- package/packages/vite-plugin/tests/on-generated.test.ts +323 -0
- package/packages/vite-plugin/tests/output-collision.test.ts +329 -0
- package/packages/vite-plugin/tests/package.test.ts +62 -0
- package/packages/vite-plugin/tests/project.test.ts +2993 -0
- package/packages/vite-plugin/tests/support.ts +339 -0
- package/packages/vite-plugin/tests/timeout.test.ts +107 -0
- package/packages/vite-plugin/tsconfig.json +23 -0
- package/packages/vite-plugin/tsconfig.typecheck.json +16 -0
- package/packages/vite-plugin/vitest.config.ts +9 -0
- package/pnpm-workspace.yaml +23 -0
- package/scripts/ai-context-files.js +72 -0
- package/scripts/assemble-docs-site.mjs +485 -0
- package/scripts/assemble-docs-site.test.mjs +568 -0
- package/scripts/build-examples.js +58 -0
- package/scripts/check-ai-context.js +963 -0
- package/scripts/check-ai-context.test.mjs +811 -0
- package/scripts/check-exports.mjs +767 -0
- package/scripts/check-exports.test.mjs +1136 -0
- package/scripts/check-snippets/ambient.d.ts +428 -0
- package/scripts/check-snippets/app/generated/backend.did +7 -0
- package/scripts/check-snippets/app/generated/backend.ts +15 -0
- package/scripts/check-snippets/app/generated/icrc1.did +88 -0
- package/scripts/check-snippets/app/generated/icrc1.ts +78 -0
- package/scripts/check-snippets/app/globals.ts +26 -0
- package/scripts/check-snippets/docs/guides/App.tsx +21 -0
- package/scripts/check-snippets/docs/guides/app/providers.tsx +23 -0
- package/scripts/check-snippets/docs/guides/auth-client.ts +47 -0
- package/scripts/check-snippets/docs/guides/canisters/backend.ts +3 -0
- package/scripts/check-snippets/docs/guides/canisters/icrc1.ts +3 -0
- package/scripts/check-snippets/docs/guides/components/LedgerCard.tsx +26 -0
- package/scripts/check-snippets/docs/guides/components/shell.tsx +11 -0
- package/scripts/check-snippets/docs/guides/components/sign-in-page.tsx +29 -0
- package/scripts/check-snippets/docs/guides/components/slow-section.tsx +5 -0
- package/scripts/check-snippets/docs/guides/generated/accounts.ts +95 -0
- package/scripts/check-snippets/docs/guides/generated/ckbtc_minter.ts +40 -0
- package/scripts/check-snippets/docs/guides/generated/icp_ledger.ts +267 -0
- package/scripts/check-snippets/docs/guides/ledger.ts +10 -0
- package/scripts/check-snippets/docs/guides/server/request-client.ts +8 -0
- package/scripts/check-snippets.mjs +993 -0
- package/scripts/check-snippets.test.mjs +338 -0
- package/scripts/check-stack.mjs +501 -0
- package/scripts/check-stack.test.mjs +800 -0
- package/scripts/clean-node-modules.js +67 -0
- package/scripts/example-projects.js +58 -0
- package/scripts/export-budget.mjs +145 -0
- package/scripts/faults.json +6749 -0
- package/scripts/lib/fault-workspace.mjs +210 -0
- package/scripts/lib/io.mjs +29 -0
- package/scripts/lib/io.test.mjs +38 -0
- package/scripts/lib/markdown-fences.mjs +56 -0
- package/scripts/lib/markdown-fences.test.mjs +84 -0
- package/scripts/lib/package-exports.mjs +54 -0
- package/scripts/lib/package-exports.test.mjs +66 -0
- package/scripts/lib/release-pr-steps.mjs +47 -0
- package/scripts/release-tag.mjs +95 -0
- package/scripts/release-tag.test.mjs +278 -0
- package/scripts/release-workflow.test.mjs +98 -0
- package/scripts/release.js +167 -0
- package/scripts/release.test.mjs +347 -0
- package/scripts/removed-v3-names.js +125 -0
- package/scripts/size-app/.size-limit.js +90 -0
- package/scripts/size-app/core.js +3 -0
- package/scripts/size-app/package.json +24 -0
- package/scripts/size-app/react.js +4 -0
- package/scripts/size-app.test.mjs +73 -0
- package/scripts/sync-ai-context-versions.js +102 -0
- package/scripts/sync-example-versions.js +151 -0
- package/scripts/test-examples-published.mjs +1585 -0
- package/scripts/test-examples-published.test.mjs +1586 -0
- package/scripts/traps/any-method-vars-not-a-list.json +5 -0
- package/scripts/traps/canister-method-unknown.json +5 -0
- package/scripts/traps/client-says-who-calls-once.json +5 -0
- package/scripts/traps/client-says-who-calls.json +5 -0
- package/scripts/traps/err-only-on-canister-err.json +14 -0
- package/scripts/traps/format-units-refuses-missing-value.json +5 -0
- package/scripts/traps/format-units-refuses-number.json +5 -0
- package/scripts/traps/format-units-unknown-option.json +5 -0
- package/scripts/traps/guard-err-is-the-methods.json +5 -0
- package/scripts/traps/guard-false-keeps-every-kind.json +5 -0
- package/scripts/traps/internals-not-exported.json +9 -0
- package/scripts/traps/network-refuses-unknown-name.json +5 -0
- package/scripts/traps/never-err-has-no-type.json +5 -0
- package/scripts/traps/options-method-unknown.json +5 -0
- package/scripts/traps/options-vars-wrong-type.json +5 -0
- package/scripts/traps/parse-units-refuses-number.json +5 -0
- package/scripts/traps/parse-units-unknown-option.json +5 -0
- package/scripts/traps/principal-is-not-plain-text.json +14 -0
- package/scripts/traps/query-options-refuses-oneway.json +5 -0
- package/scripts/traps/query-options-refuses-update.json +5 -0
- package/scripts/traps/refuse-next-unknown-option.json +5 -0
- package/scripts/traps/resend-key-reads-the-methods-arg.json +5 -0
- package/scripts/traps/resend-states-its-dedupe-key.json +5 -0
- package/scripts/traps/suspense-refuses-argument-skip.json +5 -0
- package/scripts/traps/suspense-refuses-skippable-read.json +5 -0
- package/scripts/traps/suspense-refuses-vars-skip-fits.json +5 -0
- package/scripts/traps/test-handler-method-unknown.json +5 -0
- package/scripts/traps/test-handler-nat-is-bigint.json +5 -0
- package/scripts/traps/test-handler-takes-the-domain-value.json +5 -0
- package/scripts/traps/vars-of-two-arguments-are-the-tuple.json +5 -0
- package/scripts/typecheck-examples.js +122 -0
- package/scripts/verify-faults.mjs +375 -0
- package/scripts/verify-faults.test.mjs +402 -0
- package/scripts/verify-package-artifacts.mjs +590 -0
- package/scripts/verify-peer-floors.js +399 -0
- package/scripts/verify-peer-floors.test.mjs +422 -0
- package/scripts/verify-test-fails-rules.mjs +104 -0
- package/scripts/verify-test-fails-rules.test.mjs +215 -0
- package/scripts/verify-test-fails.js +371 -0
- package/scripts/verify-traps.mjs +505 -0
- package/scripts/verify-traps.test.mjs +521 -0
- package/scripts/verify-typescript-floor.js +239 -0
- package/skill-packages/ic-reactor/SKILL.md +103 -0
- package/stack.json +8 -0
- package/tsconfig.base.json +23 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,1020 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Network resolution: which replica a client talks to, whose root key its
|
|
3
|
+
* certificates are checked against, and where a canister id may come from.
|
|
4
|
+
*
|
|
5
|
+
* Only {@link Network} is public. The rest is internal, for the client and
|
|
6
|
+
* the canister builders, and is not exported from the package entry.
|
|
7
|
+
*
|
|
8
|
+
* Everything here decides who is believed, so each rule is a positive
|
|
9
|
+
* allowlist that fails closed:
|
|
10
|
+
*
|
|
11
|
+
* - **A root key is fetched from a replica only when asked for in writing or
|
|
12
|
+
* when the host is local.** A given key is used and never fetched.
|
|
13
|
+
* Anything else is checked against the mainnet key the agent ships with, so
|
|
14
|
+
* a host that is not a local replica can never hand over a key of its own
|
|
15
|
+
* choosing.
|
|
16
|
+
* - **The `ic_env` cookie is believed only where it is as trustworthy as the
|
|
17
|
+
* replica it describes.** Cookies are not origin-isolated: any sibling
|
|
18
|
+
* subdomain of the page's registrable domain can write one. (Not so on
|
|
19
|
+
* `<id>.icp0.io` and `<id>.raw.icp0.io`: `icp0.io` and `*.raw.icp0.io` are
|
|
20
|
+
* on the Public Suffix List, so a browser refuses a cookie a sibling
|
|
21
|
+
* canister sets for them, and the cookie such a page receives is its own
|
|
22
|
+
* asset canister's.) It carries the
|
|
23
|
+
* root key certificates are verified against and the canister ids calls are
|
|
24
|
+
* routed to, and certificate verification cannot notice a substituted id
|
|
25
|
+
* (the attacker names a real canister, whose replies verify against the real
|
|
26
|
+
* root key). So the cookie is read only in a browser, and only when both the
|
|
27
|
+
* agent host and the page are a local replica, or the caller wrote
|
|
28
|
+
* `allowEnvConfig: true`. On a canister's own `icp0.io` page with a mainnet
|
|
29
|
+
* agent host, its canister ids are believed by default. Its root key is
|
|
30
|
+
* never taken for a mainnet host, which has one key, the agent's own; its
|
|
31
|
+
* Internet Identity URL is taken over `http:` only on a local page; and the
|
|
32
|
+
* opt-in itself is refused on a page under a shared parent that is not a
|
|
33
|
+
* public suffix (`ic0.app`, `github.dev`, `gitpod.io`, `loca.lt`,
|
|
34
|
+
* `devtunnels.ms`), where any stranger's page can write the cookie.
|
|
35
|
+
*
|
|
36
|
+
* @module
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import { IC_ROOT_KEY } from "@icp-sdk/core/agent"
|
|
40
|
+
import { safeGetCanisterEnv } from "@icp-sdk/core/agent/canister-env"
|
|
41
|
+
import { Principal } from "@icp-sdk/core/principal"
|
|
42
|
+
import { isServer } from "./runtime.js"
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Where the canisters are.
|
|
46
|
+
*
|
|
47
|
+
* - `"ic"`: mainnet, through `https://icp-api.io`, checked against the
|
|
48
|
+
* mainnet root key the agent ships with. Nothing is fetched.
|
|
49
|
+
* - `"local"`: icp-cli's local network, `http://127.0.0.1:8000`. Its root key
|
|
50
|
+
* is fetched from the replica before the first call. A dfx replica (port
|
|
51
|
+
* 4943) is an object: `{ host: "http://127.0.0.1:4943" }`.
|
|
52
|
+
* - `"env"`: the network of the page this code runs in, as an asset canister
|
|
53
|
+
* or a dev server describes it. In a browser, a page on a local replica or
|
|
54
|
+
* on a mainnet boundary domain routes through its own origin, and so does
|
|
55
|
+
* a page that forwards a dev server: Codespaces, Gitpod, a LAN address
|
|
56
|
+
* (RFC 1918, link-local, IPv6 ULA), a Tailscale address or name
|
|
57
|
+
* (100.64/10, `*.ts.net`), a LAN name with no dot such as `devbox`, a
|
|
58
|
+
* `*.local`, `*.test` or `*.internal` name such as `host.docker.internal`,
|
|
59
|
+
* or a tunnel (ngrok, `trycloudflare.com`, `loca.lt`, VS Code's
|
|
60
|
+
* `devtunnels.ms`). `"env"` is for localhost pages: those
|
|
61
|
+
* others never reach mainnet, but none is local. The root key
|
|
62
|
+
* comes from the `ic_env` cookie, but only where that cookie is trusted:
|
|
63
|
+
* when both the page and the replica are local, or the client was built
|
|
64
|
+
* with `allowEnvConfig: true`. On a canister's own `<id>.icp0.io` page the
|
|
65
|
+
* cookie's canister ids are believed by default, never its root key. A
|
|
66
|
+
* local replica with no key from the cookie has its root key fetched. A
|
|
67
|
+
* forwarding page is routed but is not local, so there the key is neither
|
|
68
|
+
* taken from the cookie nor fetched,
|
|
69
|
+
* and the agent checks the forwarded replica's certificates against
|
|
70
|
+
* mainnet's key, so every certified call fails verification. Name the
|
|
71
|
+
* replica with an object that has a `rootKey` (or `fetchRootKey: true`)
|
|
72
|
+
* and the canisters with `{ id }`: on a dev container or a localtunnel,
|
|
73
|
+
* `allowEnvConfig: true` is refused, because every workspace or tunnel is
|
|
74
|
+
* a sibling of every stranger's. On a
|
|
75
|
+
* mainnet host the cookie's root key is never taken, even with
|
|
76
|
+
* `allowEnvConfig: true`: the agent keeps mainnet's own. On a server there
|
|
77
|
+
* is no page and no cookie: the host is `ICP_HOST` or `IC_HOST` when
|
|
78
|
+
* `ICP_NETWORK` is `"local"` (`http://127.0.0.1:8000` if neither is set),
|
|
79
|
+
* and mainnet otherwise. With no `ICP_NETWORK`, dfx's `DFX_NETWORK=local`
|
|
80
|
+
* counts too, and its default is dfx's `http://127.0.0.1:4943`.
|
|
81
|
+
* - An object: any other replica. `rootKey` is used as given and never
|
|
82
|
+
* fetched. Without one the root key is fetched only when `host` is local
|
|
83
|
+
* (`localhost`, `*.localhost` or any loopback address), unless `fetchRootKey`
|
|
84
|
+
* says otherwise. A replica behind a Codespaces, Gitpod or custom domain is
|
|
85
|
+
* not local: pass its `rootKey`, or write `fetchRootKey: true` to trust the
|
|
86
|
+
* key it reports. `name` is the network's segment in query keys and defaults
|
|
87
|
+
* to `host`.
|
|
88
|
+
*/
|
|
89
|
+
export type Network =
|
|
90
|
+
| "ic"
|
|
91
|
+
| "local"
|
|
92
|
+
| "env"
|
|
93
|
+
| {
|
|
94
|
+
/**
|
|
95
|
+
* The replica's URL, over `http:` or `https:`. A host with no scheme is
|
|
96
|
+
* read against the page's protocol, as `HttpAgent` reads it, and is
|
|
97
|
+
* refused where there is no page. `createClient` throws a `TypeError`
|
|
98
|
+
* for any other host, such as `localhost:4943`, which reads as the
|
|
99
|
+
* scheme `localhost:`.
|
|
100
|
+
*/
|
|
101
|
+
readonly host: string
|
|
102
|
+
/**
|
|
103
|
+
* The root key certificates are verified against. Used as given and
|
|
104
|
+
* never fetched, even where `fetchRootKey` is `true`.
|
|
105
|
+
*/
|
|
106
|
+
readonly rootKey?: Uint8Array
|
|
107
|
+
/** This network's segment in query keys. Defaults to `host`. */
|
|
108
|
+
readonly name?: string
|
|
109
|
+
/**
|
|
110
|
+
* Whether to fetch the root key from the replica when `rootKey` is
|
|
111
|
+
* absent. Defaults to whether `host` is local. Writing `true` for a host
|
|
112
|
+
* that is not local trusts whatever key that host reports, which is why it
|
|
113
|
+
* is never the default.
|
|
114
|
+
*/
|
|
115
|
+
readonly fetchRootKey?: boolean
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** What the caller can decide about trusting the `ic_env` cookie. */
|
|
119
|
+
export interface NetworkOptions {
|
|
120
|
+
/**
|
|
121
|
+
* `true` trusts the cookie (root key and canister ids) wherever the code
|
|
122
|
+
* runs in a browser, `false` never reads it, and absent trusts it only when
|
|
123
|
+
* both the agent host and the page are a local replica, or (canister ids
|
|
124
|
+
* only) when the page is a canister's own `<id>.icp0.io` or
|
|
125
|
+
* `<id>.raw.icp0.io` page and the agent host is mainnet's. Set it to `true`
|
|
126
|
+
* only when every subdomain of the page's domain is trusted to write
|
|
127
|
+
* cookies. Even with `true`, the root key is never taken for a mainnet host
|
|
128
|
+
* and an `http:` Internet Identity URL only on a local page.
|
|
129
|
+
*
|
|
130
|
+
* @throws TypeError, when `true`, on a page under `ic0.app`, `github.dev`
|
|
131
|
+
* (Codespaces), `gitpod.io`, `loca.lt` (localtunnel) or `devtunnels.ms`
|
|
132
|
+
* (VS Code dev tunnels): none of them is a public suffix, so any other site
|
|
133
|
+
* under it can write the cookie this page reads.
|
|
134
|
+
*/
|
|
135
|
+
readonly allowEnvConfig?: boolean
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** A {@link Network} with every decision made. */
|
|
139
|
+
export interface ResolvedNetwork {
|
|
140
|
+
/** The network's segment in query keys: `"ic"`, `"local"`, `name ?? host` for a custom network, the resolved host for `"env"`. */
|
|
141
|
+
readonly keySegment: string
|
|
142
|
+
/** The URL the agent connects to. */
|
|
143
|
+
readonly host: string
|
|
144
|
+
/** The root key to verify with. Absent means the agent's own: mainnet's, or the one it fetches when {@link fetchRootKey} is set. */
|
|
145
|
+
readonly rootKey?: Uint8Array
|
|
146
|
+
/** Whether the agent fetches the root key from `host`. Never `true` while {@link rootKey} is set. */
|
|
147
|
+
readonly fetchRootKey: boolean
|
|
148
|
+
/** Whether the `ic_env` cookie is believed. Always `false` outside a browser page. */
|
|
149
|
+
readonly trustsEnv: boolean
|
|
150
|
+
/**
|
|
151
|
+
* Present when the cookie is believed for canister ids only, as on a
|
|
152
|
+
* canister's own `icp0.io` page by default: its root key is never taken,
|
|
153
|
+
* nor its sign-in entries (`INTERNET_IDENTITY_PROVIDER` and
|
|
154
|
+
* `PUBLIC_CANISTER_ID:internet_identity` as the canister that mints
|
|
155
|
+
* delegations), so sign-in keeps `AuthClient`'s defaults.
|
|
156
|
+
*/
|
|
157
|
+
readonly envIdsOnly?: true
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** The part of `HttpAgentOptions` a resolved network decides. */
|
|
161
|
+
export interface AgentNetworkOptions {
|
|
162
|
+
readonly host: string
|
|
163
|
+
readonly rootKey?: Uint8Array
|
|
164
|
+
readonly shouldFetchRootKey: boolean
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** A canister to call: by its id, or by the name the `ic_env` cookie gives it. */
|
|
168
|
+
export type CanisterIdTarget =
|
|
169
|
+
{ readonly id: string } | { readonly name: string }
|
|
170
|
+
|
|
171
|
+
/** Why a `{ name }` target could not be resolved. */
|
|
172
|
+
export type CanisterIdFailure = "no_browser" | "untrusted_host" | "absent"
|
|
173
|
+
|
|
174
|
+
/** The result of {@link resolveCanisterId}. */
|
|
175
|
+
export type CanisterIdResolution =
|
|
176
|
+
| { readonly ok: true; readonly id: string }
|
|
177
|
+
| {
|
|
178
|
+
readonly ok: false
|
|
179
|
+
readonly reason: CanisterIdFailure
|
|
180
|
+
readonly message: string
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** A value for an error message, without throwing on a value `JSON.stringify` rejects. */
|
|
184
|
+
const describe = (value: unknown): string => {
|
|
185
|
+
try {
|
|
186
|
+
return JSON.stringify(value) ?? String(value)
|
|
187
|
+
} catch {
|
|
188
|
+
return String(value)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Throws a `TypeError` for a key of `options` that `names` does not list, so a
|
|
194
|
+
* misspelt option fails instead of being ignored. Internal.
|
|
195
|
+
*/
|
|
196
|
+
export const checkKeys = (
|
|
197
|
+
options: object,
|
|
198
|
+
names: readonly string[],
|
|
199
|
+
what: string
|
|
200
|
+
): void => {
|
|
201
|
+
const unknown = Object.keys(options).filter((key) => !names.includes(key))
|
|
202
|
+
if (unknown.length > 0) {
|
|
203
|
+
throw new TypeError(
|
|
204
|
+
`[ic-reactor] ${what} has no option ${unknown.join(", ")}. Its options: ${names.join(", ")}.`
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Throws a `TypeError` unless `value` is `true`, `false` or absent: the text
|
|
211
|
+
* `"false"` an environment variable gives is truthy, and would trust what
|
|
212
|
+
* `false` refuses. Internal.
|
|
213
|
+
*/
|
|
214
|
+
export const checkBoolean = (value: unknown, name: string): void => {
|
|
215
|
+
if (value !== undefined && typeof value !== "boolean") {
|
|
216
|
+
throw new TypeError(
|
|
217
|
+
`[ic-reactor] ${name} is true or false, got ${describe(value)}.`
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** The keys a network object may have. */
|
|
223
|
+
const NETWORK_KEYS = ["host", "rootKey", "name", "fetchRootKey"]
|
|
224
|
+
|
|
225
|
+
/** The length of a DER-encoded BLS root key. */
|
|
226
|
+
const ROOT_KEY_LENGTH = 133
|
|
227
|
+
|
|
228
|
+
const badTarget = (target: unknown): TypeError =>
|
|
229
|
+
new TypeError(
|
|
230
|
+
`[ic-reactor] a canister target is { id: string } or { name: string }, got ${describe(target)}.`
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
/** Validates principal text, and returns the reason it is not one. */
|
|
234
|
+
const principalProblem = (text: string): string | undefined => {
|
|
235
|
+
try {
|
|
236
|
+
// `fromText` also accepts the JSON form `{"__principal__":"..."}`, which is
|
|
237
|
+
// not an id: only the text it prints back is.
|
|
238
|
+
return Principal.fromText(text).toText() === text
|
|
239
|
+
? undefined
|
|
240
|
+
: "it is not in canonical text form"
|
|
241
|
+
} catch (error) {
|
|
242
|
+
return error instanceof Error ? error.message : String(error)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const IC_HOST = "https://icp-api.io"
|
|
247
|
+
/** icp-cli's local network: its gateway's default port is 8000. */
|
|
248
|
+
const LOCAL_HOST = "http://127.0.0.1:8000"
|
|
249
|
+
/** dfx's local replica, the default only when `DFX_NETWORK` is the signal. */
|
|
250
|
+
const DFX_LOCAL_HOST = "http://127.0.0.1:4943"
|
|
251
|
+
|
|
252
|
+
/** The domains whose pages route agent traffic through their own origin. */
|
|
253
|
+
const MAINNET_DOMAINS = ["ic0.app", "icp0.io", "icp-api.io"]
|
|
254
|
+
|
|
255
|
+
/** Whether `hostname` is `domain` or one of its subdomains. */
|
|
256
|
+
const isUnder = (hostname: string, domain: string): boolean =>
|
|
257
|
+
hostname === domain || hostname.endsWith(`.${domain}`)
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Whether `hostname` is mainnet's: a boundary domain or the API host `"ic"`
|
|
261
|
+
* and the fallback use. Mainnet has one root key, the one the agent ships
|
|
262
|
+
* with, so no cookie's key is ever taken for it.
|
|
263
|
+
*/
|
|
264
|
+
const isMainnetHostname = (hostname: string | undefined): boolean =>
|
|
265
|
+
hostname !== undefined &&
|
|
266
|
+
MAINNET_DOMAINS.some((domain) => isUnder(hostname, domain))
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Parents whose subdomains are strangers' sites and which are not on the
|
|
270
|
+
* Public Suffix List, so a page on any one of them can set a cookie for the
|
|
271
|
+
* whole parent, and every other page under it reads that cookie. `ic0.app`
|
|
272
|
+
* serves every canister's frontend, `github.dev` every codespace
|
|
273
|
+
* (`*.app.github.dev`), `gitpod.io` every Gitpod workspace, `loca.lt` every
|
|
274
|
+
* localtunnel and `devtunnels.ms` every VS Code dev tunnel. `icp0.io` and
|
|
275
|
+
* `*.raw.icp0.io` are public suffixes, so they are not here: a browser
|
|
276
|
+
* refuses a cookie a sibling canister sets for them. So are the other tunnel
|
|
277
|
+
* parents {@link REMOTE_DEV_SUFFIXES} names.
|
|
278
|
+
*/
|
|
279
|
+
const SHARED_PARENTS = [
|
|
280
|
+
"ic0.app",
|
|
281
|
+
"github.dev",
|
|
282
|
+
"gitpod.io",
|
|
283
|
+
"loca.lt",
|
|
284
|
+
"devtunnels.ms",
|
|
285
|
+
]
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* A canister's own page on `icp0.io`: `<id>.icp0.io` or `<id>.raw.icp0.io`,
|
|
289
|
+
* `<id>` being principal text. Both parents are on the Public Suffix List
|
|
290
|
+
* (`icp0.io` and `*.raw.icp0.io`), so no other site can set a cookie this
|
|
291
|
+
* page receives. `ic0.app` is not on it, nor is a custom domain known to be.
|
|
292
|
+
*/
|
|
293
|
+
const ICP0_CANISTER_PAGE = /^([a-z0-9-]+)\.(?:raw\.)?icp0\.io$/
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Names that forward a dev server and its replica to the browser from
|
|
297
|
+
* elsewhere: dev containers (Codespaces, Gitpod), the special-use and
|
|
298
|
+
* internal names a LAN, mDNS or Docker gives a dev machine (`*.local`,
|
|
299
|
+
* `*.test`, `*.internal`, which covers `host.docker.internal`), the names
|
|
300
|
+
* Tailscale gives the machines of a tailnet (`*.ts.net`), and the tunnels
|
|
301
|
+
* people open to show a dev app on a phone. Their pages route through
|
|
302
|
+
* their own origin, but they are never local: on a dev container every
|
|
303
|
+
* workspace is a sibling of every stranger's, and the rest are not this
|
|
304
|
+
* machine. A production app on such a name falls back to nothing it did not
|
|
305
|
+
* ask for: it reaches its own origin, never mainnet.
|
|
306
|
+
*/
|
|
307
|
+
const REMOTE_DEV_SUFFIXES = [
|
|
308
|
+
".github.dev",
|
|
309
|
+
".gitpod.io",
|
|
310
|
+
".local",
|
|
311
|
+
".test",
|
|
312
|
+
".internal",
|
|
313
|
+
".ngrok-free.app",
|
|
314
|
+
".ngrok-free.dev",
|
|
315
|
+
".ngrok.app",
|
|
316
|
+
".ngrok.io",
|
|
317
|
+
".trycloudflare.com",
|
|
318
|
+
".loca.lt",
|
|
319
|
+
".devtunnels.ms",
|
|
320
|
+
".ts.net",
|
|
321
|
+
]
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Private and link-local addresses, which a LAN or a VM gives a dev machine
|
|
325
|
+
* (`vite --host` prints one): 10/8, 172.16/12, 192.168/16 and 169.254/16,
|
|
326
|
+
* the shared 100.64/10 that Tailscale gives each machine of a tailnet, and
|
|
327
|
+
* IPv6's unique local fc00::/7 (Tailscale's IPv6 addresses among them) and
|
|
328
|
+
* link-local fe80::/10, with the brackets a URL's `hostname` keeps around
|
|
329
|
+
* them.
|
|
330
|
+
*/
|
|
331
|
+
const PRIVATE_ADDRESS =
|
|
332
|
+
/^(?:10\.\d{1,3}|172\.(?:1[6-9]|2\d|3[01])|192\.168|169\.254|100\.(?:6[4-9]|[7-9]\d|1[01]\d|12[0-7]))\.\d{1,3}\.\d{1,3}$|^\[(?:f[cd][0-9a-f]{2}|fe[89ab][0-9a-f]):[0-9a-f:.]*\]$/i
|
|
333
|
+
|
|
334
|
+
/** An `http:` or `https:` origin: a page a web server serves. */
|
|
335
|
+
const WEB_ORIGIN = /^https?:\/\//i
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Whether the page at `origin`, whose hostname is `hostname`, forwards a dev
|
|
339
|
+
* server from elsewhere: a {@link REMOTE_DEV_SUFFIXES} name, a
|
|
340
|
+
* {@link PRIVATE_ADDRESS}, or a web page whose name has no dot, which only a
|
|
341
|
+
* LAN, a hosts file or a VPN's DNS resolves (`http://devbox:5173`). An IPv6
|
|
342
|
+
* address has no dot either, so one in brackets is not such a name. Neither
|
|
343
|
+
* is the id a browser extension's or a desktop app's own scheme puts there
|
|
344
|
+
* (`chrome-extension://<id>`, `app://name`): no dev server is behind it.
|
|
345
|
+
*/
|
|
346
|
+
const isRemoteDevPage = (origin: string, hostname: string): boolean =>
|
|
347
|
+
PRIVATE_ADDRESS.test(hostname) ||
|
|
348
|
+
REMOTE_DEV_SUFFIXES.some((suffix) => hostname.endsWith(suffix)) ||
|
|
349
|
+
(WEB_ORIGIN.test(origin) &&
|
|
350
|
+
!hostname.includes(".") &&
|
|
351
|
+
!hostname.startsWith("["))
|
|
352
|
+
|
|
353
|
+
/** 127.0.0.0/8: the entire IPv4 loopback range, not just 127.0.0.1. */
|
|
354
|
+
const IPV4_LOOPBACK = /^127\.(?:\d{1,3}\.){2}\d{1,3}$/
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Whether `hostname` is a local replica: `localhost` and its subdomains, all
|
|
358
|
+
* of 127.0.0.0/8, and the IPv6 `::1` with or without the brackets a URL's
|
|
359
|
+
* `hostname` keeps around it.
|
|
360
|
+
*
|
|
361
|
+
* This is a positive allowlist, and deliberately not "anything that is not
|
|
362
|
+
* mainnet": a production dapp on a custom domain, a Codespaces host and a
|
|
363
|
+
* Gitpod host are all outside it. The names are matched whole or as a
|
|
364
|
+
* `.localhost` subdomain, so `notlocalhost` and `localhost.example.com` are
|
|
365
|
+
* not local.
|
|
366
|
+
*/
|
|
367
|
+
const isLocalHostname = (hostname: string | undefined): boolean => {
|
|
368
|
+
if (!hostname) return false
|
|
369
|
+
return (
|
|
370
|
+
hostname === "localhost" ||
|
|
371
|
+
hostname.endsWith(".localhost") ||
|
|
372
|
+
IPV4_LOOPBACK.test(hostname) ||
|
|
373
|
+
hostname === "::1" ||
|
|
374
|
+
hostname === "[::1]"
|
|
375
|
+
)
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* The URL `HttpAgent` connects to for `host`, found the way the agent
|
|
380
|
+
* finds it (`determineHost` in `@icp-sdk/core`): on a page, a host that does
|
|
381
|
+
* not start with a scheme, such as `127.0.0.1:4943` or a bare Codespaces
|
|
382
|
+
* domain, is read against the page's protocol. The agent's scheme test is
|
|
383
|
+
* copied as is, so `localhost:4943`, which it reads as the scheme `localhost:`,
|
|
384
|
+
* has no hostname here either, and neither has a scheme-less host where there
|
|
385
|
+
* is no page. A host the agent cannot read is not local. Internal: also read
|
|
386
|
+
* by `createClient`'s host check, and not exported from the package entry.
|
|
387
|
+
*/
|
|
388
|
+
export const agentUrlOf = (host: string): URL | undefined => {
|
|
389
|
+
try {
|
|
390
|
+
return !/^[a-z]+:/.test(host) && typeof window !== "undefined"
|
|
391
|
+
? new URL(`${window.location.protocol}//${host}`)
|
|
392
|
+
: new URL(host)
|
|
393
|
+
} catch {
|
|
394
|
+
return undefined
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* A hostname without the one trailing dot a fully qualified name may end
|
|
400
|
+
* with. The URL parser keeps it, so `victim.ic0.app.` would otherwise not be
|
|
401
|
+
* under `ic0.app`, and every rule here that names a domain would miss it
|
|
402
|
+
* (all but {@link pageIsIcp0Canister}'s, which keeps it on purpose).
|
|
403
|
+
*/
|
|
404
|
+
const withoutRootDot = (hostname: string): string =>
|
|
405
|
+
hostname.endsWith(".") ? hostname.slice(0, -1) : hostname
|
|
406
|
+
|
|
407
|
+
/** The hostname of {@link agentUrlOf}, without its trailing dot. */
|
|
408
|
+
const agentHostnameOf = (host: string): string | undefined => {
|
|
409
|
+
const hostname = agentUrlOf(host)?.hostname
|
|
410
|
+
return hostname === undefined ? undefined : withoutRootDot(hostname)
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* The hostname of a page origin as the URL parser reads it, trailing dot
|
|
415
|
+
* included, or `undefined` when it is not a URL (an opaque origin reads as
|
|
416
|
+
* `"null"`).
|
|
417
|
+
*/
|
|
418
|
+
const parsedHostnameOf = (origin: string | undefined): string | undefined => {
|
|
419
|
+
if (!origin) return undefined
|
|
420
|
+
try {
|
|
421
|
+
return new URL(origin).hostname || undefined
|
|
422
|
+
} catch {
|
|
423
|
+
return undefined
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/** The hostname of a page origin without its trailing dot (see {@link parsedHostnameOf}). */
|
|
428
|
+
const pageHostnameOf = (origin: string | undefined): string | undefined => {
|
|
429
|
+
const hostname = parsedHostnameOf(origin)
|
|
430
|
+
return hostname === undefined
|
|
431
|
+
? undefined
|
|
432
|
+
: withoutRootDot(hostname) || undefined
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* The origin of the page this code runs for. A web worker has no `window`,
|
|
437
|
+
* but its global scope has a `location` (the worker script's URL), whose
|
|
438
|
+
* origin is that of the page that started it. Node has neither. Deno 2 has no
|
|
439
|
+
* `window`, and reading its `location` throws unless it was started with
|
|
440
|
+
* `--location`. React Native defines `window` with no `location` at all.
|
|
441
|
+
*/
|
|
442
|
+
const pageOrigin = (): string | undefined => {
|
|
443
|
+
try {
|
|
444
|
+
const location =
|
|
445
|
+
typeof window !== "undefined"
|
|
446
|
+
? window.location
|
|
447
|
+
: (globalThis as { location?: { origin?: string } }).location
|
|
448
|
+
return location?.origin
|
|
449
|
+
} catch {
|
|
450
|
+
return undefined
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/** Whether the page this code runs in is a local replica's page. A worker has no cookie to protect, so it is never one. */
|
|
455
|
+
const pageIsLocal = (): boolean =>
|
|
456
|
+
typeof window !== "undefined" && isLocalHostname(pageHostnameOf(pageOrigin()))
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* The page's own origin when agent traffic can route through it: a local page
|
|
460
|
+
* (a dev server or a locally deployed asset canister proxies `/api`), a
|
|
461
|
+
* mainnet boundary domain, or a page that forwards a dev server from
|
|
462
|
+
* elsewhere ({@link isRemoteDevPage}). An ordinary web host (Vercel,
|
|
463
|
+
* Cloudflare, a custom domain) cannot, so it is left to the fallback; so is a
|
|
464
|
+
* page with no usable origin.
|
|
465
|
+
*
|
|
466
|
+
* A Codespaces page used to fall back to mainnet, and a dev app in a codespace
|
|
467
|
+
* sent its local canister ids to mainnet, where whatever canister had that id
|
|
468
|
+
* answered. So did a dev page opened from a phone by its LAN address, a
|
|
469
|
+
* Docker host name or a tunnel: a wallet whose ledger has the same id locally
|
|
470
|
+
* and on mainnet showed, and after a mainnet sign-in sent, real ICP.
|
|
471
|
+
*/
|
|
472
|
+
const routableOrigin = (): string | undefined => {
|
|
473
|
+
const origin = pageOrigin()
|
|
474
|
+
const hostname = pageHostnameOf(origin)
|
|
475
|
+
if (origin === undefined || hostname === undefined) return undefined
|
|
476
|
+
const routable =
|
|
477
|
+
isLocalHostname(hostname) ||
|
|
478
|
+
isMainnetHostname(hostname) ||
|
|
479
|
+
isRemoteDevPage(origin, hostname)
|
|
480
|
+
return routable ? origin : undefined
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/** `process.env`, or `undefined` where there is no `process`. */
|
|
484
|
+
const processEnv = (): Record<string, string | undefined> | undefined => {
|
|
485
|
+
try {
|
|
486
|
+
return typeof process !== "undefined" ? process.env : undefined
|
|
487
|
+
} catch {
|
|
488
|
+
return undefined
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* The host `"env"` falls back to when no page routes the agent. With
|
|
494
|
+
* `ICP_NETWORK` set to `"local"` that is `ICP_HOST` or `IC_HOST`, else
|
|
495
|
+
* icp-cli's local network, `"local"`'s `http://127.0.0.1:8000`. The older
|
|
496
|
+
* `DFX_NETWORK` counts only when `ICP_NETWORK` is not set (which overrides
|
|
497
|
+
* it): with `DFX_NETWORK=local` it is `ICP_HOST` or `IC_HOST`, else dfx's own
|
|
498
|
+
* replica, `http://127.0.0.1:4943`, because that variable says the project
|
|
499
|
+
* runs dfx. Any other value, or none, is mainnet. v3 fell back to
|
|
500
|
+
* `https://ic0.app` here; this is the same mainnet host `"ic"` uses.
|
|
501
|
+
*/
|
|
502
|
+
const fallbackHost = (): string => {
|
|
503
|
+
const env = processEnv()
|
|
504
|
+
const fromIcp = env?.ICP_NETWORK !== undefined
|
|
505
|
+
const network = fromIcp ? env?.ICP_NETWORK : env?.DFX_NETWORK
|
|
506
|
+
if (network !== "local") return IC_HOST
|
|
507
|
+
return (
|
|
508
|
+
env?.ICP_HOST || env?.IC_HOST || (fromIcp ? LOCAL_HOST : DFX_LOCAL_HOST)
|
|
509
|
+
)
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Whether the `ic_env` cookie is believed for an agent on `host`.
|
|
514
|
+
*
|
|
515
|
+
* Both sides must be a local replica, because the page decides who can write
|
|
516
|
+
* the cookie: a document on `app.example.com` pointed at a loopback replica
|
|
517
|
+
* shares its cookie jar with every sibling of `example.com`, and the agent host
|
|
518
|
+
* says nothing about that. `allowEnvConfig` overrides the pair either way,
|
|
519
|
+
* except that outside a browser page there is no cookie to believe at all.
|
|
520
|
+
*/
|
|
521
|
+
const trustsEnvFor = (
|
|
522
|
+
host: string,
|
|
523
|
+
allowEnvConfig: boolean | undefined
|
|
524
|
+
): boolean => {
|
|
525
|
+
if (isServer()) return false
|
|
526
|
+
return (
|
|
527
|
+
allowEnvConfig ?? (isLocalHostname(agentHostnameOf(host)) && pageIsLocal())
|
|
528
|
+
)
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Whether the page is a canister's own {@link ICP0_CANISTER_PAGE}. A worker
|
|
533
|
+
* has no cookie, so it is never one. The name is matched as the URL parser
|
|
534
|
+
* reads it: `<id>.icp0.io.`, with the trailing dot, believes nothing by
|
|
535
|
+
* default, because the Public Suffix List's protection of a name written
|
|
536
|
+
* that way has not been checked.
|
|
537
|
+
*/
|
|
538
|
+
const pageIsIcp0Canister = (): boolean => {
|
|
539
|
+
if (typeof window === "undefined") return false
|
|
540
|
+
const match = ICP0_CANISTER_PAGE.exec(parsedHostnameOf(pageOrigin()) ?? "")
|
|
541
|
+
return match !== null && principalProblem(match[1]!) === undefined
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* How far the `ic_env` cookie is believed for an agent on `host`: the
|
|
546
|
+
* {@link ResolvedNetwork} fields `trustsEnv` and `envIdsOnly`. With no
|
|
547
|
+
* `allowEnvConfig`, a canister's own `icp0.io` page talking to mainnet has
|
|
548
|
+
* its cookie believed for canister ids: icp-cli's asset canister serves it,
|
|
549
|
+
* and no sibling can write it.
|
|
550
|
+
*/
|
|
551
|
+
const envTrustFor = (
|
|
552
|
+
host: string,
|
|
553
|
+
allowEnvConfig: boolean | undefined
|
|
554
|
+
): Pick<ResolvedNetwork, "trustsEnv" | "envIdsOnly"> => {
|
|
555
|
+
if (trustsEnvFor(host, allowEnvConfig)) return { trustsEnv: true }
|
|
556
|
+
return allowEnvConfig === undefined &&
|
|
557
|
+
!isServer() &&
|
|
558
|
+
isMainnetHostname(agentHostnameOf(host)) &&
|
|
559
|
+
pageIsIcp0Canister()
|
|
560
|
+
? { trustsEnv: true, envIdsOnly: true }
|
|
561
|
+
: { trustsEnv: false }
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Refuses `allowEnvConfig` on a browser page under a {@link SHARED_PARENTS}
|
|
566
|
+
* domain, where it would believe a cookie any stranger's page can set.
|
|
567
|
+
*/
|
|
568
|
+
const refuseSharedParent = (allowEnvConfig: boolean | undefined): void => {
|
|
569
|
+
if (!allowEnvConfig || isServer()) return
|
|
570
|
+
const origin = pageOrigin()
|
|
571
|
+
const hostname = pageHostnameOf(origin)
|
|
572
|
+
const parent =
|
|
573
|
+
hostname === undefined
|
|
574
|
+
? undefined
|
|
575
|
+
: SHARED_PARENTS.find((domain) => isUnder(hostname, domain))
|
|
576
|
+
if (parent === undefined) return
|
|
577
|
+
throw new TypeError(
|
|
578
|
+
`[ic-reactor] allowEnvConfig: true is refused on ${origin}: ${parent} is not a public suffix, ` +
|
|
579
|
+
`so any other site under it can set the ic_env cookie this page reads, with canister ids of its choosing. ` +
|
|
580
|
+
`Name the canisters with { id } (bake the ids in at build time), and a replica that is not mainnet ` +
|
|
581
|
+
`with { host, rootKey }.`
|
|
582
|
+
)
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Entry `key` of the `ic_env` cookie, as `@icp-sdk/core` reads it, or `null`
|
|
587
|
+
* when a page that is not local receives more than one `ic_env` cookie and
|
|
588
|
+
* they differ. A browser keeps one for each path or domain a cookie was set
|
|
589
|
+
* for, and `@icp-sdk/core` reads the first it lists, which need not be the
|
|
590
|
+
* one the frontend's canister set last, so differing copies are believed for
|
|
591
|
+
* nothing. A local page reads the first, as before: a redeploy there often
|
|
592
|
+
* leaves an older copy at another path, and only the machine itself can set
|
|
593
|
+
* one. Only ever called for a trusted page.
|
|
594
|
+
*/
|
|
595
|
+
const cookieEntry = (key: string): unknown => {
|
|
596
|
+
try {
|
|
597
|
+
return !pageIsLocal() &&
|
|
598
|
+
new Set(
|
|
599
|
+
document.cookie
|
|
600
|
+
.split(";")
|
|
601
|
+
.map((cookie) => cookie.trim())
|
|
602
|
+
.filter((cookie) => cookie.startsWith("ic_env="))
|
|
603
|
+
).size > 1
|
|
604
|
+
? null
|
|
605
|
+
: safeGetCanisterEnv()?.[key]
|
|
606
|
+
} catch {
|
|
607
|
+
return undefined
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/** The root key the cookie carries, or `undefined` when it carries none. */
|
|
612
|
+
const cookieRootKey = (): Uint8Array | undefined => {
|
|
613
|
+
const rootKey = cookieEntry("IC_ROOT_KEY")
|
|
614
|
+
return rootKey instanceof Uint8Array ? rootKey : undefined
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/** Builds a {@link ResolvedNetwork}, leaving `rootKey` out rather than `undefined`. */
|
|
618
|
+
const resolved = (
|
|
619
|
+
parts: Omit<ResolvedNetwork, "rootKey"> & { rootKey?: Uint8Array }
|
|
620
|
+
): ResolvedNetwork => {
|
|
621
|
+
const { rootKey, ...rest } = parts
|
|
622
|
+
return rootKey === undefined ? rest : { ...rest, rootKey }
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Decides everything about a network that the client needs before it builds an
|
|
627
|
+
* agent: the host, the root key (given, taken from a trusted cookie, or to be
|
|
628
|
+
* fetched), the key segment, and whether the `ic_env` cookie is believed.
|
|
629
|
+
*
|
|
630
|
+
* Reads the page it runs in, so it is called once, when the client is built.
|
|
631
|
+
* The legacy v3 option `allowEnvRootKey` is not carried over: `allowEnvConfig`
|
|
632
|
+
* is the only opt-in, and it covers the whole cookie.
|
|
633
|
+
*
|
|
634
|
+
* @throws TypeError for a network that is not one of the allowed shapes (a
|
|
635
|
+
* network object with a key it does not have, a `fetchRootKey` that is not a
|
|
636
|
+
* boolean, or a `rootKey` that is not 133 bytes in a `Uint8Array`), an
|
|
637
|
+
* `allowEnvConfig` that is not a boolean, or `allowEnvConfig: true` on a page
|
|
638
|
+
* under a shared parent that is not a public suffix: a programmer error at
|
|
639
|
+
* build time.
|
|
640
|
+
*/
|
|
641
|
+
export const resolveNetwork = (
|
|
642
|
+
network: Network,
|
|
643
|
+
options: NetworkOptions = {}
|
|
644
|
+
): ResolvedNetwork => {
|
|
645
|
+
const { allowEnvConfig } = options
|
|
646
|
+
checkBoolean(allowEnvConfig, "allowEnvConfig")
|
|
647
|
+
refuseSharedParent(allowEnvConfig)
|
|
648
|
+
|
|
649
|
+
if (network === "ic") {
|
|
650
|
+
return resolved({
|
|
651
|
+
keySegment: "ic",
|
|
652
|
+
host: IC_HOST,
|
|
653
|
+
fetchRootKey: false,
|
|
654
|
+
...envTrustFor(IC_HOST, allowEnvConfig),
|
|
655
|
+
})
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
if (network === "local") {
|
|
659
|
+
return resolved({
|
|
660
|
+
keySegment: "local",
|
|
661
|
+
host: LOCAL_HOST,
|
|
662
|
+
fetchRootKey: true,
|
|
663
|
+
...envTrustFor(LOCAL_HOST, allowEnvConfig),
|
|
664
|
+
})
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
if (network === "env") {
|
|
668
|
+
const host = routableOrigin() ?? fallbackHost()
|
|
669
|
+
const trust = envTrustFor(host, allowEnvConfig)
|
|
670
|
+
// The key is the replica's, so it is taken only from a cookie that is as
|
|
671
|
+
// trustworthy as the replica, and never for mainnet, whose key the agent
|
|
672
|
+
// already holds (a cookie believed for ids only is always a mainnet
|
|
673
|
+
// host's). Without one, a local replica is asked.
|
|
674
|
+
const rootKey =
|
|
675
|
+
trust.trustsEnv && !isMainnetHostname(agentHostnameOf(host))
|
|
676
|
+
? cookieRootKey()
|
|
677
|
+
: undefined
|
|
678
|
+
return resolved({
|
|
679
|
+
keySegment: host,
|
|
680
|
+
host,
|
|
681
|
+
rootKey,
|
|
682
|
+
fetchRootKey:
|
|
683
|
+
rootKey === undefined && isLocalHostname(agentHostnameOf(host)),
|
|
684
|
+
...trust,
|
|
685
|
+
})
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
if (
|
|
689
|
+
typeof network !== "object" ||
|
|
690
|
+
network === null ||
|
|
691
|
+
typeof network.host !== "string"
|
|
692
|
+
) {
|
|
693
|
+
throw new TypeError(
|
|
694
|
+
`[ic-reactor] unknown network ${describe(network)}: expected "ic", "local", "env", or { host: string, rootKey?, name?, fetchRootKey? }.`
|
|
695
|
+
)
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
checkKeys(network, NETWORK_KEYS, "a network object")
|
|
699
|
+
const { host, name, rootKey, fetchRootKey } = network
|
|
700
|
+
checkBoolean(fetchRootKey, "fetchRootKey")
|
|
701
|
+
if (
|
|
702
|
+
rootKey !== undefined &&
|
|
703
|
+
!(rootKey instanceof Uint8Array && rootKey.length === ROOT_KEY_LENGTH)
|
|
704
|
+
) {
|
|
705
|
+
throw new TypeError(
|
|
706
|
+
`[ic-reactor] rootKey is a Uint8Array of ${ROOT_KEY_LENGTH} bytes (decode hex first), got ${
|
|
707
|
+
rootKey instanceof Uint8Array
|
|
708
|
+
? `${rootKey.length} bytes`
|
|
709
|
+
: describe(rootKey)
|
|
710
|
+
}.`
|
|
711
|
+
)
|
|
712
|
+
}
|
|
713
|
+
return resolved({
|
|
714
|
+
keySegment: name ?? host,
|
|
715
|
+
host,
|
|
716
|
+
rootKey,
|
|
717
|
+
// A key the caller gave is the one to verify with, so nothing is fetched to
|
|
718
|
+
// replace it. Otherwise a replica is asked only when it is local, or when
|
|
719
|
+
// the caller wrote it out.
|
|
720
|
+
fetchRootKey:
|
|
721
|
+
rootKey === undefined &&
|
|
722
|
+
(fetchRootKey ?? isLocalHostname(agentHostnameOf(host))),
|
|
723
|
+
...envTrustFor(host, allowEnvConfig),
|
|
724
|
+
})
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* The agent options a resolved network decides, so that every `HttpAgent` is
|
|
729
|
+
* built from one place. A given or cookie-supplied `rootKey` is passed and
|
|
730
|
+
* `shouldFetchRootKey` is `false`: the agent holds that key from the start and
|
|
731
|
+
* never requests `/api/v2/status`. With neither, the agent holds mainnet's, or
|
|
732
|
+
* (when `shouldFetchRootKey` is `true`) asks the replica on its first request.
|
|
733
|
+
*/
|
|
734
|
+
export const agentOptionsFor = (net: ResolvedNetwork): AgentNetworkOptions => ({
|
|
735
|
+
host: net.host,
|
|
736
|
+
...(net.rootKey === undefined ? {} : { rootKey: net.rootKey }),
|
|
737
|
+
shouldFetchRootKey: net.fetchRootKey,
|
|
738
|
+
})
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* The canister a target names.
|
|
742
|
+
*
|
|
743
|
+
* `{ id }` is validated now, and an id that is not principal text throws a
|
|
744
|
+
* `TypeError` that names it: a programmer error at build time, never something
|
|
745
|
+
* a deployed app hits at run time.
|
|
746
|
+
*
|
|
747
|
+
* `{ name }` is read from the `PUBLIC_CANISTER_ID:<name>` entry of the
|
|
748
|
+
* `ic_env` cookie, and only when the network trusts the cookie. It never
|
|
749
|
+
* throws and sends nothing: where the id cannot be had it says why, in one of
|
|
750
|
+
* three ways that need three different fixes, so a refused cookie is not
|
|
751
|
+
* reported as an absent one, and a server render is not blamed on the host
|
|
752
|
+
* for having no cookie jar:
|
|
753
|
+
*
|
|
754
|
+
* - `no_browser`: there is no page, so no cookie jar to read.
|
|
755
|
+
* - `untrusted_host`: the cookie is there to read but not trusted for this
|
|
756
|
+
* network, so it was not read; or a page that is not local holds more
|
|
757
|
+
* than one `ic_env` cookie and they differ, so none is believed.
|
|
758
|
+
* - `absent`: the cookie was read and has no usable entry under that name.
|
|
759
|
+
* An entry that is not principal text counts as absent.
|
|
760
|
+
*/
|
|
761
|
+
export const resolveCanisterId = (
|
|
762
|
+
target: CanisterIdTarget,
|
|
763
|
+
net: ResolvedNetwork
|
|
764
|
+
): CanisterIdResolution => {
|
|
765
|
+
if (typeof target !== "object" || target === null) throw badTarget(target)
|
|
766
|
+
|
|
767
|
+
if ("id" in target) {
|
|
768
|
+
const problem = principalProblem(target.id)
|
|
769
|
+
if (problem !== undefined) {
|
|
770
|
+
throw new TypeError(
|
|
771
|
+
`[ic-reactor] ${describe(target.id)} is not a valid canister id: ${problem}`
|
|
772
|
+
)
|
|
773
|
+
}
|
|
774
|
+
return { ok: true, id: target.id }
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
if (typeof target.name !== "string") throw badTarget(target)
|
|
778
|
+
|
|
779
|
+
const key = `PUBLIC_CANISTER_ID:${target.name}`
|
|
780
|
+
const what = `the canister id for { name: ${describe(target.name)} }`
|
|
781
|
+
const fail = (
|
|
782
|
+
reason: CanisterIdFailure,
|
|
783
|
+
message: string
|
|
784
|
+
): CanisterIdResolution => ({
|
|
785
|
+
ok: false,
|
|
786
|
+
reason,
|
|
787
|
+
message: `[ic-reactor] cannot resolve ${what}: ${message}`,
|
|
788
|
+
})
|
|
789
|
+
|
|
790
|
+
if (isServer()) {
|
|
791
|
+
return fail(
|
|
792
|
+
"no_browser",
|
|
793
|
+
`there is no ic_env cookie to read outside a browser page (a server render, or a web worker). ` +
|
|
794
|
+
`Use { id } for a canister you call from there.`
|
|
795
|
+
)
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// A cookie that is not trusted is not read; one that is, but disagrees with
|
|
799
|
+
// another `ic_env` cookie of the page, is refused the same way.
|
|
800
|
+
const value = net.trustsEnv ? cookieEntry(key) : null
|
|
801
|
+
if (value === null) {
|
|
802
|
+
const origin = pageOrigin()
|
|
803
|
+
return fail(
|
|
804
|
+
"untrusted_host",
|
|
805
|
+
net.trustsEnv
|
|
806
|
+
? `the page's ic_env cookies differ. Use { id } (bake the id in at build time).`
|
|
807
|
+
: `the ic_env cookie is not trusted for this network (agent host ${net.host}` +
|
|
808
|
+
// The agent host is often the library's mainnet fallback rather
|
|
809
|
+
// than anywhere the reader recognises, so name the page too.
|
|
810
|
+
`${origin ? `, page ${origin}` : ""}), so it was not read. ` +
|
|
811
|
+
`It is trusted only when both the agent host and the page are a local replica ` +
|
|
812
|
+
`(localhost, *.localhost or a loopback address), or, for canister ids, on a canister's own ` +
|
|
813
|
+
`<id>.icp0.io page with a mainnet host, because elsewhere a sibling subdomain can write it. ` +
|
|
814
|
+
`Use { id } (bake the id in at build time) or, only if you trust every subdomain of this domain, ` +
|
|
815
|
+
`set allowEnvConfig: true, which trusts the cookie's canister ids, and its root key for a replica that is not mainnet.`
|
|
816
|
+
)
|
|
817
|
+
}
|
|
818
|
+
if (typeof value !== "string" || value === "") {
|
|
819
|
+
return fail(
|
|
820
|
+
"absent",
|
|
821
|
+
`the ic_env cookie has no "${key}" entry. Check the name against the canister's name in the project config, or use { id }.`
|
|
822
|
+
)
|
|
823
|
+
}
|
|
824
|
+
// The cookie is trusted, not validated: a value that is not a principal must
|
|
825
|
+
// not become a canister id.
|
|
826
|
+
if (principalProblem(value) !== undefined) {
|
|
827
|
+
return fail(
|
|
828
|
+
"absent",
|
|
829
|
+
`the "${key}" entry of the ic_env cookie is not principal text. Use { id }.`
|
|
830
|
+
)
|
|
831
|
+
}
|
|
832
|
+
return { ok: true, id: value }
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// ---------------------------------------------------------------------------
|
|
836
|
+
// What the auth factory is told about the network
|
|
837
|
+
// ---------------------------------------------------------------------------
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* Internet Identity's canister: its id on mainnet, and the id icp-cli installs
|
|
841
|
+
* its built-in Internet Identity at on every local network it starts.
|
|
842
|
+
*/
|
|
843
|
+
const INTERNET_IDENTITY = "rdmx6-jaaaa-aaaaa-aaadq-cai"
|
|
844
|
+
|
|
845
|
+
/** Where a sign-in ceremony runs and which canister mints its delegations: `@icp-sdk/auth` 10's `identityProvider`. */
|
|
846
|
+
export interface IdentityProvider {
|
|
847
|
+
readonly authorizeUrl: string
|
|
848
|
+
readonly canisterId: string
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/**
|
|
852
|
+
* What the client hands its `auth` factory, under `@icp-sdk/auth` 10's own
|
|
853
|
+
* option names, so that `(network) => new AuthClient(network)` signs in on
|
|
854
|
+
* the client's network.
|
|
855
|
+
*/
|
|
856
|
+
export interface AuthNetwork {
|
|
857
|
+
readonly agentOptions: AgentNetworkOptions
|
|
858
|
+
readonly identityProvider?: IdentityProvider
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/** The result of {@link authNetworkFor}. */
|
|
862
|
+
export interface AuthNetworkResolution {
|
|
863
|
+
readonly network: AuthNetwork
|
|
864
|
+
/**
|
|
865
|
+
* Set when the network is not mainnet's and no identity provider could be
|
|
866
|
+
* named for it: why, as a clause for a warning. An auth built without one
|
|
867
|
+
* signs in with mainnet's Internet Identity, whose delegations a replica
|
|
868
|
+
* with another root key rejects.
|
|
869
|
+
*/
|
|
870
|
+
readonly gap?: string
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
const hexOf = (bytes: Uint8Array): string =>
|
|
874
|
+
Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("")
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* Whether the agent verifies against mainnet's root key: it fetches none, and
|
|
878
|
+
* holds either none of its own (the agent's default is mainnet's) or one equal
|
|
879
|
+
* to mainnet's (a mainnet asset canister's cookie carries it).
|
|
880
|
+
*/
|
|
881
|
+
const verifiesAsMainnet = (net: ResolvedNetwork): boolean =>
|
|
882
|
+
!net.fetchRootKey &&
|
|
883
|
+
(net.rootKey === undefined || hexOf(net.rootKey) === IC_ROOT_KEY)
|
|
884
|
+
|
|
885
|
+
/**
|
|
886
|
+
* The cookie's `INTERNET_IDENTITY_PROVIDER`, when it is an `https:` URL, or an
|
|
887
|
+
* `http:` one on a local page. Over plain `http:` from a page that is not
|
|
888
|
+
* local, anyone on the path could stand in for the sign-in page.
|
|
889
|
+
*/
|
|
890
|
+
const cookieAuthorizeUrl = (): string | undefined => {
|
|
891
|
+
const value = cookieEntry("INTERNET_IDENTITY_PROVIDER")
|
|
892
|
+
if (typeof value !== "string" || value === "") return undefined
|
|
893
|
+
try {
|
|
894
|
+
const { protocol } = new URL(value)
|
|
895
|
+
return protocol === "https:" || (protocol === "http:" && pageIsLocal())
|
|
896
|
+
? value
|
|
897
|
+
: undefined
|
|
898
|
+
} catch {
|
|
899
|
+
return undefined
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/** The cookie's `PUBLIC_CANISTER_ID:internet_identity`, when it is principal text. */
|
|
904
|
+
const cookieInternetIdentity = (): string | undefined => {
|
|
905
|
+
const value = cookieEntry("PUBLIC_CANISTER_ID:internet_identity")
|
|
906
|
+
return typeof value === "string" &&
|
|
907
|
+
value !== "" &&
|
|
908
|
+
principalProblem(value) === undefined
|
|
909
|
+
? value
|
|
910
|
+
: undefined
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
/**
|
|
914
|
+
* icp-cli's built-in Internet Identity for a gateway at `host`:
|
|
915
|
+
* `id.ai.localhost` on the gateway's scheme and port. The same rule as the
|
|
916
|
+
* Vite plugin's `localInternetIdentityProvider`, which puts this URL in the
|
|
917
|
+
* `ic_env` cookie.
|
|
918
|
+
*/
|
|
919
|
+
const builtInAuthorizeUrl = (host: string): string | undefined => {
|
|
920
|
+
const url = agentUrlOf(host)
|
|
921
|
+
if (url === undefined) return undefined
|
|
922
|
+
return `${url.protocol}//id.ai.localhost${url.port ? `:${url.port}` : ""}/authorize`
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* What the client tells its `auth` factory: the agent options of its own
|
|
927
|
+
* agents, and the identity provider to sign in with, by these rules in order.
|
|
928
|
+
*
|
|
929
|
+
* 1. An `ic_env` cookie believed in full (never one believed for canister ids
|
|
930
|
+
* only, as on a canister's `icp0.io` page by default) that names
|
|
931
|
+
* `INTERNET_IDENTITY_PROVIDER` over `https:` (or `http:` on a local page):
|
|
932
|
+
* that URL, with the cookie's `PUBLIC_CANISTER_ID:internet_identity`, or
|
|
933
|
+
* Internet Identity's own id when it names none.
|
|
934
|
+
* 2. A cookie believed in full that names an `internet_identity` canister but
|
|
935
|
+
* no URL (the project deploys its own): none, and a gap unless the network
|
|
936
|
+
* is mainnet's. Its URL cannot be derived from its id.
|
|
937
|
+
* 3. `"local"`, or a network object whose host is local: icp-cli's built-in
|
|
938
|
+
* Internet Identity on that host's port.
|
|
939
|
+
* 4. `"env"` on a local page: none, and a gap. The agent's host is the
|
|
940
|
+
* page's (a dev server, or a gateway serving an asset canister), which
|
|
941
|
+
* does not say where icp-cli's built-in Internet Identity is.
|
|
942
|
+
* 5. Any other network that does not verify against mainnet's root key: none,
|
|
943
|
+
* and a gap.
|
|
944
|
+
* 6. Mainnet: none. `AuthClient`'s defaults are mainnet's.
|
|
945
|
+
*
|
|
946
|
+
* Reads the cookie, so it is called when the factory is, in a browser.
|
|
947
|
+
*/
|
|
948
|
+
export const authNetworkFor = (
|
|
949
|
+
network: Network,
|
|
950
|
+
net: ResolvedNetwork
|
|
951
|
+
): AuthNetworkResolution => {
|
|
952
|
+
const agentOptions = agentOptionsFor(net)
|
|
953
|
+
// Who signs a person in is not a canister id, so a cookie believed for ids
|
|
954
|
+
// only does not choose it.
|
|
955
|
+
const full = net.trustsEnv && !net.envIdsOnly
|
|
956
|
+
const authorizeUrl = full ? cookieAuthorizeUrl() : undefined
|
|
957
|
+
const projectCanister = full ? cookieInternetIdentity() : undefined
|
|
958
|
+
|
|
959
|
+
if (authorizeUrl !== undefined) {
|
|
960
|
+
return {
|
|
961
|
+
network: {
|
|
962
|
+
agentOptions,
|
|
963
|
+
identityProvider: {
|
|
964
|
+
authorizeUrl,
|
|
965
|
+
canisterId: projectCanister ?? INTERNET_IDENTITY,
|
|
966
|
+
},
|
|
967
|
+
},
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
const mainnet = verifiesAsMainnet(net)
|
|
972
|
+
if (projectCanister !== undefined) {
|
|
973
|
+
return mainnet
|
|
974
|
+
? { network: { agentOptions } }
|
|
975
|
+
: {
|
|
976
|
+
network: { agentOptions },
|
|
977
|
+
gap:
|
|
978
|
+
`the ic_env cookie names an internet_identity canister (${projectCanister}) ` +
|
|
979
|
+
`but no INTERNET_IDENTITY_PROVIDER, and its sign-in URL cannot be derived from its id`,
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
const localHost =
|
|
984
|
+
network === "local" ||
|
|
985
|
+
(typeof network === "object" &&
|
|
986
|
+
isLocalHostname(agentHostnameOf(network.host)))
|
|
987
|
+
const builtIn = localHost ? builtInAuthorizeUrl(net.host) : undefined
|
|
988
|
+
if (builtIn !== undefined) {
|
|
989
|
+
return {
|
|
990
|
+
network: {
|
|
991
|
+
agentOptions,
|
|
992
|
+
identityProvider: {
|
|
993
|
+
authorizeUrl: builtIn,
|
|
994
|
+
canisterId: INTERNET_IDENTITY,
|
|
995
|
+
},
|
|
996
|
+
},
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
if (network === "env" && pageIsLocal()) {
|
|
1001
|
+
const why = net.trustsEnv
|
|
1002
|
+
? `whose ic_env cookie names no INTERNET_IDENTITY_PROVIDER`
|
|
1003
|
+
: `with allowEnvConfig: false, so no INTERNET_IDENTITY_PROVIDER is read from the ic_env cookie`
|
|
1004
|
+
return {
|
|
1005
|
+
network: { agentOptions },
|
|
1006
|
+
gap:
|
|
1007
|
+
`network "env" on a local page ${why}: ` +
|
|
1008
|
+
`the agent's host (${net.host}) does not say where icp-cli's built-in Internet Identity is`,
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
if (!mainnet) {
|
|
1013
|
+
return {
|
|
1014
|
+
network: { agentOptions },
|
|
1015
|
+
gap: `its root key is not mainnet's, and nothing names an identity provider for it`,
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
return { network: { agentOptions } }
|
|
1020
|
+
}
|