@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,1272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `createTestClient` from `@ic-reactor/core/testing`: a real client over a
|
|
3
|
+
* fake replica, with canisters written as functions of domain values and a
|
|
4
|
+
* sign-in the test controls.
|
|
5
|
+
*
|
|
6
|
+
* Every test here runs in Node, where `createClient` never builds its auth
|
|
7
|
+
* (there is no page to sign in on). The test client has to work there anyway,
|
|
8
|
+
* so a sign-in, a sign-out and a switch of account are all exercised without a
|
|
9
|
+
* browser. The canisters are the generated fixtures: the ICRC-1 ledger for
|
|
10
|
+
* domain values and `Ok`/`Err` replies, the shapes service for the reply
|
|
11
|
+
* collapse, and the management canister for effective-canister-id routing.
|
|
12
|
+
*/
|
|
13
|
+
import { afterEach, describe, expect, it, vi } from "vitest"
|
|
14
|
+
import { c, principal, type WithMode } from "@meantjs/schema"
|
|
15
|
+
import { Ed25519KeyIdentity } from "@icp-sdk/core/identity"
|
|
16
|
+
import { internalsOf } from "../src/client.js"
|
|
17
|
+
import { isReactorError } from "../src/index.js"
|
|
18
|
+
import { createTestClient, type TestHandlers } from "../src/testing/index.js"
|
|
19
|
+
import {
|
|
20
|
+
ANONYMOUS,
|
|
21
|
+
LEDGER,
|
|
22
|
+
MANAGEMENT,
|
|
23
|
+
SHAPES,
|
|
24
|
+
sleep,
|
|
25
|
+
} from "./canister-helpers.js"
|
|
26
|
+
import { onPage } from "./client-helpers.js"
|
|
27
|
+
import { stubProcessEnv } from "./network-helpers.js"
|
|
28
|
+
import * as icrc1 from "./fixtures/icrc1.js"
|
|
29
|
+
import * as management from "./fixtures/management.js"
|
|
30
|
+
import * as shapes from "./fixtures/shapes.js"
|
|
31
|
+
|
|
32
|
+
/** Seeds 1 and 2: the same principals in every run, on every machine. */
|
|
33
|
+
const SEED_1 = "psith-oknjz-x73tv-7x3p4-a2sji-7o6lo-g2754-yfgfl-3vlqe-irrrt-4ae"
|
|
34
|
+
const SEED_2 = "xledz-fktfc-4ywwn-gai5u-ieqce-5x4qc-7lpel-o4ubn-ucngz-7lrzo-5ae"
|
|
35
|
+
|
|
36
|
+
type Options = NonNullable<Parameters<typeof createTestClient>[0]>
|
|
37
|
+
type Test = ReturnType<typeof createTestClient>
|
|
38
|
+
|
|
39
|
+
const made: Test[] = []
|
|
40
|
+
|
|
41
|
+
/** A test client that is disposed when the test ends. */
|
|
42
|
+
function setup(options?: Options) {
|
|
43
|
+
const test = createTestClient(options)
|
|
44
|
+
made.push(test)
|
|
45
|
+
return test
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
afterEach(() => {
|
|
49
|
+
for (const test of made.splice(0)) test.client.dispose()
|
|
50
|
+
vi.unstubAllGlobals()
|
|
51
|
+
vi.unstubAllEnvs()
|
|
52
|
+
vi.restoreAllMocks()
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
/** The shapes canister, called through a test client that mocks it. */
|
|
56
|
+
function withShapes(handlers: TestHandlers<shapes.Actor>, options?: Options) {
|
|
57
|
+
const test = setup(options)
|
|
58
|
+
test.mock<shapes.Actor>(shapes.actor, SHAPES, handlers)
|
|
59
|
+
return {
|
|
60
|
+
...test,
|
|
61
|
+
canister: test.client.canister<shapes.Actor>(shapes.actor, { id: SHAPES }),
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The canister requests (queries and calls) the fake saw. */
|
|
66
|
+
const canisterRequests = (test: Pick<Test, "requests">) =>
|
|
67
|
+
test.requests.filter(
|
|
68
|
+
(request) => request.endpoint === "query" || request.endpoint === "call"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
describe("who a handler is told called", () => {
|
|
72
|
+
it("is the principal that is signed in, by default the identity of seed 1", async () => {
|
|
73
|
+
const { canister, auth } = withShapes({
|
|
74
|
+
who: ({ caller }) => caller,
|
|
75
|
+
address: (seed, { caller }) => `${seed}:${caller}`,
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
// A query and an update, both signed by the signed-in principal.
|
|
79
|
+
await expect(canister.who()).resolves.toBe(SEED_1)
|
|
80
|
+
await expect(canister.address("x")).resolves.toBe(`x:${SEED_1}`)
|
|
81
|
+
expect(auth.getPrincipal()?.toText()).toBe(SEED_1)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it("is the other principal after a switch of account, in Node", async () => {
|
|
85
|
+
// Node is a server to `createClient`: with no page, it never builds an
|
|
86
|
+
// auth. If the test client lost that difference, the switch would change
|
|
87
|
+
// nothing the canister could see.
|
|
88
|
+
expect(typeof window).toBe("undefined")
|
|
89
|
+
const { canister, client, auth } = withShapes({
|
|
90
|
+
who: ({ caller }) => caller,
|
|
91
|
+
address: (seed, { caller }) => `${seed}:${caller}`,
|
|
92
|
+
})
|
|
93
|
+
await expect(canister.who()).resolves.toBe(SEED_1)
|
|
94
|
+
|
|
95
|
+
auth.switchTo(2)
|
|
96
|
+
|
|
97
|
+
expect(client.caller()).toBe(SEED_2)
|
|
98
|
+
await expect(canister.who()).resolves.toBe(SEED_2)
|
|
99
|
+
await expect(canister.address("y")).resolves.toBe(`y:${SEED_2}`)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it("is the principal that signed the request, even when the account is switched while the call is in flight", async () => {
|
|
103
|
+
// The request carries its sender, and the fake verifies it. A handler that
|
|
104
|
+
// were told whoever is signed in when it runs would see the account that
|
|
105
|
+
// was switched to after the call went out. The switch is made after 0, 1,
|
|
106
|
+
// 2 ... microtasks of a call that is already on its way, until it has
|
|
107
|
+
// landed between the signing and the handler a few times, whatever the
|
|
108
|
+
// number of microtasks that takes: past that window the handler has
|
|
109
|
+
// already run, and each call is a full signed and certified one, so a
|
|
110
|
+
// longer sweep would only cost time that grows with the load on the
|
|
111
|
+
// machine.
|
|
112
|
+
const told: string[] = []
|
|
113
|
+
const live: string[] = []
|
|
114
|
+
const { canister, auth, client, requests } = withShapes({
|
|
115
|
+
who: ({ caller }) => {
|
|
116
|
+
told.push(caller)
|
|
117
|
+
live.push(client.caller())
|
|
118
|
+
return caller
|
|
119
|
+
},
|
|
120
|
+
address: (seed, { caller }) => {
|
|
121
|
+
told.push(caller)
|
|
122
|
+
live.push(client.caller())
|
|
123
|
+
return seed
|
|
124
|
+
},
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
const SWEEP_LIMIT = 40
|
|
128
|
+
const LANDINGS_WANTED = 3
|
|
129
|
+
const landings: number[] = []
|
|
130
|
+
for (const send of [
|
|
131
|
+
() => canister.who(),
|
|
132
|
+
() => canister.address("x"),
|
|
133
|
+
] as const) {
|
|
134
|
+
let landed = 0
|
|
135
|
+
for (
|
|
136
|
+
let microtasks = 0;
|
|
137
|
+
microtasks < SWEEP_LIMIT && landed < LANDINGS_WANTED;
|
|
138
|
+
microtasks += 1
|
|
139
|
+
) {
|
|
140
|
+
auth.switchTo(1)
|
|
141
|
+
const before = live.length
|
|
142
|
+
const call = send()
|
|
143
|
+
for (let tick = 0; tick < microtasks; tick += 1) {
|
|
144
|
+
await Promise.resolve()
|
|
145
|
+
}
|
|
146
|
+
auth.switchTo(2)
|
|
147
|
+
// A call whose caller changed before it went out is cancelled: it
|
|
148
|
+
// sent nothing, so it told no handler anything.
|
|
149
|
+
await call.catch(() => undefined)
|
|
150
|
+
// The handler ran, and by then the client was on the other account.
|
|
151
|
+
if (live.length > before && live[live.length - 1] === SEED_2) {
|
|
152
|
+
landed += 1
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
landings.push(landed)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Every call went out as the account it was made as, and the handler was
|
|
159
|
+
// told that, and not the other account the client is on by then.
|
|
160
|
+
expect(told).toEqual(canisterRequests({ requests }).map((r) => r.caller))
|
|
161
|
+
expect(told.length).toBeGreaterThan(0)
|
|
162
|
+
expect(told.every((caller) => caller === SEED_1)).toBe(true)
|
|
163
|
+
// The sweep reached the moment the test is about, for the query and for
|
|
164
|
+
// the update: a call signed by one principal, run after the client had
|
|
165
|
+
// moved on to the other.
|
|
166
|
+
expect(landings).toEqual([LANDINGS_WANTED, LANDINGS_WANTED])
|
|
167
|
+
expect(live).toContain(SEED_2)
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
it("is the identity it was given, whether a seed or a key", async () => {
|
|
171
|
+
const key = Ed25519KeyIdentity.generate()
|
|
172
|
+
const byKey = withShapes({ who: ({ caller }) => caller }, { identity: key })
|
|
173
|
+
const bySeed = withShapes({ who: ({ caller }) => caller }, { identity: 2 })
|
|
174
|
+
|
|
175
|
+
await expect(byKey.canister.who()).resolves.toBe(
|
|
176
|
+
key.getPrincipal().toText()
|
|
177
|
+
)
|
|
178
|
+
await expect(bySeed.canister.who()).resolves.toBe(SEED_2)
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it("is the anonymous principal for a client that starts signed out, and refuses its updates", async () => {
|
|
182
|
+
const calls: string[] = []
|
|
183
|
+
const { canister, auth, requests } = withShapes(
|
|
184
|
+
{
|
|
185
|
+
who: ({ caller }) => caller,
|
|
186
|
+
address: (seed) => {
|
|
187
|
+
calls.push(seed)
|
|
188
|
+
return seed
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
{ signedIn: false }
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
await expect(canister.who()).resolves.toBe(ANONYMOUS)
|
|
195
|
+
await expect(canister.address("x")).rejects.toMatchObject({
|
|
196
|
+
kind: "unauthenticated",
|
|
197
|
+
mayHaveExecuted: false,
|
|
198
|
+
})
|
|
199
|
+
expect(calls).toEqual([])
|
|
200
|
+
expect(requests.filter((request) => request.endpoint === "call")).toEqual(
|
|
201
|
+
[]
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
// `identity` is what a later sign-in signs in as.
|
|
205
|
+
await auth.signIn()
|
|
206
|
+
await expect(canister.address("x")).resolves.toBe("x")
|
|
207
|
+
await expect(canister.who()).resolves.toBe(SEED_1)
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
it("signs in and out through the client, as an app does", async () => {
|
|
211
|
+
const { client, canister } = withShapes(
|
|
212
|
+
{ who: ({ caller }) => caller },
|
|
213
|
+
{ identity: 2, signedIn: false }
|
|
214
|
+
)
|
|
215
|
+
const seen: string[] = []
|
|
216
|
+
client.subscribe(() => seen.push(client.authState().status))
|
|
217
|
+
|
|
218
|
+
await client.signIn()
|
|
219
|
+
await expect(canister.who()).resolves.toBe(SEED_2)
|
|
220
|
+
await client.signOut()
|
|
221
|
+
await expect(canister.who()).resolves.toBe(ANONYMOUS)
|
|
222
|
+
|
|
223
|
+
expect(seen).toEqual(["signed-in", "anonymous"])
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
it("is anonymous once the session expires, though the principal is still named", async () => {
|
|
227
|
+
const { canister, auth } = withShapes({ who: ({ caller }) => caller })
|
|
228
|
+
|
|
229
|
+
auth.expire()
|
|
230
|
+
|
|
231
|
+
expect(auth.getStatus().state).toBe("expired")
|
|
232
|
+
await expect(canister.who()).resolves.toBe(ANONYMOUS)
|
|
233
|
+
})
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
describe("the handlers' values", () => {
|
|
237
|
+
/** A small ICRC-1 ledger: balances by owner text, transfers debit the caller. */
|
|
238
|
+
function ledger(balances: Map<string, bigint>) {
|
|
239
|
+
const test = setup()
|
|
240
|
+
const fee = 10_000n
|
|
241
|
+
test.mock<icrc1.Actor>(icrc1.actor, LEDGER, {
|
|
242
|
+
icrc1_fee: () => fee,
|
|
243
|
+
icrc1_decimals: () => 8,
|
|
244
|
+
icrc1_balance_of: ({ owner }) => balances.get(owner) ?? 0n,
|
|
245
|
+
icrc1_minting_account: () => null,
|
|
246
|
+
icrc1_supported_standards: () => [
|
|
247
|
+
{ name: "ICRC-1", url: "https://github.com/dfinity/ICRC-1" },
|
|
248
|
+
],
|
|
249
|
+
icrc1_metadata: () => [["icrc1:symbol", { tag: "Text", value: "ICP" }]],
|
|
250
|
+
icrc1_transfer: (arg, { caller }) => {
|
|
251
|
+
const debit = arg.amount + fee
|
|
252
|
+
const balance = balances.get(caller) ?? 0n
|
|
253
|
+
if (balance < debit) {
|
|
254
|
+
return {
|
|
255
|
+
tag: "Err",
|
|
256
|
+
value: { tag: "InsufficientFunds", value: { balance } },
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
balances.set(caller, balance - debit)
|
|
260
|
+
balances.set(
|
|
261
|
+
arg.to.owner,
|
|
262
|
+
(balances.get(arg.to.owner) ?? 0n) + arg.amount
|
|
263
|
+
)
|
|
264
|
+
return { tag: "Ok", value: 1n }
|
|
265
|
+
},
|
|
266
|
+
})
|
|
267
|
+
return {
|
|
268
|
+
...test,
|
|
269
|
+
canister: test.client.canister<icrc1.Actor>(icrc1.actor, { id: LEDGER }),
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const to = principal("rrkah-fqaaa-aaaaa-aaaaq-cai")
|
|
274
|
+
const transfer = (amount: bigint): icrc1.TransferArg => ({
|
|
275
|
+
to: { owner: to, subaccount: null },
|
|
276
|
+
amount,
|
|
277
|
+
fee: null,
|
|
278
|
+
memo: null,
|
|
279
|
+
from_subaccount: null,
|
|
280
|
+
created_at_time: null,
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
it("take and return what a generated module's types say: bigint, principals, variants", async () => {
|
|
284
|
+
const balances = new Map([[SEED_1, 1_000_000n]])
|
|
285
|
+
const { canister } = ledger(balances)
|
|
286
|
+
|
|
287
|
+
await expect(canister.icrc1_fee()).resolves.toBe(10_000n)
|
|
288
|
+
await expect(canister.icrc1_decimals()).resolves.toBe(8)
|
|
289
|
+
await expect(canister.icrc1_minting_account()).resolves.toBeNull()
|
|
290
|
+
// A vec is one result, not the list of results.
|
|
291
|
+
await expect(canister.icrc1_supported_standards()).resolves.toEqual([
|
|
292
|
+
{ name: "ICRC-1", url: "https://github.com/dfinity/ICRC-1" },
|
|
293
|
+
])
|
|
294
|
+
await expect(canister.icrc1_metadata()).resolves.toEqual([
|
|
295
|
+
["icrc1:symbol", { tag: "Text", value: "ICP" }],
|
|
296
|
+
])
|
|
297
|
+
// The handler returned `{ tag: "Ok", value }`; the client unwraps it.
|
|
298
|
+
await expect(canister.icrc1_transfer(transfer(500n))).resolves.toBe(1n)
|
|
299
|
+
await expect(
|
|
300
|
+
canister.icrc1_balance_of({ owner: to, subaccount: null })
|
|
301
|
+
).resolves.toBe(500n)
|
|
302
|
+
expect(balances.get(SEED_1)).toBe(1_000_000n - 500n - 10_000n)
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
it("reject a direct call with the typed Err arm when the handler returns one", async () => {
|
|
306
|
+
const { canister } = ledger(new Map([[SEED_1, 5n]]))
|
|
307
|
+
|
|
308
|
+
const failure: unknown = await canister
|
|
309
|
+
.icrc1_transfer(transfer(500n))
|
|
310
|
+
.catch((error: unknown) => error)
|
|
311
|
+
|
|
312
|
+
expect(isReactorError(failure)).toBe(true)
|
|
313
|
+
expect(failure).toMatchObject({
|
|
314
|
+
kind: "canister_err",
|
|
315
|
+
err: { tag: "InsufficientFunds", value: { balance: 5n } },
|
|
316
|
+
mayHaveExecuted: false,
|
|
317
|
+
})
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
it("take the arguments in order, then the context", async () => {
|
|
321
|
+
const seen: unknown[][] = []
|
|
322
|
+
const { canister } = withShapes({
|
|
323
|
+
pair: (left, right, ctx) => {
|
|
324
|
+
seen.push([left, right, ctx])
|
|
325
|
+
return { left, right }
|
|
326
|
+
},
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
await expect(canister.pair(7n, "seven")).resolves.toEqual({
|
|
330
|
+
left: 7n,
|
|
331
|
+
right: "seven",
|
|
332
|
+
})
|
|
333
|
+
expect(seen).toEqual([[7n, "seven", { caller: SEED_1 }]])
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
it("return one value, a tuple or nothing, as the method's results are", async () => {
|
|
337
|
+
const { canister } = withShapes({
|
|
338
|
+
one: async (n) => n + 1n,
|
|
339
|
+
many: () => [1n, "two", true],
|
|
340
|
+
nothing: () => undefined,
|
|
341
|
+
note: () => undefined,
|
|
342
|
+
small: (n) => n + 1,
|
|
343
|
+
bytes: (bytes) => bytes.slice().reverse(),
|
|
344
|
+
maybe: (value) => value,
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
await expect(canister.one(1n)).resolves.toBe(2n)
|
|
348
|
+
await expect(canister.many()).resolves.toEqual([1n, "two", true])
|
|
349
|
+
await expect(canister.nothing()).resolves.toBeUndefined()
|
|
350
|
+
await expect(canister.note("hello")).resolves.toBeUndefined()
|
|
351
|
+
await expect(canister.small(7)).resolves.toBe(8)
|
|
352
|
+
await expect(canister.bytes(new Uint8Array([1, 2, 3]))).resolves.toEqual(
|
|
353
|
+
new Uint8Array([3, 2, 1])
|
|
354
|
+
)
|
|
355
|
+
await expect(canister.maybe({ some: null })).resolves.toEqual({
|
|
356
|
+
some: null,
|
|
357
|
+
})
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
it("take and return a blob over a million bytes, as a wasm upload and download do", async () => {
|
|
361
|
+
const chunk = new Uint8Array(2_000_000).fill(9)
|
|
362
|
+
const { canister } = withShapes({ bytes: (bytes) => bytes })
|
|
363
|
+
const reply = await canister.bytes(chunk)
|
|
364
|
+
// Byte by byte, toEqual takes seconds on two million entries.
|
|
365
|
+
expect(Buffer.from(reply).equals(Buffer.from(chunk))).toBe(true)
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
it("return as many values as the maxElements the test client was given", async () => {
|
|
369
|
+
// A reply of values that take no bytes: past `@meantjs/schema`'s default
|
|
370
|
+
// budget, within the one the test asked for.
|
|
371
|
+
type Nulls = { nulls: (() => Promise<null[]>) & WithMode<"query"> }
|
|
372
|
+
const nulls = c.service({ nulls: c.func([], [c.vec(c.null)], "query") })
|
|
373
|
+
const test = setup({ maxElements: 1_200_000 })
|
|
374
|
+
test.mock<Nulls>(nulls, SHAPES, {
|
|
375
|
+
nulls: () => Array.from({ length: 1_100_000 }, () => null),
|
|
376
|
+
})
|
|
377
|
+
const canister = test.client.canister<Nulls>(nulls, { id: SHAPES })
|
|
378
|
+
await expect(canister.nulls()).resolves.toHaveLength(1_100_000)
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
it("return a reply the client decodes under a maxElements below its length", async () => {
|
|
382
|
+
// The client's budget is maxElements plus the reply's bytes, so 100
|
|
383
|
+
// one-byte nats decode under maxElements 10, and the mock must send them.
|
|
384
|
+
type Nats = { nats: (() => Promise<bigint[]>) & WithMode<"query"> }
|
|
385
|
+
const nats = c.service({ nats: c.func([], [c.vec(c.nat)], "query") })
|
|
386
|
+
const test = setup({ maxElements: 10 })
|
|
387
|
+
test.mock<Nats>(nats, SHAPES, {
|
|
388
|
+
nats: () => Array.from({ length: 100 }, (_, i) => BigInt(i)),
|
|
389
|
+
})
|
|
390
|
+
const canister = test.client.canister<Nats>(nats, { id: SHAPES })
|
|
391
|
+
await expect(canister.nats()).resolves.toHaveLength(100)
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
it("make a void update that returns whatever it likes a method with no results", async () => {
|
|
395
|
+
const seen: string[] = []
|
|
396
|
+
const { canister } = withShapes({
|
|
397
|
+
// A handler that stores a value and returns it by habit.
|
|
398
|
+
note: (text) => {
|
|
399
|
+
seen.push(text)
|
|
400
|
+
return seen.length as never
|
|
401
|
+
},
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
await expect(canister.note("a")).resolves.toBeUndefined()
|
|
405
|
+
expect(seen).toEqual(["a"])
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
it("answer a oneway with the handler run, and a lower-case result like any other", async () => {
|
|
409
|
+
const fired: string[] = []
|
|
410
|
+
const { canister } = withShapes({
|
|
411
|
+
fire: (text) => {
|
|
412
|
+
fired.push(text)
|
|
413
|
+
},
|
|
414
|
+
outcome: (n) =>
|
|
415
|
+
n === 0n ? { tag: "err", value: "zero" } : { tag: "ok", value: n },
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
await expect(canister.fire("now")).resolves.toBeUndefined()
|
|
419
|
+
expect(fired).toEqual(["now"])
|
|
420
|
+
await expect(canister.outcome(3n)).resolves.toBe(3n)
|
|
421
|
+
await expect(canister.outcome(0n)).rejects.toMatchObject({
|
|
422
|
+
kind: "canister_err",
|
|
423
|
+
err: "zero",
|
|
424
|
+
})
|
|
425
|
+
})
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
describe("the paths a call takes", () => {
|
|
429
|
+
it("answers a query method on the query path and an update on the call path", async () => {
|
|
430
|
+
const { canister, requests } = withShapes({
|
|
431
|
+
one: (n) => n,
|
|
432
|
+
composite: (n) => n,
|
|
433
|
+
bump: (n) => ({ tag: "ok", value: n }),
|
|
434
|
+
})
|
|
435
|
+
|
|
436
|
+
await canister.one(1n)
|
|
437
|
+
await canister.composite(2n)
|
|
438
|
+
await canister.bump(3n)
|
|
439
|
+
|
|
440
|
+
expect(
|
|
441
|
+
requests
|
|
442
|
+
.filter((r) => r.endpoint === "query" || r.endpoint === "call")
|
|
443
|
+
.map((r) => [r.endpoint, r.methodName, r.caller])
|
|
444
|
+
).toEqual([
|
|
445
|
+
["query", "one", SEED_1],
|
|
446
|
+
["query", "composite", SEED_1],
|
|
447
|
+
["call", "bump", SEED_1],
|
|
448
|
+
])
|
|
449
|
+
})
|
|
450
|
+
|
|
451
|
+
it("answers a query method sent as a replicated call, as a certified canister does", async () => {
|
|
452
|
+
const test = setup()
|
|
453
|
+
test.mock<shapes.Actor>(shapes.actor, SHAPES, { one: (n) => n * 2n })
|
|
454
|
+
const certified = test.client.canister<shapes.Actor>(shapes.actor, {
|
|
455
|
+
id: SHAPES,
|
|
456
|
+
certified: true,
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
await expect(certified.one(4n)).resolves.toBe(8n)
|
|
460
|
+
|
|
461
|
+
expect(canisterRequests(test)).toMatchObject([
|
|
462
|
+
{ endpoint: "call", methodName: "one" },
|
|
463
|
+
])
|
|
464
|
+
})
|
|
465
|
+
|
|
466
|
+
it("refuses to run an update method as a query", async () => {
|
|
467
|
+
// The client believes `ping` is a query; the canister it mocks says it is
|
|
468
|
+
// an update. A canister cannot answer a query call to an update method.
|
|
469
|
+
const asQuery = c.service({ ping: c.func([], [c.text], "query") })
|
|
470
|
+
const asUpdate = c.service({ ping: c.func([], [c.text], "update") })
|
|
471
|
+
const test = setup()
|
|
472
|
+
test.mock<{ ping: () => Promise<string> }>(asUpdate, SHAPES, {
|
|
473
|
+
ping: () => "pong",
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
await expect(
|
|
477
|
+
test.client
|
|
478
|
+
.canister<{ ping: () => Promise<string> }>(asQuery, { id: SHAPES })
|
|
479
|
+
.ping()
|
|
480
|
+
).rejects.toMatchObject({
|
|
481
|
+
kind: "rejected",
|
|
482
|
+
rejectCode: 5,
|
|
483
|
+
message: expect.stringContaining("ping is an update method"),
|
|
484
|
+
})
|
|
485
|
+
})
|
|
486
|
+
|
|
487
|
+
it("answers the management canister, routed by the effective canister id", async () => {
|
|
488
|
+
const test = setup()
|
|
489
|
+
const stopped: string[] = []
|
|
490
|
+
test.mock<management.Actor>(management.actor, MANAGEMENT, {
|
|
491
|
+
stop_canister: ({ canister_id }, { caller }) => {
|
|
492
|
+
stopped.push(`${canister_id} by ${caller}`)
|
|
493
|
+
},
|
|
494
|
+
upload_chunk: ({ chunk }) => ({ hash: chunk.slice(0, 2) }),
|
|
495
|
+
})
|
|
496
|
+
const ic = test.client.canister<management.Actor>(management.actor, {
|
|
497
|
+
id: MANAGEMENT,
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
await expect(
|
|
501
|
+
ic.stop_canister({ canister_id: principal(SHAPES) })
|
|
502
|
+
).resolves.toBeUndefined()
|
|
503
|
+
await expect(
|
|
504
|
+
ic.upload_chunk({
|
|
505
|
+
canister_id: principal(LEDGER),
|
|
506
|
+
chunk: new Uint8Array([9, 8, 7]),
|
|
507
|
+
})
|
|
508
|
+
).resolves.toEqual({ hash: new Uint8Array([9, 8]) })
|
|
509
|
+
|
|
510
|
+
expect(stopped).toEqual([`${SHAPES} by ${SEED_1}`])
|
|
511
|
+
expect(canisterRequests(test)).toMatchObject([
|
|
512
|
+
{
|
|
513
|
+
endpoint: "call",
|
|
514
|
+
canisterId: MANAGEMENT,
|
|
515
|
+
effectiveCanisterId: SHAPES,
|
|
516
|
+
methodName: "stop_canister",
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
endpoint: "call",
|
|
520
|
+
canisterId: MANAGEMENT,
|
|
521
|
+
effectiveCanisterId: LEDGER,
|
|
522
|
+
methodName: "upload_chunk",
|
|
523
|
+
},
|
|
524
|
+
])
|
|
525
|
+
})
|
|
526
|
+
|
|
527
|
+
it("replaces the canister at an id when it is mocked again", async () => {
|
|
528
|
+
const test = setup()
|
|
529
|
+
test.mock<shapes.Actor>(shapes.actor, SHAPES, { one: () => 1n })
|
|
530
|
+
const canister = test.client.canister<shapes.Actor>(shapes.actor, {
|
|
531
|
+
id: SHAPES,
|
|
532
|
+
})
|
|
533
|
+
await expect(canister.one(0n)).resolves.toBe(1n)
|
|
534
|
+
|
|
535
|
+
test.mock<shapes.Actor>(shapes.actor, SHAPES, { one: () => 2n })
|
|
536
|
+
|
|
537
|
+
await expect(canister.one(0n)).resolves.toBe(2n)
|
|
538
|
+
})
|
|
539
|
+
})
|
|
540
|
+
|
|
541
|
+
describe("the failures a test makes happen", () => {
|
|
542
|
+
it("rejects an update with the code reject() was given, and the handler ran once", async () => {
|
|
543
|
+
let ran = 0
|
|
544
|
+
const { canister, reject } = withShapes({
|
|
545
|
+
bump: () => {
|
|
546
|
+
ran += 1
|
|
547
|
+
return reject(4, "not today")
|
|
548
|
+
},
|
|
549
|
+
})
|
|
550
|
+
|
|
551
|
+
await expect(canister.bump(1n)).rejects.toMatchObject({
|
|
552
|
+
kind: "rejected",
|
|
553
|
+
rejectCode: 4,
|
|
554
|
+
mayHaveExecuted: true,
|
|
555
|
+
message: expect.stringContaining("not today"),
|
|
556
|
+
})
|
|
557
|
+
// A reject 4 proves the canister ran: it is never sent again.
|
|
558
|
+
await sleep(1_100)
|
|
559
|
+
expect(ran).toBe(1)
|
|
560
|
+
})
|
|
561
|
+
|
|
562
|
+
it("rejects a query too, and a query that was rejected may not have changed anything", async () => {
|
|
563
|
+
const { canister, reject } = withShapes({ one: () => reject(4) })
|
|
564
|
+
|
|
565
|
+
await expect(canister.one(1n)).rejects.toMatchObject({
|
|
566
|
+
kind: "rejected",
|
|
567
|
+
rejectCode: 4,
|
|
568
|
+
mayHaveExecuted: false,
|
|
569
|
+
})
|
|
570
|
+
})
|
|
571
|
+
|
|
572
|
+
it("rejects a call to a canister it holds none at with canister_not_found, on a query and an update", async () => {
|
|
573
|
+
// Like a mainnet replica for an id inside its range that holds no
|
|
574
|
+
// canister, the fake rejects with code 3 and IC0301. (A boundary node
|
|
575
|
+
// answers HTTP 400 for an id outside every range; the code is the same.)
|
|
576
|
+
const test = setup()
|
|
577
|
+
const missing = test.client.canister<shapes.Actor>(shapes.actor, {
|
|
578
|
+
id: LEDGER,
|
|
579
|
+
})
|
|
580
|
+
|
|
581
|
+
await expect(missing.one(1n)).rejects.toMatchObject({
|
|
582
|
+
kind: "rejected",
|
|
583
|
+
rejectCode: 3,
|
|
584
|
+
code: "canister_not_found",
|
|
585
|
+
mayHaveExecuted: false,
|
|
586
|
+
})
|
|
587
|
+
await expect(missing.bump(1n)).rejects.toMatchObject({
|
|
588
|
+
kind: "rejected",
|
|
589
|
+
rejectCode: 3,
|
|
590
|
+
code: "canister_not_found",
|
|
591
|
+
mayHaveExecuted: false,
|
|
592
|
+
})
|
|
593
|
+
expect(canisterRequests(test)).toHaveLength(2)
|
|
594
|
+
})
|
|
595
|
+
|
|
596
|
+
it("loses the reply to the next update only, and the handler ran exactly once", async () => {
|
|
597
|
+
let ran = 0
|
|
598
|
+
const { canister, dropNextReply, requests } = withShapes({
|
|
599
|
+
bump: (n) => {
|
|
600
|
+
ran += 1
|
|
601
|
+
return { tag: "ok", value: n }
|
|
602
|
+
},
|
|
603
|
+
})
|
|
604
|
+
dropNextReply()
|
|
605
|
+
|
|
606
|
+
await expect(canister.bump(1n)).rejects.toMatchObject({
|
|
607
|
+
kind: "outcome_unknown",
|
|
608
|
+
mayHaveExecuted: true,
|
|
609
|
+
})
|
|
610
|
+
// Nothing re-sends it, however long the client waits.
|
|
611
|
+
await sleep(1_100)
|
|
612
|
+
expect(ran).toBe(1)
|
|
613
|
+
expect(requests.filter((request) => request.dropped)).toMatchObject([
|
|
614
|
+
{ endpoint: "call", methodName: "bump" },
|
|
615
|
+
])
|
|
616
|
+
|
|
617
|
+
// The loss was for that one call: the next works, and runs.
|
|
618
|
+
await expect(canister.bump(2n)).resolves.toBe(2n)
|
|
619
|
+
expect(ran).toBe(2)
|
|
620
|
+
})
|
|
621
|
+
|
|
622
|
+
it("refuses the next request with an HTTP status before the canister sees it", async () => {
|
|
623
|
+
let ran = 0
|
|
624
|
+
const { canister, refuseNext, requests } = withShapes({
|
|
625
|
+
bump: (n) => {
|
|
626
|
+
ran += 1
|
|
627
|
+
return { tag: "ok", value: n }
|
|
628
|
+
},
|
|
629
|
+
})
|
|
630
|
+
refuseNext(429)
|
|
631
|
+
|
|
632
|
+
// A refused update is sent again once, and the second send is let through.
|
|
633
|
+
await expect(canister.bump(5n)).resolves.toBe(5n)
|
|
634
|
+
|
|
635
|
+
expect(ran).toBe(1)
|
|
636
|
+
expect(canisterRequests({ requests })).toMatchObject([
|
|
637
|
+
{ refused: expect.stringContaining("429") },
|
|
638
|
+
{ methodName: "bump" },
|
|
639
|
+
])
|
|
640
|
+
})
|
|
641
|
+
|
|
642
|
+
it("traps when a handler throws anything but reject()", async () => {
|
|
643
|
+
const { canister } = withShapes({
|
|
644
|
+
bump: () => {
|
|
645
|
+
throw new Error("the ledger is corrupt")
|
|
646
|
+
},
|
|
647
|
+
})
|
|
648
|
+
|
|
649
|
+
await expect(canister.bump(1n)).rejects.toMatchObject({
|
|
650
|
+
kind: "rejected",
|
|
651
|
+
rejectCode: 5,
|
|
652
|
+
mayHaveExecuted: true,
|
|
653
|
+
message: expect.stringContaining("the ledger is corrupt"),
|
|
654
|
+
})
|
|
655
|
+
})
|
|
656
|
+
|
|
657
|
+
it("traps a call to a method the test gave no handler, naming it", async () => {
|
|
658
|
+
const { canister } = withShapes({ one: () => 1n })
|
|
659
|
+
|
|
660
|
+
await expect(canister.who()).rejects.toMatchObject({
|
|
661
|
+
kind: "rejected",
|
|
662
|
+
rejectCode: 5,
|
|
663
|
+
message: expect.stringContaining("no handler for who"),
|
|
664
|
+
})
|
|
665
|
+
})
|
|
666
|
+
|
|
667
|
+
it("traps a call whose arguments do not decode with the mocked service", async () => {
|
|
668
|
+
// The client sends a nat; the canister it mocks was told `one` takes text.
|
|
669
|
+
const mocked = c.service({ one: c.func([c.text], [c.nat], "query") })
|
|
670
|
+
const test = setup()
|
|
671
|
+
test.mock<{ one: (text: string) => Promise<bigint> }>(mocked, SHAPES, {
|
|
672
|
+
one: () => 1n,
|
|
673
|
+
})
|
|
674
|
+
const canister = test.client.canister<shapes.Actor>(shapes.actor, {
|
|
675
|
+
id: SHAPES,
|
|
676
|
+
})
|
|
677
|
+
|
|
678
|
+
await expect(canister.one(1n)).rejects.toMatchObject({
|
|
679
|
+
kind: "rejected",
|
|
680
|
+
rejectCode: 5,
|
|
681
|
+
message: expect.stringContaining("do not decode with the mocked service"),
|
|
682
|
+
})
|
|
683
|
+
})
|
|
684
|
+
|
|
685
|
+
it("traps a handler that returns something its method's result does not hold", async () => {
|
|
686
|
+
const { canister } = withShapes({
|
|
687
|
+
one: () => "one" as never,
|
|
688
|
+
many: () => [1n] as never,
|
|
689
|
+
})
|
|
690
|
+
|
|
691
|
+
await expect(canister.one(1n)).rejects.toMatchObject({
|
|
692
|
+
kind: "rejected",
|
|
693
|
+
rejectCode: 5,
|
|
694
|
+
message: expect.stringContaining(
|
|
695
|
+
"returned a value that is not its result"
|
|
696
|
+
),
|
|
697
|
+
})
|
|
698
|
+
await expect(canister.many()).rejects.toMatchObject({
|
|
699
|
+
kind: "rejected",
|
|
700
|
+
rejectCode: 5,
|
|
701
|
+
message: expect.stringContaining("returns a tuple of 3 values"),
|
|
702
|
+
})
|
|
703
|
+
})
|
|
704
|
+
})
|
|
705
|
+
|
|
706
|
+
describe("a refusal aimed at one method or canister", () => {
|
|
707
|
+
/** A canister that names no canister on the fake. */
|
|
708
|
+
const NOWHERE = "2y4s5-zaaaa-aaad7-7777q-cai"
|
|
709
|
+
const to = principal("rrkah-fqaaa-aaaaa-aaaaq-cai")
|
|
710
|
+
const transferArg: icrc1.TransferArg = {
|
|
711
|
+
to: { owner: to, subaccount: null },
|
|
712
|
+
amount: 5n,
|
|
713
|
+
fee: null,
|
|
714
|
+
memo: null,
|
|
715
|
+
from_subaccount: null,
|
|
716
|
+
created_at_time: null,
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/** A ledger at LEDGER and the shapes canister at SHAPES, counting runs. */
|
|
720
|
+
function twoCanisters() {
|
|
721
|
+
const runs = { transfer: 0, balance: 0, fee: 0, one: 0 }
|
|
722
|
+
const test = setup()
|
|
723
|
+
test.mock<icrc1.Actor>(icrc1.actor, LEDGER, {
|
|
724
|
+
icrc1_fee: () => {
|
|
725
|
+
runs.fee += 1
|
|
726
|
+
return 10_000n
|
|
727
|
+
},
|
|
728
|
+
icrc1_balance_of: () => {
|
|
729
|
+
runs.balance += 1
|
|
730
|
+
return 7n
|
|
731
|
+
},
|
|
732
|
+
icrc1_transfer: () => {
|
|
733
|
+
runs.transfer += 1
|
|
734
|
+
return { tag: "Ok", value: 1n }
|
|
735
|
+
},
|
|
736
|
+
})
|
|
737
|
+
test.mock<shapes.Actor>(shapes.actor, SHAPES, {
|
|
738
|
+
one: (n) => {
|
|
739
|
+
runs.one += 1
|
|
740
|
+
return n
|
|
741
|
+
},
|
|
742
|
+
})
|
|
743
|
+
return {
|
|
744
|
+
...test,
|
|
745
|
+
runs,
|
|
746
|
+
ledger: test.client.canister<icrc1.Actor>(icrc1.actor, { id: LEDGER }),
|
|
747
|
+
shapes: test.client.canister<shapes.Actor>(shapes.actor, { id: SHAPES }),
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/** Whether each request for `method` was refused, in order. */
|
|
752
|
+
const refusedOf = (test: Pick<Test, "requests">, method: string) =>
|
|
753
|
+
test.requests
|
|
754
|
+
.filter((request) => request.methodName === method)
|
|
755
|
+
.map((request) => request.refused !== undefined)
|
|
756
|
+
|
|
757
|
+
it.each([
|
|
758
|
+
["icrc1_transfer", "icrc1_balance_of"],
|
|
759
|
+
["icrc1_balance_of", "icrc1_transfer"],
|
|
760
|
+
] as const)(
|
|
761
|
+
"refuses only %s while a concurrent %s is answered",
|
|
762
|
+
async (refused, answered) => {
|
|
763
|
+
const test = twoCanisters()
|
|
764
|
+
// Three sends: the first, and the two re-sends the client allows itself
|
|
765
|
+
// after a 429, for a read as for a write.
|
|
766
|
+
test.refuseNext(429, { method: refused, times: 3 })
|
|
767
|
+
|
|
768
|
+
const [transfer, balance] = await Promise.allSettled([
|
|
769
|
+
test.ledger.icrc1_transfer(transferArg),
|
|
770
|
+
test.ledger.icrc1_balance_of({ owner: to, subaccount: null }),
|
|
771
|
+
])
|
|
772
|
+
const outcomes = { icrc1_transfer: transfer, icrc1_balance_of: balance }
|
|
773
|
+
|
|
774
|
+
expect(outcomes[answered]).toMatchObject({ status: "fulfilled" })
|
|
775
|
+
expect(outcomes[refused]).toMatchObject({
|
|
776
|
+
status: "rejected",
|
|
777
|
+
reason: { kind: "not_delivered", httpStatus: 429 },
|
|
778
|
+
})
|
|
779
|
+
expect(refusedOf(test, refused)).toEqual([true, true, true])
|
|
780
|
+
expect(refusedOf(test, answered)).toEqual([false])
|
|
781
|
+
expect(test.runs).toMatchObject(
|
|
782
|
+
refused === "icrc1_transfer"
|
|
783
|
+
? { transfer: 0, balance: 1 }
|
|
784
|
+
: { transfer: 1, balance: 0 }
|
|
785
|
+
)
|
|
786
|
+
}
|
|
787
|
+
)
|
|
788
|
+
|
|
789
|
+
it("refuses only the canister it names, mocked or not, whatever the method", async () => {
|
|
790
|
+
const test = twoCanisters()
|
|
791
|
+
test.refuseNext(400, { canister: SHAPES })
|
|
792
|
+
test.refuseNext(400, { canister: NOWHERE })
|
|
793
|
+
|
|
794
|
+
await expect(test.ledger.icrc1_fee()).resolves.toBe(10_000n)
|
|
795
|
+
await expect(test.shapes.one(1n)).rejects.toMatchObject({
|
|
796
|
+
kind: "not_delivered",
|
|
797
|
+
httpStatus: 400,
|
|
798
|
+
})
|
|
799
|
+
// As a gateway answers for a canister that does not exist, not the reject
|
|
800
|
+
// code 3 the fake would answer once the request got in.
|
|
801
|
+
const nowhere = test.client.canister<icrc1.Actor>(icrc1.actor, {
|
|
802
|
+
id: NOWHERE,
|
|
803
|
+
})
|
|
804
|
+
await expect(nowhere.icrc1_fee()).rejects.toMatchObject({
|
|
805
|
+
kind: "not_delivered",
|
|
806
|
+
httpStatus: 400,
|
|
807
|
+
})
|
|
808
|
+
|
|
809
|
+
expect(test.runs).toMatchObject({ fee: 1, one: 0 })
|
|
810
|
+
// Each refusal was used once, so the next calls are answered.
|
|
811
|
+
await expect(test.shapes.one(2n)).resolves.toBe(2n)
|
|
812
|
+
expect(canisterRequests(test).map((r) => r.refused !== undefined)).toEqual([
|
|
813
|
+
false,
|
|
814
|
+
true,
|
|
815
|
+
true,
|
|
816
|
+
false,
|
|
817
|
+
])
|
|
818
|
+
})
|
|
819
|
+
|
|
820
|
+
it("aims at the canister a management call names, aaaaa-aa, not the canister it is routed by", async () => {
|
|
821
|
+
const test = setup()
|
|
822
|
+
const stopped: string[] = []
|
|
823
|
+
test.mock<management.Actor>(management.actor, MANAGEMENT, {
|
|
824
|
+
stop_canister: ({ canister_id }) => {
|
|
825
|
+
stopped.push(`${canister_id}`)
|
|
826
|
+
},
|
|
827
|
+
})
|
|
828
|
+
const ic = test.client.canister<management.Actor>(management.actor, {
|
|
829
|
+
id: MANAGEMENT,
|
|
830
|
+
})
|
|
831
|
+
// Armed first, so a refusal matched by the routing id would be used here.
|
|
832
|
+
test.refuseNext(400, { canister: SHAPES })
|
|
833
|
+
test.refuseNext(403, { canister: MANAGEMENT })
|
|
834
|
+
|
|
835
|
+
await expect(
|
|
836
|
+
ic.stop_canister({ canister_id: principal(SHAPES) })
|
|
837
|
+
).rejects.toMatchObject({ kind: "not_delivered", httpStatus: 403 })
|
|
838
|
+
await expect(
|
|
839
|
+
ic.stop_canister({ canister_id: principal(SHAPES) })
|
|
840
|
+
).resolves.toBeUndefined()
|
|
841
|
+
|
|
842
|
+
expect(stopped).toEqual([SHAPES])
|
|
843
|
+
expect(
|
|
844
|
+
canisterRequests(test).map((r) => [
|
|
845
|
+
r.canisterId,
|
|
846
|
+
r.effectiveCanisterId,
|
|
847
|
+
r.refused !== undefined,
|
|
848
|
+
])
|
|
849
|
+
).toEqual([
|
|
850
|
+
[MANAGEMENT, SHAPES, true],
|
|
851
|
+
[MANAGEMENT, SHAPES, false],
|
|
852
|
+
])
|
|
853
|
+
})
|
|
854
|
+
|
|
855
|
+
it("is matched in the order refusals were armed, and the count form still refuses whatever comes next", async () => {
|
|
856
|
+
// Read as 4.0.0-beta.1 reads it: `refuseNext(status, times?)` refuses the
|
|
857
|
+
// next query or call of any method. An aimed refusal armed before it
|
|
858
|
+
// does not hold it back, and waits for its own method.
|
|
859
|
+
const test = twoCanisters()
|
|
860
|
+
test.refuseNext(400, { method: "icrc1_transfer" })
|
|
861
|
+
test.refuseNext(403, 2)
|
|
862
|
+
|
|
863
|
+
await expect(test.ledger.icrc1_fee()).rejects.toMatchObject({
|
|
864
|
+
httpStatus: 403,
|
|
865
|
+
})
|
|
866
|
+
await expect(test.shapes.one(1n)).rejects.toMatchObject({
|
|
867
|
+
httpStatus: 403,
|
|
868
|
+
})
|
|
869
|
+
await expect(test.ledger.icrc1_transfer(transferArg)).rejects.toMatchObject(
|
|
870
|
+
{ kind: "not_delivered", httpStatus: 400 }
|
|
871
|
+
)
|
|
872
|
+
await expect(test.ledger.icrc1_fee()).resolves.toBe(10_000n)
|
|
873
|
+
|
|
874
|
+
expect(test.runs).toMatchObject({ fee: 1, one: 0, transfer: 0 })
|
|
875
|
+
})
|
|
876
|
+
|
|
877
|
+
// dropNextReply({ method, canister, times }) (#882): the same matching as
|
|
878
|
+
// refuseNext's, for a lost reply.
|
|
879
|
+
it("dropNextReply({ method }) loses the write's reply, not a certified read's made first", async () => {
|
|
880
|
+
const test = twoCanisters()
|
|
881
|
+
const certified = test.client.canister<icrc1.Actor>(icrc1.actor, {
|
|
882
|
+
id: LEDGER,
|
|
883
|
+
certified: true,
|
|
884
|
+
})
|
|
885
|
+
test.dropNextReply({ method: "icrc1_transfer" })
|
|
886
|
+
|
|
887
|
+
await expect(certified.icrc1_fee()).resolves.toBe(10_000n)
|
|
888
|
+
await expect(test.ledger.icrc1_transfer(transferArg)).rejects.toMatchObject(
|
|
889
|
+
{ kind: "outcome_unknown", mayHaveExecuted: true }
|
|
890
|
+
)
|
|
891
|
+
expect(test.runs).toMatchObject({ fee: 1, transfer: 1 })
|
|
892
|
+
expect(
|
|
893
|
+
canisterRequests(test).map((r) => [r.methodName, r.dropped === true])
|
|
894
|
+
).toEqual([
|
|
895
|
+
["icrc1_fee", false],
|
|
896
|
+
["icrc1_transfer", true],
|
|
897
|
+
])
|
|
898
|
+
// Used up: the next transfer is answered.
|
|
899
|
+
await expect(test.ledger.icrc1_transfer(transferArg)).resolves.toBe(1n)
|
|
900
|
+
})
|
|
901
|
+
|
|
902
|
+
it("dropNextReply({ canister }) matches the canister a call names, aaaaa-aa for a management call", async () => {
|
|
903
|
+
const test = setup()
|
|
904
|
+
const stopped: string[] = []
|
|
905
|
+
test.mock<management.Actor>(management.actor, MANAGEMENT, {
|
|
906
|
+
stop_canister: ({ canister_id }) => {
|
|
907
|
+
stopped.push(`${canister_id}`)
|
|
908
|
+
},
|
|
909
|
+
})
|
|
910
|
+
test.mock<shapes.Actor>(shapes.actor, SHAPES, {
|
|
911
|
+
bump: (n) => ({ tag: "ok", value: n }),
|
|
912
|
+
})
|
|
913
|
+
const ic = test.client.canister<management.Actor>(management.actor, {
|
|
914
|
+
id: MANAGEMENT,
|
|
915
|
+
})
|
|
916
|
+
const shapesCanister = test.client.canister<shapes.Actor>(shapes.actor, {
|
|
917
|
+
id: SHAPES,
|
|
918
|
+
})
|
|
919
|
+
// Aimed at SHAPES, the canister the management call is routed by: the
|
|
920
|
+
// management call names aaaaa-aa, so it is answered and the drop waits.
|
|
921
|
+
test.dropNextReply({ canister: SHAPES })
|
|
922
|
+
|
|
923
|
+
await expect(
|
|
924
|
+
ic.stop_canister({ canister_id: principal(SHAPES) })
|
|
925
|
+
).resolves.toBeUndefined()
|
|
926
|
+
await expect(shapesCanister.bump(1n)).rejects.toMatchObject({
|
|
927
|
+
kind: "outcome_unknown",
|
|
928
|
+
})
|
|
929
|
+
test.dropNextReply({ canister: MANAGEMENT })
|
|
930
|
+
await expect(shapesCanister.bump(2n)).resolves.toBe(2n)
|
|
931
|
+
await expect(
|
|
932
|
+
ic.stop_canister({ canister_id: principal(SHAPES) })
|
|
933
|
+
).rejects.toMatchObject({ kind: "outcome_unknown" })
|
|
934
|
+
|
|
935
|
+
expect(stopped).toEqual([SHAPES, SHAPES])
|
|
936
|
+
expect(
|
|
937
|
+
canisterRequests(test).map((r) => [r.canisterId, r.dropped === true])
|
|
938
|
+
).toEqual([
|
|
939
|
+
[MANAGEMENT, false],
|
|
940
|
+
[SHAPES, true],
|
|
941
|
+
[SHAPES, false],
|
|
942
|
+
[MANAGEMENT, true],
|
|
943
|
+
])
|
|
944
|
+
})
|
|
945
|
+
|
|
946
|
+
it("dropNextReply() with no argument still loses whatever call request comes next", async () => {
|
|
947
|
+
const test = twoCanisters()
|
|
948
|
+
const certified = test.client.canister<icrc1.Actor>(icrc1.actor, {
|
|
949
|
+
id: LEDGER,
|
|
950
|
+
certified: true,
|
|
951
|
+
})
|
|
952
|
+
test.dropNextReply()
|
|
953
|
+
// A certified read goes out as a call: its reply is the one lost, and
|
|
954
|
+
// the client sends the read again.
|
|
955
|
+
await expect(certified.icrc1_fee()).resolves.toBe(10_000n)
|
|
956
|
+
await expect(test.ledger.icrc1_transfer(transferArg)).resolves.toBe(1n)
|
|
957
|
+
expect(
|
|
958
|
+
canisterRequests(test).map((r) => [r.methodName, r.dropped === true])
|
|
959
|
+
).toEqual([
|
|
960
|
+
["icrc1_fee", true],
|
|
961
|
+
["icrc1_fee", false],
|
|
962
|
+
["icrc1_transfer", false],
|
|
963
|
+
])
|
|
964
|
+
// The read is not an update: its handler ran for both requests.
|
|
965
|
+
expect(test.runs).toMatchObject({ fee: 2, transfer: 1 })
|
|
966
|
+
})
|
|
967
|
+
})
|
|
968
|
+
|
|
969
|
+
describe("what it leaves alone", () => {
|
|
970
|
+
it("never replaces or uses globalThis.fetch", async () => {
|
|
971
|
+
const real = vi.fn(() => {
|
|
972
|
+
throw new Error("the network was reached")
|
|
973
|
+
})
|
|
974
|
+
vi.stubGlobal("fetch", real)
|
|
975
|
+
|
|
976
|
+
const test = setup()
|
|
977
|
+
test.mock<shapes.Actor>(shapes.actor, SHAPES, {
|
|
978
|
+
one: (n) => n,
|
|
979
|
+
bump: (n) => ({ tag: "ok", value: n }),
|
|
980
|
+
})
|
|
981
|
+
test.dropNextReply()
|
|
982
|
+
test.refuseNext(503)
|
|
983
|
+
const canister = test.client.canister<shapes.Actor>(shapes.actor, {
|
|
984
|
+
id: SHAPES,
|
|
985
|
+
})
|
|
986
|
+
await canister.one(1n)
|
|
987
|
+
await canister.bump(1n).catch(() => undefined)
|
|
988
|
+
await canister.bump(2n).catch(() => undefined)
|
|
989
|
+
test.client.dispose()
|
|
990
|
+
|
|
991
|
+
expect(globalThis.fetch).toBe(real)
|
|
992
|
+
expect(real).not.toHaveBeenCalled()
|
|
993
|
+
})
|
|
994
|
+
|
|
995
|
+
it("keeps two test clients apart: their canisters, callers and logs", async () => {
|
|
996
|
+
const first = setup({ identity: 1 })
|
|
997
|
+
const second = setup({ identity: 2 })
|
|
998
|
+
first.mock<shapes.Actor>(shapes.actor, SHAPES, {
|
|
999
|
+
address: (seed, { caller }) => `first:${seed}:${caller}`,
|
|
1000
|
+
})
|
|
1001
|
+
second.mock<shapes.Actor>(shapes.actor, SHAPES, {
|
|
1002
|
+
address: (seed, { caller }) => `second:${seed}:${caller}`,
|
|
1003
|
+
})
|
|
1004
|
+
const call = (test: typeof first) =>
|
|
1005
|
+
test.client
|
|
1006
|
+
.canister<shapes.Actor>(shapes.actor, { id: SHAPES })
|
|
1007
|
+
.address("a")
|
|
1008
|
+
|
|
1009
|
+
await expect(call(first)).resolves.toBe(`first:a:${SEED_1}`)
|
|
1010
|
+
await expect(call(second)).resolves.toBe(`second:a:${SEED_2}`)
|
|
1011
|
+
|
|
1012
|
+
// Each log holds only its own client's call.
|
|
1013
|
+
expect(canisterRequests(first)).toMatchObject([{ caller: SEED_1 }])
|
|
1014
|
+
expect(canisterRequests(second)).toMatchObject([{ caller: SEED_2 }])
|
|
1015
|
+
})
|
|
1016
|
+
|
|
1017
|
+
it("disposes its auth with the client once the client has used it, and leaves one it never built", async () => {
|
|
1018
|
+
// The client builds its auth on first use, and owns only what it built.
|
|
1019
|
+
// A test can therefore borrow `auth` from a client it never used and hand
|
|
1020
|
+
// it to another one.
|
|
1021
|
+
const unused = setup()
|
|
1022
|
+
unused.client.dispose()
|
|
1023
|
+
expect(unused.auth.disposed).toBe(false)
|
|
1024
|
+
|
|
1025
|
+
const used = setup()
|
|
1026
|
+
expect(used.client.caller()).toBe(SEED_1)
|
|
1027
|
+
used.client.dispose()
|
|
1028
|
+
expect(used.auth.disposed).toBe(true)
|
|
1029
|
+
})
|
|
1030
|
+
|
|
1031
|
+
it("owns its auth once a key, query options or a mutation run read the caller", async () => {
|
|
1032
|
+
// `queryKey()` and `queryOptions()` read the caller without calling or
|
|
1033
|
+
// subscribing, and a mutation from `mutationOptions()` reads it when it
|
|
1034
|
+
// runs: each makes the client build the auth, so it disposes it, as the
|
|
1035
|
+
// documentation says.
|
|
1036
|
+
const keyed = withShapes({ one: (n) => n })
|
|
1037
|
+
keyed.client.queryKey(keyed.canister)
|
|
1038
|
+
keyed.client.dispose()
|
|
1039
|
+
expect(keyed.auth.disposed).toBe(true)
|
|
1040
|
+
|
|
1041
|
+
const read = withShapes({ one: (n) => n })
|
|
1042
|
+
read.client.queryOptions(read.canister, "one", 1n)
|
|
1043
|
+
read.client.dispose()
|
|
1044
|
+
expect(read.auth.disposed).toBe(true)
|
|
1045
|
+
|
|
1046
|
+
const written = withShapes({ one: (n) => n })
|
|
1047
|
+
await written.client.mutationOptions(written.canister, "one").mutationFn(1n)
|
|
1048
|
+
written.client.dispose()
|
|
1049
|
+
expect(written.auth.disposed).toBe(true)
|
|
1050
|
+
})
|
|
1051
|
+
|
|
1052
|
+
it("holds the fake's own root key, so no root key is ever fetched", async () => {
|
|
1053
|
+
const { canister, requests } = withShapes({ one: (n) => n })
|
|
1054
|
+
|
|
1055
|
+
await canister.one(1n)
|
|
1056
|
+
|
|
1057
|
+
expect(requests.map((request) => request.endpoint)).not.toContain("status")
|
|
1058
|
+
})
|
|
1059
|
+
})
|
|
1060
|
+
|
|
1061
|
+
describe("the network it believes it is on", () => {
|
|
1062
|
+
const ONE = { one: (n: bigint) => n + 1n }
|
|
1063
|
+
|
|
1064
|
+
it("is a replica of its own by default", async () => {
|
|
1065
|
+
const { client, canister } = withShapes(ONE)
|
|
1066
|
+
|
|
1067
|
+
await expect(canister.one(1n)).resolves.toBe(2n)
|
|
1068
|
+
expect(client.network).toMatch(/^https?:\/\//)
|
|
1069
|
+
})
|
|
1070
|
+
|
|
1071
|
+
it("is mainnet's key segment and host for `ic`, answered by the fake", async () => {
|
|
1072
|
+
const test = withShapes(ONE, { network: "ic" })
|
|
1073
|
+
|
|
1074
|
+
await expect(test.canister.one(1n)).resolves.toBe(2n)
|
|
1075
|
+
|
|
1076
|
+
expect(test.client.network).toBe("ic")
|
|
1077
|
+
expect(test.client.queryKey(test.canister, "one", 1n)).toContain("ic")
|
|
1078
|
+
// The client is on mainnet's host as far as it can tell (what it would
|
|
1079
|
+
// trust there, it trusts here); the fake answers on that host.
|
|
1080
|
+
expect(internalsOf(test.client).network.host).toBe("https://icp-api.io")
|
|
1081
|
+
expect(canisterRequests(test)).toMatchObject([{ canisterId: SHAPES }])
|
|
1082
|
+
})
|
|
1083
|
+
|
|
1084
|
+
it("keeps the name a network object is given, and ignores a root key of its own", async () => {
|
|
1085
|
+
const test = withShapes(ONE, {
|
|
1086
|
+
network: {
|
|
1087
|
+
host: "https://replica.example.test",
|
|
1088
|
+
name: "staging",
|
|
1089
|
+
// Not the fake's key: the fake signs with its own, so this must go.
|
|
1090
|
+
rootKey: new Uint8Array(133).fill(1),
|
|
1091
|
+
},
|
|
1092
|
+
})
|
|
1093
|
+
|
|
1094
|
+
await expect(test.canister.one(1n)).resolves.toBe(2n)
|
|
1095
|
+
expect(test.client.network).toBe("staging")
|
|
1096
|
+
expect(internalsOf(test.client).network.host).toBe(
|
|
1097
|
+
"https://replica.example.test"
|
|
1098
|
+
)
|
|
1099
|
+
})
|
|
1100
|
+
|
|
1101
|
+
it("follows a local network to its host", async () => {
|
|
1102
|
+
const test = withShapes(ONE, { network: "local" })
|
|
1103
|
+
|
|
1104
|
+
await expect(test.canister.one(1n)).resolves.toBe(2n)
|
|
1105
|
+
expect(test.client.network).toBe("local")
|
|
1106
|
+
expect(internalsOf(test.client).network.host).toBe("http://127.0.0.1:8000")
|
|
1107
|
+
})
|
|
1108
|
+
|
|
1109
|
+
it("follows the environment's host for `env`", async () => {
|
|
1110
|
+
stubProcessEnv()
|
|
1111
|
+
// `env` outside a page says once that a { name } canister cannot resolve.
|
|
1112
|
+
vi.spyOn(console, "warn").mockImplementation(() => {})
|
|
1113
|
+
const test = withShapes(ONE, { network: "env" })
|
|
1114
|
+
|
|
1115
|
+
await expect(test.canister.one(1n)).resolves.toBe(2n)
|
|
1116
|
+
expect(test.client.network).toBe("https://icp-api.io")
|
|
1117
|
+
})
|
|
1118
|
+
|
|
1119
|
+
it("works the same on a page, where createClient builds the auth itself", async () => {
|
|
1120
|
+
onPage()
|
|
1121
|
+
expect(typeof window).toBe("object")
|
|
1122
|
+
const { canister, auth } = withShapes({ who: ({ caller }) => caller })
|
|
1123
|
+
|
|
1124
|
+
await expect(canister.who()).resolves.toBe(SEED_1)
|
|
1125
|
+
auth.switchTo(2)
|
|
1126
|
+
await expect(canister.who()).resolves.toBe(SEED_2)
|
|
1127
|
+
})
|
|
1128
|
+
})
|
|
1129
|
+
|
|
1130
|
+
describe("what it refuses", () => {
|
|
1131
|
+
it("an option it does not have, naming it", () => {
|
|
1132
|
+
expect(() => createTestClient({ seed: 2 } as unknown as Options)).toThrow(
|
|
1133
|
+
/no option seed.*Its options: identity, signedIn/
|
|
1134
|
+
)
|
|
1135
|
+
})
|
|
1136
|
+
|
|
1137
|
+
it("a boolean option that is not a boolean, and a network createClient would refuse", () => {
|
|
1138
|
+
onPage()
|
|
1139
|
+
// "false" is truthy: it started signed in, and trusted the ic_env cookie.
|
|
1140
|
+
expect(() =>
|
|
1141
|
+
createTestClient({ signedIn: "false" } as unknown as Options)
|
|
1142
|
+
).toThrow(
|
|
1143
|
+
new TypeError(
|
|
1144
|
+
'[ic-reactor] createTestClient(): signedIn is true or false, got "false".'
|
|
1145
|
+
)
|
|
1146
|
+
)
|
|
1147
|
+
for (const network of [undefined, "ic", { host: "https://x.example" }]) {
|
|
1148
|
+
expect(() =>
|
|
1149
|
+
createTestClient({
|
|
1150
|
+
network,
|
|
1151
|
+
allowEnvConfig: "false",
|
|
1152
|
+
} as unknown as Options)
|
|
1153
|
+
).toThrow(
|
|
1154
|
+
new TypeError(
|
|
1155
|
+
'[ic-reactor] allowEnvConfig is true or false, got "false".'
|
|
1156
|
+
)
|
|
1157
|
+
)
|
|
1158
|
+
}
|
|
1159
|
+
expect(() =>
|
|
1160
|
+
createTestClient({
|
|
1161
|
+
network: { host: "https://x.example", rootkey: new Uint8Array(133) },
|
|
1162
|
+
} as unknown as Options)
|
|
1163
|
+
).toThrow(/a network object has no option rootkey\./)
|
|
1164
|
+
expect(() =>
|
|
1165
|
+
createTestClient({
|
|
1166
|
+
network: { host: "https://x.example", fetchRootKey: "false" },
|
|
1167
|
+
} as unknown as Options)
|
|
1168
|
+
).toThrow(/fetchRootKey is true or false, got "false"\./)
|
|
1169
|
+
})
|
|
1170
|
+
|
|
1171
|
+
it("an identity that is neither an Identity nor a seed, null included", () => {
|
|
1172
|
+
for (const identity of [null, "seed", {}, true]) {
|
|
1173
|
+
expect(() =>
|
|
1174
|
+
createTestClient({ identity } as unknown as Options)
|
|
1175
|
+
).toThrow(/identity is an Identity or a seed/)
|
|
1176
|
+
}
|
|
1177
|
+
})
|
|
1178
|
+
|
|
1179
|
+
it("a seed that is not a non-negative integer", () => {
|
|
1180
|
+
expect(() => createTestClient({ identity: -1 })).toThrow(RangeError)
|
|
1181
|
+
expect(() => createTestClient({ identity: 1.5 })).toThrow(RangeError)
|
|
1182
|
+
})
|
|
1183
|
+
|
|
1184
|
+
it("a mock of something that is not a service, or an id that is not a canister", () => {
|
|
1185
|
+
const { mock } = setup()
|
|
1186
|
+
|
|
1187
|
+
expect(() => mock(c.nat as never, SHAPES, {})).toThrow(
|
|
1188
|
+
/takes the service schema of a generated module/
|
|
1189
|
+
)
|
|
1190
|
+
expect(() =>
|
|
1191
|
+
mock<shapes.Actor>(shapes.actor, "not-a-canister", {})
|
|
1192
|
+
).toThrow(/takes a canister id.*"not-a-canister"/)
|
|
1193
|
+
})
|
|
1194
|
+
|
|
1195
|
+
it("handlers for a method the service lacks, or that are not functions", () => {
|
|
1196
|
+
const { mock } = setup()
|
|
1197
|
+
|
|
1198
|
+
expect(() =>
|
|
1199
|
+
mock<shapes.Actor>(shapes.actor, SHAPES, {
|
|
1200
|
+
...({ unknown_method: () => 1n } as object),
|
|
1201
|
+
})
|
|
1202
|
+
).toThrow(/handler for unknown_method, which the service does not have/)
|
|
1203
|
+
expect(() =>
|
|
1204
|
+
mock<shapes.Actor>(shapes.actor, SHAPES, {
|
|
1205
|
+
one: 5 as never,
|
|
1206
|
+
})
|
|
1207
|
+
).toThrow(/handler for one is a function.*got number/)
|
|
1208
|
+
expect(() =>
|
|
1209
|
+
mock<shapes.Actor>(shapes.actor, SHAPES, null as never)
|
|
1210
|
+
).toThrow(/handlers as an object/)
|
|
1211
|
+
})
|
|
1212
|
+
|
|
1213
|
+
it("a refusal that cannot be armed: a status, a count, an option, a method or a canister", () => {
|
|
1214
|
+
const { refuseNext } = setup()
|
|
1215
|
+
|
|
1216
|
+
expect(() => refuseNext(200, { method: "icrc1_transfer" })).toThrow(
|
|
1217
|
+
new RangeError("refuseNext: 200 is not an HTTP error status (400 to 599)")
|
|
1218
|
+
)
|
|
1219
|
+
expect(() => refuseNext(429, { times: 0 })).toThrow(
|
|
1220
|
+
new RangeError("refuseNext: `times` is 0, not a count of at least 1")
|
|
1221
|
+
)
|
|
1222
|
+
expect(() => refuseNext(429, { methd: "icrc1_transfer" } as never)).toThrow(
|
|
1223
|
+
new TypeError(
|
|
1224
|
+
"refuseNext() has no option methd. Its options: method, canister, times."
|
|
1225
|
+
)
|
|
1226
|
+
)
|
|
1227
|
+
expect(() => refuseNext(429, { method: "" })).toThrow(
|
|
1228
|
+
new TypeError(
|
|
1229
|
+
'refuseNext: `method` is the name of a canister method, got ""'
|
|
1230
|
+
)
|
|
1231
|
+
)
|
|
1232
|
+
expect(() => refuseNext(429, { method: 5 } as never)).toThrow(
|
|
1233
|
+
new TypeError(
|
|
1234
|
+
"refuseNext: `method` is the name of a canister method, got number"
|
|
1235
|
+
)
|
|
1236
|
+
)
|
|
1237
|
+
expect(() => refuseNext(429, { canister: "ledger" })).toThrow(
|
|
1238
|
+
new TypeError(
|
|
1239
|
+
'refuseNext: `canister` is a canister id, such as "ryjl3-tyaaa-aaaaa-aaaba-cai" or "aaaaa-aa", got "ledger"'
|
|
1240
|
+
)
|
|
1241
|
+
)
|
|
1242
|
+
// dropNextReply takes the same options, checked the same way.
|
|
1243
|
+
const { dropNextReply } = setup()
|
|
1244
|
+
expect(() => dropNextReply({ times: 0 })).toThrow(
|
|
1245
|
+
new RangeError("dropNextReply: `times` is 0, not a count of at least 1")
|
|
1246
|
+
)
|
|
1247
|
+
expect(() => dropNextReply({ methd: "x" } as never)).toThrow(
|
|
1248
|
+
new TypeError(
|
|
1249
|
+
"dropNextReply() has no option methd. Its options: method, canister, times."
|
|
1250
|
+
)
|
|
1251
|
+
)
|
|
1252
|
+
expect(() => dropNextReply({ method: "" })).toThrow(
|
|
1253
|
+
new TypeError(
|
|
1254
|
+
'dropNextReply: `method` is the name of a canister method, got ""'
|
|
1255
|
+
)
|
|
1256
|
+
)
|
|
1257
|
+
expect(() => dropNextReply({ canister: "ledger" })).toThrow(
|
|
1258
|
+
new TypeError(
|
|
1259
|
+
'dropNextReply: `canister` is a canister id, such as "ryjl3-tyaaa-aaaaa-aaaba-cai" or "aaaaa-aa", got "ledger"'
|
|
1260
|
+
)
|
|
1261
|
+
)
|
|
1262
|
+
expect(() => dropNextReply(3 as never)).toThrow(
|
|
1263
|
+
new TypeError(
|
|
1264
|
+
"dropNextReply() takes { method, canister, times }, got number."
|
|
1265
|
+
)
|
|
1266
|
+
)
|
|
1267
|
+
// The published form is checked as it was.
|
|
1268
|
+
expect(() => refuseNext(429, 0)).toThrow(
|
|
1269
|
+
new RangeError("refuseNext: `times` is 0, not a count of at least 1")
|
|
1270
|
+
)
|
|
1271
|
+
})
|
|
1272
|
+
})
|