@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.
Files changed (1041) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.yml +108 -0
  2. package/.github/ISSUE_TEMPLATE/config.yml +8 -0
  3. package/.github/ISSUE_TEMPLATE/feature_request.yml +77 -0
  4. package/.github/actions/setup-rust-wasm/action.yml +25 -0
  5. package/.github/dependabot.yml +107 -0
  6. package/.github/workflows/auth-latest.yml +67 -0
  7. package/.github/workflows/b3pay-site.yml +49 -0
  8. package/.github/workflows/ci.yml +430 -0
  9. package/.github/workflows/codeql.yml +41 -0
  10. package/.github/workflows/dependency-review.yml +68 -0
  11. package/.github/workflows/docs-check.yml +98 -0
  12. package/.github/workflows/docs.yml +265 -0
  13. package/.github/workflows/e2e.yml +90 -0
  14. package/.github/workflows/examples-published.yml +97 -0
  15. package/.github/workflows/release.yml +326 -0
  16. package/.husky/pre-commit +12 -0
  17. package/.prettierignore +109 -0
  18. package/.prettierrc +6 -0
  19. package/AGENTS.md +103 -0
  20. package/CHANGELOG.md +3268 -0
  21. package/CLAUDE.md +93 -0
  22. package/CNAME +1 -0
  23. package/CODE_OF_CONDUCT.md +7 -0
  24. package/CONTRIBUTING.md +433 -0
  25. package/LICENSE +21 -0
  26. package/README.md +2 -2
  27. package/SECURITY.md +74 -0
  28. package/b3pay-module.json +194 -0
  29. package/docs/.remarkrc.json +10 -0
  30. package/docs/README.md +64 -0
  31. package/docs/astro.config.mjs +205 -0
  32. package/docs/favicon.svg +15 -0
  33. package/docs/package.json +39 -0
  34. package/docs/public/favicon.svg +15 -0
  35. package/docs/public/ic-reactor-social-card.png +0 -0
  36. package/docs/scripts/b3pay-static.mjs +22 -0
  37. package/docs/scripts/check-example-docs.mjs +214 -0
  38. package/docs/scripts/check-links.mjs +212 -0
  39. package/docs/scripts/fix-dist-links.js +125 -0
  40. package/docs/scripts/fix-frontmatter.js +71 -0
  41. package/docs/scripts/remove-redundant-h1.cjs +48 -0
  42. package/docs/scripts/rewrite-typedoc-links.js +110 -0
  43. package/docs/scripts/split-index.js +69 -0
  44. package/docs/src/assets/ic-reactor-hero-dark.svg +23 -0
  45. package/docs/src/assets/ic-reactor-hero-light.svg +23 -0
  46. package/docs/src/assets/ic-reactor-lockup-dark.svg +15 -0
  47. package/docs/src/assets/ic-reactor-lockup-light.svg +15 -0
  48. package/docs/src/assets/ic-reactor-mark-dark.svg +14 -0
  49. package/docs/src/assets/ic-reactor-mark-light.svg +14 -0
  50. package/docs/src/assets/layers/candid-core-layer-icon-dark.svg +4 -0
  51. package/docs/src/assets/layers/candid-core-layer-icon-light.svg +4 -0
  52. package/docs/src/assets/layers/candid-profile-layer-icon-dark.svg +4 -0
  53. package/docs/src/assets/layers/candid-profile-layer-icon-light.svg +4 -0
  54. package/docs/src/assets/layers/ic-reactor-layer-icon-dark.svg +4 -0
  55. package/docs/src/assets/layers/ic-reactor-layer-icon-light.svg +4 -0
  56. package/docs/src/components/LibraryCards.astro +172 -0
  57. package/docs/src/components/LibraryIcon.astro +28 -0
  58. package/docs/src/components/SiteTitle.astro +241 -0
  59. package/docs/src/content/docs/404.mdx +19 -0
  60. package/docs/src/content/docs/build/agent-tools.mdx +593 -0
  61. package/docs/src/content/docs/build/icrc1-wallet.mdx +619 -0
  62. package/docs/src/content/docs/build/ssr-per-user.mdx +552 -0
  63. package/docs/src/content/docs/examples/canister-studio.mdx +95 -0
  64. package/docs/src/content/docs/examples/icrc-ledger.mdx +45 -0
  65. package/docs/src/content/docs/examples/index.mdx +38 -0
  66. package/docs/src/content/docs/examples/next-ssr.mdx +73 -0
  67. package/docs/src/content/docs/examples/node-agent-tool.mdx +80 -0
  68. package/docs/src/content/docs/examples/vite-wallet.mdx +71 -0
  69. package/docs/src/content/docs/guides/auth.mdx +1181 -0
  70. package/docs/src/content/docs/guides/client.mdx +551 -0
  71. package/docs/src/content/docs/guides/deploy.mdx +180 -0
  72. package/docs/src/content/docs/guides/errors.mdx +475 -0
  73. package/docs/src/content/docs/guides/getting-started.mdx +620 -0
  74. package/docs/src/content/docs/guides/reads.mdx +586 -0
  75. package/docs/src/content/docs/guides/ssr.mdx +529 -0
  76. package/docs/src/content/docs/guides/testing.mdx +401 -0
  77. package/docs/src/content/docs/guides/values.mdx +261 -0
  78. package/docs/src/content/docs/guides/vite-plugin.mdx +690 -0
  79. package/docs/src/content/docs/guides/writes.mdx +572 -0
  80. package/docs/src/content/docs/ic-reactor/index.mdx +167 -0
  81. package/docs/src/content/docs/index.mdx +65 -0
  82. package/docs/src/content/docs/migrating-from-3.mdx +535 -0
  83. package/docs/src/content/docs/packages/core.mdx +186 -0
  84. package/docs/src/content/docs/packages/mcp.mdx +163 -0
  85. package/docs/src/content/docs/packages/react.mdx +207 -0
  86. package/docs/src/content/docs/packages/vite-plugin.mdx +73 -0
  87. package/docs/src/content/docs/versioning.mdx +104 -0
  88. package/docs/src/content.config.ts +7 -0
  89. package/docs/src/styles/custom.css +189 -0
  90. package/docs/tsconfig.typedoc.json +26 -0
  91. package/docs/typedoc-entry.ts +17 -0
  92. package/docs/typedoc.json +37 -0
  93. package/e2e/Cargo.lock +646 -0
  94. package/e2e/Cargo.toml +3 -0
  95. package/e2e/README.md +101 -0
  96. package/e2e/frontends/build.mjs +94 -0
  97. package/e2e/frontends/index.html +10 -0
  98. package/e2e/global-setup.ts +65 -0
  99. package/e2e/icp.yaml +38 -0
  100. package/e2e/package.json +40 -0
  101. package/e2e/setup.ts +6 -0
  102. package/e2e/src/actor/Cargo.toml +14 -0
  103. package/e2e/src/actor/hello_actor.did +10 -0
  104. package/e2e/src/actor/src/lib.rs +73 -0
  105. package/e2e/src/calls.test.ts +132 -0
  106. package/e2e/src/declarations/hello_actor.envelope.json +175 -0
  107. package/e2e/src/declarations/hello_actor.ts +15 -0
  108. package/e2e/src/errors.test.ts +107 -0
  109. package/e2e/src/frontend-headers.test.ts +253 -0
  110. package/e2e/src/identity.test.ts +61 -0
  111. package/e2e/src/react.test.tsx +59 -0
  112. package/e2e/src/replica.ts +70 -0
  113. package/e2e/src/vite-env.test.ts +108 -0
  114. package/e2e/test.sh +47 -0
  115. package/e2e/tsconfig.json +20 -0
  116. package/e2e/tsconfig.typecheck.json +9 -0
  117. package/e2e/vitest.config.ts +22 -0
  118. package/eslint.config.mjs +100 -0
  119. package/evals/PREREGISTRATION.md +1346 -0
  120. package/evals/README.md +756 -0
  121. package/evals/conditions/thin/condition.json +8 -0
  122. package/evals/conditions/thin/docs/candid-core-cli.README.md +90 -0
  123. package/evals/conditions/thin/docs/candid-core-schema.README.md +339 -0
  124. package/evals/conditions/thin/package.json +25 -0
  125. package/evals/conditions/thin/starter/src/generated/icrc1.ts +57 -0
  126. package/evals/conditions/thin-guide/condition.json +10 -0
  127. package/evals/conditions/thin-guide/docs/llms.txt +292 -0
  128. package/evals/conditions/v3/condition.json +9 -0
  129. package/evals/conditions/v3/docs/ic-reactor-core.llms.txt +232 -0
  130. package/evals/conditions/v3/docs/ic-reactor-react.llms.txt +280 -0
  131. package/evals/conditions/v3/docs/llms-full.txt +1053 -0
  132. package/evals/conditions/v3/package.json +26 -0
  133. package/evals/conditions/v3/starter/src/declarations/icrc1.did.d.ts +95 -0
  134. package/evals/conditions/v3/starter/src/declarations/icrc1.did.js +140 -0
  135. package/evals/conditions/v4/PORTING.md +384 -0
  136. package/evals/conditions/v4/README.md +106 -0
  137. package/evals/conditions/v4/condition.json +5 -0
  138. package/evals/conditions/v4/docs/llms.txt +323 -0
  139. package/evals/conditions/v4/package.json +27 -0
  140. package/evals/conditions/v4/starter/src/generated/icrc1.ts +78 -0
  141. package/evals/conditions/v4-proto/condition.json +6 -0
  142. package/evals/conditions/v4-proto/docs/llms.txt +291 -0
  143. package/evals/conditions/v4-proto/lib/SHIM-NOTES.md +20 -0
  144. package/evals/conditions/v4-proto/lib/package.json +27 -0
  145. package/evals/conditions/v4-proto/lib/src/client.ts +471 -0
  146. package/evals/conditions/v4-proto/lib/src/errors.ts +214 -0
  147. package/evals/conditions/v4-proto/lib/src/index.ts +36 -0
  148. package/evals/conditions/v4-proto/lib/src/principal.ts +175 -0
  149. package/evals/conditions/v4-proto/lib/src/service.ts +69 -0
  150. package/evals/conditions/v4-proto/lib/src/types.ts +174 -0
  151. package/evals/conditions/v4-proto/lib/test/runtime.test.ts +170 -0
  152. package/evals/conditions/v4-proto/lib/test/tsconfig.json +10 -0
  153. package/evals/conditions/v4-proto/lib/test/types.test-d.ts +145 -0
  154. package/evals/conditions/v4-proto/lib/test/vitest.config.mjs +8 -0
  155. package/evals/conditions/v4-proto/lib/tsconfig.json +17 -0
  156. package/evals/conditions/v4-proto/package.json +26 -0
  157. package/evals/conditions/v4-proto/starter/src/generated/icrc1.service.ts +19 -0
  158. package/evals/conditions/v4-proto/starter/src/generated/icrc1.ts +57 -0
  159. package/evals/drive.mjs +1221 -0
  160. package/evals/gate.mjs +161 -0
  161. package/evals/harness/aggregate.mjs +534 -0
  162. package/evals/harness/aggregate.test.mjs +344 -0
  163. package/evals/harness/assemble.mjs +216 -0
  164. package/evals/harness/assemble.test.mjs +143 -0
  165. package/evals/harness/auth.mjs +91 -0
  166. package/evals/harness/check-docs.mjs +157 -0
  167. package/evals/harness/declarations/icrc1.did.d.ts +95 -0
  168. package/evals/harness/declarations/icrc1.did.js +140 -0
  169. package/evals/harness/drive-auth.test.mjs +584 -0
  170. package/evals/harness/drive.test.mjs +509 -0
  171. package/evals/harness/fake-auth.ts +67 -0
  172. package/evals/harness/fake-ledger.ts +178 -0
  173. package/evals/harness/gate-plan.mjs +104 -0
  174. package/evals/harness/gate-plan.test.mjs +243 -0
  175. package/evals/harness/icrc1.did +88 -0
  176. package/evals/harness/judge.mjs +63 -0
  177. package/evals/harness/judge.test.mjs +81 -0
  178. package/evals/harness/leak-scan.mjs +1373 -0
  179. package/evals/harness/leak-scan.test.mjs +1300 -0
  180. package/evals/harness/probes/amounts.ts +14 -0
  181. package/evals/harness/probes/node-tool.probe.ts +48 -0
  182. package/evals/harness/probes/probe.mjs +149 -0
  183. package/evals/harness/probes/react-wallet.probe.ts +96 -0
  184. package/evals/harness/public/support/auth.ts +38 -0
  185. package/evals/harness/public/support/fake-replica.d.ts +28 -0
  186. package/evals/harness/public/support/fake-replica.js +434 -0
  187. package/evals/harness/public/support/ledger.idl.d.ts +95 -0
  188. package/evals/harness/public/support/ledger.idl.js +140 -0
  189. package/evals/harness/public/support/world.ts +179 -0
  190. package/evals/harness/runs.mjs +139 -0
  191. package/evals/harness/sandbox.mjs +88 -0
  192. package/evals/harness/sandbox.test.mjs +134 -0
  193. package/evals/harness/ship.mjs +264 -0
  194. package/evals/harness/ship.test.mjs +552 -0
  195. package/evals/harness/world.ts +291 -0
  196. package/evals/package.json +26 -0
  197. package/evals/pnpm-lock.yaml +1537 -0
  198. package/evals/pnpm-workspace.yaml +12 -0
  199. package/evals/results/2026-09-30T14-22-05-924Z/pilot.json +384 -0
  200. package/evals/results/2026-09-30T14-22-05-924Z/pilot.rescanned.json +404 -0
  201. package/evals/results/2026-09-30T14-22-05-924Z/pilot.rescanned.rescored.json +404 -0
  202. package/evals/results/2026-09-30T14-22-05-924Z/plan.json +240 -0
  203. package/evals/results/2026-09-30T14-22-05-924Z/summary.json +1800 -0
  204. package/evals/results/2026-09-30T14-22-05-924Z/summary.rescanned.json +1843 -0
  205. package/evals/results/2026-09-30T14-22-05-924Z/summary.rescanned.rescored.json +2100 -0
  206. package/evals/results/2026-09-30T15-48-23-343Z/pilot.json +404 -0
  207. package/evals/results/2026-09-30T15-48-23-343Z/pilot.rescored.json +404 -0
  208. package/evals/results/2026-09-30T15-48-23-343Z/plan.json +242 -0
  209. package/evals/results/2026-09-30T15-48-23-343Z/summary.json +1843 -0
  210. package/evals/results/2026-09-30T15-48-23-343Z/summary.rescored.json +2195 -0
  211. package/evals/results/2026-10-03-beta1-gate/pilot.json +162 -0
  212. package/evals/results/2026-10-03-beta1-gate/plan.json +141 -0
  213. package/evals/results/2026-10-03-beta1-gate/summary.json +983 -0
  214. package/evals/results/2026-10-05-ga-gate/contaminated-run.json +55 -0
  215. package/evals/results/2026-10-05-ga-gate/pilot.json +162 -0
  216. package/evals/results/2026-10-05-ga-gate/plan.json +457 -0
  217. package/evals/results/2026-10-05-ga-gate/summary.json +1037 -0
  218. package/evals/results/2026-10-05-ga-gate/summary.rescanned.json +1037 -0
  219. package/evals/results/README.md +31 -0
  220. package/evals/runs-pilot.log +150 -0
  221. package/evals/runs-pilot2.log +150 -0
  222. package/evals/score.mjs +256 -0
  223. package/evals/setup.mjs +547 -0
  224. package/evals/tasks/node-tool/faulty/thin-accepts-exponent/meta.json +5 -0
  225. package/evals/tasks/node-tool/faulty/thin-accepts-exponent/src/index.ts +121 -0
  226. package/evals/tasks/node-tool/faulty/thin-anonymous-transfer/meta.json +5 -0
  227. package/evals/tasks/node-tool/faulty/thin-anonymous-transfer/src/index.ts +116 -0
  228. package/evals/tasks/node-tool/faulty/thin-every-reject-may-have-executed/meta.json +9 -0
  229. package/evals/tasks/node-tool/faulty/thin-every-reject-may-have-executed/src/index.ts +118 -0
  230. package/evals/tasks/node-tool/faulty/thin-fetches-root-key-off-local/meta.json +5 -0
  231. package/evals/tasks/node-tool/faulty/thin-fetches-root-key-off-local/src/index.ts +118 -0
  232. package/evals/tasks/node-tool/faulty/thin-ignores-root-key/meta.json +5 -0
  233. package/evals/tasks/node-tool/faulty/thin-ignores-root-key/src/index.ts +117 -0
  234. package/evals/tasks/node-tool/faulty/thin-misclassifies-reject-codes/meta.json +9 -0
  235. package/evals/tasks/node-tool/faulty/thin-misclassifies-reject-codes/src/index.ts +119 -0
  236. package/evals/tasks/node-tool/faulty/thin-no-nat64-cap/meta.json +9 -0
  237. package/evals/tasks/node-tool/faulty/thin-no-nat64-cap/src/index.ts +118 -0
  238. package/evals/tasks/node-tool/faulty/thin-number-amount/meta.json +10 -0
  239. package/evals/tasks/node-tool/faulty/thin-number-amount/src/index.ts +117 -0
  240. package/evals/tasks/node-tool/faulty/thin-truncates-fraction-digits/meta.json +5 -0
  241. package/evals/tasks/node-tool/faulty/thin-truncates-fraction-digits/src/index.ts +120 -0
  242. package/evals/tasks/node-tool/faulty/thin-unvalidated-recipient/meta.json +5 -0
  243. package/evals/tasks/node-tool/faulty/thin-unvalidated-recipient/src/index.ts +118 -0
  244. package/evals/tasks/node-tool/faulty/v3-retry-call-errors/meta.json +11 -0
  245. package/evals/tasks/node-tool/faulty/v3-retry-call-errors/src/index.ts +149 -0
  246. package/evals/tasks/node-tool/faulty/v4-anonymous-identity-sent/meta.json +5 -0
  247. package/evals/tasks/node-tool/faulty/v4-anonymous-identity-sent/src/index.ts +71 -0
  248. package/evals/tasks/node-tool/faulty/v4-never-may-have-executed/meta.json +9 -0
  249. package/evals/tasks/node-tool/faulty/v4-never-may-have-executed/src/index.ts +84 -0
  250. package/evals/tasks/node-tool/faulty/v4-proto-never-may-have-executed/meta.json +9 -0
  251. package/evals/tasks/node-tool/faulty/v4-proto-never-may-have-executed/src/index.ts +72 -0
  252. package/evals/tasks/node-tool/faulty/v4-proto-refuses-nat64-max/meta.json +5 -0
  253. package/evals/tasks/node-tool/faulty/v4-proto-refuses-nat64-max/src/index.ts +72 -0
  254. package/evals/tasks/node-tool/faulty/v4-refuses-nat64-max/meta.json +5 -0
  255. package/evals/tasks/node-tool/faulty/v4-refuses-nat64-max/src/index.ts +84 -0
  256. package/evals/tasks/node-tool/hidden/node-tool.test.ts +312 -0
  257. package/evals/tasks/node-tool/prompt.md +55 -0
  258. package/evals/tasks/node-tool/prompt.minimal.md +30 -0
  259. package/evals/tasks/node-tool/solutions/thin/reference/src/index.ts +118 -0
  260. package/evals/tasks/node-tool/solutions/thin/reference-module-scope/src/index.ts +145 -0
  261. package/evals/tasks/node-tool/solutions/thin-guide/reference/src/index.ts +118 -0
  262. package/evals/tasks/node-tool/solutions/thin-guide/reference-module-scope/src/index.ts +145 -0
  263. package/evals/tasks/node-tool/solutions/v3/reference/src/index.ts +140 -0
  264. package/evals/tasks/node-tool/solutions/v3/reference-module-scope/src/index.ts +153 -0
  265. package/evals/tasks/node-tool/solutions/v4/reference/src/index.ts +84 -0
  266. package/evals/tasks/node-tool/solutions/v4/reference-module-scope/src/index.ts +105 -0
  267. package/evals/tasks/node-tool/solutions/v4-proto/reference/src/index.ts +72 -0
  268. package/evals/tasks/node-tool/solutions/v4-proto/reference-module-scope/src/index.ts +87 -0
  269. package/evals/tasks/node-tool/starter/src/contract.ts +31 -0
  270. package/evals/tasks/node-tool/starter/src/index.ts +7 -0
  271. package/evals/tasks/node-tool/starter/test/smoke.test.ts +22 -0
  272. package/evals/tasks/node-tool/starter/tsconfig.json +17 -0
  273. package/evals/tasks/node-tool/task.json +63 -0
  274. package/evals/tasks/react-wallet/faulty/thin-accepts-exponent/meta.json +5 -0
  275. package/evals/tasks/react-wallet/faulty/thin-accepts-exponent/src/Wallet.tsx +216 -0
  276. package/evals/tasks/react-wallet/faulty/thin-anonymous-transfer/meta.json +5 -0
  277. package/evals/tasks/react-wallet/faulty/thin-anonymous-transfer/src/Wallet.tsx +212 -0
  278. package/evals/tasks/react-wallet/faulty/thin-both-auth-buttons/meta.json +5 -0
  279. package/evals/tasks/react-wallet/faulty/thin-both-auth-buttons/src/Wallet.tsx +210 -0
  280. package/evals/tasks/react-wallet/faulty/thin-constant-balance-key/meta.json +8 -0
  281. package/evals/tasks/react-wallet/faulty/thin-constant-balance-key/src/Wallet.tsx +213 -0
  282. package/evals/tasks/react-wallet/faulty/thin-no-nat64-cap/meta.json +9 -0
  283. package/evals/tasks/react-wallet/faulty/thin-no-nat64-cap/src/Wallet.tsx +213 -0
  284. package/evals/tasks/react-wallet/faulty/thin-number-balance/meta.json +5 -0
  285. package/evals/tasks/react-wallet/faulty/thin-number-balance/src/Wallet.tsx +213 -0
  286. package/evals/tasks/react-wallet/faulty/thin-transfer-as-query/meta.json +5 -0
  287. package/evals/tasks/react-wallet/faulty/thin-transfer-as-query/src/Wallet.tsx +254 -0
  288. package/evals/tasks/react-wallet/faulty/thin-truncates-fraction-digits/meta.json +5 -0
  289. package/evals/tasks/react-wallet/faulty/thin-truncates-fraction-digits/src/Wallet.tsx +215 -0
  290. package/evals/tasks/react-wallet/faulty/thin-unguarded-balance-effect/meta.json +5 -0
  291. package/evals/tasks/react-wallet/faulty/thin-unguarded-balance-effect/src/Wallet.tsx +214 -0
  292. package/evals/tasks/react-wallet/faulty/v3-http-4xx-unknown/meta.json +5 -0
  293. package/evals/tasks/react-wallet/faulty/v3-http-4xx-unknown/src/Wallet.tsx +224 -0
  294. package/evals/tasks/react-wallet/faulty/v3-mutation-retry-3/meta.json +12 -0
  295. package/evals/tasks/react-wallet/faulty/v3-mutation-retry-3/src/Wallet.tsx +225 -0
  296. package/evals/tasks/react-wallet/faulty/v3-unknown-as-error/meta.json +11 -0
  297. package/evals/tasks/react-wallet/faulty/v3-unknown-as-error/src/Wallet.tsx +182 -0
  298. package/evals/tasks/react-wallet/faulty/v4-every-reject-unknown/meta.json +5 -0
  299. package/evals/tasks/react-wallet/faulty/v4-every-reject-unknown/src/Wallet.tsx +160 -0
  300. package/evals/tasks/react-wallet/faulty/v4-keep-previous-data/meta.json +5 -0
  301. package/evals/tasks/react-wallet/faulty/v4-keep-previous-data/src/Wallet.tsx +166 -0
  302. package/evals/tasks/react-wallet/faulty/v4-proto-every-reject-unknown/meta.json +5 -0
  303. package/evals/tasks/react-wallet/faulty/v4-proto-every-reject-unknown/src/Wallet.tsx +161 -0
  304. package/evals/tasks/react-wallet/faulty/v4-proto-keep-previous-data/meta.json +5 -0
  305. package/evals/tasks/react-wallet/faulty/v4-proto-keep-previous-data/src/Wallet.tsx +163 -0
  306. package/evals/tasks/react-wallet/faulty/v4-proto-retry-spread/meta.json +12 -0
  307. package/evals/tasks/react-wallet/faulty/v4-proto-retry-spread/src/Wallet.tsx +162 -0
  308. package/evals/tasks/react-wallet/faulty/v4-proto-status-not-idle/meta.json +5 -0
  309. package/evals/tasks/react-wallet/faulty/v4-proto-status-not-idle/src/Wallet.tsx +163 -0
  310. package/evals/tasks/react-wallet/faulty/v4-retry-spread/meta.json +12 -0
  311. package/evals/tasks/react-wallet/faulty/v4-retry-spread/src/Wallet.tsx +163 -0
  312. package/evals/tasks/react-wallet/faulty/v4-status-not-idle/meta.json +5 -0
  313. package/evals/tasks/react-wallet/faulty/v4-status-not-idle/src/Wallet.tsx +162 -0
  314. package/evals/tasks/react-wallet/hidden/react-wallet.test.ts +536 -0
  315. package/evals/tasks/react-wallet/prompt.md +88 -0
  316. package/evals/tasks/react-wallet/prompt.minimal.md +51 -0
  317. package/evals/tasks/react-wallet/solutions/thin/reference/src/Wallet.tsx +213 -0
  318. package/evals/tasks/react-wallet/solutions/thin/reference-module-scope/src/Wallet.tsx +227 -0
  319. package/evals/tasks/react-wallet/solutions/thin-guide/reference/src/Wallet.tsx +213 -0
  320. package/evals/tasks/react-wallet/solutions/thin-guide/reference-module-scope/src/Wallet.tsx +227 -0
  321. package/evals/tasks/react-wallet/solutions/v3/reference/src/Wallet.tsx +224 -0
  322. package/evals/tasks/react-wallet/solutions/v3/reference-module-scope/src/Wallet.tsx +230 -0
  323. package/evals/tasks/react-wallet/solutions/v4/reference/src/Wallet.tsx +160 -0
  324. package/evals/tasks/react-wallet/solutions/v4/reference-module-scope/src/Wallet.tsx +178 -0
  325. package/evals/tasks/react-wallet/solutions/v4-proto/reference/src/Wallet.tsx +161 -0
  326. package/evals/tasks/react-wallet/solutions/v4-proto/reference-module-scope/src/Wallet.tsx +161 -0
  327. package/evals/tasks/react-wallet/starter/src/Wallet.tsx +12 -0
  328. package/evals/tasks/react-wallet/starter/src/auth.ts +16 -0
  329. package/evals/tasks/react-wallet/starter/src/config.ts +14 -0
  330. package/evals/tasks/react-wallet/starter/src/ii-auth.ts +43 -0
  331. package/evals/tasks/react-wallet/starter/src/main.tsx +15 -0
  332. package/evals/tasks/react-wallet/starter/test/smoke.test.ts +34 -0
  333. package/evals/tasks/react-wallet/starter/tsconfig.json +18 -0
  334. package/evals/tasks/react-wallet/task.json +84 -0
  335. package/examples/canister-studio/.stackblitzrc +4 -0
  336. package/examples/canister-studio/README.md +183 -0
  337. package/examples/canister-studio/did/ckbtc_minter.did +806 -0
  338. package/examples/canister-studio/did/icp_ledger.did +586 -0
  339. package/examples/canister-studio/did/icrc1.did +88 -0
  340. package/examples/canister-studio/package.json +45 -0
  341. package/examples/canister-studio/scripts/smoke.ts +188 -0
  342. package/examples/canister-studio/src/calls/CacheKeyPanel.tsx +104 -0
  343. package/examples/canister-studio/src/calls/ConfirmWrite.tsx +265 -0
  344. package/examples/canister-studio/src/calls/ErrorPanel.test.tsx +113 -0
  345. package/examples/canister-studio/src/calls/ErrorPanel.tsx +214 -0
  346. package/examples/canister-studio/src/calls/MethodRunner.test.tsx +354 -0
  347. package/examples/canister-studio/src/calls/MethodRunner.tsx +57 -0
  348. package/examples/canister-studio/src/calls/ReadRunner.tsx +244 -0
  349. package/examples/canister-studio/src/calls/ResendPanel.tsx +165 -0
  350. package/examples/canister-studio/src/calls/ShareLink.tsx +59 -0
  351. package/examples/canister-studio/src/calls/WriteRunner.test.tsx +678 -0
  352. package/examples/canister-studio/src/calls/WriteRunner.tsx +670 -0
  353. package/examples/canister-studio/src/calls/dedupe.ts +189 -0
  354. package/examples/canister-studio/src/calls/error-info.ts +168 -0
  355. package/examples/canister-studio/src/calls/history.test.ts +284 -0
  356. package/examples/canister-studio/src/calls/history.ts +287 -0
  357. package/examples/canister-studio/src/calls/kinds.ts +68 -0
  358. package/examples/canister-studio/src/calls/prepare.ts +32 -0
  359. package/examples/canister-studio/src/calls/query-options.ts +30 -0
  360. package/examples/canister-studio/src/calls/read.ts +47 -0
  361. package/examples/canister-studio/src/calls/readback.ts +100 -0
  362. package/examples/canister-studio/src/calls/record.ts +123 -0
  363. package/examples/canister-studio/src/calls/safe-calls.test.ts +299 -0
  364. package/examples/canister-studio/src/calls/share.ts +46 -0
  365. package/examples/canister-studio/src/calls/testing/doubles.tsx +108 -0
  366. package/examples/canister-studio/src/calls/testing/fixtures.ts +185 -0
  367. package/examples/canister-studio/src/calls/useCanisterHandle.ts +35 -0
  368. package/examples/canister-studio/src/canisters/ckbtc_minter.envelope.json +4228 -0
  369. package/examples/canister-studio/src/canisters/ckbtc_minter.ts +868 -0
  370. package/examples/canister-studio/src/canisters/icp_ledger.envelope.json +3454 -0
  371. package/examples/canister-studio/src/canisters/icp_ledger.ts +584 -0
  372. package/examples/canister-studio/src/canisters/icrc1.envelope.json +840 -0
  373. package/examples/canister-studio/src/canisters/icrc1.ts +78 -0
  374. package/examples/canister-studio/src/components/ActionButton.tsx +32 -0
  375. package/examples/canister-studio/src/components/AppHeader.tsx +83 -0
  376. package/examples/canister-studio/src/components/AppShell.tsx +29 -0
  377. package/examples/canister-studio/src/components/AuthButton.tsx +79 -0
  378. package/examples/canister-studio/src/components/Badge.tsx +79 -0
  379. package/examples/canister-studio/src/components/CanisterHeader.tsx +200 -0
  380. package/examples/canister-studio/src/components/CanisterJump.tsx +130 -0
  381. package/examples/canister-studio/src/components/Card.tsx +64 -0
  382. package/examples/canister-studio/src/components/Code.tsx +101 -0
  383. package/examples/canister-studio/src/components/CopyButton.tsx +126 -0
  384. package/examples/canister-studio/src/components/Disclosure.tsx +34 -0
  385. package/examples/canister-studio/src/components/EmptyState.tsx +34 -0
  386. package/examples/canister-studio/src/components/MethodList.test.tsx +137 -0
  387. package/examples/canister-studio/src/components/MethodList.tsx +244 -0
  388. package/examples/canister-studio/src/components/Notice.tsx +36 -0
  389. package/examples/canister-studio/src/components/ProvenanceBadge.tsx +44 -0
  390. package/examples/canister-studio/src/components/RouteError.tsx +42 -0
  391. package/examples/canister-studio/src/components/SectionBoundary.tsx +76 -0
  392. package/examples/canister-studio/src/components/Spinner.tsx +33 -0
  393. package/examples/canister-studio/src/components/Tabs.tsx +96 -0
  394. package/examples/canister-studio/src/components/canister/CanisterLoadFailure.test.tsx +56 -0
  395. package/examples/canister-studio/src/components/canister/CanisterLoadFailure.tsx +165 -0
  396. package/examples/canister-studio/src/components/canister/CanisterNotFound.tsx +59 -0
  397. package/examples/canister-studio/src/components/canister/CanisterPending.tsx +33 -0
  398. package/examples/canister-studio/src/components/canister/MethodOverview.tsx +93 -0
  399. package/examples/canister-studio/src/components/canister/MethodWorkspace.test.tsx +201 -0
  400. package/examples/canister-studio/src/components/canister/MethodWorkspace.tsx +187 -0
  401. package/examples/canister-studio/src/components/canister/shared-call.test.ts +186 -0
  402. package/examples/canister-studio/src/components/canister/shared-call.ts +133 -0
  403. package/examples/canister-studio/src/components/canister/shared-link-loader.test.ts +116 -0
  404. package/examples/canister-studio/src/components/canister/use-canister-page.ts +44 -0
  405. package/examples/canister-studio/src/components/canister/workspace-key.test.ts +27 -0
  406. package/examples/canister-studio/src/components/canister/workspace-key.ts +36 -0
  407. package/examples/canister-studio/src/components/canister-id-input.ts +69 -0
  408. package/examples/canister-studio/src/components/canister-names.ts +11 -0
  409. package/examples/canister-studio/src/components/download.ts +34 -0
  410. package/examples/canister-studio/src/components/fallbacks.tsx +33 -0
  411. package/examples/canister-studio/src/components/helpers.test.ts +107 -0
  412. package/examples/canister-studio/src/components/history/HistoryList.tsx +284 -0
  413. package/examples/canister-studio/src/components/home/FeaturedLedger.tsx +149 -0
  414. package/examples/canister-studio/src/components/home/OpenCanister.tsx +99 -0
  415. package/examples/canister-studio/src/components/home/featured.test.tsx +92 -0
  416. package/examples/canister-studio/src/components/home/featured.ts +64 -0
  417. package/examples/canister-studio/src/components/interface/CandidTab.tsx +38 -0
  418. package/examples/canister-studio/src/components/interface/CompatReport.tsx +92 -0
  419. package/examples/canister-studio/src/components/interface/CompatibilityTab.tsx +190 -0
  420. package/examples/canister-studio/src/components/interface/ProjectTab.tsx +196 -0
  421. package/examples/canister-studio/src/components/interface/TypeScriptTab.tsx +114 -0
  422. package/examples/canister-studio/src/components/interface/tools.test.tsx +169 -0
  423. package/examples/canister-studio/src/components/modes.ts +31 -0
  424. package/examples/canister-studio/src/components/playground/MethodPicker.tsx +67 -0
  425. package/examples/canister-studio/src/components/playground/PlaygroundForms.tsx +112 -0
  426. package/examples/canister-studio/src/components/playground/PlaygroundTools.tsx +219 -0
  427. package/examples/canister-studio/src/components/playground/sample.ts +43 -0
  428. package/examples/canister-studio/src/components/playground/target.test.tsx +86 -0
  429. package/examples/canister-studio/src/components/primitives.test.tsx +121 -0
  430. package/examples/canister-studio/src/components/provenance.ts +38 -0
  431. package/examples/canister-studio/src/components/route-render.test.tsx +71 -0
  432. package/examples/canister-studio/src/components/use-task.ts +56 -0
  433. package/examples/canister-studio/src/components/wallet/BalancePanel.tsx +110 -0
  434. package/examples/canister-studio/src/components/wallet/LedgerSummary.tsx +63 -0
  435. package/examples/canister-studio/src/components/wallet/MinterPanel.tsx +255 -0
  436. package/examples/canister-studio/src/components/wallet/SendPanel.test.tsx +297 -0
  437. package/examples/canister-studio/src/components/wallet/SendPanel.tsx +446 -0
  438. package/examples/canister-studio/src/components/wallet/ledgers.test.ts +36 -0
  439. package/examples/canister-studio/src/components/wallet/ledgers.ts +111 -0
  440. package/examples/canister-studio/src/forms/JsonPanel.tsx +157 -0
  441. package/examples/canister-studio/src/forms/MethodForm.test.tsx +369 -0
  442. package/examples/canister-studio/src/forms/MethodForm.tsx +368 -0
  443. package/examples/canister-studio/src/forms/TypedForm.tsx +55 -0
  444. package/examples/canister-studio/src/forms/context.ts +62 -0
  445. package/examples/canister-studio/src/forms/convert.ts +131 -0
  446. package/examples/canister-studio/src/forms/edge.test.ts +252 -0
  447. package/examples/canister-studio/src/forms/engine.test.ts +358 -0
  448. package/examples/canister-studio/src/forms/engine.ts +1492 -0
  449. package/examples/canister-studio/src/forms/fields/BytesField.tsx +210 -0
  450. package/examples/canister-studio/src/forms/fields/Field.tsx +106 -0
  451. package/examples/canister-studio/src/forms/fields/FieldShell.tsx +101 -0
  452. package/examples/canister-studio/src/forms/fields/Group.tsx +74 -0
  453. package/examples/canister-studio/src/forms/fields/ListField.tsx +256 -0
  454. package/examples/canister-studio/src/forms/fields/Segmented.tsx +78 -0
  455. package/examples/canister-studio/src/forms/fields/TimestampField.tsx +108 -0
  456. package/examples/canister-studio/src/forms/fields/UnitsField.tsx +119 -0
  457. package/examples/canister-studio/src/forms/fields/composites.tsx +389 -0
  458. package/examples/canister-studio/src/forms/fields/leaves.tsx +350 -0
  459. package/examples/canister-studio/src/forms/fields.test.tsx +396 -0
  460. package/examples/canister-studio/src/forms/helpers.test.ts +225 -0
  461. package/examples/canister-studio/src/forms/hints.ts +128 -0
  462. package/examples/canister-studio/src/forms/json.ts +119 -0
  463. package/examples/canister-studio/src/forms/parsing.test.ts +217 -0
  464. package/examples/canister-studio/src/forms/useMethodForm.ts +251 -0
  465. package/examples/canister-studio/src/interface/browser.test.ts +23 -0
  466. package/examples/canister-studio/src/interface/browser.ts +153 -0
  467. package/examples/canister-studio/src/interface/cache.ts +68 -0
  468. package/examples/canister-studio/src/interface/cli-cache.test.ts +155 -0
  469. package/examples/canister-studio/src/interface/cli-cache.ts +125 -0
  470. package/examples/canister-studio/src/interface/compile.server.test.ts +198 -0
  471. package/examples/canister-studio/src/interface/compile.server.ts +262 -0
  472. package/examples/canister-studio/src/interface/compile.test.ts +108 -0
  473. package/examples/canister-studio/src/interface/compile.ts +305 -0
  474. package/examples/canister-studio/src/interface/docs.test.ts +75 -0
  475. package/examples/canister-studio/src/interface/docs.ts +100 -0
  476. package/examples/canister-studio/src/interface/functions.test.ts +42 -0
  477. package/examples/canister-studio/src/interface/functions.ts +200 -0
  478. package/examples/canister-studio/src/interface/live-candid.server.ts +91 -0
  479. package/examples/canister-studio/src/interface/live-loader.test.ts +515 -0
  480. package/examples/canister-studio/src/interface/live-loader.ts +364 -0
  481. package/examples/canister-studio/src/interface/load.test.ts +328 -0
  482. package/examples/canister-studio/src/interface/load.ts +249 -0
  483. package/examples/canister-studio/src/interface/read-candid.test.ts +185 -0
  484. package/examples/canister-studio/src/interface/read-candid.ts +233 -0
  485. package/examples/canister-studio/src/interface/server-result.ts +42 -0
  486. package/examples/canister-studio/src/interface/typed.ts +68 -0
  487. package/examples/canister-studio/src/interface/types.ts +121 -0
  488. package/examples/canister-studio/src/lib/format.test.ts +107 -0
  489. package/examples/canister-studio/src/lib/format.ts +139 -0
  490. package/examples/canister-studio/src/lib/hex.test.ts +43 -0
  491. package/examples/canister-studio/src/lib/hex.ts +33 -0
  492. package/examples/canister-studio/src/lib/ids.test.ts +70 -0
  493. package/examples/canister-studio/src/lib/ids.ts +93 -0
  494. package/examples/canister-studio/src/lib/reactor.test.ts +55 -0
  495. package/examples/canister-studio/src/lib/reactor.ts +55 -0
  496. package/examples/canister-studio/src/lib/registry.test.ts +74 -0
  497. package/examples/canister-studio/src/lib/registry.ts +238 -0
  498. package/examples/canister-studio/src/lib/schema.ts +148 -0
  499. package/examples/canister-studio/src/routeTree.gen.ts +227 -0
  500. package/examples/canister-studio/src/router.test.ts +290 -0
  501. package/examples/canister-studio/src/router.tsx +127 -0
  502. package/examples/canister-studio/src/routes/__root.tsx +58 -0
  503. package/examples/canister-studio/src/routes/canister.$canisterId.index.tsx +172 -0
  504. package/examples/canister-studio/src/routes/canister.$canisterId.interface.tsx +98 -0
  505. package/examples/canister-studio/src/routes/canister.$canisterId.tsx +91 -0
  506. package/examples/canister-studio/src/routes/history.tsx +55 -0
  507. package/examples/canister-studio/src/routes/index.tsx +169 -0
  508. package/examples/canister-studio/src/routes/playground.tsx +286 -0
  509. package/examples/canister-studio/src/routes/safety.tsx +57 -0
  510. package/examples/canister-studio/src/routes/wallet.tsx +213 -0
  511. package/examples/canister-studio/src/safety/LabTransferForm.tsx +84 -0
  512. package/examples/canister-studio/src/safety/RequestLog.tsx +79 -0
  513. package/examples/canister-studio/src/safety/SafetyLab.test.tsx +89 -0
  514. package/examples/canister-studio/src/safety/SafetyLab.tsx +140 -0
  515. package/examples/canister-studio/src/safety/ScenarioCard.tsx +262 -0
  516. package/examples/canister-studio/src/safety/lab-run.ts +283 -0
  517. package/examples/canister-studio/src/safety/mock-ledger.test.ts +99 -0
  518. package/examples/canister-studio/src/safety/mock-ledger.ts +263 -0
  519. package/examples/canister-studio/src/safety/scenarios.test.ts +157 -0
  520. package/examples/canister-studio/src/safety/scenarios.ts +924 -0
  521. package/examples/canister-studio/src/styles.css +3453 -0
  522. package/examples/canister-studio/src/styles.test.ts +46 -0
  523. package/examples/canister-studio/src/values/CallPreview.test.tsx +86 -0
  524. package/examples/canister-studio/src/values/CallPreview.tsx +148 -0
  525. package/examples/canister-studio/src/values/ErrValueView.tsx +66 -0
  526. package/examples/canister-studio/src/values/Inspector.tsx +133 -0
  527. package/examples/canister-studio/src/values/ReplyView.test.tsx +341 -0
  528. package/examples/canister-studio/src/values/ReplyView.tsx +164 -0
  529. package/examples/canister-studio/src/values/candid-text.test.ts +516 -0
  530. package/examples/canister-studio/src/values/candid-text.ts +479 -0
  531. package/examples/canister-studio/src/values/hints.ts +58 -0
  532. package/examples/canister-studio/src/values/json.test.ts +66 -0
  533. package/examples/canister-studio/src/values/json.ts +31 -0
  534. package/examples/canister-studio/src/values/leaves.tsx +309 -0
  535. package/examples/canister-studio/src/values/schema.test.ts +123 -0
  536. package/examples/canister-studio/src/values/schema.ts +78 -0
  537. package/examples/canister-studio/src/values/test-router.tsx +21 -0
  538. package/examples/canister-studio/src/values/tree.tsx +670 -0
  539. package/examples/canister-studio/src/values/use-sha256.ts +29 -0
  540. package/examples/canister-studio/tsconfig.json +20 -0
  541. package/examples/canister-studio/vite.config.ts +50 -0
  542. package/examples/canister-studio/vitest.config.ts +20 -0
  543. package/examples/icrc-ledger/.stackblitzrc +4 -0
  544. package/examples/icrc-ledger/README.md +100 -0
  545. package/examples/icrc-ledger/ckbtc_minter.did +12 -0
  546. package/examples/icrc-ledger/icp_ledger.did +147 -0
  547. package/examples/icrc-ledger/icrc1.did +88 -0
  548. package/examples/icrc-ledger/index.html +13 -0
  549. package/examples/icrc-ledger/package.json +40 -0
  550. package/examples/icrc-ledger/scripts/smoke.ts +151 -0
  551. package/examples/icrc-ledger/src/App.tsx +71 -0
  552. package/examples/icrc-ledger/src/ErrorPanel.tsx +105 -0
  553. package/examples/icrc-ledger/src/MainnetTab.tsx +450 -0
  554. package/examples/icrc-ledger/src/SandboxTab.test.tsx +379 -0
  555. package/examples/icrc-ledger/src/SandboxTab.tsx +761 -0
  556. package/examples/icrc-ledger/src/blocks.test.ts +247 -0
  557. package/examples/icrc-ledger/src/blocks.ts +140 -0
  558. package/examples/icrc-ledger/src/canisters/ckbtc_minter.envelope.json +96 -0
  559. package/examples/icrc-ledger/src/canisters/ckbtc_minter.ts +16 -0
  560. package/examples/icrc-ledger/src/canisters/icp_ledger.envelope.json +690 -0
  561. package/examples/icrc-ledger/src/canisters/icp_ledger.ts +223 -0
  562. package/examples/icrc-ledger/src/canisters/icrc1.envelope.json +840 -0
  563. package/examples/icrc-ledger/src/canisters/icrc1.ts +78 -0
  564. package/examples/icrc-ledger/src/format.test.ts +41 -0
  565. package/examples/icrc-ledger/src/format.ts +68 -0
  566. package/examples/icrc-ledger/src/ledger.ts +15 -0
  567. package/examples/icrc-ledger/src/main.tsx +10 -0
  568. package/examples/icrc-ledger/src/minter.ts +16 -0
  569. package/examples/icrc-ledger/src/sandbox.test.ts +464 -0
  570. package/examples/icrc-ledger/src/sandbox.ts +281 -0
  571. package/examples/icrc-ledger/src/styles.css +391 -0
  572. package/examples/icrc-ledger/src/transfer-form.test.ts +62 -0
  573. package/examples/icrc-ledger/src/transfer-form.ts +97 -0
  574. package/examples/icrc-ledger/tsconfig.json +19 -0
  575. package/examples/icrc-ledger/vite.config.ts +44 -0
  576. package/examples/icrc-ledger/vitest.config.ts +12 -0
  577. package/examples/next-ssr/.stackblitzrc +4 -0
  578. package/examples/next-ssr/README.md +112 -0
  579. package/examples/next-ssr/icrc1.did +88 -0
  580. package/examples/next-ssr/next.config.ts +10 -0
  581. package/examples/next-ssr/package.json +41 -0
  582. package/examples/next-ssr/scripts/smoke.ts +169 -0
  583. package/examples/next-ssr/src/app/account/CertifiedBalances.tsx +58 -0
  584. package/examples/next-ssr/src/app/account/account.test.tsx +165 -0
  585. package/examples/next-ssr/src/app/account/page.tsx +82 -0
  586. package/examples/next-ssr/src/app/api/balance/[ledger]/[principal]/route.ts +10 -0
  587. package/examples/next-ssr/src/app/globals.css +278 -0
  588. package/examples/next-ssr/src/app/layout.tsx +34 -0
  589. package/examples/next-ssr/src/app/page.tsx +46 -0
  590. package/examples/next-ssr/src/app/providers.browser.test.tsx +127 -0
  591. package/examples/next-ssr/src/app/providers.test.tsx +73 -0
  592. package/examples/next-ssr/src/app/providers.tsx +60 -0
  593. package/examples/next-ssr/src/canisters/icrc1.envelope.json +840 -0
  594. package/examples/next-ssr/src/canisters/icrc1.ts +78 -0
  595. package/examples/next-ssr/src/components/BalanceTable.tsx +43 -0
  596. package/examples/next-ssr/src/components/LedgerCard.test.tsx +96 -0
  597. package/examples/next-ssr/src/components/LedgerCard.tsx +230 -0
  598. package/examples/next-ssr/src/components/LedgerError.tsx +32 -0
  599. package/examples/next-ssr/src/components/LedgerSections.tsx +22 -0
  600. package/examples/next-ssr/src/components/MyBalances.test.tsx +253 -0
  601. package/examples/next-ssr/src/components/MyBalances.tsx +129 -0
  602. package/examples/next-ssr/src/components/SessionBadge.tsx +18 -0
  603. package/examples/next-ssr/src/components/hydration.test.tsx +259 -0
  604. package/examples/next-ssr/src/components/server-data.ts +10 -0
  605. package/examples/next-ssr/src/format.ts +46 -0
  606. package/examples/next-ssr/src/ledgers.ts +38 -0
  607. package/examples/next-ssr/src/server/balance-route.test.ts +151 -0
  608. package/examples/next-ssr/src/server/balance-route.ts +157 -0
  609. package/examples/next-ssr/src/server/error-summary.test.ts +34 -0
  610. package/examples/next-ssr/src/server/error-summary.ts +37 -0
  611. package/examples/next-ssr/src/server/parse-owner.test.ts +39 -0
  612. package/examples/next-ssr/src/server/parse-owner.ts +34 -0
  613. package/examples/next-ssr/src/server/prefetch-ledgers.test.ts +137 -0
  614. package/examples/next-ssr/src/server/prefetch-ledgers.ts +70 -0
  615. package/examples/next-ssr/src/server/read-balances.test.ts +103 -0
  616. package/examples/next-ssr/src/server/read-balances.ts +78 -0
  617. package/examples/next-ssr/src/server/request-client.rsc.test.tsx +110 -0
  618. package/examples/next-ssr/src/server/request-client.ts +24 -0
  619. package/examples/next-ssr/src/testing/mock-ledgers.ts +115 -0
  620. package/examples/next-ssr/src/testing/react-server-dom-turbopack.d.ts +15 -0
  621. package/examples/next-ssr/src/testing/setup.ts +10 -0
  622. package/examples/next-ssr/tsconfig.json +31 -0
  623. package/examples/next-ssr/vitest.config.ts +59 -0
  624. package/examples/node-agent-tool/.stackblitzrc +4 -0
  625. package/examples/node-agent-tool/README.md +165 -0
  626. package/examples/node-agent-tool/icp.yaml +11 -0
  627. package/examples/node-agent-tool/icrc1.did +88 -0
  628. package/examples/node-agent-tool/package.json +32 -0
  629. package/examples/node-agent-tool/src/canisters/icrc1.envelope.json +840 -0
  630. package/examples/node-agent-tool/src/canisters/icrc1.ts +78 -0
  631. package/examples/node-agent-tool/src/cli.test.ts +64 -0
  632. package/examples/node-agent-tool/src/cli.ts +23 -0
  633. package/examples/node-agent-tool/src/command-line.ts +159 -0
  634. package/examples/node-agent-tool/src/commands/balance.ts +73 -0
  635. package/examples/node-agent-tool/src/commands/demo.test.ts +51 -0
  636. package/examples/node-agent-tool/src/commands/demo.ts +359 -0
  637. package/examples/node-agent-tool/src/commands/info.ts +69 -0
  638. package/examples/node-agent-tool/src/commands/reads.test.ts +133 -0
  639. package/examples/node-agent-tool/src/commands/resend.test.ts +363 -0
  640. package/examples/node-agent-tool/src/commands/transfer.test.ts +313 -0
  641. package/examples/node-agent-tool/src/commands/transfer.ts +415 -0
  642. package/examples/node-agent-tool/src/commands/watch.test.ts +159 -0
  643. package/examples/node-agent-tool/src/commands/watch.ts +135 -0
  644. package/examples/node-agent-tool/src/commands/whoami.ts +36 -0
  645. package/examples/node-agent-tool/src/context.ts +20 -0
  646. package/examples/node-agent-tool/src/dispatch.ts +70 -0
  647. package/examples/node-agent-tool/src/failure.test.ts +59 -0
  648. package/examples/node-agent-tool/src/failure.ts +176 -0
  649. package/examples/node-agent-tool/src/identity.test.ts +242 -0
  650. package/examples/node-agent-tool/src/identity.ts +228 -0
  651. package/examples/node-agent-tool/src/input.ts +110 -0
  652. package/examples/node-agent-tool/src/ledgers.test.ts +74 -0
  653. package/examples/node-agent-tool/src/ledgers.ts +62 -0
  654. package/examples/node-agent-tool/src/mock-ledger.ts +211 -0
  655. package/examples/node-agent-tool/src/network.test.ts +128 -0
  656. package/examples/node-agent-tool/src/network.ts +101 -0
  657. package/examples/node-agent-tool/src/output.test.ts +208 -0
  658. package/examples/node-agent-tool/src/output.ts +163 -0
  659. package/examples/node-agent-tool/src/run.ts +73 -0
  660. package/examples/node-agent-tool/src/test-kit.ts +167 -0
  661. package/examples/node-agent-tool/tsconfig.json +18 -0
  662. package/examples/node-agent-tool/vitest.config.ts +8 -0
  663. package/examples/vite-wallet/.stackblitzrc +4 -0
  664. package/examples/vite-wallet/Cargo.lock +646 -0
  665. package/examples/vite-wallet/Cargo.toml +5 -0
  666. package/examples/vite-wallet/README.md +107 -0
  667. package/examples/vite-wallet/backend/Cargo.toml +12 -0
  668. package/examples/vite-wallet/backend/backend.did +37 -0
  669. package/examples/vite-wallet/backend/src/lib.rs +133 -0
  670. package/examples/vite-wallet/icp.yaml +44 -0
  671. package/examples/vite-wallet/index.html +13 -0
  672. package/examples/vite-wallet/ledger.did +54 -0
  673. package/examples/vite-wallet/package.json +46 -0
  674. package/examples/vite-wallet/public/.ic-assets.json5 +26 -0
  675. package/examples/vite-wallet/scripts/faucet-args.test.ts +30 -0
  676. package/examples/vite-wallet/scripts/faucet-args.ts +41 -0
  677. package/examples/vite-wallet/scripts/faucet.ts +41 -0
  678. package/examples/vite-wallet/src/AddressBook.test.tsx +191 -0
  679. package/examples/vite-wallet/src/AddressBook.tsx +147 -0
  680. package/examples/vite-wallet/src/App.tsx +53 -0
  681. package/examples/vite-wallet/src/Balance.test.tsx +57 -0
  682. package/examples/vite-wallet/src/Balance.tsx +58 -0
  683. package/examples/vite-wallet/src/EnvironmentPanel.test.tsx +83 -0
  684. package/examples/vite-wallet/src/EnvironmentPanel.tsx +103 -0
  685. package/examples/vite-wallet/src/ErrorNote.tsx +70 -0
  686. package/examples/vite-wallet/src/Profile.test.tsx +145 -0
  687. package/examples/vite-wallet/src/Profile.tsx +92 -0
  688. package/examples/vite-wallet/src/SendIcp.test.tsx +231 -0
  689. package/examples/vite-wallet/src/SendIcp.tsx +244 -0
  690. package/examples/vite-wallet/src/SendToContact.test.tsx +113 -0
  691. package/examples/vite-wallet/src/SendToContact.tsx +102 -0
  692. package/examples/vite-wallet/src/SignIn.test.tsx +106 -0
  693. package/examples/vite-wallet/src/SignIn.tsx +120 -0
  694. package/examples/vite-wallet/src/Wallet.test.tsx +89 -0
  695. package/examples/vite-wallet/src/account-switch.test.tsx +108 -0
  696. package/examples/vite-wallet/src/auth/dev-accounts.test.ts +113 -0
  697. package/examples/vite-wallet/src/auth/dev-accounts.ts +216 -0
  698. package/examples/vite-wallet/src/auth/local-page.test.ts +28 -0
  699. package/examples/vite-wallet/src/auth/local-page.ts +20 -0
  700. package/examples/vite-wallet/src/auth/wallet-auth.test.ts +159 -0
  701. package/examples/vite-wallet/src/auth/wallet-auth.ts +131 -0
  702. package/examples/vite-wallet/src/canisters/backend.envelope.json +275 -0
  703. package/examples/vite-wallet/src/canisters/backend.ts +58 -0
  704. package/examples/vite-wallet/src/canisters/ledger.envelope.json +451 -0
  705. package/examples/vite-wallet/src/canisters/ledger.ts +56 -0
  706. package/examples/vite-wallet/src/client.ts +45 -0
  707. package/examples/vite-wallet/src/main.tsx +18 -0
  708. package/examples/vite-wallet/src/optimistic-contacts.ts +113 -0
  709. package/examples/vite-wallet/src/refusal.ts +17 -0
  710. package/examples/vite-wallet/src/styles.css +282 -0
  711. package/examples/vite-wallet/src/test/test-wallet.tsx +322 -0
  712. package/examples/vite-wallet/src/token.ts +50 -0
  713. package/examples/vite-wallet/src/transfer.test.ts +178 -0
  714. package/examples/vite-wallet/src/transfer.ts +110 -0
  715. package/examples/vite-wallet/src/use-canisters.ts +49 -0
  716. package/examples/vite-wallet/tsconfig.json +19 -0
  717. package/examples/vite-wallet/vite.config.ts +47 -0
  718. package/examples/vite-wallet/vitest.config.ts +17 -0
  719. package/package.json +9 -4
  720. package/packages/core/.size-limit.js +47 -0
  721. package/packages/core/LICENSE +21 -0
  722. package/packages/core/README.md +84 -0
  723. package/packages/core/llms.txt +610 -0
  724. package/packages/core/package.json +89 -0
  725. package/packages/core/src/call.ts +961 -0
  726. package/packages/core/src/canister.ts +259 -0
  727. package/packages/core/src/client.ts +2061 -0
  728. package/packages/core/src/errors.ts +1079 -0
  729. package/packages/core/src/hydration.ts +239 -0
  730. package/packages/core/src/index.ts +23 -0
  731. package/packages/core/src/keys.ts +108 -0
  732. package/packages/core/src/management.ts +138 -0
  733. package/packages/core/src/network.ts +1020 -0
  734. package/packages/core/src/options.ts +1000 -0
  735. package/packages/core/src/runtime.ts +50 -0
  736. package/packages/core/src/testing/fake-replica.ts +1166 -0
  737. package/packages/core/src/testing/index.ts +16 -0
  738. package/packages/core/src/testing/test-auth.ts +340 -0
  739. package/packages/core/src/testing/test-client.ts +559 -0
  740. package/packages/core/src/types.ts +541 -0
  741. package/packages/core/src/units.ts +299 -0
  742. package/packages/core/src/version.ts +4 -0
  743. package/packages/core/tests/canister-helpers.ts +187 -0
  744. package/packages/core/tests/canister-id.test.ts +158 -0
  745. package/packages/core/tests/canister-unwrap.test.ts +94 -0
  746. package/packages/core/tests/canister.test-d.ts +406 -0
  747. package/packages/core/tests/canister.test.ts +1921 -0
  748. package/packages/core/tests/client-account-change.test.ts +131 -0
  749. package/packages/core/tests/client-agents.test.ts +402 -0
  750. package/packages/core/tests/client-auth-network.test.ts +719 -0
  751. package/packages/core/tests/client-disposed.test.ts +403 -0
  752. package/packages/core/tests/client-helpers.ts +136 -0
  753. package/packages/core/tests/client-hydration.test.ts +166 -0
  754. package/packages/core/tests/client-pinned.test.ts +626 -0
  755. package/packages/core/tests/client-session.test.ts +348 -0
  756. package/packages/core/tests/client.test-d.ts +181 -0
  757. package/packages/core/tests/client.test.ts +1162 -0
  758. package/packages/core/tests/encode.test.ts +171 -0
  759. package/packages/core/tests/env-canister-id.test.ts +666 -0
  760. package/packages/core/tests/env-cookie-copies.test.ts +182 -0
  761. package/packages/core/tests/error-guard.test.ts +222 -0
  762. package/packages/core/tests/errors.test-d.ts +280 -0
  763. package/packages/core/tests/errors.test.ts +1359 -0
  764. package/packages/core/tests/fixtures/README.md +28 -0
  765. package/packages/core/tests/fixtures/accounts.did +21 -0
  766. package/packages/core/tests/fixtures/accounts.envelope.json +367 -0
  767. package/packages/core/tests/fixtures/accounts.ts +26 -0
  768. package/packages/core/tests/fixtures/archive.did +31 -0
  769. package/packages/core/tests/fixtures/archive.envelope.json +259 -0
  770. package/packages/core/tests/fixtures/archive.ts +55 -0
  771. package/packages/core/tests/fixtures/icrc1.did +88 -0
  772. package/packages/core/tests/fixtures/icrc1.envelope.json +840 -0
  773. package/packages/core/tests/fixtures/icrc1.ts +78 -0
  774. package/packages/core/tests/fixtures/management.did +724 -0
  775. package/packages/core/tests/fixtures/management.envelope.json +4455 -0
  776. package/packages/core/tests/fixtures/management.ts +486 -0
  777. package/packages/core/tests/fixtures/shapes.did +33 -0
  778. package/packages/core/tests/fixtures/shapes.envelope.json +440 -0
  779. package/packages/core/tests/fixtures/shapes.ts +56 -0
  780. package/packages/core/tests/fixtures/skippable.did +15 -0
  781. package/packages/core/tests/fixtures/skippable.envelope.json +182 -0
  782. package/packages/core/tests/fixtures/skippable.ts +33 -0
  783. package/packages/core/tests/func.test.ts +154 -0
  784. package/packages/core/tests/identity-attributes.test.ts +214 -0
  785. package/packages/core/tests/keys.test.ts +289 -0
  786. package/packages/core/tests/management.test.ts +206 -0
  787. package/packages/core/tests/network-helpers.ts +121 -0
  788. package/packages/core/tests/network.test-d.ts +78 -0
  789. package/packages/core/tests/network.test.ts +1051 -0
  790. package/packages/core/tests/options.test.ts +1594 -0
  791. package/packages/core/tests/redirect-sign-in.test.ts +393 -0
  792. package/packages/core/tests/resend.test-d.ts +81 -0
  793. package/packages/core/tests/resend.test.ts +723 -0
  794. package/packages/core/tests/skip.test-d.ts +125 -0
  795. package/packages/core/tests/testing-entry.test.ts +73 -0
  796. package/packages/core/tests/testing-fake-replica.test.ts +1242 -0
  797. package/packages/core/tests/testing-test-auth.test-d.ts +84 -0
  798. package/packages/core/tests/testing-test-auth.test.ts +483 -0
  799. package/packages/core/tests/testing-test-client.test-d.ts +184 -0
  800. package/packages/core/tests/testing-test-client.test.ts +1272 -0
  801. package/packages/core/tests/time-realm.test.ts +63 -0
  802. package/packages/core/tests/time.test.ts +238 -0
  803. package/packages/core/tests/traps/README.md +39 -0
  804. package/packages/core/tests/traps/anonymous-write.test.ts +145 -0
  805. package/packages/core/tests/traps/classification-table.test.ts +256 -0
  806. package/packages/core/tests/traps/err-arm.test.ts +144 -0
  807. package/packages/core/tests/traps/fetch-spy.ts +39 -0
  808. package/packages/core/tests/traps/invalidation-after-write.test.ts +178 -0
  809. package/packages/core/tests/traps/ledger.ts +248 -0
  810. package/packages/core/tests/traps/root-key-off-local.test.ts +130 -0
  811. package/packages/core/tests/traps/stale-data-across-users.test.ts +173 -0
  812. package/packages/core/tests/traps/unresolved-name.test.ts +155 -0
  813. package/packages/core/tests/traps/update-as-query.test.ts +244 -0
  814. package/packages/core/tests/traps/update-not-resent.test.ts +126 -0
  815. package/packages/core/tests/traps/update-resent-when-never-delivered.test.ts +159 -0
  816. package/packages/core/tests/traps.test-d.ts +476 -0
  817. package/packages/core/tests/units.test-d.ts +118 -0
  818. package/packages/core/tests/units.test.ts +634 -0
  819. package/packages/core/tsconfig.json +19 -0
  820. package/packages/core/tsconfig.typecheck.json +16 -0
  821. package/packages/core/vitest.config.ts +13 -0
  822. package/packages/mcp/LICENSE +21 -0
  823. package/packages/mcp/README.md +189 -0
  824. package/packages/mcp/package.json +62 -0
  825. package/packages/mcp/src/candid/candid-text.test.ts +455 -0
  826. package/packages/mcp/src/candid/candid-text.ts +501 -0
  827. package/packages/mcp/src/candid/contract.test.ts +398 -0
  828. package/packages/mcp/src/candid/formats.ts +155 -0
  829. package/packages/mcp/src/candid/hex.ts +29 -0
  830. package/packages/mcp/src/candid/json-schema.test.ts +758 -0
  831. package/packages/mcp/src/candid/json-schema.ts +454 -0
  832. package/packages/mcp/src/candid/json.test.ts +1207 -0
  833. package/packages/mcp/src/candid/json.ts +1053 -0
  834. package/packages/mcp/src/candid/schema-walk.ts +189 -0
  835. package/packages/mcp/src/candid/type-text.test.ts +179 -0
  836. package/packages/mcp/src/candid/type-text.ts +238 -0
  837. package/packages/mcp/src/cli.ts +134 -0
  838. package/packages/mcp/src/identity.ts +188 -0
  839. package/packages/mcp/src/interface/cache.test.ts +48 -0
  840. package/packages/mcp/src/interface/cache.ts +68 -0
  841. package/packages/mcp/src/interface/cli-cache.test.ts +150 -0
  842. package/packages/mcp/src/interface/cli-cache.ts +122 -0
  843. package/packages/mcp/src/interface/compile-worker.test.ts +246 -0
  844. package/packages/mcp/src/interface/compile-worker.ts +290 -0
  845. package/packages/mcp/src/interface/compile.test.ts +155 -0
  846. package/packages/mcp/src/interface/compile.ts +281 -0
  847. package/packages/mcp/src/interface/docs.test.ts +105 -0
  848. package/packages/mcp/src/interface/docs.ts +86 -0
  849. package/packages/mcp/src/interface/fixtures/icrc1.did +88 -0
  850. package/packages/mcp/src/interface/hex.ts +8 -0
  851. package/packages/mcp/src/interface/ids.test.ts +71 -0
  852. package/packages/mcp/src/interface/ids.ts +87 -0
  853. package/packages/mcp/src/interface/loader.test.ts +804 -0
  854. package/packages/mcp/src/interface/loader.ts +519 -0
  855. package/packages/mcp/src/interface/read-candid.test.ts +300 -0
  856. package/packages/mcp/src/interface/read-candid.ts +259 -0
  857. package/packages/mcp/src/interface/types.ts +113 -0
  858. package/packages/mcp/src/network.ts +122 -0
  859. package/packages/mcp/src/options.ts +149 -0
  860. package/packages/mcp/src/server/approval.ts +159 -0
  861. package/packages/mcp/src/server/naming.ts +43 -0
  862. package/packages/mcp/src/server/results.ts +102 -0
  863. package/packages/mcp/src/server/server.ts +797 -0
  864. package/packages/mcp/src/server/tools.ts +116 -0
  865. package/packages/mcp/src/usage.ts +40 -0
  866. package/packages/mcp/tests/approval.test.ts +60 -0
  867. package/packages/mcp/tests/identity.test.ts +234 -0
  868. package/packages/mcp/tests/naming.test.ts +62 -0
  869. package/packages/mcp/tests/options.test.ts +140 -0
  870. package/packages/mcp/tests/server.test.ts +997 -0
  871. package/packages/mcp/tsconfig.json +19 -0
  872. package/packages/mcp/tsconfig.typecheck.json +10 -0
  873. package/packages/mcp/vitest.config.ts +10 -0
  874. package/packages/react/LICENSE +21 -0
  875. package/packages/react/README.md +328 -0
  876. package/packages/react/llms.txt +95 -0
  877. package/packages/react/package.json +95 -0
  878. package/packages/react/src/index.tsx +678 -0
  879. package/packages/react/tests/abandoned-render.test.tsx +816 -0
  880. package/packages/react/tests/auth.test.tsx +315 -0
  881. package/packages/react/tests/caller-keys.test.tsx +2334 -0
  882. package/packages/react/tests/client-time.test.ts +40 -0
  883. package/packages/react/tests/entry.test.ts +52 -0
  884. package/packages/react/tests/helpers.tsx +291 -0
  885. package/packages/react/tests/hydration.test.tsx +262 -0
  886. package/packages/react/tests/ownership.test.tsx +260 -0
  887. package/packages/react/tests/provider.server.test.tsx +169 -0
  888. package/packages/react/tests/query-client.test.tsx +107 -0
  889. package/packages/react/tests/strict-mode.test.tsx +288 -0
  890. package/packages/react/tests/suspense.test-d.tsx +357 -0
  891. package/packages/react/tests/two-query-cores.test.tsx +135 -0
  892. package/packages/react/tests/types.test-d.tsx +63 -0
  893. package/packages/react/tsconfig.json +23 -0
  894. package/packages/react/tsconfig.test.json +8 -0
  895. package/packages/react/tsconfig.typecheck.json +16 -0
  896. package/packages/react/vitest.config.ts +39 -0
  897. package/packages/vite-plugin/LICENSE +21 -0
  898. package/packages/vite-plugin/README.md +532 -0
  899. package/packages/vite-plugin/package.json +76 -0
  900. package/packages/vite-plugin/src/describe-value.test.ts +43 -0
  901. package/packages/vite-plugin/src/describe-value.ts +167 -0
  902. package/packages/vite-plugin/src/dev-environment.test.ts +747 -0
  903. package/packages/vite-plugin/src/dev-environment.ts +510 -0
  904. package/packages/vite-plugin/src/env.ts +431 -0
  905. package/packages/vite-plugin/src/fetch.ts +348 -0
  906. package/packages/vite-plugin/src/file-identity.ts +106 -0
  907. package/packages/vite-plugin/src/generate.ts +691 -0
  908. package/packages/vite-plugin/src/index.test.ts +1243 -0
  909. package/packages/vite-plugin/src/index.ts +2565 -0
  910. package/packages/vite-plugin/src/interface.ts +339 -0
  911. package/packages/vite-plugin/src/local-host.ts +34 -0
  912. package/packages/vite-plugin/src/options.ts +641 -0
  913. package/packages/vite-plugin/src/principal-text.test.ts +87 -0
  914. package/packages/vite-plugin/src/principal-text.ts +98 -0
  915. package/packages/vite-plugin/tests/close.test.ts +141 -0
  916. package/packages/vite-plugin/tests/environment-mode.test.ts +136 -0
  917. package/packages/vite-plugin/tests/fake-ic.ts +214 -0
  918. package/packages/vite-plugin/tests/fetch-sdk.test.ts +27 -0
  919. package/packages/vite-plugin/tests/fetch.test.ts +1196 -0
  920. package/packages/vite-plugin/tests/fixtures/fake-cli/bin.js +90 -0
  921. package/packages/vite-plugin/tests/fixtures/fake-cli/package.json +8 -0
  922. package/packages/vite-plugin/tests/fixtures/icrc1.did +88 -0
  923. package/packages/vite-plugin/tests/generation.test.ts +1036 -0
  924. package/packages/vite-plugin/tests/icp-command.test.ts +194 -0
  925. package/packages/vite-plugin/tests/icp-timeout.test.ts +83 -0
  926. package/packages/vite-plugin/tests/no-process-exit.test.ts +83 -0
  927. package/packages/vite-plugin/tests/on-generated.test.ts +323 -0
  928. package/packages/vite-plugin/tests/output-collision.test.ts +329 -0
  929. package/packages/vite-plugin/tests/package.test.ts +62 -0
  930. package/packages/vite-plugin/tests/project.test.ts +2993 -0
  931. package/packages/vite-plugin/tests/support.ts +339 -0
  932. package/packages/vite-plugin/tests/timeout.test.ts +107 -0
  933. package/packages/vite-plugin/tsconfig.json +23 -0
  934. package/packages/vite-plugin/tsconfig.typecheck.json +16 -0
  935. package/packages/vite-plugin/vitest.config.ts +9 -0
  936. package/pnpm-workspace.yaml +23 -0
  937. package/scripts/ai-context-files.js +72 -0
  938. package/scripts/assemble-docs-site.mjs +485 -0
  939. package/scripts/assemble-docs-site.test.mjs +568 -0
  940. package/scripts/build-examples.js +58 -0
  941. package/scripts/check-ai-context.js +963 -0
  942. package/scripts/check-ai-context.test.mjs +811 -0
  943. package/scripts/check-exports.mjs +767 -0
  944. package/scripts/check-exports.test.mjs +1136 -0
  945. package/scripts/check-snippets/ambient.d.ts +428 -0
  946. package/scripts/check-snippets/app/generated/backend.did +7 -0
  947. package/scripts/check-snippets/app/generated/backend.ts +15 -0
  948. package/scripts/check-snippets/app/generated/icrc1.did +88 -0
  949. package/scripts/check-snippets/app/generated/icrc1.ts +78 -0
  950. package/scripts/check-snippets/app/globals.ts +26 -0
  951. package/scripts/check-snippets/docs/guides/App.tsx +21 -0
  952. package/scripts/check-snippets/docs/guides/app/providers.tsx +23 -0
  953. package/scripts/check-snippets/docs/guides/auth-client.ts +47 -0
  954. package/scripts/check-snippets/docs/guides/canisters/backend.ts +3 -0
  955. package/scripts/check-snippets/docs/guides/canisters/icrc1.ts +3 -0
  956. package/scripts/check-snippets/docs/guides/components/LedgerCard.tsx +26 -0
  957. package/scripts/check-snippets/docs/guides/components/shell.tsx +11 -0
  958. package/scripts/check-snippets/docs/guides/components/sign-in-page.tsx +29 -0
  959. package/scripts/check-snippets/docs/guides/components/slow-section.tsx +5 -0
  960. package/scripts/check-snippets/docs/guides/generated/accounts.ts +95 -0
  961. package/scripts/check-snippets/docs/guides/generated/ckbtc_minter.ts +40 -0
  962. package/scripts/check-snippets/docs/guides/generated/icp_ledger.ts +267 -0
  963. package/scripts/check-snippets/docs/guides/ledger.ts +10 -0
  964. package/scripts/check-snippets/docs/guides/server/request-client.ts +8 -0
  965. package/scripts/check-snippets.mjs +993 -0
  966. package/scripts/check-snippets.test.mjs +338 -0
  967. package/scripts/check-stack.mjs +501 -0
  968. package/scripts/check-stack.test.mjs +800 -0
  969. package/scripts/clean-node-modules.js +67 -0
  970. package/scripts/example-projects.js +58 -0
  971. package/scripts/export-budget.mjs +145 -0
  972. package/scripts/faults.json +6749 -0
  973. package/scripts/lib/fault-workspace.mjs +210 -0
  974. package/scripts/lib/io.mjs +29 -0
  975. package/scripts/lib/io.test.mjs +38 -0
  976. package/scripts/lib/markdown-fences.mjs +56 -0
  977. package/scripts/lib/markdown-fences.test.mjs +84 -0
  978. package/scripts/lib/package-exports.mjs +54 -0
  979. package/scripts/lib/package-exports.test.mjs +66 -0
  980. package/scripts/lib/release-pr-steps.mjs +47 -0
  981. package/scripts/release-tag.mjs +95 -0
  982. package/scripts/release-tag.test.mjs +278 -0
  983. package/scripts/release-workflow.test.mjs +98 -0
  984. package/scripts/release.js +167 -0
  985. package/scripts/release.test.mjs +347 -0
  986. package/scripts/removed-v3-names.js +125 -0
  987. package/scripts/size-app/.size-limit.js +90 -0
  988. package/scripts/size-app/core.js +3 -0
  989. package/scripts/size-app/package.json +24 -0
  990. package/scripts/size-app/react.js +4 -0
  991. package/scripts/size-app.test.mjs +73 -0
  992. package/scripts/sync-ai-context-versions.js +102 -0
  993. package/scripts/sync-example-versions.js +151 -0
  994. package/scripts/test-examples-published.mjs +1585 -0
  995. package/scripts/test-examples-published.test.mjs +1586 -0
  996. package/scripts/traps/any-method-vars-not-a-list.json +5 -0
  997. package/scripts/traps/canister-method-unknown.json +5 -0
  998. package/scripts/traps/client-says-who-calls-once.json +5 -0
  999. package/scripts/traps/client-says-who-calls.json +5 -0
  1000. package/scripts/traps/err-only-on-canister-err.json +14 -0
  1001. package/scripts/traps/format-units-refuses-missing-value.json +5 -0
  1002. package/scripts/traps/format-units-refuses-number.json +5 -0
  1003. package/scripts/traps/format-units-unknown-option.json +5 -0
  1004. package/scripts/traps/guard-err-is-the-methods.json +5 -0
  1005. package/scripts/traps/guard-false-keeps-every-kind.json +5 -0
  1006. package/scripts/traps/internals-not-exported.json +9 -0
  1007. package/scripts/traps/network-refuses-unknown-name.json +5 -0
  1008. package/scripts/traps/never-err-has-no-type.json +5 -0
  1009. package/scripts/traps/options-method-unknown.json +5 -0
  1010. package/scripts/traps/options-vars-wrong-type.json +5 -0
  1011. package/scripts/traps/parse-units-refuses-number.json +5 -0
  1012. package/scripts/traps/parse-units-unknown-option.json +5 -0
  1013. package/scripts/traps/principal-is-not-plain-text.json +14 -0
  1014. package/scripts/traps/query-options-refuses-oneway.json +5 -0
  1015. package/scripts/traps/query-options-refuses-update.json +5 -0
  1016. package/scripts/traps/refuse-next-unknown-option.json +5 -0
  1017. package/scripts/traps/resend-key-reads-the-methods-arg.json +5 -0
  1018. package/scripts/traps/resend-states-its-dedupe-key.json +5 -0
  1019. package/scripts/traps/suspense-refuses-argument-skip.json +5 -0
  1020. package/scripts/traps/suspense-refuses-skippable-read.json +5 -0
  1021. package/scripts/traps/suspense-refuses-vars-skip-fits.json +5 -0
  1022. package/scripts/traps/test-handler-method-unknown.json +5 -0
  1023. package/scripts/traps/test-handler-nat-is-bigint.json +5 -0
  1024. package/scripts/traps/test-handler-takes-the-domain-value.json +5 -0
  1025. package/scripts/traps/vars-of-two-arguments-are-the-tuple.json +5 -0
  1026. package/scripts/typecheck-examples.js +122 -0
  1027. package/scripts/verify-faults.mjs +375 -0
  1028. package/scripts/verify-faults.test.mjs +402 -0
  1029. package/scripts/verify-package-artifacts.mjs +590 -0
  1030. package/scripts/verify-peer-floors.js +399 -0
  1031. package/scripts/verify-peer-floors.test.mjs +422 -0
  1032. package/scripts/verify-test-fails-rules.mjs +104 -0
  1033. package/scripts/verify-test-fails-rules.test.mjs +215 -0
  1034. package/scripts/verify-test-fails.js +371 -0
  1035. package/scripts/verify-traps.mjs +505 -0
  1036. package/scripts/verify-traps.test.mjs +521 -0
  1037. package/scripts/verify-typescript-floor.js +239 -0
  1038. package/skill-packages/ic-reactor/SKILL.md +103 -0
  1039. package/stack.json +8 -0
  1040. package/tsconfig.base.json +23 -0
  1041. package/tsconfig.json +8 -0
@@ -0,0 +1,1242 @@
1
+ /**
2
+ * `createFakeReplica`, the fake replica behind `createTestClient` (internal:
3
+ * the testing entry exports only `createTestClient`): a fake replica an
4
+ * `HttpAgent` is given as its `fetch`, so nothing global is stubbed.
5
+ *
6
+ * It refuses what a replica refuses, so a test that passes through it cannot
7
+ * be hiding a client that signs with the wrong key; it rejects a call whose
8
+ * handler throws as a replica rejects a trap, once, rather than failing the
9
+ * HTTP request the agent then retries; it answers only its own host, so an
10
+ * agent built for another fails at once; and it can fail a call the way a
11
+ * network does: with a reject code, a lost reply or an HTTP refusal.
12
+ *
13
+ * The agents here are raw `HttpAgent`s, and the canisters speak bytes, so the
14
+ * tests read the errors as `@icp-sdk/core` raises them, by `kind`, code name
15
+ * and reject code or HTTP status, which is how a classifier has to read them.
16
+ */
17
+ import { describe, it, expect, afterEach, vi } from "vitest"
18
+ import {
19
+ Actor,
20
+ ErrorKindEnum,
21
+ HttpAgent,
22
+ QueryResponseStatus,
23
+ SignIdentity,
24
+ type HttpAgentOptions,
25
+ type PublicKey,
26
+ type Signature,
27
+ } from "@icp-sdk/core/agent"
28
+ import { IDL } from "@icp-sdk/core/candid"
29
+ import {
30
+ DelegationChain,
31
+ DelegationIdentity,
32
+ ECDSAKeyIdentity,
33
+ Ed25519KeyIdentity,
34
+ } from "@icp-sdk/core/identity"
35
+ import { Secp256k1KeyIdentity } from "@icp-sdk/core/identity/secp256k1"
36
+ import { Principal } from "@icp-sdk/core/principal"
37
+ import {
38
+ createFakeReplica,
39
+ type FakeCanister,
40
+ type FakeReplica,
41
+ } from "../src/testing/fake-replica.js"
42
+
43
+ const CANISTER = "rdmx6-jaaaa-aaaaa-aaadq-cai"
44
+ const OTHER_CANISTER = "rrkah-fqaaa-aaaaa-aaaaq-cai"
45
+ const DEFAULT_HOST = "http://127.0.0.1:8000"
46
+
47
+ // A fake replica is handed to agents, never installed: whatever a test does,
48
+ // the global `fetch` is the one the file started with.
49
+ const originalFetch = globalThis.fetch
50
+ afterEach(() => {
51
+ expect(globalThis.fetch).toBe(originalFetch)
52
+ })
53
+
54
+ const EMPTY_ARG = new Uint8Array(IDL.encode([], []))
55
+ const encodeCaller = (caller: Principal) =>
56
+ new Uint8Array(IDL.encode([IDL.Principal], [caller]))
57
+ const decodeCaller = (reply: Uint8Array) =>
58
+ IDL.decode([IDL.Principal], reply)[0] as unknown as Principal
59
+
60
+ const answerWithCaller: NonNullable<FakeCanister["update"]> = (
61
+ _method,
62
+ _arg,
63
+ { caller }
64
+ ) => encodeCaller(caller)
65
+
66
+ /** The reply, as text, of a canister that answers with a fixed string. */
67
+ const answerWithText = (text: string) => () =>
68
+ new Uint8Array(IDL.encode([IDL.Text], [text]))
69
+ const decodeText = (reply: Uint8Array) =>
70
+ IDL.decode([IDL.Text], reply)[0] as string
71
+
72
+ const whoamiInterface: IDL.InterfaceFactory = ({ IDL }) =>
73
+ IDL.Service({ whoami_query: IDL.Func([], [IDL.Principal], ["query"]) })
74
+
75
+ type NodeKeyStore = NonNullable<HttpAgentOptions["subnetNodeKeyExpirableStore"]>
76
+
77
+ /**
78
+ * A store of node keys for one agent. By default an agent shares one per host
79
+ * through IndexedDB, which the test setup installs.
80
+ */
81
+ function nodeKeysOfOneAgent(): NodeKeyStore {
82
+ const entries = new Map<string, Parameters<NodeKeyStore["set"]>[1]>()
83
+ return {
84
+ expirationTime: 60_000,
85
+ get: (key) => Promise.resolve(entries.get(key)),
86
+ set: (key, value) => {
87
+ entries.set(key, value)
88
+ return Promise.resolve()
89
+ },
90
+ delete: (key) => {
91
+ entries.delete(key)
92
+ return Promise.resolve()
93
+ },
94
+ }
95
+ }
96
+
97
+ /** An agent that talks to `replica`, and only to it. */
98
+ function agentOn(
99
+ replica: FakeReplica,
100
+ options: Partial<HttpAgentOptions> = {}
101
+ ): HttpAgent {
102
+ return HttpAgent.createSync({
103
+ host: replica.host,
104
+ fetch: replica.fetch,
105
+ rootKey: replica.rootKey,
106
+ // A refusal is final; retrying it only slows the test down.
107
+ retryTimes: 0,
108
+ subnetNodeKeyExpirableStore: nodeKeysOfOneAgent(),
109
+ ...options,
110
+ })
111
+ }
112
+
113
+ /** Retries at once, so a test can count what an agent re-sends. */
114
+ const retriesAtOnce = (retryTimes: number): Partial<HttpAgentOptions> => ({
115
+ retryTimes,
116
+ backoffStrategy: () => ({ next: () => 0 }),
117
+ })
118
+
119
+ const update = (
120
+ agent: HttpAgent,
121
+ methodName = "whoami",
122
+ canisterId = CANISTER
123
+ ) => agent.update(canisterId, { methodName, arg: EMPTY_ARG })
124
+
125
+ /** The reply of a query that is answered, as the agent hands it over. */
126
+ async function queryReply(
127
+ agent: HttpAgent,
128
+ methodName = "whoami_query",
129
+ canisterId = CANISTER
130
+ ) {
131
+ const response = await agent.query(canisterId, {
132
+ methodName,
133
+ arg: EMPTY_ARG,
134
+ })
135
+ if (response.status !== QueryResponseStatus.Replied) {
136
+ throw new Error(`the query was rejected with code ${response.reject_code}`)
137
+ }
138
+ return response.reply.arg
139
+ }
140
+
141
+ /** What an agent call rejects with. */
142
+ const rejection = (call: Promise<unknown>): Promise<unknown> =>
143
+ call.then(
144
+ () => {
145
+ throw new Error("expected the call to reject")
146
+ },
147
+ (error: unknown) => error
148
+ )
149
+
150
+ /**
151
+ * What a classifier reads off an `@icp-sdk/core` error: structurally, by
152
+ * `kind` and the code's `name`, never by `instanceof`, so that an error from
153
+ * another copy of the SDK reads the same.
154
+ */
155
+ function shapeOf(error: unknown) {
156
+ const { kind, code } = error as {
157
+ kind?: unknown
158
+ code?: {
159
+ name?: unknown
160
+ rejectCode?: unknown
161
+ rejectMessage?: unknown
162
+ rejectErrorCode?: unknown
163
+ status?: unknown
164
+ }
165
+ }
166
+ return {
167
+ kind,
168
+ code: code?.name,
169
+ rejectCode: code?.rejectCode,
170
+ rejectMessage: code?.rejectMessage,
171
+ rejectErrorCode: code?.rejectErrorCode,
172
+ status: code?.status,
173
+ }
174
+ }
175
+
176
+ describe("a fake replica an agent is given as its fetch", () => {
177
+ it("answers update calls and queries through agent.update and agent.query", async () => {
178
+ const identity = Ed25519KeyIdentity.generate()
179
+ const replica = createFakeReplica({
180
+ canisters: {
181
+ [CANISTER]: { update: answerWithCaller, query: answerWithCaller },
182
+ },
183
+ })
184
+ const agent = agentOn(replica, { identity })
185
+
186
+ const updated = await update(agent)
187
+ const queried = await queryReply(agent)
188
+
189
+ expect(decodeCaller(updated.reply).toText()).toBe(
190
+ identity.getPrincipal().toText()
191
+ )
192
+ expect(decodeCaller(queried).toText()).toBe(
193
+ identity.getPrincipal().toText()
194
+ )
195
+ expect(
196
+ replica.requests.filter((request) => request.methodName)
197
+ ).toMatchObject([
198
+ { endpoint: "call", canisterId: CANISTER, methodName: "whoami" },
199
+ { endpoint: "query", canisterId: CANISTER, methodName: "whoami_query" },
200
+ ])
201
+ })
202
+
203
+ it("serves an agent that fetches its root key, as it does from a local replica", async () => {
204
+ const replica = createFakeReplica({
205
+ canisters: { [CANISTER]: { query: answerWithCaller } },
206
+ })
207
+ const agent = HttpAgent.createSync({
208
+ host: replica.host,
209
+ fetch: replica.fetch,
210
+ shouldFetchRootKey: true,
211
+ retryTimes: 0,
212
+ subnetNodeKeyExpirableStore: nodeKeysOfOneAgent(),
213
+ })
214
+
215
+ expect(await agent.fetchRootKey()).toEqual(replica.rootKey)
216
+ expect(decodeCaller(await queryReply(agent)).isAnonymous()).toBe(true)
217
+ expect(replica.requests[0]).toEqual({ endpoint: "status" })
218
+ })
219
+
220
+ it("never reaches the global fetch", async () => {
221
+ const spy = vi.spyOn(globalThis, "fetch")
222
+ try {
223
+ const replica = createFakeReplica({
224
+ canisters: { [CANISTER]: { update: answerWithCaller } },
225
+ })
226
+ await update(agentOn(replica))
227
+
228
+ expect(spy).not.toHaveBeenCalled()
229
+ expect(replica.requests.length).toBeGreaterThan(0)
230
+ } finally {
231
+ spy.mockRestore()
232
+ }
233
+ })
234
+
235
+ it("has no state of a fake created before it", async () => {
236
+ // A fake made earlier, and left running, does not see this one's calls.
237
+ const earlier = createFakeReplica({
238
+ canisters: { [CANISTER]: { query: answerWithCaller } },
239
+ })
240
+ const replica = createFakeReplica({
241
+ canisters: { [CANISTER]: { query: answerWithCaller } },
242
+ })
243
+
244
+ await queryReply(agentOn(replica))
245
+
246
+ expect(earlier.requests).toEqual([])
247
+ expect(replica.requests.length).toBeGreaterThan(0)
248
+ })
249
+ })
250
+
251
+ describe("two fake replicas in one process", () => {
252
+ /** A canister that counts its calls and answers with its fake's name. */
253
+ function namedCanister(name: string) {
254
+ const state = { updates: 0 }
255
+ const canister: FakeCanister = {
256
+ query: answerWithText(name),
257
+ update() {
258
+ state.updates += 1
259
+ return new Uint8Array(
260
+ IDL.encode([IDL.Text], [`${name} #${state.updates}`])
261
+ )
262
+ },
263
+ }
264
+ return { state, canister }
265
+ }
266
+
267
+ it("each sign with a root key of their own", () => {
268
+ const first = createFakeReplica()
269
+ const second = createFakeReplica()
270
+
271
+ expect(first.rootKey).not.toEqual(second.rootKey)
272
+ })
273
+
274
+ it("serve two agents on one host with no crosstalk", async () => {
275
+ // The same host on purpose: an injected fetch does not depend on it.
276
+ const a = namedCanister("alpha")
277
+ const b = namedCanister("beta")
278
+ const alpha = createFakeReplica({ canisters: { [CANISTER]: a.canister } })
279
+ const beta = createFakeReplica({ canisters: { [CANISTER]: b.canister } })
280
+ expect(alpha.host).toBe(beta.host)
281
+ const agentA = agentOn(alpha)
282
+ const agentB = agentOn(beta)
283
+
284
+ const [queryA, queryB, updateA, updateB] = await Promise.all([
285
+ queryReply(agentA, "greet"),
286
+ queryReply(agentB, "greet"),
287
+ update(agentA, "bump"),
288
+ update(agentB, "bump"),
289
+ ])
290
+
291
+ expect(decodeText(queryA)).toBe("alpha")
292
+ expect(decodeText(queryB)).toBe("beta")
293
+ expect(decodeText(updateA.reply)).toBe("alpha #1")
294
+ expect(decodeText(updateB.reply)).toBe("beta #1")
295
+ // Each fake ran its own canister once and logged only its own requests.
296
+ expect([a.state.updates, b.state.updates]).toEqual([1, 1])
297
+ const calls = (replica: FakeReplica) =>
298
+ replica.requests.filter((request) => request.methodName)
299
+ expect(calls(alpha)).toHaveLength(2)
300
+ expect(calls(beta)).toHaveLength(2)
301
+ })
302
+
303
+ it("serve agents that share the default node-key store, which refresh what the other fake cached", async () => {
304
+ // The agent caches a subnet's node keys under a name that holds only the
305
+ // host, so two fakes on one host share the cache. Their node keys differ,
306
+ // so the agent finds the cached ones do not verify and reads fresh ones.
307
+ const alpha = createFakeReplica({
308
+ canisters: { [CANISTER]: { query: answerWithText("alpha") } },
309
+ })
310
+ const beta = createFakeReplica({
311
+ canisters: { [CANISTER]: { query: answerWithText("beta") } },
312
+ })
313
+ const shared = (replica: FakeReplica) =>
314
+ HttpAgent.createSync({
315
+ host: replica.host,
316
+ fetch: replica.fetch,
317
+ rootKey: replica.rootKey,
318
+ retryTimes: 0,
319
+ })
320
+
321
+ expect(decodeText(await queryReply(shared(alpha), "greet"))).toBe("alpha")
322
+ expect(decodeText(await queryReply(shared(beta), "greet"))).toBe("beta")
323
+ expect(decodeText(await queryReply(shared(alpha), "greet"))).toBe("alpha")
324
+ })
325
+
326
+ it("answer an agent that holds the other's root key with a certificate it rejects", async () => {
327
+ const alpha = createFakeReplica({
328
+ canisters: { [CANISTER]: { update: answerWithCaller } },
329
+ })
330
+ const beta = createFakeReplica()
331
+ // Alpha's answers, checked against beta's root key.
332
+ const agent = agentOn(alpha, { rootKey: beta.rootKey })
333
+
334
+ const error = await rejection(update(agent))
335
+
336
+ expect(shapeOf(error).kind).toBe(ErrorKindEnum.Trust)
337
+ // Control: the right root key accepts the same fake.
338
+ await expect(update(agentOn(alpha))).resolves.toBeDefined()
339
+ })
340
+
341
+ it("keep their own canisters: one does not run what only the other has", async () => {
342
+ const alpha = createFakeReplica({
343
+ canisters: { [CANISTER]: { query: answerWithText("alpha") } },
344
+ })
345
+ const beta = createFakeReplica({
346
+ canisters: { [OTHER_CANISTER]: { query: answerWithText("beta") } },
347
+ })
348
+
349
+ const response = await agentOn(beta).query(CANISTER, {
350
+ methodName: "greet",
351
+ arg: EMPTY_ARG,
352
+ })
353
+
354
+ expect(response).toMatchObject({
355
+ status: QueryResponseStatus.Rejected,
356
+ reject_code: 3,
357
+ })
358
+ expect(decodeText(await queryReply(agentOn(alpha), "greet"))).toBe("alpha")
359
+ })
360
+ })
361
+
362
+ describe("the fake replica's request checks", () => {
363
+ const trace = { runs: 0 }
364
+ const countingCaller: FakeCanister["update"] = (method, arg, context) => {
365
+ trace.runs += 1
366
+ return answerWithCaller(method, arg, context)
367
+ }
368
+
369
+ function replicaForChecks() {
370
+ trace.runs = 0
371
+ return createFakeReplica({
372
+ canisters: {
373
+ [CANISTER]: { update: countingCaller, query: countingCaller },
374
+ },
375
+ })
376
+ }
377
+
378
+ /** Why the most recent request was refused, if it was. */
379
+ const lastRefusal = (replica: FakeReplica) =>
380
+ replica.requests[replica.requests.length - 1]?.refused
381
+
382
+ /** A session key and a chain from a fresh root to it, as sign-in makes. */
383
+ async function delegatedSession(targets?: string[]) {
384
+ const root = Ed25519KeyIdentity.generate()
385
+ const session = await ECDSAKeyIdentity.generate()
386
+ const chain = await DelegationChain.create(
387
+ root,
388
+ session.getPublicKey(),
389
+ new Date(Date.now() + 60_000),
390
+ targets
391
+ ? { targets: targets.map((id) => Principal.fromText(id)) }
392
+ : undefined
393
+ )
394
+ return { root, session, chain }
395
+ }
396
+
397
+ /** Signs with its own key, and claims another principal as the sender. */
398
+ class ForgedSender extends SignIdentity {
399
+ private readonly key = Ed25519KeyIdentity.generate()
400
+ constructor(private readonly claimed: Principal) {
401
+ super()
402
+ }
403
+ getPublicKey(): PublicKey {
404
+ return this.key.getPublicKey()
405
+ }
406
+ sign(blob: Uint8Array): Promise<Signature> {
407
+ return this.key.sign(blob)
408
+ }
409
+ override getPrincipal(): Principal {
410
+ return this.claimed
411
+ }
412
+ }
413
+
414
+ it("refuses a forged sender before any canister sees it", async () => {
415
+ // The request is signed, and its signature verifies against the key it
416
+ // carries, but the principal it claims is not that key's.
417
+ const replica = replicaForChecks()
418
+ const victim = Ed25519KeyIdentity.generate().getPrincipal()
419
+ const agent = agentOn(replica, { identity: new ForgedSender(victim) })
420
+
421
+ const updated = await rejection(update(agent))
422
+ expect(shapeOf(updated)).toMatchObject({
423
+ kind: ErrorKindEnum.Protocol,
424
+ code: "HttpErrorCode",
425
+ status: 400,
426
+ })
427
+ expect(lastRefusal(replica)).toBe(
428
+ "the sender is not the principal of sender_pubkey"
429
+ )
430
+
431
+ const queried = await rejection(queryReply(agent))
432
+ expect(shapeOf(queried)).toMatchObject({
433
+ kind: ErrorKindEnum.Protocol,
434
+ status: 400,
435
+ })
436
+ expect(lastRefusal(replica)).toBe(
437
+ "the sender is not the principal of sender_pubkey"
438
+ )
439
+ expect(trace.runs).toBe(0)
440
+ })
441
+
442
+ it("answers an honest sender to the same canister as that sender", async () => {
443
+ const replica = replicaForChecks()
444
+ const identity = Ed25519KeyIdentity.generate()
445
+
446
+ const answer = await update(agentOn(replica, { identity }))
447
+
448
+ expect(decodeCaller(answer.reply).toText()).toBe(
449
+ identity.getPrincipal().toText()
450
+ )
451
+ expect(trace.runs).toBe(1)
452
+ })
453
+
454
+ it("accepts calls and queries signed through a delegation, as its root", async () => {
455
+ const replica = replicaForChecks()
456
+ const { root, session, chain } = await delegatedSession([CANISTER])
457
+ const agent = agentOn(replica, {
458
+ identity: DelegationIdentity.fromDelegation(session, chain),
459
+ })
460
+
461
+ expect(decodeCaller((await update(agent)).reply).toText()).toBe(
462
+ root.getPrincipal().toText()
463
+ )
464
+ expect(decodeCaller(await queryReply(agent)).toText()).toBe(
465
+ root.getPrincipal().toText()
466
+ )
467
+ expect(replica.requests.filter((request) => request.refused)).toEqual([])
468
+ expect(
469
+ replica.requests
470
+ .filter((request) => request.methodName)
471
+ .map((request) => request.caller)
472
+ ).toEqual([root.getPrincipal().toText(), root.getPrincipal().toText()])
473
+ })
474
+
475
+ it.each([
476
+ ["Ed25519", () => Ed25519KeyIdentity.generate()],
477
+ ["ECDSA P-256", () => ECDSAKeyIdentity.generate()],
478
+ ["secp256k1", () => Secp256k1KeyIdentity.generate()],
479
+ ])("accepts calls signed by an %s key", async (_, generate) => {
480
+ const identity = await generate()
481
+ const agent = agentOn(replicaForChecks(), { identity })
482
+
483
+ expect(decodeCaller((await update(agent)).reply).toText()).toBe(
484
+ identity.getPrincipal().toText()
485
+ )
486
+ })
487
+
488
+ it("answers an anonymous call as the anonymous principal", async () => {
489
+ const agent = agentOn(replicaForChecks())
490
+
491
+ expect(decodeCaller(await queryReply(agent)).isAnonymous()).toBe(true)
492
+ })
493
+
494
+ it("refuses a request signed by a key the delegation does not name", async () => {
495
+ const replica = replicaForChecks()
496
+ const { chain } = await delegatedSession([CANISTER])
497
+ const impostor = await ECDSAKeyIdentity.generate()
498
+ const agent = agentOn(replica, {
499
+ identity: DelegationIdentity.fromDelegation(impostor, chain),
500
+ })
501
+
502
+ await expect(update(agent)).rejects.toThrow()
503
+ expect(lastRefusal(replica)).toBe(
504
+ "sender_sig is not the signing key's signature over the request"
505
+ )
506
+ expect(trace.runs).toBe(0)
507
+ })
508
+
509
+ it("refuses a delegation that does not allow calls to the canister", async () => {
510
+ const replica = replicaForChecks()
511
+ const { session, chain } = await delegatedSession([OTHER_CANISTER])
512
+ const agent = agentOn(replica, {
513
+ identity: DelegationIdentity.fromDelegation(session, chain),
514
+ })
515
+
516
+ await expect(update(agent)).rejects.toThrow()
517
+ expect(lastRefusal(replica)).toBe(
518
+ `a delegation does not allow calls to ${CANISTER}`
519
+ )
520
+ })
521
+
522
+ it("refuses a delegation signed by a key other than the one before it", async () => {
523
+ const replica = replicaForChecks()
524
+ const { session, chain } = await delegatedSession([CANISTER])
525
+ const forged = DelegationChain.fromDelegations(
526
+ chain.delegations,
527
+ // Claims another root for the same signed delegation.
528
+ Ed25519KeyIdentity.generate().getPublicKey().toDer()
529
+ )
530
+ const agent = agentOn(replica, {
531
+ identity: DelegationIdentity.fromDelegation(session, forged),
532
+ })
533
+
534
+ await expect(update(agent)).rejects.toThrow()
535
+ expect(lastRefusal(replica)).toBe(
536
+ "a delegation is not signed by the key before it"
537
+ )
538
+ })
539
+
540
+ it("refuses a request signed by a kind of key it cannot check, and says so", async () => {
541
+ const replica = replicaForChecks()
542
+ // A key under an algorithm identifier none of the three it checks use.
543
+ const der = new Uint8Array([0x30, 0x0a, 0x30, 0x03, 0x06, 0x01, 0x2a, 1])
544
+ class UnknownKeyIdentity extends SignIdentity {
545
+ getPublicKey(): PublicKey {
546
+ return { toDer: () => der, rawKey: der, derKey: der } as PublicKey
547
+ }
548
+ sign(): Promise<Signature> {
549
+ return Promise.resolve(new Uint8Array(64) as Signature)
550
+ }
551
+ }
552
+ const agent = agentOn(replica, { identity: new UnknownKeyIdentity() })
553
+
554
+ await expect(update(agent)).rejects.toThrow()
555
+ expect(lastRefusal(replica)).toBe(
556
+ "the request is signed by a kind of key the fake replica cannot check"
557
+ )
558
+ })
559
+ })
560
+
561
+ describe("a canister call the fake replica rejects", () => {
562
+ function trappingReplica() {
563
+ const trace = { runs: 0 }
564
+ const trap = () => {
565
+ trace.runs += 1
566
+ throw new Error("the canister is broken")
567
+ }
568
+ const replica = createFakeReplica({
569
+ canisters: { [CANISTER]: { query: trap, update: trap } },
570
+ })
571
+ return { replica, trace }
572
+ }
573
+
574
+ it("rejects an update as a trap, once, when its handler throws", async () => {
575
+ // The agent retries an HTTP failure three times by default, so a handler
576
+ // failure answered as one ran the canister four times.
577
+ const { replica, trace } = trappingReplica()
578
+ const agent = agentOn(replica, retriesAtOnce(3))
579
+
580
+ const error = await rejection(update(agent))
581
+
582
+ expect(shapeOf(error)).toMatchObject({
583
+ kind: ErrorKindEnum.Reject,
584
+ code: "CertifiedRejectErrorCode",
585
+ rejectCode: 5,
586
+ rejectErrorCode: "IC0503",
587
+ rejectMessage: `Canister ${CANISTER} trapped: the canister is broken`,
588
+ })
589
+ expect(trace.runs).toBe(1)
590
+ })
591
+
592
+ it("rejects a query as a trap, once, when its handler throws", async () => {
593
+ const { replica, trace } = trappingReplica()
594
+ const agent = agentOn(replica, retriesAtOnce(3))
595
+
596
+ const response = await agent.query(CANISTER, {
597
+ methodName: "whoami_query",
598
+ arg: EMPTY_ARG,
599
+ })
600
+
601
+ expect(response).toMatchObject({
602
+ status: QueryResponseStatus.Rejected,
603
+ reject_code: 5,
604
+ error_code: "IC0503",
605
+ reject_message: `Canister ${CANISTER} trapped: the canister is broken`,
606
+ })
607
+ expect(trace.runs).toBe(1)
608
+ })
609
+
610
+ it("rejects a call to a canister it does not run", async () => {
611
+ const replica = createFakeReplica({
612
+ canisters: { [CANISTER]: { query: answerWithCaller } },
613
+ })
614
+ const agent = agentOn(replica)
615
+
616
+ const error = await rejection(update(agent, "whoami", OTHER_CANISTER))
617
+ expect(shapeOf(error)).toMatchObject({
618
+ kind: ErrorKindEnum.Reject,
619
+ rejectCode: 3,
620
+ rejectErrorCode: "IC0301",
621
+ })
622
+ expect(shapeOf(error).rejectMessage).toContain(
623
+ `no canister is installed at ${OTHER_CANISTER}`
624
+ )
625
+ expect(
626
+ await agent.query(OTHER_CANISTER, {
627
+ methodName: "whoami_query",
628
+ arg: EMPTY_ARG,
629
+ })
630
+ ).toMatchObject({ status: QueryResponseStatus.Rejected, reject_code: 3 })
631
+ })
632
+
633
+ it("routes a call to the canister it names, not the effective canister", async () => {
634
+ // `effectiveCanisterId` only picks the subnet a request goes through, as
635
+ // for a management canister call. The canister named in the request
636
+ // answers it.
637
+ const answerWith = (id: string) => () =>
638
+ encodeCaller(Principal.fromText(id))
639
+ const replica = createFakeReplica({
640
+ canisters: {
641
+ [CANISTER]: {
642
+ query: answerWith(CANISTER),
643
+ update: answerWith(CANISTER),
644
+ },
645
+ [OTHER_CANISTER]: {
646
+ query: answerWith(OTHER_CANISTER),
647
+ update: answerWith(OTHER_CANISTER),
648
+ },
649
+ },
650
+ })
651
+ const agent = agentOn(replica)
652
+ const effectiveCanisterId = Principal.fromText(OTHER_CANISTER)
653
+
654
+ const updated = await agent.update(CANISTER, {
655
+ methodName: "whoami",
656
+ arg: EMPTY_ARG,
657
+ effectiveCanisterId,
658
+ })
659
+ const queried = await agent.query(CANISTER, {
660
+ methodName: "whoami_query",
661
+ arg: EMPTY_ARG,
662
+ effectiveCanisterId,
663
+ })
664
+
665
+ expect(decodeCaller(updated.reply).toText()).toBe(CANISTER)
666
+ expect(queried).toMatchObject({ status: QueryResponseStatus.Replied })
667
+ expect(
668
+ replica.requests
669
+ .filter((request) => request.methodName)
670
+ .map((request) => request.canisterId)
671
+ ).toEqual([CANISTER, CANISTER])
672
+ // The log keeps the effective canister id too, so a test can tell which
673
+ // one a client chose.
674
+ expect(
675
+ replica.requests
676
+ .filter((request) => request.methodName)
677
+ .map((request) => request.effectiveCanisterId)
678
+ ).toEqual([OTHER_CANISTER, OTHER_CANISTER])
679
+ })
680
+
681
+ it("logs the canister a request is addressed to as its effective canister id by default", async () => {
682
+ const replica = createFakeReplica({
683
+ canisters: { [CANISTER]: { update: answerWithCaller } },
684
+ })
685
+ const agent = agentOn(replica)
686
+
687
+ await update(agent)
688
+ await agent.readState(
689
+ { canisterId: Principal.fromText(CANISTER) },
690
+ { paths: [[new TextEncoder().encode("time")]] }
691
+ )
692
+
693
+ const requests = replica.requests.filter(
694
+ (request) => request.endpoint !== "status"
695
+ )
696
+ expect(requests.map((request) => request.endpoint)).toContain("call")
697
+ expect(requests.map((request) => request.endpoint)).toContain("read_state")
698
+ for (const request of requests) {
699
+ expect(request).toMatchObject({
700
+ canisterId: CANISTER,
701
+ effectiveCanisterId: CANISTER,
702
+ })
703
+ }
704
+ })
705
+
706
+ it("tells a management canister call from the canister it is routed by", async () => {
707
+ // A call to `aaaaa-aa` is routed by a canister its arguments name, which
708
+ // the client picks: the log has to show that choice, or a test cannot
709
+ // tell a right pick from a wrong one.
710
+ const management = "aaaaa-aa"
711
+ const replica = createFakeReplica({
712
+ canisters: { [management]: { update: answerWithText("managed") } },
713
+ })
714
+ const agent = agentOn(replica)
715
+
716
+ const reply = await agent.update(management, {
717
+ methodName: "canister_status",
718
+ arg: EMPTY_ARG,
719
+ effectiveCanisterId: Principal.fromText(CANISTER),
720
+ })
721
+
722
+ expect(decodeText(reply.reply)).toBe("managed")
723
+ const call = replica.requests.find((request) => request.endpoint === "call")
724
+ expect(call).toMatchObject({
725
+ canisterId: management,
726
+ effectiveCanisterId: CANISTER,
727
+ methodName: "canister_status",
728
+ })
729
+ })
730
+
731
+ it("rejects the calls a canister without a handler for them receives", async () => {
732
+ const replica = createFakeReplica({ canisters: { [CANISTER]: {} } })
733
+ const agent = agentOn(replica)
734
+
735
+ const queried = await agent.query(CANISTER, {
736
+ methodName: "whoami_query",
737
+ arg: EMPTY_ARG,
738
+ })
739
+ expect(queried).toMatchObject({ status: QueryResponseStatus.Rejected })
740
+ expect((queried as { reject_message: string }).reject_message).toContain(
741
+ `canister ${CANISTER} answers no queries`
742
+ )
743
+ await expect(update(agent)).rejects.toThrow(
744
+ `canister ${CANISTER} answers no update calls`
745
+ )
746
+ })
747
+
748
+ it("runs a canister added after it was created", async () => {
749
+ const replica = createFakeReplica()
750
+ const agent = agentOn(replica)
751
+ const before = await agent.query(CANISTER, {
752
+ methodName: "greet",
753
+ arg: EMPTY_ARG,
754
+ })
755
+ expect(before).toMatchObject({ status: QueryResponseStatus.Rejected })
756
+
757
+ replica.addCanister(CANISTER, { query: answerWithText("first") })
758
+ expect(decodeText(await queryReply(agent, "greet"))).toBe("first")
759
+ replica.addCanister(CANISTER, { query: answerWithText("second") })
760
+ expect(decodeText(await queryReply(agent, "greet"))).toBe("second")
761
+ })
762
+ })
763
+
764
+ describe("the fault hooks", () => {
765
+ /** A canister whose update and query handlers count their runs. */
766
+ function countingReplica(handlers: Partial<FakeCanister> = {}): {
767
+ replica: FakeReplica
768
+ runs: { update: number; query: number }
769
+ } {
770
+ const runs = { update: 0, query: 0 }
771
+ const replica: FakeReplica = createFakeReplica({
772
+ canisters: {
773
+ [CANISTER]: {
774
+ update: (...args) => {
775
+ runs.update += 1
776
+ return (handlers.update ?? answerWithCaller)(...args)
777
+ },
778
+ query: (...args) => {
779
+ runs.query += 1
780
+ return (handlers.query ?? answerWithCaller)(...args)
781
+ },
782
+ },
783
+ },
784
+ })
785
+ return { replica, runs }
786
+ }
787
+
788
+ describe("reject", () => {
789
+ const CODES = [1, 2, 3, 4, 5, 6] as const
790
+
791
+ it.each(CODES)(
792
+ "reject(%i) from an update reaches the agent as a certified reject with that code",
793
+ async (code) => {
794
+ const { replica, runs } = countingReplica({
795
+ update: () => replica.reject(code, `no: ${code}`),
796
+ })
797
+ const agent = agentOn(replica, retriesAtOnce(3))
798
+
799
+ const error = await rejection(update(agent))
800
+
801
+ expect(shapeOf(error)).toMatchObject({
802
+ kind: ErrorKindEnum.Reject,
803
+ code: "CertifiedRejectErrorCode",
804
+ rejectCode: code,
805
+ rejectMessage: `no: ${code}`,
806
+ })
807
+ // A reject is an answer, not a failed request: no retry re-ran it.
808
+ expect(runs.update).toBe(1)
809
+ }
810
+ )
811
+
812
+ it.each(CODES)(
813
+ "reject(%i) from a query reaches the agent as a rejected query with that code",
814
+ async (code) => {
815
+ const { replica, runs } = countingReplica({
816
+ query: () => replica.reject(code, `no: ${code}`),
817
+ })
818
+ const agent = agentOn(replica, retriesAtOnce(3))
819
+
820
+ // The agent hands a rejected query back, signed, rather than throwing.
821
+ const response = await agent.query(CANISTER, {
822
+ methodName: "whoami_query",
823
+ arg: EMPTY_ARG,
824
+ })
825
+ expect(response).toMatchObject({
826
+ status: QueryResponseStatus.Rejected,
827
+ reject_code: code,
828
+ reject_message: `no: ${code}`,
829
+ })
830
+
831
+ // An actor raises it as the error a classifier reads.
832
+ const actor = Actor.createActor<{ whoami_query(): Promise<Principal> }>(
833
+ whoamiInterface,
834
+ { agent, canisterId: CANISTER }
835
+ )
836
+ const error = await rejection(actor.whoami_query())
837
+ expect(shapeOf(error)).toMatchObject({
838
+ kind: ErrorKindEnum.Reject,
839
+ code: "UncertifiedRejectErrorCode",
840
+ rejectCode: code,
841
+ rejectMessage: `no: ${code}`,
842
+ })
843
+ expect(runs.query).toBe(2)
844
+ }
845
+ )
846
+
847
+ it("names the reject code when no message is given", async () => {
848
+ const { replica } = countingReplica({
849
+ update: () => replica.reject(2),
850
+ })
851
+
852
+ const error = await rejection(update(agentOn(replica)))
853
+
854
+ expect(shapeOf(error).rejectMessage).toBe(
855
+ "fake replica: the canister rejected the call with reject code 2 (SYS_TRANSIENT)"
856
+ )
857
+ })
858
+
859
+ it("sends no error code, as a reject that is not a trap has none", async () => {
860
+ const { replica } = countingReplica({
861
+ update: () => replica.reject(4, "declined"),
862
+ })
863
+
864
+ const error = await rejection(update(agentOn(replica)))
865
+
866
+ expect(shapeOf(error).rejectErrorCode).toBeUndefined()
867
+ })
868
+
869
+ it("leaves the next call to the canister's own handler", async () => {
870
+ let reject = true
871
+ const { replica } = countingReplica({
872
+ update: (method, arg, context) => {
873
+ if (reject) replica.reject(4)
874
+ return answerWithCaller(method, arg, context)
875
+ },
876
+ })
877
+ const agent = agentOn(replica)
878
+
879
+ await expect(update(agent)).rejects.toThrow()
880
+ reject = false
881
+ await expect(update(agent)).resolves.toBeDefined()
882
+ })
883
+ })
884
+
885
+ describe("dropNextReply", () => {
886
+ it("runs the update and then fails the agent with a network error", async () => {
887
+ const { replica, runs } = countingReplica()
888
+ const agent = agentOn(replica)
889
+ replica.dropNextReply()
890
+
891
+ const error = await rejection(update(agent))
892
+
893
+ expect(shapeOf(error)).toMatchObject({
894
+ kind: ErrorKindEnum.Transport,
895
+ code: "HttpFetchErrorCode",
896
+ })
897
+ // The canister ran, so the call may have had its effect.
898
+ expect(runs.update).toBe(1)
899
+ expect(replica.requests.filter((r) => r.endpoint === "call")).toEqual([
900
+ expect.objectContaining({ methodName: "whoami", dropped: true }),
901
+ ])
902
+ })
903
+
904
+ it("runs the update once however often the agent re-sends it", async () => {
905
+ // The re-sends carry the same request id, which the fake remembers as
906
+ // lost: they fail the same way and run nothing.
907
+ const { replica, runs } = countingReplica()
908
+ const agent = agentOn(replica, retriesAtOnce(3))
909
+ replica.dropNextReply()
910
+
911
+ const error = await rejection(update(agent))
912
+
913
+ expect(shapeOf(error).kind).toBe(ErrorKindEnum.Transport)
914
+ expect(runs.update).toBe(1)
915
+ const calls = replica.requests.filter((r) => r.endpoint === "call")
916
+ expect(calls).toHaveLength(4)
917
+ expect(calls.every((call) => call.dropped)).toBe(true)
918
+ })
919
+
920
+ it("fails a read of the lost call's status the same way", async () => {
921
+ // A real replica would still answer it; the fake keeps the call
922
+ // outcome-unknown, as a partition for that request id does.
923
+ const { replica } = countingReplica()
924
+ const agent = agentOn(replica)
925
+ replica.dropNextReply()
926
+ const error = await rejection(update(agent))
927
+ const { requestId } = (
928
+ error as { code: { requestContext: { requestId: Uint8Array } } }
929
+ ).code.requestContext
930
+
931
+ const read = await rejection(
932
+ agent.readState(
933
+ { canisterId: Principal.fromText(CANISTER) },
934
+ {
935
+ paths: [[new TextEncoder().encode("request_status"), requestId]],
936
+ }
937
+ )
938
+ )
939
+
940
+ expect(shapeOf(read)).toMatchObject({
941
+ kind: ErrorKindEnum.Transport,
942
+ code: "HttpFetchErrorCode",
943
+ })
944
+ expect(replica.requests[replica.requests.length - 1]).toMatchObject({
945
+ endpoint: "read_state",
946
+ dropped: true,
947
+ })
948
+ })
949
+
950
+ it("loses only the next update, and leaves queries alone", async () => {
951
+ const { replica, runs } = countingReplica()
952
+ const agent = agentOn(replica)
953
+ replica.dropNextReply()
954
+
955
+ // A query is not an update: it is answered, and the drop stays armed.
956
+ expect(decodeCaller(await queryReply(agent)).isAnonymous()).toBe(true)
957
+ await expect(update(agent)).rejects.toThrow()
958
+ // The one after that is answered.
959
+ expect(decodeCaller((await update(agent)).reply).isAnonymous()).toBe(true)
960
+
961
+ expect(runs).toEqual({ update: 2, query: 1 })
962
+ })
963
+
964
+ it("a canister's changed state is the only trace of the lost call", async () => {
965
+ let balance = 0
966
+ const { replica } = countingReplica({
967
+ update: () => {
968
+ balance += 10
969
+ return new Uint8Array(IDL.encode([IDL.Nat], [balance]))
970
+ },
971
+ })
972
+ const agent = agentOn(replica)
973
+ replica.dropNextReply()
974
+
975
+ await expect(update(agent, "deposit")).rejects.toThrow()
976
+
977
+ expect(balance).toBe(10)
978
+ })
979
+ })
980
+
981
+ describe("refuseNext", () => {
982
+ it.each([429, 503])(
983
+ "answers the next update with HTTP %i before the canister sees it",
984
+ async (status) => {
985
+ const { replica, runs } = countingReplica()
986
+ const agent = agentOn(replica)
987
+ replica.refuseNext(status)
988
+
989
+ const error = await rejection(update(agent))
990
+
991
+ expect(shapeOf(error)).toMatchObject({
992
+ kind: ErrorKindEnum.Protocol,
993
+ code: "HttpErrorCode",
994
+ status,
995
+ })
996
+ expect(runs.update).toBe(0)
997
+ expect(replica.requests.filter((r) => r.endpoint === "call")).toEqual([
998
+ {
999
+ endpoint: "call",
1000
+ canisterId: CANISTER,
1001
+ effectiveCanisterId: CANISTER,
1002
+ methodName: "whoami",
1003
+ refused: `refuseNext(${status}) answered the request with HTTP ${status}`,
1004
+ },
1005
+ ])
1006
+ }
1007
+ )
1008
+
1009
+ it("answers the next query the same way, and the one after normally", async () => {
1010
+ const { replica, runs } = countingReplica()
1011
+ const agent = agentOn(replica)
1012
+ replica.refuseNext(429)
1013
+
1014
+ const error = await rejection(queryReply(agent))
1015
+
1016
+ expect(shapeOf(error)).toMatchObject({
1017
+ kind: ErrorKindEnum.Protocol,
1018
+ code: "HttpErrorCode",
1019
+ status: 429,
1020
+ })
1021
+ expect(runs.query).toBe(0)
1022
+ expect(decodeCaller(await queryReply(agent)).isAnonymous()).toBe(true)
1023
+ expect(runs.query).toBe(1)
1024
+ })
1025
+
1026
+ it("does not count the requests an agent makes on its own", async () => {
1027
+ // The status request and the read_state an agent makes to trust a query
1028
+ // are the agent's housekeeping, not a call a test is counting: they are
1029
+ // answered while the refusal waits for the next call.
1030
+ const { replica, runs } = countingReplica()
1031
+ const agent = HttpAgent.createSync({
1032
+ host: replica.host,
1033
+ fetch: replica.fetch,
1034
+ shouldFetchRootKey: true,
1035
+ retryTimes: 0,
1036
+ subnetNodeKeyExpirableStore: nodeKeysOfOneAgent(),
1037
+ })
1038
+ replica.refuseNext(429)
1039
+
1040
+ await agent.fetchRootKey()
1041
+ await agent.fetchSubnetKeys({ canisterId: Principal.fromText(CANISTER) })
1042
+ expect(
1043
+ replica.requests.map((r) => [r.endpoint, Boolean(r.refused)])
1044
+ ).toEqual([
1045
+ ["status", false],
1046
+ ["read_state", false],
1047
+ ])
1048
+
1049
+ const error = await rejection(queryReply(agent))
1050
+ expect(shapeOf(error).status).toBe(429)
1051
+ expect(runs.query).toBe(0)
1052
+ })
1053
+
1054
+ it("aimed at a canister or a method, never refuses the requests an agent makes on its own", async () => {
1055
+ // The read_state an agent makes to trust a query is routed by the
1056
+ // canister the refusal names, and the status request names nothing:
1057
+ // neither is a query or call, so neither matches, and the refusal waits
1058
+ // for the canister's next query.
1059
+ const { replica, runs } = countingReplica()
1060
+ const agent = HttpAgent.createSync({
1061
+ host: replica.host,
1062
+ fetch: replica.fetch,
1063
+ shouldFetchRootKey: true,
1064
+ retryTimes: 0,
1065
+ subnetNodeKeyExpirableStore: nodeKeysOfOneAgent(),
1066
+ })
1067
+ replica.refuseNext(429, { canister: CANISTER })
1068
+ replica.refuseNext(503, { method: "whoami_query" })
1069
+
1070
+ await agent.fetchRootKey()
1071
+ await agent.fetchSubnetKeys({ canisterId: Principal.fromText(CANISTER) })
1072
+ expect(
1073
+ replica.requests.map((r) => [r.endpoint, Boolean(r.refused)])
1074
+ ).toEqual([
1075
+ ["status", false],
1076
+ ["read_state", false],
1077
+ ])
1078
+
1079
+ expect(shapeOf(await rejection(queryReply(agent))).status).toBe(429)
1080
+ expect(shapeOf(await rejection(queryReply(agent))).status).toBe(503)
1081
+ expect(runs.query).toBe(0)
1082
+ })
1083
+
1084
+ it("refuses as many sends as it is told to, so a retry can get through", async () => {
1085
+ const { replica, runs } = countingReplica()
1086
+ const agent = agentOn(replica, retriesAtOnce(2))
1087
+ replica.refuseNext(429, 2)
1088
+
1089
+ const answer = await update(agent)
1090
+
1091
+ expect(decodeCaller(answer.reply).isAnonymous()).toBe(true)
1092
+ expect(runs.update).toBe(1)
1093
+ expect(
1094
+ replica.requests
1095
+ .filter((r) => r.endpoint === "call")
1096
+ .map((r) => Boolean(r.refused))
1097
+ ).toEqual([true, true, false])
1098
+ })
1099
+
1100
+ it("refuses what is not an HTTP error status", () => {
1101
+ const { replica } = countingReplica()
1102
+
1103
+ expect(() => replica.refuseNext(200)).toThrow(RangeError)
1104
+ expect(() => replica.refuseNext(429.5)).toThrow(RangeError)
1105
+ expect(() => replica.refuseNext(429, 0)).toThrow(RangeError)
1106
+ })
1107
+
1108
+ it("comes before the signature check, as a gateway refuses first", async () => {
1109
+ const { replica, runs } = countingReplica()
1110
+ const impostor = new (class extends SignIdentity {
1111
+ private readonly key = Ed25519KeyIdentity.generate()
1112
+ getPublicKey() {
1113
+ return this.key.getPublicKey()
1114
+ }
1115
+ sign(blob: Uint8Array) {
1116
+ return this.key.sign(blob)
1117
+ }
1118
+ override getPrincipal() {
1119
+ return Ed25519KeyIdentity.generate().getPrincipal()
1120
+ }
1121
+ })()
1122
+ replica.refuseNext(429)
1123
+
1124
+ const error = await rejection(
1125
+ update(agentOn(replica, { identity: impostor }))
1126
+ )
1127
+
1128
+ expect(shapeOf(error).status).toBe(429)
1129
+ expect(runs.update).toBe(0)
1130
+ })
1131
+ })
1132
+ })
1133
+
1134
+ describe("the fake replica's routing", () => {
1135
+ it('answers http://127.0.0.1:8000, the client\'s "local", when no host is given', async () => {
1136
+ const replica = createFakeReplica({
1137
+ canisters: { [CANISTER]: { query: answerWithCaller } },
1138
+ })
1139
+
1140
+ expect(replica.host).toBe(DEFAULT_HOST)
1141
+ expect(decodeCaller(await queryReply(agentOn(replica))).isAnonymous()).toBe(
1142
+ true
1143
+ )
1144
+ })
1145
+
1146
+ it("answers the host it is given, whatever its path", () => {
1147
+ const replica = createFakeReplica({
1148
+ host: "http://localhost:8080/some/path",
1149
+ })
1150
+
1151
+ expect(replica.host).toBe("http://localhost:8080")
1152
+ })
1153
+
1154
+ it("fails a canister call sent to another host at once, naming its own", async () => {
1155
+ const logged = vi.spyOn(console, "error").mockImplementation(() => {})
1156
+ try {
1157
+ const replica = createFakeReplica({ host: DEFAULT_HOST })
1158
+ // An agent built for mainnet, handed the fake's fetch by mistake.
1159
+ const agent = agentOn(replica, { host: "https://icp-api.io" })
1160
+
1161
+ const error = await rejection(queryReply(agent))
1162
+
1163
+ expect(shapeOf(error)).toMatchObject({
1164
+ kind: ErrorKindEnum.Transport,
1165
+ code: "HttpFetchErrorCode",
1166
+ })
1167
+ expect(String(error)).toContain(
1168
+ `fake replica: no route to https://icp-api.io. The fake answers ${DEFAULT_HOST}`
1169
+ )
1170
+ } finally {
1171
+ logged.mockRestore()
1172
+ }
1173
+ })
1174
+
1175
+ it("logs a misrouted origin once, as the agent and the query retry it", async () => {
1176
+ // Retries can hold the thrown error back until after the test timed
1177
+ // out, so the log is what names the cause.
1178
+ const logged = vi.spyOn(console, "error").mockImplementation(() => {})
1179
+ try {
1180
+ const replica = createFakeReplica({ host: DEFAULT_HOST })
1181
+ for (let attempt = 0; attempt < 3; attempt += 1) {
1182
+ await expect(
1183
+ replica.fetch(
1184
+ `http://localhost:3000/api/v3/canister/${CANISTER}/query`
1185
+ )
1186
+ ).rejects.toThrow("fake replica: no route to http://localhost:3000")
1187
+ }
1188
+ await expect(
1189
+ replica.fetch("https://icp-api.io/api/v2/status")
1190
+ ).rejects.toThrow("fake replica: no route to https://icp-api.io")
1191
+
1192
+ expect(logged.mock.calls.map(([message]) => message)).toEqual([
1193
+ expect.stringContaining("no route to http://localhost:3000"),
1194
+ expect.stringContaining("no route to https://icp-api.io"),
1195
+ ])
1196
+ } finally {
1197
+ logged.mockRestore()
1198
+ }
1199
+ })
1200
+
1201
+ it("refuses what is not the IC API, which has no fetch of its own to go to", async () => {
1202
+ const replica = createFakeReplica({ host: DEFAULT_HOST })
1203
+
1204
+ await expect(replica.fetch(`${DEFAULT_HOST}/config.json`)).rejects.toThrow(
1205
+ "is not an IC API request"
1206
+ )
1207
+ await expect(
1208
+ replica.fetch("https://api.example.com/api/v1/users")
1209
+ ).rejects.toThrow("is not an IC API request")
1210
+ })
1211
+
1212
+ describe("with no host given", () => {
1213
+ afterEach(() => {
1214
+ vi.unstubAllGlobals()
1215
+ })
1216
+
1217
+ it("answers the page's origin when it is local, as an agent built with no host calls it", () => {
1218
+ // As in Vitest's jsdom and happy-dom environments.
1219
+ vi.stubGlobal("location", new URL("http://localhost:3000/"))
1220
+
1221
+ expect(createFakeReplica().host).toBe("http://localhost:3000")
1222
+ })
1223
+
1224
+ it.each([
1225
+ ["a mainnet page", "https://abcde-aaaaa-aaaaa-aaaaa-cai.icp0.io/"],
1226
+ ["an opaque origin", "file:///tmp/index.html"],
1227
+ ])("answers http://127.0.0.1:8000 on %s", (_, href) => {
1228
+ vi.stubGlobal("location", new URL(href))
1229
+
1230
+ expect(createFakeReplica().host).toBe(DEFAULT_HOST)
1231
+ })
1232
+ })
1233
+
1234
+ it("refuses a canister key that is not a canister ID", () => {
1235
+ expect(() =>
1236
+ createFakeReplica({ canisters: { "test-canister": {} } })
1237
+ ).toThrow('"test-canister" in `canisters` is not a canister ID')
1238
+ expect(() => createFakeReplica().addCanister("test-canister", {})).toThrow(
1239
+ '"test-canister" in `canisters` is not a canister ID'
1240
+ )
1241
+ })
1242
+ })