Build zkApps with Claude
o1js is plain TypeScript, which means modern coding agents like Claude, Codex, and Gemini can write circuits with you. Drop the snippet below into your CLAUDE.md to give your agent the context it needs to be useful from line one.
Why pair an agent with o1js?
Zero-knowledge circuits look like normal code but behave differently — values are provable, control flow is constrained, and APIs come from a fast-moving library. A thin layer of project guidance keeps an AI assistant from generating subtly wrong circuits.
number / boolean.compile() before prove, version pairing with @o1js/native.Works with:
A starter CLAUDE.md
Drop this file at the root of your project (or append to your existing CLAUDE.md). Claude Code reads it automatically on every session. The same content works as AGENTS.md for Codex and GEMINI.md for Gemini CLI.
# o1js project guidelines
This project uses **o1js**, a TypeScript library for writing zero-knowledge
programs that compile to provable circuits and run on the Mina blockchain.
## Docs first
- The canonical docs live at https://docs.o1labs.org/o1js — consult them
before guessing an API.
- The o1js source of truth is https://github.com/o1-labs/o1js. When unsure
about a signature, look it up there rather than inferring.
## Provable types, not native JS
Inside `ZkProgram` methods and `SmartContract` methods, use o1js types
(`Field`, `Bool`, `UInt32`, `UInt64`, `PublicKey`, ...) — never raw
`number`, `boolean`, or `bigint`. Native values won't be constrained
in the circuit.
```ts
// YES
let sum = a.add(b);
let ok = x.equals(Field(0));
// NO — these are not provable
let sum = a + b;
let ok = x === 0;
```
## Control flow in circuits
- No `if` / `switch` on provable values. Use `Provable.if(cond, a, b)`
or `Bool` combinators.
- No early `return` based on provable values — both branches always
execute in a circuit.
- Loops must have a bound known at compile time.
## ZkProgram shape
```ts
import { Field, ZkProgram } from 'o1js';
const MyProgram = ZkProgram({
name: 'my-program',
publicInput: Field,
methods: {
run: {
privateInputs: [Field],
async method(pub: Field, priv: Field) {
priv.mul(priv).assertEquals(pub);
},
},
},
});
await MyProgram.compile();
const { proof } = await MyProgram.run(Field(25), Field(5));
```
Method bodies are `async`. `compile()` must run before `prove` or
`verify`. Prefer `assertEquals` / `assertLessThan` over throwing.
## Native prover (o1js 2.15.0+)
For 2–2.5x faster proving and larger circuits, call `setBackend('native')`
once at startup, before any other o1js usage. Requires
`@o1js/native` to be installed.
## What to avoid
- Don't import from deep paths like `o1js/dist/...`. Use the public
entrypoint.
- Don't `console.log` inside a method body and treat the output as
proof-relevant — the method runs both during witness generation and
constraint synthesis.
- Don't mix versions of `o1js` and `@o1js/native` — they must match.
## When in doubt
Ask the user before introducing new dependencies, changing the proof
system backend, or restructuring `ZkProgram` / `SmartContract`
boundaries.
Add it to your project
Copy the snippet above
Use the Copy button on the code block. The whole file is meant to be dropped in as-is.
Save it at the repo root
Save as CLAUDE.md (or AGENTS.md / GEMINI.md) at the top level of your project. The agent picks it up on the next session — no install step.
Edit it for your codebase
Add a short paragraph about what your project actually does, where the circuits live, and any in-house conventions. The more specific, the better the suggestions.
Tips that pay off
Small habits that keep AI-assisted o1js development on the rails:
- Pin your o1js version in
package.jsonand mention it inCLAUDE.md— APIs do change between releases. - Ask the agent to write the test first. ZK circuits are much easier to verify with a concrete
compile+prove+verifyround-trip than by reading the code. - When the agent invents an API, paste the relevant docs section into chat. Models recover quickly when grounded.
- For larger projects, split context: keep a global
CLAUDE.mdwith o1js rules and a per- package one with that package's invariants.
