Skip to main content

zkApps and o1js FAQ

Answers to common questions about zkApps (zero knowledge apps) and o1js, a TypeScript library for writing zk smart contracts.

How do I stay up to date with zkApps and o1js?

Follow the official O(1) Labs channels:

Where can I ask questions and contribute answers?

Mina Protocol Discord is the most popular place where Mina enthusiasts and technical contributors gather.

Join us in these zkApps channels:

What files do I use to write zkApps?

There are many approaches to building a smart contract. For the zkApp tutorials, most examples follow this convention:

  • index.ts: The entry point of your project that imports all smart contract classes you want access to and exports them to your smart contract.
  • main.ts: How you interact with the smart contract. For example, the import statement brings in objects and methods from o1js that you use to interact with your smart contract.
  • <yourcontract>.ts: Your specific smart contract logic.

Where can I find the o1js API reference documentation?

See the autogenerated o1js reference documentation with doc comments, like the Provable module.

What is ZkProgram?

A general-purpose API for creating zk proofs. A ZkProgram is similar to a zkApp smart contract but isn't tied to an on-chain account.

What is the difference between getActions and fetchActions?

Use the appropriate module to work with the live network or with historical archive nodes:

Does o1js compile my JavaScript code to an arithmetic circuit?

No, o1js does NOT compile into anything else. In contrast to other zk ecosystems, o1js is just a JS library. It creates zk circuits from user code by executing that code. If you have a smart contract with a @method myMethod(), for example, o1js simply calls myMethod(); during proof generation.

This works because o1js sets up some global state - a "circuit" - where it collects variables and constraints. The use of functions like Field.mul or Bool.assertEquals inside your smart contract methods add corresponding variables and constraints to the global circuit.

This has some implications:

  • To turn your logic into a proof, you must use o1js built-in datatypes such as Field and use the o1js functions that operate on them, like Field.mul().
    • A statement like x.mul(y) adds a generic PLONK gate to your circuit and returns a variable that you can use in further statements that get wired to the multiplication gate.
    • Some o1js methods allow you to convert normal JavaScript datatypes into Field elements and back, such as Encoding.stringToFields(). Methods like this that don't add anything to your circuit are typically clarified in a doc comment.
  • Conventional JavaScript code such as 'hello world'.split('').join(' ') that doesn't use o1js built-ins are not included in your zk proof since it doesn't add anything to your circuit.
    • Why? Because it doesn't call any of the functions that build the circuit.
    • There's nothing wrong with having non-circuit code inside your method, as long as you're aware of what it's (not) doing.
  • It's fine to use if-statements, for-loops, arrays, objects, and any other JavaScript language constructs to facilitate writing circuits. However, be aware that these flexible constructs don't allow you to overcome the static nature of circuits.

This example asserts that a Field element x is not equal to 5, 10 or 15:

// good
for (let y of [5, 10, 15]) {
x.equals(y).assertFalse();
}

The previous for-loop example just stitches together a fixed number of o1js commands, which is fine. However, the following snippet, where the loop's length is determined from user input, won't work:

// bad
@method myMethod(x: Field, n: Field) {
let n0 = Number(n.toString()); // nope
for (let y = 0; y < n0; y += 5) {
x.equals(y).assertFalse();
}
}

This example fails for two reasons:

  1. n.toString() can't be used in circuit code at all. It throws an error during SmartContract.compile() because during compile(), variables like n don't have any JS values attached to them; they represent abstract variables used to build up an abstract arithmetic circuit. So, in general, you can't use any of the methods that read out the JS value of your Field elements: Field.toString(), Field.toBigInt(), Bool.toBoolean() etc.
  2. More subtly, your methods must create the same constraints every time because a proof cannot be verified against a verification key for a differing set of constraints. The code above adds x.equals(y).assertFalse() on condition of the value of n which leads to constraints varying between executions of the proof.