@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,924 @@
|
|
|
1
|
+
import { principal, type Principal } from "@meantjs/schema"
|
|
2
|
+
import { isReactorError } from "@ic-reactor/core"
|
|
3
|
+
import { MutationObserver, type QueryKey } from "@tanstack/react-query"
|
|
4
|
+
import {
|
|
5
|
+
actor as icrc1Actor,
|
|
6
|
+
type Actor as Icrc1Actor,
|
|
7
|
+
type TransferArg,
|
|
8
|
+
type TransferError,
|
|
9
|
+
} from "../canisters/icrc1.ts"
|
|
10
|
+
import { nowNanos } from "../forms/convert.ts"
|
|
11
|
+
import type { DynActor } from "../lib/reactor.ts"
|
|
12
|
+
import {
|
|
13
|
+
callUntyped,
|
|
14
|
+
clip,
|
|
15
|
+
describeFailure,
|
|
16
|
+
failureOf,
|
|
17
|
+
fieldsOf,
|
|
18
|
+
issuePaths,
|
|
19
|
+
logged,
|
|
20
|
+
present,
|
|
21
|
+
runInLab,
|
|
22
|
+
sent,
|
|
23
|
+
settle,
|
|
24
|
+
show,
|
|
25
|
+
valueOf,
|
|
26
|
+
type LoggedRequest,
|
|
27
|
+
type Recorder,
|
|
28
|
+
type Scenario,
|
|
29
|
+
type ScenarioReply,
|
|
30
|
+
} from "./lab-run.ts"
|
|
31
|
+
import {
|
|
32
|
+
createLab,
|
|
33
|
+
LAB_FEE,
|
|
34
|
+
LAB_INDEX_ID,
|
|
35
|
+
LAB_LEDGER_ID,
|
|
36
|
+
labIndexActor,
|
|
37
|
+
type Lab,
|
|
38
|
+
type LabIndexActor,
|
|
39
|
+
} from "./mock-ledger.ts"
|
|
40
|
+
|
|
41
|
+
export type {
|
|
42
|
+
LabeledError,
|
|
43
|
+
LoggedRequest,
|
|
44
|
+
Scenario,
|
|
45
|
+
ScenarioReply,
|
|
46
|
+
ScenarioResult,
|
|
47
|
+
ScenarioStep,
|
|
48
|
+
ScenarioVerdict,
|
|
49
|
+
} from "./lab-run.ts"
|
|
50
|
+
|
|
51
|
+
// The ten scenarios of the Safety lab. Each runs against a fresh
|
|
52
|
+
// createTestClient() (mock-ledger.ts) and states, as checks, what ic-reactor
|
|
53
|
+
// must do; lab-run.ts turns any check that does not hold into a failed
|
|
54
|
+
// verdict.
|
|
55
|
+
|
|
56
|
+
/** The setup every scenario starts from (shown once, above the cards). */
|
|
57
|
+
export const LAB_SETUP = `import { createTestClient } from "@ic-reactor/core/testing"
|
|
58
|
+
import { MutationObserver } from "@tanstack/react-query"
|
|
59
|
+
import { actor, type Actor, type TransferArg } from "./canisters/icrc1"
|
|
60
|
+
|
|
61
|
+
// LEDGER: the ckBTC ledger's id, "mxzaz-hqaaa-aaaar-qaada-cai" (mocked here).
|
|
62
|
+
// seed1, seed2: the principals of the test identities of seeds 1 and 2.
|
|
63
|
+
const { client, auth, mock, reject, dropNextReply, refuseNext, requests } =
|
|
64
|
+
createTestClient({ network: "ic" }) // signed in as the identity of seed 1
|
|
65
|
+
// Balances (seed 1 holds 1 ckBTC), fee 10, created_at_time deduplication:
|
|
66
|
+
mock<Actor>(actor, LEDGER, ledgerHandlers)
|
|
67
|
+
const ledger = client.canister<Actor>(actor, { id: LEDGER })
|
|
68
|
+
// What useMutation runs, without React:
|
|
69
|
+
const transfer = new MutationObserver(
|
|
70
|
+
client.queryClient,
|
|
71
|
+
client.mutationOptions(ledger, "icrc1_transfer"),
|
|
72
|
+
)
|
|
73
|
+
const arg = { to: { owner: seed2, subaccount: null }, amount: 25_000n,
|
|
74
|
+
fee: null, memo: null, from_subaccount: null,
|
|
75
|
+
created_at_time: BigInt(Date.now()) * 1_000_000n }
|
|
76
|
+
const dedupe = { dedupedBy: (a: TransferArg) => a.created_at_time }`
|
|
77
|
+
|
|
78
|
+
/** What the identity of seed 1 holds when a scenario starts: 1 ckBTC. */
|
|
79
|
+
export const START_BALANCE = 100_000_000n
|
|
80
|
+
|
|
81
|
+
/** Who the transfers pay: the identity of seed 2. */
|
|
82
|
+
const SEED2 = principal(
|
|
83
|
+
"xledz-fktfc-4ywwn-gai5u-ieqce-5x4qc-7lpel-o4ubn-ucngz-7lrzo-5ae"
|
|
84
|
+
)
|
|
85
|
+
const ANONYMOUS = "2vxsx-fae"
|
|
86
|
+
|
|
87
|
+
/** The typed Err payload of a transfer's `canister_err`. */
|
|
88
|
+
function transferErr(lab: Lab, error: unknown): TransferError | undefined {
|
|
89
|
+
return isReactorError(error, lab.ledger, "icrc1_transfer") &&
|
|
90
|
+
error.kind === "canister_err"
|
|
91
|
+
? error.err
|
|
92
|
+
: undefined
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const transferArg = (overrides: Partial<TransferArg> = {}): TransferArg => ({
|
|
96
|
+
to: { owner: SEED2, subaccount: null },
|
|
97
|
+
amount: 25_000n,
|
|
98
|
+
fee: null,
|
|
99
|
+
memo: null,
|
|
100
|
+
from_subaccount: null,
|
|
101
|
+
created_at_time: nowNanos(),
|
|
102
|
+
...overrides,
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
const accountOf = (owner: Principal) => ({ owner, subaccount: null })
|
|
106
|
+
|
|
107
|
+
/** The caller as a checked principal (the test auth's text is always principal text). */
|
|
108
|
+
const callerOf = (lab: Lab): Principal => principal(lab.client.caller())
|
|
109
|
+
|
|
110
|
+
/** What `useMutation(client.mutationOptions(ledger, "icrc1_transfer"))` runs, without React. */
|
|
111
|
+
function transferObserver(lab: Lab) {
|
|
112
|
+
return new MutationObserver(
|
|
113
|
+
lab.client.queryClient,
|
|
114
|
+
lab.client.mutationOptions(lab.ledger, "icrc1_transfer")
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const dedupedBy = { dedupedBy: (arg: TransferArg) => arg.created_at_time }
|
|
119
|
+
|
|
120
|
+
/** The caller's balance, read back as a direct query. */
|
|
121
|
+
async function readBack(
|
|
122
|
+
lab: Lab,
|
|
123
|
+
r: Recorder,
|
|
124
|
+
expected: bigint
|
|
125
|
+
): Promise<ScenarioReply | undefined> {
|
|
126
|
+
const balance = valueOf(
|
|
127
|
+
r,
|
|
128
|
+
"Read the balance back (icrc1_balance_of)",
|
|
129
|
+
await settle(() => lab.ledger.icrc1_balance_of(accountOf(callerOf(lab))))
|
|
130
|
+
)
|
|
131
|
+
if (balance === undefined) return undefined
|
|
132
|
+
r.same("The balance is what one execution leaves", balance, expected)
|
|
133
|
+
return { method: "icrc1_balance_of", value: balance, tokens: true }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
// The scenarios
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
const anonymousWrite: Scenario = {
|
|
141
|
+
id: "anonymous-write",
|
|
142
|
+
title: "An anonymous write is refused before it is sent",
|
|
143
|
+
rule: "A write is sent as whoever is signed in when it runs. If nobody is, the client refuses it (unauthenticated) before calling anything.",
|
|
144
|
+
narrative:
|
|
145
|
+
"Nobody is signed in, and the page runs a transfer through the same mutation options useMutation takes. The client looks up the caller when the mutation runs, finds the anonymous principal and refuses: nothing is signed, nothing reaches the replica, and the error says for certain that nothing ran.",
|
|
146
|
+
expected:
|
|
147
|
+
"unauthenticated / anonymous_write, mayHaveExecuted false, 0 requests.",
|
|
148
|
+
code: `createTestClient({ network: "ic", signedIn: false })
|
|
149
|
+
client.caller() // "2vxsx-fae"
|
|
150
|
+
|
|
151
|
+
await transfer.mutate(arg)
|
|
152
|
+
// ReactorError: kind "unauthenticated", code "anonymous_write",
|
|
153
|
+
// mayHaveExecuted false
|
|
154
|
+
requests.length // 0`,
|
|
155
|
+
run: () =>
|
|
156
|
+
runInLab({ signedIn: false }, async (lab, r) => {
|
|
157
|
+
r.same("Nobody is signed in", lab.client.caller(), ANONYMOUS)
|
|
158
|
+
const error = failureOf(
|
|
159
|
+
r,
|
|
160
|
+
"transfer.mutate(arg) is refused",
|
|
161
|
+
await settle(() => transferObserver(lab).mutate(transferArg()))
|
|
162
|
+
)
|
|
163
|
+
r.same("kind", fieldsOf(error)?.kind, "unauthenticated")
|
|
164
|
+
r.same("code", fieldsOf(error)?.code, "anonymous_write")
|
|
165
|
+
r.same("mayHaveExecuted", fieldsOf(error)?.mayHaveExecuted, false)
|
|
166
|
+
r.same("Requests the replica received", lab.kit.requests.length, 0)
|
|
167
|
+
r.same("Transfers the ledger ran", lab.mock.executed, 0)
|
|
168
|
+
return { error }
|
|
169
|
+
}),
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** A transfer argument that does not encode: a principal that is not one, a negative nat. */
|
|
173
|
+
const BAD_TRANSFER: Readonly<Record<string, unknown>> = {
|
|
174
|
+
to: { owner: "alice", subaccount: null },
|
|
175
|
+
amount: -5n,
|
|
176
|
+
fee: null,
|
|
177
|
+
memo: null,
|
|
178
|
+
from_subaccount: null,
|
|
179
|
+
created_at_time: null,
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const invalidArgs: Scenario = {
|
|
183
|
+
id: "invalid-args",
|
|
184
|
+
title: "Arguments that do not encode never leave the page",
|
|
185
|
+
rule: "Arguments that do not encode make the call reject invalid_args, with the codec's issues; a render never throws on half-typed input.",
|
|
186
|
+
narrative:
|
|
187
|
+
"A transfer whose owner is not principal text and whose amount is a negative nat is sent through an untyped view of the ledger, which is what a form over a runtime interface holds. The codec refuses it before anything is signed. Its issue names the field, rooted at $args[0] (the codec stops at the first field that fails, so fixing the owner moves the issue to the amount), and the form below puts each issue on its field with the same paths. A read with the same mistake gets a $invalid key instead of throwing during render.",
|
|
188
|
+
expected:
|
|
189
|
+
"invalid_args at $args[0].to.owner, then at $args[0].amount once the owner is fixed; 0 requests.",
|
|
190
|
+
code: `const untyped = client.canister<DynActor>(actor, { id: LEDGER })
|
|
191
|
+
await untyped.icrc1_transfer({
|
|
192
|
+
to: { owner: "alice", subaccount: null }, // not principal text
|
|
193
|
+
amount: -5n, // a nat is never negative
|
|
194
|
+
fee: null, memo: null, from_subaccount: null, created_at_time: null,
|
|
195
|
+
})
|
|
196
|
+
// ReactorError: kind "invalid_args", issues: [{ path: "$args[0].to.owner", … }]
|
|
197
|
+
// with the owner fixed, the issue moves to "$args[0].amount"
|
|
198
|
+
|
|
199
|
+
client.queryKey(untyped, "icrc1_balance_of", { owner: "alice", subaccount: null })
|
|
200
|
+
// [..., "$invalid", …]: the read rejects invalid_args when it runs`,
|
|
201
|
+
run: () =>
|
|
202
|
+
runInLab({ fund: START_BALANCE }, async (lab, r) => {
|
|
203
|
+
// The same canister object, typed for values that are not checked yet.
|
|
204
|
+
const untyped = lab.client.canister<DynActor>(icrc1Actor, {
|
|
205
|
+
id: LAB_LEDGER_ID,
|
|
206
|
+
})
|
|
207
|
+
r.same(
|
|
208
|
+
"The untyped view is the same canister object",
|
|
209
|
+
Object.is(untyped, lab.ledger),
|
|
210
|
+
true
|
|
211
|
+
)
|
|
212
|
+
const error = failureOf(
|
|
213
|
+
r,
|
|
214
|
+
"icrc1_transfer(bad argument) is refused",
|
|
215
|
+
await settle(() => callUntyped(untyped, "icrc1_transfer", BAD_TRANSFER))
|
|
216
|
+
)
|
|
217
|
+
r.same("kind", fieldsOf(error)?.kind, "invalid_args")
|
|
218
|
+
r.same("mayHaveExecuted", fieldsOf(error)?.mayHaveExecuted, false)
|
|
219
|
+
const paths = issuePaths(error)
|
|
220
|
+
r.check(
|
|
221
|
+
"Its issues are rooted at $args[0]",
|
|
222
|
+
paths.length > 0 && paths.every((p) => p.startsWith("$args[0]")),
|
|
223
|
+
show(paths)
|
|
224
|
+
)
|
|
225
|
+
r.check(
|
|
226
|
+
"The issue is on $args[0].to.owner, the first field that fails",
|
|
227
|
+
paths.includes("$args[0].to.owner"),
|
|
228
|
+
show(paths)
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
// The codec stops at the first field that fails: fix it and the next
|
|
232
|
+
// one is named.
|
|
233
|
+
const ownerFixed = {
|
|
234
|
+
...BAD_TRANSFER,
|
|
235
|
+
to: { owner: SEED2, subaccount: null },
|
|
236
|
+
}
|
|
237
|
+
const next = failureOf(
|
|
238
|
+
r,
|
|
239
|
+
"With the owner fixed, it is refused again",
|
|
240
|
+
await settle(() => callUntyped(untyped, "icrc1_transfer", ownerFixed))
|
|
241
|
+
)
|
|
242
|
+
r.check(
|
|
243
|
+
"Now the issue is on $args[0].amount",
|
|
244
|
+
issuePaths(next).includes("$args[0].amount"),
|
|
245
|
+
show(issuePaths(next))
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
const badAccount = { owner: "alice", subaccount: null }
|
|
249
|
+
const key = await settle(() =>
|
|
250
|
+
lab.client.queryKey(untyped, "icrc1_balance_of", badAccount)
|
|
251
|
+
)
|
|
252
|
+
r.check(
|
|
253
|
+
"A read of the same mistake builds a $invalid key without throwing",
|
|
254
|
+
key.ok && key.value.includes("$invalid"),
|
|
255
|
+
key.ok ? show(key.value.slice(4)) : describeFailure(key.error)
|
|
256
|
+
)
|
|
257
|
+
const read = await settle(() =>
|
|
258
|
+
lab.client.queryClient.fetchQuery(
|
|
259
|
+
lab.client.queryOptions(untyped, "icrc1_balance_of", badAccount)
|
|
260
|
+
)
|
|
261
|
+
)
|
|
262
|
+
const readError = failureOf(r, "Running that read is refused", read)
|
|
263
|
+
r.same("The read's kind", fieldsOf(readError)?.kind, "invalid_args")
|
|
264
|
+
r.same("Requests the replica received", lab.kit.requests.length, 0)
|
|
265
|
+
return {
|
|
266
|
+
error,
|
|
267
|
+
form: { method: "icrc1_transfer", values: [BAD_TRANSFER] },
|
|
268
|
+
}
|
|
269
|
+
}),
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const lostReply: Scenario = {
|
|
273
|
+
id: "lost-reply",
|
|
274
|
+
title: "A lost reply: re-send safely, and it runs once",
|
|
275
|
+
rule: "Send the same write again after mayHaveExecuted: true only to a method that deduplicates, such as an ICRC-1 transfer with created_at_time set, and only through resendOf.",
|
|
276
|
+
narrative:
|
|
277
|
+
"The ledger runs the transfer, but its reply is lost on the way back. The client cannot know whether it ran, so it says outcome_unknown and mayHaveExecuted: true, and retries nothing. Because the argument carries created_at_time, resendOf offers to send the very same bytes again, as the same sender. The ledger recognises the transaction and answers Duplicate: it executed exactly once, and the balance read back agrees.",
|
|
278
|
+
expected:
|
|
279
|
+
"outcome_unknown (mayHaveExecuted true), resendOf offered, send() → canister_err Duplicate, 1 execution.",
|
|
280
|
+
code: `dropNextReply()
|
|
281
|
+
await transfer.mutate(arg)
|
|
282
|
+
// ReactorError: kind "outcome_unknown", mayHaveExecuted true
|
|
283
|
+
|
|
284
|
+
const error = transfer.getCurrentResult().error
|
|
285
|
+
const again = client.resendOf(error, ledger, "icrc1_transfer", dedupe)
|
|
286
|
+
again.arg === arg // the very object that was sent
|
|
287
|
+
await again.send()
|
|
288
|
+
// ReactorError: kind "canister_err", err { tag: "Duplicate", … }`,
|
|
289
|
+
run: () =>
|
|
290
|
+
runInLab({ fund: START_BALANCE }, async (lab, r) => {
|
|
291
|
+
const arg = transferArg()
|
|
292
|
+
const observer = transferObserver(lab)
|
|
293
|
+
lab.kit.dropNextReply()
|
|
294
|
+
r.note("dropNextReply(): the ledger will run, its reply will be lost")
|
|
295
|
+
const error = failureOf(
|
|
296
|
+
r,
|
|
297
|
+
"transfer.mutate(arg) fails",
|
|
298
|
+
await settle(() => observer.mutate(arg))
|
|
299
|
+
)
|
|
300
|
+
r.check(
|
|
301
|
+
"The mutation keeps that error",
|
|
302
|
+
observer.getCurrentResult().error === error
|
|
303
|
+
)
|
|
304
|
+
r.same("kind", fieldsOf(error)?.kind, "outcome_unknown")
|
|
305
|
+
r.same("mayHaveExecuted", fieldsOf(error)?.mayHaveExecuted, true)
|
|
306
|
+
r.same("The ledger did run it", lab.mock.executed, 1)
|
|
307
|
+
r.same(
|
|
308
|
+
"The client sent it once (no blind retry)",
|
|
309
|
+
sent(lab, "icrc1_transfer").length,
|
|
310
|
+
1
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
const again = lab.client.resendOf(
|
|
314
|
+
error,
|
|
315
|
+
lab.ledger,
|
|
316
|
+
"icrc1_transfer",
|
|
317
|
+
dedupedBy
|
|
318
|
+
)
|
|
319
|
+
if (!present(r, "resendOf offers a re-send", again)) {
|
|
320
|
+
return { error }
|
|
321
|
+
}
|
|
322
|
+
r.check("It offers the very object that was sent", again.arg === arg)
|
|
323
|
+
r.same(
|
|
324
|
+
"It re-sends as the original sender",
|
|
325
|
+
again.from,
|
|
326
|
+
lab.client.caller()
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
const duplicate = failureOf(
|
|
330
|
+
r,
|
|
331
|
+
"again.send() is answered with an Err",
|
|
332
|
+
await settle(() => again.send())
|
|
333
|
+
)
|
|
334
|
+
r.same("kind", fieldsOf(duplicate)?.kind, "canister_err")
|
|
335
|
+
const err = transferErr(lab, duplicate)
|
|
336
|
+
r.same("The Err is Duplicate", err?.tag, "Duplicate")
|
|
337
|
+
if (err?.tag === "Duplicate") {
|
|
338
|
+
r.same("duplicate_of the first block", err.value.duplicate_of, 0n)
|
|
339
|
+
}
|
|
340
|
+
r.same("The ledger ran it exactly once", lab.mock.executed, 1)
|
|
341
|
+
r.same(
|
|
342
|
+
"The re-send reached the replica",
|
|
343
|
+
sent(lab, "icrc1_transfer").length,
|
|
344
|
+
2
|
|
345
|
+
)
|
|
346
|
+
const reply = await readBack(lab, r, START_BALANCE - arg.amount - LAB_FEE)
|
|
347
|
+
return {
|
|
348
|
+
error,
|
|
349
|
+
moreErrors: [{ label: "The re-send: Duplicate", error: duplicate }],
|
|
350
|
+
...(reply === undefined ? {} : { reply }),
|
|
351
|
+
}
|
|
352
|
+
}),
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const throttled: Scenario = {
|
|
356
|
+
id: "http-429",
|
|
357
|
+
title: "HTTP 429: the client re-sends once, by itself",
|
|
358
|
+
rule: "The client itself re-sends an update, at most twice, only after a failure proving the canister never got it (reject code 2, status 429); any other failure is final.",
|
|
359
|
+
narrative:
|
|
360
|
+
"The boundary node throttles the first transfer with HTTP 429 before any canister sees it. That status proves the update never got in, so the client waits briefly and sends the same request again on its own: the mutation still has retry: false, and the caller sees one success. The log shows two calls, the first refused.",
|
|
361
|
+
expected:
|
|
362
|
+
"Ok (block index) after 2 icrc1_transfer calls, the first refused 429.",
|
|
363
|
+
code: `refuseNext(429, { method: "icrc1_transfer" })
|
|
364
|
+
await transfer.mutate({ ...arg, created_at_time: null }) // 0n: the block index
|
|
365
|
+
requests.filter((q) => q.methodName === "icrc1_transfer")
|
|
366
|
+
// [{ refused: "refuseNext(429) answered …" }, { caller: "psith-…" }]`,
|
|
367
|
+
run: () =>
|
|
368
|
+
runInLab({ fund: START_BALANCE }, async (lab, r) => {
|
|
369
|
+
lab.kit.refuseNext(429, { method: "icrc1_transfer" })
|
|
370
|
+
r.note('refuseNext(429, { method: "icrc1_transfer" })')
|
|
371
|
+
const options = lab.client.mutationOptions(lab.ledger, "icrc1_transfer")
|
|
372
|
+
r.same("The mutation options never retry", options.retry, false)
|
|
373
|
+
const block = valueOf(
|
|
374
|
+
r,
|
|
375
|
+
"transfer.mutate(arg) succeeds",
|
|
376
|
+
await settle(() =>
|
|
377
|
+
new MutationObserver(lab.client.queryClient, options).mutate(
|
|
378
|
+
transferArg({ created_at_time: null })
|
|
379
|
+
)
|
|
380
|
+
)
|
|
381
|
+
)
|
|
382
|
+
const calls = sent(lab, "icrc1_transfer")
|
|
383
|
+
r.same("icrc1_transfer calls the replica received", calls.length, 2)
|
|
384
|
+
r.check(
|
|
385
|
+
"The first was refused with 429",
|
|
386
|
+
calls[0]?.refused?.includes("429") === true,
|
|
387
|
+
calls[0]?.refused
|
|
388
|
+
)
|
|
389
|
+
r.same("The second went through", calls[1]?.refused, undefined)
|
|
390
|
+
r.same("The ledger ran it once", lab.mock.executed, 1)
|
|
391
|
+
return block === undefined
|
|
392
|
+
? {}
|
|
393
|
+
: { reply: { method: "icrc1_transfer", value: block, tokens: false } }
|
|
394
|
+
}),
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const trapped: Scenario = {
|
|
398
|
+
id: "reject-5",
|
|
399
|
+
title: "A trap (reject 5) may have executed: no blind retry",
|
|
400
|
+
rule: "Read mayHaveExecuted on every failed write. A method with no deduplication key has no safe re-send: read the state back.",
|
|
401
|
+
narrative:
|
|
402
|
+
"The ledger traps while handling the transfer. A canister can commit work before it traps (an inter-canister call made earlier commits its side), so ic-reactor cannot promise nothing happened: kind rejected, reject code 5, mayHaveExecuted true. It sends nothing again by itself, and since this argument has no created_at_time, resendOf offers nothing either: the right move is to read the state back. Here the trap rolled the transfer back, so the balance is unchanged.",
|
|
403
|
+
expected:
|
|
404
|
+
"rejected, rejectCode 5, mayHaveExecuted true, 1 call, resendOf undefined.",
|
|
405
|
+
code: `mock<Actor>(actor, LEDGER, {
|
|
406
|
+
...ledgerHandlers,
|
|
407
|
+
icrc1_transfer: () => { throw new Error("assertion failed: …") }, // a trap
|
|
408
|
+
})
|
|
409
|
+
await transfer.mutate({ ...arg, created_at_time: null })
|
|
410
|
+
// ReactorError: kind "rejected", rejectCode 5, mayHaveExecuted true
|
|
411
|
+
|
|
412
|
+
client.resendOf(error, ledger, "icrc1_transfer", dedupe)
|
|
413
|
+
// undefined: nothing to deduplicate on, so read the state back:
|
|
414
|
+
await ledger.icrc1_balance_of({ owner: seed1, subaccount: null })`,
|
|
415
|
+
run: () =>
|
|
416
|
+
runInLab({ fund: START_BALANCE }, async (lab, r) => {
|
|
417
|
+
lab.mock.failNextTransfer({
|
|
418
|
+
kind: "trap",
|
|
419
|
+
message: "assertion failed: the memo is longer than 32 bytes",
|
|
420
|
+
})
|
|
421
|
+
r.note("The ledger's next transfer traps")
|
|
422
|
+
const error = failureOf(
|
|
423
|
+
r,
|
|
424
|
+
"transfer.mutate(arg) fails",
|
|
425
|
+
await settle(() =>
|
|
426
|
+
transferObserver(lab).mutate(transferArg({ created_at_time: null }))
|
|
427
|
+
)
|
|
428
|
+
)
|
|
429
|
+
r.same("kind", fieldsOf(error)?.kind, "rejected")
|
|
430
|
+
r.same("rejectCode", fieldsOf(error)?.rejectCode, 5)
|
|
431
|
+
r.same("mayHaveExecuted", fieldsOf(error)?.mayHaveExecuted, true)
|
|
432
|
+
r.same(
|
|
433
|
+
"It was sent once (no blind retry)",
|
|
434
|
+
sent(lab, "icrc1_transfer").length,
|
|
435
|
+
1
|
|
436
|
+
)
|
|
437
|
+
r.same(
|
|
438
|
+
"resendOf offers nothing without a created_at_time",
|
|
439
|
+
lab.client.resendOf(error, lab.ledger, "icrc1_transfer", dedupedBy),
|
|
440
|
+
undefined
|
|
441
|
+
)
|
|
442
|
+
const reply = await readBack(lab, r, START_BALANCE)
|
|
443
|
+
return { error, ...(reply === undefined ? {} : { reply }) }
|
|
444
|
+
}),
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const destinationInvalid: Scenario = {
|
|
448
|
+
id: "reject-3",
|
|
449
|
+
title: "Reject 3: certainly not executed",
|
|
450
|
+
rule: "A reject 1 or 3 proves nothing ran: mayHaveExecuted is false, nothing is invalidated, and no re-send is offered.",
|
|
451
|
+
narrative:
|
|
452
|
+
"The replica rejects the transfer with code 3 (destination invalid), the reject a call gets when the canister cannot take it at all. That proves the canister never ran it, so mayHaveExecuted is false. Even though the argument carries created_at_time, resendOf offers nothing: there is no unknown outcome to resolve, and a fresh attempt is simply a new write.",
|
|
453
|
+
expected:
|
|
454
|
+
"rejected, rejectCode 3, mayHaveExecuted false, resendOf undefined, balance unchanged.",
|
|
455
|
+
code: `mock<Actor>(actor, LEDGER, {
|
|
456
|
+
...ledgerHandlers,
|
|
457
|
+
icrc1_transfer: () => reject(3, "Requested canister has no wasm module"),
|
|
458
|
+
})
|
|
459
|
+
await transfer.mutate(arg) // arg has created_at_time
|
|
460
|
+
// ReactorError: kind "rejected", rejectCode 3, mayHaveExecuted false
|
|
461
|
+
client.resendOf(error, ledger, "icrc1_transfer", dedupe)
|
|
462
|
+
// undefined: the outcome is known, nothing to resolve`,
|
|
463
|
+
run: () =>
|
|
464
|
+
runInLab({ fund: START_BALANCE }, async (lab, r) => {
|
|
465
|
+
lab.mock.failNextTransfer({
|
|
466
|
+
kind: "reject",
|
|
467
|
+
code: 3,
|
|
468
|
+
message: "Requested canister has no wasm module",
|
|
469
|
+
})
|
|
470
|
+
r.note("The ledger's next transfer is rejected with code 3")
|
|
471
|
+
const error = failureOf(
|
|
472
|
+
r,
|
|
473
|
+
"transfer.mutate(arg) fails",
|
|
474
|
+
await settle(() => transferObserver(lab).mutate(transferArg()))
|
|
475
|
+
)
|
|
476
|
+
r.same("kind", fieldsOf(error)?.kind, "rejected")
|
|
477
|
+
r.same("rejectCode", fieldsOf(error)?.rejectCode, 3)
|
|
478
|
+
r.same("mayHaveExecuted", fieldsOf(error)?.mayHaveExecuted, false)
|
|
479
|
+
r.same(
|
|
480
|
+
"resendOf offers nothing: the outcome is known",
|
|
481
|
+
lab.client.resendOf(error, lab.ledger, "icrc1_transfer", dedupedBy),
|
|
482
|
+
undefined
|
|
483
|
+
)
|
|
484
|
+
r.same("It was sent once", sent(lab, "icrc1_transfer").length, 1)
|
|
485
|
+
r.same("Transfers the ledger ran", lab.mock.executed, 0)
|
|
486
|
+
const reply = await readBack(lab, r, START_BALANCE)
|
|
487
|
+
return { error, ...(reply === undefined ? {} : { reply }) }
|
|
488
|
+
}),
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const canisterErr: Scenario = {
|
|
492
|
+
id: "canister-err",
|
|
493
|
+
title: "An Err result is a typed canister_err",
|
|
494
|
+
rule: "A method whose result is variant { Ok; Err } resolves with the Ok value; Err rejects as kind canister_err, with the Err value in err.",
|
|
495
|
+
narrative:
|
|
496
|
+
'The transfer asks for more than the account holds. The ledger answers Err InsufficientFunds, a reply, not a failure of the call: ic-reactor rejects with kind canister_err, mayHaveExecuted false, and the Err payload in err. Narrowed with isReactorError(error, ledger, "icrc1_transfer"), err is typed as the ledger\'s TransferError, so the balance in it is a bigint with no cast.',
|
|
497
|
+
expected:
|
|
498
|
+
'canister_err, mayHaveExecuted false, err { tag: "InsufficientFunds", value: { balance } }.',
|
|
499
|
+
code: `try {
|
|
500
|
+
await ledger.icrc1_transfer({ ...arg, amount: 2_000_000_000n })
|
|
501
|
+
} catch (error) {
|
|
502
|
+
if (isReactorError(error, ledger, "icrc1_transfer") &&
|
|
503
|
+
error.kind === "canister_err" &&
|
|
504
|
+
error.err.tag === "InsufficientFunds") {
|
|
505
|
+
error.err.value.balance // bigint, typed from the .did
|
|
506
|
+
}
|
|
507
|
+
}`,
|
|
508
|
+
run: () =>
|
|
509
|
+
runInLab({ fund: START_BALANCE }, async (lab, r) => {
|
|
510
|
+
const error = failureOf(
|
|
511
|
+
r,
|
|
512
|
+
"icrc1_transfer(20 ckBTC) fails",
|
|
513
|
+
await settle(() =>
|
|
514
|
+
lab.ledger.icrc1_transfer(transferArg({ amount: 2_000_000_000n }))
|
|
515
|
+
)
|
|
516
|
+
)
|
|
517
|
+
if (isReactorError(error, lab.ledger, "icrc1_transfer")) {
|
|
518
|
+
r.same("kind", error.kind, "canister_err")
|
|
519
|
+
r.same("mayHaveExecuted", error.mayHaveExecuted, false)
|
|
520
|
+
const err = error.kind === "canister_err" ? error.err : undefined
|
|
521
|
+
r.same("The Err is InsufficientFunds", err?.tag, "InsufficientFunds")
|
|
522
|
+
if (err?.tag === "InsufficientFunds") {
|
|
523
|
+
r.same(
|
|
524
|
+
"Its balance is the account's",
|
|
525
|
+
err.value.balance,
|
|
526
|
+
START_BALANCE
|
|
527
|
+
)
|
|
528
|
+
}
|
|
529
|
+
} else {
|
|
530
|
+
r.check(
|
|
531
|
+
'isReactorError(error, ledger, "icrc1_transfer") narrows it',
|
|
532
|
+
false,
|
|
533
|
+
describeFailure(error)
|
|
534
|
+
)
|
|
535
|
+
}
|
|
536
|
+
r.same("Transfers the ledger ran", lab.mock.executed, 0)
|
|
537
|
+
return { error }
|
|
538
|
+
}),
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const callerChanged: Scenario = {
|
|
542
|
+
id: "caller-changed",
|
|
543
|
+
title: "The caller changes: reads and re-sends refuse",
|
|
544
|
+
rule: "A read is made as the caller current when its options are built, and a re-send goes out only as its sender; for anyone else nothing is sent (caller_changed). The read is cancelled; the re-send stays outcome_unknown, since the write it re-sends may have run.",
|
|
545
|
+
narrative:
|
|
546
|
+
"Options for a balance read are built while seed 1 is signed in, then the user switches to seed 2 before the read runs: the query function refuses to send seed 1's read as seed 2. Then a transfer by seed 1 loses its reply. After a switch to seed 2, resendOf offers nothing, and the offer already in hand refuses to go out as the wrong sender. Back as seed 1, the offer returns and the re-send answers Duplicate.",
|
|
547
|
+
expected:
|
|
548
|
+
"cancelled / caller_changed for the read, outcome_unknown / caller_changed for send() as seed 2, 0 extra requests; offer restored for seed 1.",
|
|
549
|
+
code: `const account = { owner: seed2, subaccount: null }
|
|
550
|
+
const read = client.queryOptions(ledger, "icrc1_balance_of", account) // as seed 1
|
|
551
|
+
auth.switchTo(2)
|
|
552
|
+
await client.queryClient.fetchQuery(read)
|
|
553
|
+
// ReactorError: kind "cancelled", code "caller_changed"
|
|
554
|
+
|
|
555
|
+
auth.switchTo(1); dropNextReply(); await transfer.mutate(arg) // outcome_unknown
|
|
556
|
+
const again = client.resendOf(error, ledger, "icrc1_transfer", dedupe)
|
|
557
|
+
auth.switchTo(2)
|
|
558
|
+
client.resendOf(error, ledger, "icrc1_transfer", dedupe) // undefined
|
|
559
|
+
await again.send() // outcome_unknown / caller_changed, nothing sent
|
|
560
|
+
auth.switchTo(1)
|
|
561
|
+
await again.send() // canister_err Duplicate: it ran once`,
|
|
562
|
+
run: () =>
|
|
563
|
+
runInLab({ fund: START_BALANCE }, async (lab, r) => {
|
|
564
|
+
const { auth } = lab.kit
|
|
565
|
+
const seed1 = lab.client.caller()
|
|
566
|
+
const read = lab.client.queryOptions(
|
|
567
|
+
lab.ledger,
|
|
568
|
+
"icrc1_balance_of",
|
|
569
|
+
accountOf(SEED2)
|
|
570
|
+
)
|
|
571
|
+
r.same("The read's key holds seed 1", read.queryKey[2], seed1)
|
|
572
|
+
auth.switchTo(2)
|
|
573
|
+
r.note("auth.switchTo(2) before the read runs", lab.client.caller())
|
|
574
|
+
const before = lab.kit.requests.length
|
|
575
|
+
const readError = failureOf(
|
|
576
|
+
r,
|
|
577
|
+
"Running seed 1's read as seed 2 is refused",
|
|
578
|
+
await settle(() => lab.client.queryClient.fetchQuery(read))
|
|
579
|
+
)
|
|
580
|
+
r.same("kind", fieldsOf(readError)?.kind, "cancelled")
|
|
581
|
+
r.same("code", fieldsOf(readError)?.code, "caller_changed")
|
|
582
|
+
r.same("Nothing was sent", lab.kit.requests.length, before)
|
|
583
|
+
|
|
584
|
+
auth.switchTo(1)
|
|
585
|
+
r.note("auth.switchTo(1); dropNextReply(); transfer.mutate(arg)")
|
|
586
|
+
const arg = transferArg()
|
|
587
|
+
lab.kit.dropNextReply()
|
|
588
|
+
const lost = failureOf(
|
|
589
|
+
r,
|
|
590
|
+
"The transfer's reply is lost",
|
|
591
|
+
await settle(() => transferObserver(lab).mutate(arg))
|
|
592
|
+
)
|
|
593
|
+
const again = lab.client.resendOf(
|
|
594
|
+
lost,
|
|
595
|
+
lab.ledger,
|
|
596
|
+
"icrc1_transfer",
|
|
597
|
+
dedupedBy
|
|
598
|
+
)
|
|
599
|
+
if (!present(r, "resendOf offers a re-send to seed 1", again)) {
|
|
600
|
+
return { error: readError }
|
|
601
|
+
}
|
|
602
|
+
auth.switchTo(2)
|
|
603
|
+
r.note("auth.switchTo(2)")
|
|
604
|
+
r.same(
|
|
605
|
+
"resendOf offers nothing to seed 2",
|
|
606
|
+
lab.client.resendOf(lost, lab.ledger, "icrc1_transfer", dedupedBy),
|
|
607
|
+
undefined
|
|
608
|
+
)
|
|
609
|
+
const calls = sent(lab, "icrc1_transfer").length
|
|
610
|
+
const refused = failureOf(
|
|
611
|
+
r,
|
|
612
|
+
"again.send() as seed 2 is refused",
|
|
613
|
+
await settle(() => again.send())
|
|
614
|
+
)
|
|
615
|
+
// The write it re-sends may have run: its outcome stays unknown.
|
|
616
|
+
r.same("kind", fieldsOf(refused)?.kind, "outcome_unknown")
|
|
617
|
+
r.same("mayHaveExecuted", fieldsOf(refused)?.mayHaveExecuted, true)
|
|
618
|
+
r.same("code", fieldsOf(refused)?.code, "caller_changed")
|
|
619
|
+
r.same("Nothing was sent", sent(lab, "icrc1_transfer").length, calls)
|
|
620
|
+
|
|
621
|
+
auth.switchTo(1)
|
|
622
|
+
r.note("auth.switchTo(1)")
|
|
623
|
+
r.check(
|
|
624
|
+
"The offer is back for its sender",
|
|
625
|
+
lab.client.resendOf(lost, lab.ledger, "icrc1_transfer", dedupedBy) !==
|
|
626
|
+
undefined
|
|
627
|
+
)
|
|
628
|
+
const duplicate = failureOf(
|
|
629
|
+
r,
|
|
630
|
+
"again.send() as seed 1 is answered",
|
|
631
|
+
await settle(() => again.send())
|
|
632
|
+
)
|
|
633
|
+
r.same(
|
|
634
|
+
"The answer is Duplicate",
|
|
635
|
+
transferErr(lab, duplicate)?.tag,
|
|
636
|
+
"Duplicate"
|
|
637
|
+
)
|
|
638
|
+
r.same("The ledger ran it exactly once", lab.mock.executed, 1)
|
|
639
|
+
return {
|
|
640
|
+
error: readError,
|
|
641
|
+
moreErrors: [
|
|
642
|
+
{ label: "The re-send as seed 2", error: refused, afterResend: true },
|
|
643
|
+
],
|
|
644
|
+
}
|
|
645
|
+
}),
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/** The key segments after the caller's: what two callers' keys of one read share. */
|
|
649
|
+
const afterCaller = (key: QueryKey): string => JSON.stringify(key.slice(3))
|
|
650
|
+
|
|
651
|
+
const cacheIsolation: Scenario = {
|
|
652
|
+
id: "cache-isolation",
|
|
653
|
+
title: "One read, two principals, two keys",
|
|
654
|
+
rule: "Keys are [\"ic-reactor\", network, caller, canisterId, method, args]: one principal's cached answer is never served to another; after a sign-in, sign-out or switch, the new caller's key is empty until it loads.",
|
|
655
|
+
narrative:
|
|
656
|
+
"The same balance read, with the same argument, is made by seed 1 and then by seed 2. Their keys differ only in the caller segment, so each principal gets its own cache entry, and seed 2 starts with nothing, whatever the cache holds for seed 1. After a sign-out the anonymous key is empty too: nothing cached for a principal is shown to anyone else.",
|
|
657
|
+
expected:
|
|
658
|
+
"Two keys that differ only at the caller; seed 2's and the anonymous key start empty.",
|
|
659
|
+
code: `const account = { owner: seed2, subaccount: null }
|
|
660
|
+
await client.queryClient.fetchQuery(
|
|
661
|
+
client.queryOptions(ledger, "icrc1_balance_of", account)) // as seed 1
|
|
662
|
+
auth.switchTo(2)
|
|
663
|
+
client.queryClient.getQueryData(
|
|
664
|
+
client.queryKey(ledger, "icrc1_balance_of", account)) // undefined
|
|
665
|
+
await client.queryClient.fetchQuery(
|
|
666
|
+
client.queryOptions(ledger, "icrc1_balance_of", account)) // as seed 2
|
|
667
|
+
await auth.signOut()
|
|
668
|
+
client.queryClient.getQueryData(
|
|
669
|
+
client.queryKey(ledger, "icrc1_balance_of", account)) // undefined`,
|
|
670
|
+
run: () =>
|
|
671
|
+
runInLab({ fund: START_BALANCE }, async (lab, r) => {
|
|
672
|
+
const { auth } = lab.kit
|
|
673
|
+
const { queryClient } = lab.client
|
|
674
|
+
lab.mock.fund(SEED2, 5_000n)
|
|
675
|
+
const account = accountOf(SEED2)
|
|
676
|
+
const keyNow = (): QueryKey =>
|
|
677
|
+
lab.client.queryKey(lab.ledger, "icrc1_balance_of", account)
|
|
678
|
+
|
|
679
|
+
const first = lab.client.queryOptions(
|
|
680
|
+
lab.ledger,
|
|
681
|
+
"icrc1_balance_of",
|
|
682
|
+
account
|
|
683
|
+
)
|
|
684
|
+
const value1 = valueOf(
|
|
685
|
+
r,
|
|
686
|
+
"Seed 1 reads the balance",
|
|
687
|
+
await settle(() => queryClient.fetchQuery(first))
|
|
688
|
+
)
|
|
689
|
+
const key1 = first.queryKey
|
|
690
|
+
r.same("Its key holds seed 1", key1[2], lab.client.caller())
|
|
691
|
+
|
|
692
|
+
auth.switchTo(2)
|
|
693
|
+
r.note("auth.switchTo(2)", lab.client.caller())
|
|
694
|
+
r.same(
|
|
695
|
+
"Seed 2's key starts empty",
|
|
696
|
+
queryClient.getQueryData(keyNow()),
|
|
697
|
+
undefined
|
|
698
|
+
)
|
|
699
|
+
const second = lab.client.queryOptions(
|
|
700
|
+
lab.ledger,
|
|
701
|
+
"icrc1_balance_of",
|
|
702
|
+
account
|
|
703
|
+
)
|
|
704
|
+
const value2 = valueOf(
|
|
705
|
+
r,
|
|
706
|
+
"Seed 2 reads the same balance",
|
|
707
|
+
await settle(() => queryClient.fetchQuery(second))
|
|
708
|
+
)
|
|
709
|
+
const key2 = second.queryKey
|
|
710
|
+
r.same("Same answer for both", value2, value1)
|
|
711
|
+
r.check(
|
|
712
|
+
"The keys differ",
|
|
713
|
+
JSON.stringify(key1) !== JSON.stringify(key2),
|
|
714
|
+
`${show(key1[2])} vs ${show(key2[2])}`
|
|
715
|
+
)
|
|
716
|
+
r.check(
|
|
717
|
+
"Only in the caller segment",
|
|
718
|
+
JSON.stringify(key1.slice(0, 2)) === JSON.stringify(key2.slice(0, 2)) &&
|
|
719
|
+
afterCaller(key1) === afterCaller(key2)
|
|
720
|
+
)
|
|
721
|
+
|
|
722
|
+
await auth.signOut()
|
|
723
|
+
r.note("auth.signOut()", lab.client.caller())
|
|
724
|
+
r.same(
|
|
725
|
+
"The anonymous key is empty",
|
|
726
|
+
queryClient.getQueryData(keyNow()),
|
|
727
|
+
undefined
|
|
728
|
+
)
|
|
729
|
+
r.same(
|
|
730
|
+
"Reads cached for the anonymous principal",
|
|
731
|
+
queryClient.getQueryCache().findAll({
|
|
732
|
+
queryKey: lab.client.queryKey(lab.ledger),
|
|
733
|
+
}).length,
|
|
734
|
+
0
|
|
735
|
+
)
|
|
736
|
+
return value1 === undefined
|
|
737
|
+
? {}
|
|
738
|
+
: { reply: { method: "icrc1_balance_of", value: value1, tokens: true } }
|
|
739
|
+
}),
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
const certifiedTwin: Scenario = {
|
|
743
|
+
id: "certified-twin",
|
|
744
|
+
title: "The certified twin: its own keys, no composite queries",
|
|
745
|
+
rule: 'The target { id, certified: true } certifies query replies; it is a separate canister object with keys ending in "certified", and a composite query on it is refused.',
|
|
746
|
+
narrative:
|
|
747
|
+
'The certified twin of the ledger is a different canister object. Its balance read goes out as a replicated call whose certificate the client verifies, and it caches under a key that ends in "certified", so a certified answer and an uncertified one never mix. A composite query cannot be certified: building its read options on the certified twin of the index throws a TypeError at once, while the plain index answers it.',
|
|
748
|
+
expected:
|
|
749
|
+
'Separate keys ("certified" last), query vs call in the log, TypeError for the certified composite query.',
|
|
750
|
+
code: `const account = { owner: seed1, subaccount: null }
|
|
751
|
+
const certified = client.canister<Actor>(actor, { id: LEDGER, certified: true })
|
|
752
|
+
client.queryKey(certified, "icrc1_balance_of", account).at(-1) // "certified"
|
|
753
|
+
await client.queryClient.fetchQuery(
|
|
754
|
+
client.queryOptions(certified, "icrc1_balance_of", account)) // a replicated call
|
|
755
|
+
|
|
756
|
+
// An index canister whose balance_via_ledger is a composite query:
|
|
757
|
+
const index = client.canister<Index>(indexActor, { id: INDEX, certified: true })
|
|
758
|
+
client.queryOptions(index, "balance_via_ledger", account)
|
|
759
|
+
// TypeError: a composite query cannot be certified`,
|
|
760
|
+
run: () =>
|
|
761
|
+
runInLab({ fund: START_BALANCE }, async (lab, r) => {
|
|
762
|
+
const { queryClient } = lab.client
|
|
763
|
+
const account = accountOf(callerOf(lab))
|
|
764
|
+
const certified = lab.client.canister<Icrc1Actor>(icrc1Actor, {
|
|
765
|
+
id: LAB_LEDGER_ID,
|
|
766
|
+
certified: true,
|
|
767
|
+
})
|
|
768
|
+
r.check(
|
|
769
|
+
"The certified twin is another canister object",
|
|
770
|
+
certified !== lab.ledger
|
|
771
|
+
)
|
|
772
|
+
const plainKey = lab.client.queryKey(
|
|
773
|
+
lab.ledger,
|
|
774
|
+
"icrc1_balance_of",
|
|
775
|
+
account
|
|
776
|
+
)
|
|
777
|
+
const certifiedKey = lab.client.queryKey(
|
|
778
|
+
certified,
|
|
779
|
+
"icrc1_balance_of",
|
|
780
|
+
account
|
|
781
|
+
)
|
|
782
|
+
r.same(
|
|
783
|
+
'The certified key ends in "certified"',
|
|
784
|
+
certifiedKey.at(-1),
|
|
785
|
+
"certified"
|
|
786
|
+
)
|
|
787
|
+
r.check(
|
|
788
|
+
"The plain key ends in the argument bytes",
|
|
789
|
+
plainKey.at(-1) !== "certified",
|
|
790
|
+
clip(String(plainKey.at(-1)))
|
|
791
|
+
)
|
|
792
|
+
r.check(
|
|
793
|
+
"The keys differ",
|
|
794
|
+
JSON.stringify(plainKey) !== JSON.stringify(certifiedKey)
|
|
795
|
+
)
|
|
796
|
+
|
|
797
|
+
const plain = valueOf(
|
|
798
|
+
r,
|
|
799
|
+
"The plain read",
|
|
800
|
+
await settle(() =>
|
|
801
|
+
queryClient.fetchQuery(
|
|
802
|
+
lab.client.queryOptions(lab.ledger, "icrc1_balance_of", account)
|
|
803
|
+
)
|
|
804
|
+
)
|
|
805
|
+
)
|
|
806
|
+
const cert = valueOf(
|
|
807
|
+
r,
|
|
808
|
+
"The certified read",
|
|
809
|
+
await settle(() =>
|
|
810
|
+
queryClient.fetchQuery(
|
|
811
|
+
lab.client.queryOptions(certified, "icrc1_balance_of", account)
|
|
812
|
+
)
|
|
813
|
+
)
|
|
814
|
+
)
|
|
815
|
+
r.same("Both read the same balance", cert, plain)
|
|
816
|
+
const balanceRequests = sent(lab, "icrc1_balance_of").map(
|
|
817
|
+
(q) => q.endpoint
|
|
818
|
+
)
|
|
819
|
+
r.same(
|
|
820
|
+
"The plain read went out as a query, the certified one as a call",
|
|
821
|
+
balanceRequests.join(" then "),
|
|
822
|
+
"query then call"
|
|
823
|
+
)
|
|
824
|
+
const cache = queryClient.getQueryCache()
|
|
825
|
+
const plainEntry = cache.find({ queryKey: plainKey, exact: true })
|
|
826
|
+
const certifiedEntry = cache.find({ queryKey: certifiedKey, exact: true })
|
|
827
|
+
r.check(
|
|
828
|
+
"Each twin has its own cache entry",
|
|
829
|
+
plainEntry !== undefined &&
|
|
830
|
+
certifiedEntry !== undefined &&
|
|
831
|
+
plainEntry !== certifiedEntry
|
|
832
|
+
)
|
|
833
|
+
// Keys are prefixes of each other: the canister's prefix matches the
|
|
834
|
+
// certified key too, which is how a write invalidates both twins.
|
|
835
|
+
r.same(
|
|
836
|
+
"The canister's key prefix matches both, so a write invalidates both",
|
|
837
|
+
cache.findAll({ queryKey: lab.client.queryKey(lab.ledger) }).length,
|
|
838
|
+
2
|
|
839
|
+
)
|
|
840
|
+
|
|
841
|
+
const certifiedIndex = lab.client.canister<LabIndexActor>(labIndexActor, {
|
|
842
|
+
id: LAB_INDEX_ID,
|
|
843
|
+
certified: true,
|
|
844
|
+
})
|
|
845
|
+
const refused = await settle(() =>
|
|
846
|
+
lab.client.queryOptions(certifiedIndex, "balance_via_ledger", account)
|
|
847
|
+
)
|
|
848
|
+
r.check(
|
|
849
|
+
"A composite query on the certified twin throws a TypeError",
|
|
850
|
+
!refused.ok && refused.error instanceof TypeError,
|
|
851
|
+
refused.ok ? "it built options" : describeFailure(refused.error)
|
|
852
|
+
)
|
|
853
|
+
valueOf(
|
|
854
|
+
r,
|
|
855
|
+
"The plain index answers the composite query",
|
|
856
|
+
await settle(() =>
|
|
857
|
+
queryClient.fetchQuery(
|
|
858
|
+
lab.client.queryOptions(lab.index, "balance_via_ledger", account)
|
|
859
|
+
)
|
|
860
|
+
)
|
|
861
|
+
)
|
|
862
|
+
return plain === undefined
|
|
863
|
+
? {}
|
|
864
|
+
: { reply: { method: "icrc1_balance_of", value: plain, tokens: true } }
|
|
865
|
+
}),
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
export const SCENARIOS: readonly Scenario[] = [
|
|
869
|
+
anonymousWrite,
|
|
870
|
+
invalidArgs,
|
|
871
|
+
lostReply,
|
|
872
|
+
throttled,
|
|
873
|
+
trapped,
|
|
874
|
+
destinationInvalid,
|
|
875
|
+
canisterErr,
|
|
876
|
+
callerChanged,
|
|
877
|
+
cacheIsolation,
|
|
878
|
+
certifiedTwin,
|
|
879
|
+
]
|
|
880
|
+
|
|
881
|
+
/** What a transfer sent from the lab's form did. */
|
|
882
|
+
export type LabSend =
|
|
883
|
+
| {
|
|
884
|
+
readonly ok: true
|
|
885
|
+
readonly block: bigint
|
|
886
|
+
readonly requests: readonly LoggedRequest[]
|
|
887
|
+
}
|
|
888
|
+
| {
|
|
889
|
+
readonly ok: false
|
|
890
|
+
readonly error: unknown
|
|
891
|
+
readonly requests: readonly LoggedRequest[]
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* Sends `vars` (the form's corrected transfer) as seed 1 to a fresh lab, so
|
|
896
|
+
* the invalid-arguments card can show the fixed call going through. Never
|
|
897
|
+
* rejects: a failure, the lab's own included, is the outcome.
|
|
898
|
+
*/
|
|
899
|
+
export async function sendTransferInLab(vars: unknown): Promise<LabSend> {
|
|
900
|
+
let lab: Lab | undefined
|
|
901
|
+
try {
|
|
902
|
+
lab = await createLab({ fund: START_BALANCE })
|
|
903
|
+
const untyped = lab.client.canister<DynActor>(icrc1Actor, {
|
|
904
|
+
id: LAB_LEDGER_ID,
|
|
905
|
+
})
|
|
906
|
+
const block = await callUntyped(untyped, "icrc1_transfer", vars)
|
|
907
|
+
const requests = lab.kit.requests.map(logged)
|
|
908
|
+
return typeof block === "bigint"
|
|
909
|
+
? { ok: true, block, requests }
|
|
910
|
+
: {
|
|
911
|
+
ok: false,
|
|
912
|
+
error: new TypeError("the reply is not a block index"),
|
|
913
|
+
requests,
|
|
914
|
+
}
|
|
915
|
+
} catch (error) {
|
|
916
|
+
return {
|
|
917
|
+
ok: false,
|
|
918
|
+
error,
|
|
919
|
+
requests: lab === undefined ? [] : lab.kit.requests.map(logged),
|
|
920
|
+
}
|
|
921
|
+
} finally {
|
|
922
|
+
lab?.client.dispose()
|
|
923
|
+
}
|
|
924
|
+
}
|