Getting started with ZKApps on Mina: everything you need to know to write, test and deploy your Zero-Knowledge App

Getting started with ZKApps on Mina: everything you need to know to write, test and deploy your Zero-Knowledge App

Table of contents

Today, we're diving deep into the complex world of Zero-Knowledge Proofs (ZKPs) and discovering how to build state-of-the-art applications harnessing this technology on the blockchain. Whether you're a seasoned developer or an inquisitive newbie, this guide will arm you with the understanding and tools needed to grasp the concepts, set up your development environment, write and test your smart contract, and finally deploy and interact with it on the Mina Network.

We'll explore all the main functionalities and I'll share some handy tricks to streamline your development process.

🖐️ But first, let’s start with a quick introduction to some foundational concepts.

If you're already familiar with the basics of Zero-Knowledge Proofs and the Mina Protocol, you can skip this section and jump straight to the tutorial by clicking here.

Zero Knowledge what Proof? 🤯

Zero-Knowledge Proofs (ZKPs) are cryptographic techniques that enable one party to prove to another that they know a specific piece of information without revealing the information itself. This ensures privacy and security during the verification process.

To make this concept clearer, let's use a real-world analogy:

🌎 Picture yourself in a cutting-edge, ultra-secure data center where you hold the access card to a restricted server room. You need to prove to a security guard that you have the credentials to enter this room, but you don't want to hand over your precious access card details.

Here's how you can achieve this in a zero-knowledge way:

  1. Proof of Knowledge: Instead of giving the guard your access card, you simply walk up to the door and swipe your card. The door unlocks, proving you indeed have the right credentials.

  2. Zero Knowledge Transfer: The guard sees the door unlock but doesn't gain access to your card's details or any sensitive information. They only witness the action and its result.

  3. Verification: The guard is fully convinced that you possess the correct credentials without needing to know the specifics of your access card.

Now, in the digital world: In computing, ZKPs allow you to demonstrate that you performed a specific computation correctly without disclosing your private data or inputs. It's like unlocking the door (proving the computation) without showing the security guard (verifier) your access card (private data). This technique ensures both privacy and security.

Real-World Applications of ZKPs

ZKPs aren't just sci-fi jargon—they're revolutionizing various fields right now! Here are some examples of where these cryptographic wizards are flexing their muscles:

  • Privacy-preserving identity verification: Proving who you are without spilling all your personal beans.

  • Confidential financial transactions: Ensuring your money moves are as secretive as a ninja.

  • Secure voting systems: Making sure your vote counts without revealing your choice.

  • Private credential verification: Verifying you have the right stuff without showing your credentials.

  • Supply chain privacy: Keeping track of products without snooping on the details.

  • Gaming: Proving you made a killer move without giving away your strategy.

Remember, this isn't just cool tech—it's the cutting-edge stuff shaping the future of privacy and scalability in blockchain applications. And guess what? We're only scratching the surface! There are endless possibilities still to be discovered. That's what makes it so exciting; it's like being part of the frontier that’s shaping the digital future.

👀 But fair warning: ZKPs are a complex concept. They might twist your brain into a knot initially, and that's totally normal. It’s like solving a puzzle—sometimes you need a few tries to get it right.

What’s the Mina Protocol?

Mina is a groundbreaking blockchain protocol that's changing the game with its innovative approach to Zero-Knowledge (ZK) applications. Here's what makes Mina stand out:

  1. Recursive Zero-Knowledge Proofs: Mina leverages infinitely recursive ZK proofs to verify its blockchain. This means it can prove the entire state of the blockchain with a single, constant-sized proof, making it incredibly efficient.

  2. Platform for Decentralized ZK Apps: Mina provides a robust platform for developers to build decentralized ZK applications (zkApps). These zkApps are Turing complete, meaning they can perform any computation, and they offer enhanced privacy, off-chain computation, and composability.

  3. Tiny but Mighty: Despite its powerful capabilities, Mina maintains a constant size of just 22KB. This makes it incredibly lightweight and accessible, even for users with limited hardware resources. This is achieved through zk-SNARKs, which compress the entire blockchain state into a small, easily verifiable proof.

  4. Privacy and Security: With zkApps, Mina ensures that data never leaves your local device. Only proofs are submitted to the chain, not personal data, ensuring privacy and security.

How zk-SNARKs Enable Constant Size:

Instead of storing the entire transaction history, Mina nodes only need to store this compact proof. When a new block is produced, a new proof is generated that verifies the validity of all previous transactions and the new block. Any node can quickly verify this proof, regardless of its computational resources.

If you want to know more about mina you can check the docs 👉https://docs.minaprotocol.com/

ZKApps

ZKApps are programs whose execution can be proven without revealing the underlying data used in the computation. This is achieved through zk-proofs, as I explained earlier.

The key benefit of zkApps on Mina is that they perform off-chain execution — all computational tasks are carried out off-chain, resulting in a flat gas fee during verification. This offers a significant advantage over blockchains that rely on on-chain computations, which often lead to high and unpredictable gas fees.

zkApp development Frameworks

When developing a zkApp within the Mina ecosystem, you have two robust frameworks at your disposal, each designed to streamline and optimize different solution types. Read the next section to discover the ideal match for your project’s unique needs.

Choosing Your Weapon: o1js vs Protokit for Mina zkApps 🚀

Mina Protocol offers two powerful frameworks for zkApp development:

  1. o1js - For L1 development and infrastructure

  2. Protokit - For building zkApp-chains (Mina L2) and complex applications

Let's break down each one and see how they can help you build zkApps!

o1js: the native Mina L1 framework 🛠️

o1js is your go-to framework when you want to build directly on Mina's L1. Think of it as the "Solidity" of Mina, but with the power of zero-knowledge proofs baked in!

It provides high-level abstractions for defining circuits in TypeScript, making zk-proof development accessible to developers without deep cryptographic expertise. It lets you write arbitrary zk programs leveraging a rich set of built-in provable operations, like basic arithmetic, hashing, signatures, boolean operations, comparisons, and more.

Here's a simple example of what a basic zkApp contract might look like using o1js:

import { SmartContract, State, method, Field, UInt64, Poseidon } from 'o1js';


class Counter extends SmartContract {

  @state(Field) public commitment = State();


  @method()

  public increment(a: UInt64, b: UInt64) {

    const hash = Poseidon.hash(a.toFields());

    const commitment = this.commitment.get();

    this.commitment.assertEquals(commitment); // Precondition: Current commitment matches

    commitment.assertEquals(hash, "Nope, wrong number!"); // Input validation

    const incremented = a.add(b);

    const incrementedCommitment = Poseidon.hash(incremented.toFields());

    this.commitment.set(incrementedCommitment); // Update on-chain state

  }

}

This example demonstrates a zkApp that increments a counter. The commitment stores the hash of the current counter value. The increment method verifies that the provided input matches the current commitment, increments the counter, and updates the commitment with the new hash. The crucial aspect is that this entire computation can be proven without revealing the actual counter value.

Now, you might be wondering: "This looks similar to other smart contract languages, but what makes it special?" 🤔

The magic lies in how o1js handles state and zero-knowledge proofs. Every method in your contract automatically generates ZK proofs when executed, providing privacy and verifiability by default!

Key Considerations for o1js Development 📝
  1. State Management

    • Limited to 8 on-chain field elements

    • Need more storage? Use the Offchain Storage API, or Store Merkle three roots on the contract.

    • Dealing with concurrent users requires careful planning

  2. Protocol Limitations

    • Subject to L1 throughput constraints

    • Need to handle state updates carefully

What can I use O1js for?
  • Writing general-purpose zk circuits

  • Writing zk smart contracts on Mina

You should consider using o1js when:
  • Your project should be able to work within the protocol’s throughput limitations.

  • Your project should not require more than eight on-chain field elements to manage state.

    • Note: If your project requires more than eight on-chain field elements to manage state, consider using the experimental Offchain Storage API.
  • Your project should be able to manage state efficiently, as o1js contracts do not provide any easy-to-use equivalent for access to shared states.

👀 SnarkyJs aclaration

o1js has taken over as the go-to library for creating and interacting with smart contracts in the Mina ecosystem. But you might come across several tutorials where the library is referred to as 'SnarkyJS.'

The transition from SnarkyJS to o1js is designed to be seamless. Developers can easily update their zkApp-cli with a simple command:

npm i -g zkapp-cli@latest

For those still using SnarkyJS, removing it and installing o1js is straightforward, ensuring that existing zkApps can continue functioning without the need for redeployments:

npm remove snarkyjs && npm install o1js

👀 For existing zkApps, make sure to update your imports from snarkyjs to o1js

This makes the upgrade process smooth and hassle-free, so you can focus on developing innovative applications without any interruptions.

If you want to delve into the key concepts of o1js, you can explore the Mina Protocol page . In this article, however, we're not going to focus on the basics of o1js. Instead, we'll equip you with the knowledge and tools to write, test, and deploy your smart contract seamlessly.

Protokit: The L2 Powerhouse 🏗️

When your application needs more horsepower or complex state management, Protokit comes to the rescue! It's designed for building zkApp-chains (L2s) and offers a more familiar development experience for those coming from EVM chains.

Let's look at a simple Protokit example:

Why Choose Protokit? 🎯
  1. Unlimited Storage: No more 8-field limitation!

  2. Concurrent Users: Built-in support for multiple users accessing shared state

  3. Familiar Experience: If you know Solidity, you'll feel right at home

  4. High Throughput: Perfect for applications that need to scale

You should consider using Protokit when:
  • Your project requires high throughput and multiple concurrent users.

  • Your project requires shared or global state access.

  • You are already familiar with building on Ethereum.

The Million Dollar Question 💭

"But which one should I choose?" 🤔

  • Pick o1js if you're all about that L1 life and want to build core infrastructure

  • Go with Protokit if you need high throughput or are building the next big multi-user dApp

You can see a complete comparation of both framework in the mina documentation 👉 https://docs.minaprotocol.com/zkapps/zkapp-development-frameworks#framework-comparison

Take into account Protokit vs. zkApps

Understanding the differences between Protokit and zkApps is essential. While zkApps are individual smart contracts designed for specific functionalities, Protokit allows developers to create entire blockchains with custom rules. This framework manages the complexities of block production, consensus, and state management within zk-rollups.

O1js provides tools to create zkApps with verifiable computations, while Protokit combines these zkApps into scalable, privacy-preserving blockchains. O1js serves as the building block by defining the circuits that power Protokit's zkVM and application logic, leveraging familiar TypeScript syntax and high-level abstractions. Meanwhile, Protokit acts as the orchestrator, managing the intricacies of running a zk-rollup, including block production, consensus, and communication with the Mina main chain. This allows developers to focus on building their application logic without worrying about the underlying infrastructure.


🛠️Preparing our o1js environment

These are all the tools that we need to start developing zero knowledge proof apps in mina blockchain using o1js.

Let's get our hands dirty! Here's what you'll need:

1. Install Node.js

First, head over to nodejs.org and download the latest LTS version. After installation, verify it works by doing the following command:

$ node --version
$ npm --version

2. Install Visual Studio Code

Download and install VS Code from code.visualstudio.com. It's an excellent editor for TypeScript development.

3. Install ZK App CLI

The ZK App CLI is your Swiss Army knife for ZK app development, it provides CLI interface for scaffolding zk-apps and outlines the process of creating, developing and managing zk-apps.

$ npm install -g zkapp-cli

Verify the installation:

$ zk --version

You should get something as:

For a more detailed system check, you can do the following command:

$ zk system

With this command, you'll receive detailed information about your system. This can be incredibly useful if you encounter any issues and need support. By sharing this information with other developers, you can quickly convey your current system specifications, making it easier for them to understand your setup and provide relevant assistance.

Now that we have all the tools we need, we can start a new project with o1js

Now, we are going to generate a ZKApp and trying to deploy it to the Mina blockchain

💡Tip: To update to the latest version of the zkApp CLI you can use:

$ npm update -g zkapp-cli

Why Use TypeScript for zkApps?

zkApps are crafted using TypeScript for some solid reasons. Here's why the O(1) Labs team made this tech-savvy choice:

  • Familiar Territory: TypeScript is built on JavaScript, which most developers know and love. It’s like upgrading from a bicycle to a motorbike—familiar yet more powerful.

  • Type Safety: With TypeScript, you get static typing, meaning fewer bugs and cleaner code. It's like having a spell-checker for your coding syntax.

Even if you're new to TypeScript, fear not! You don’t need to be a TypeScript wizard to follow along.

If you're craving a deeper dive into why TypeScript was chosen and the essentials of O(1) Labs’ zk framework, I highly recommend watching this video where Gregor Mitscha-Baude, one of the masterminds behind O1js MinaProtocol, explains the design and technical decisions. 👉 https://www.youtube.com/watch?v=D5ASDWd7Xto.

Setting up our ZK Project

To create a new project, use the following command:

$ zk project <project_name>

This command would start the process of create us a new developer environment with all the essentials to start writing our ZKApps, but before, the CLI would ask us some followup questions as seen in the image below:

For this tutorial, we are going to use the “none” option

When finish all the installation process you should have something like this

Project file structure breakdown 📁

Let's examine the key files in our newly created project:

.
├── .github/

│   └── workflows/

│       └── ci.yml               # GitHub Actions CI configuration file

│

├── build/

│   ├── src/                     # Compiled source files

│   ├── _hash                    # Build hash file

│   ├── cache.json              # Cache configuration

│   └── node_modules/           # Build-specific dependencies

│

├── src/

│   ├── Add.test.ts            # Test file for Add contract

│   ├── Add.ts                 # Main smart contract implementation

│   ├── index.ts               # Entry point file

│   └── interact.ts            # Contract interaction utilities

│

├── .eslintrc.cjs              # ESLint configuration

├── .gitattributes            # Git attributes configuration

├── .gitignore                # Git ignore rules

├── .npmignore               # npm package ignore rules

├── .prettierignore          # Prettier ignore rules

├── .prettierrc              # Prettier configuration

├── babel.config.js          # Babel transpiler configuration

├── config.json              # Project configuration

├── jest-resolver.js         # Jest test resolver

├── jest.config.js           # Jest test configuration

├── LICENSE                  # Project license file

├── package-lock.json        # Lock file for npm dependencies

├── package.json             # Project metadata and dependencies

├── README.md               # Project documentation

└── tsconfig.json           # TypeScript configuration

1. The smart contract (add.ts file)

This is the main smart contract file containing the ZK app logic

import { Field, SmartContract, state, State, method } from 'o1js';

export class Add extends SmartContract {

    @state(Field) num = State<Field>();

    init() {

        super.init();

        this.num.set(Field(1));

    }

    @method async update() {

        const currentState = this.num.getAndRequireEquals();

        const newState = currentState.add(2);

        this.num.set(newState);

    }

}

Let's break down this smart contract:

  • It defines a simple contract called Add

  • Initializes a state variable num with value 1

  • Has an update method that adds 2 to the current value

  • Uses decorators (@state, @method) to define state and methods

2. Interaction script (interact.ts file)

This file demonstrates how to interact with your smart contract and generate zero-knowledge proofs. We'll discuss it in more detail later in the article.

Deployment process 🚀

For make this possible we first need to create our network configuration:

This would allow us to set up a network in where we can send our transaction to

For that, we run the following command

$ zk config

You'll need to provide:

  1. Network name, could be anything (e.g., "testnet")

  2. API URL

    1. We are going to use the Berkeley network so we need to put the following 👉 https://api.minascan.io/node/devnet/v1/graphql

Take into account:

The name of the test network in the url to use could change, as the network continues to develop but rn you could use the Berkeley network

  1. Fee amount (typically 0.1 MINA)

Fees are used to pay for transaction processing and smart contract

When finishing this it would update the config.json file with all the specification we give during the setting

And you would get an output like the following

Some quick notes 📝

On testnets

If you're not familiar with test environments, it's a controlled setting where you can deploy and test your projects safely. This environment mimics real-world conditions but without any real-world consequences, allowing you to catch errors, fine-tune performance, and ensure everything works as expected before going live.

It provides a sandbox for experimentation and debugging, ensuring your smart contract or application is rock-solid before it's deployed in a real-world scenario.

On transaction cost

When building zero-knowledge proofs in the context of a blockchain, we need to make a small payment for every transaction sent to the blockchain. In Ethereum, this payment is made using ETH, while in the Mina blockchain, we use MINA.

In the case of Mina, transaction costs are lower because we are not performing computations directly on the blockchain. Instead, the computations are done off-chain, and only the resulting proofs are verified on-chain. This significantly reduces the computational load and, consequently, the transaction fees.

Getting test MINA Tokens

Now, you may be asking: "But how am I going to pay to interact with the Mina network? Do I have to spend real money for this?" 😨

The answer is no, as I said earlier, we are going to deploy our smart contract to a testnet network, and yes we have to pay for every transaction we make on the network, but we could use fake MINA to make those transactions.

And one way to obtaining fake mina is by going to a faucet

Faucets

A faucet is a development tool that gives you free test tokens to experiment with blockchain applications without using real money. These tokens only work on test networks (testnets), not on main networks (mainnet).

You can get free test MINA tokens from the faucet. Here's how:

  1. Visit the Mina faucet website

    1. You can see the faucet link when completing the zk config command 👉 https://faucet.minaprotocol.com/

Or use https://faucet.minaprotocol.com/?address=<FEE-PAYER-ACCOUNT>

  1. Input your generated address

    1. ZK config generate us a key pair, that's what it gives of an address, to publicly identify our deployed verifier (Our smart contract)

You can see it from the output of zk-config

  1. Click "Request"

  2. Wait approximately 3 minutes for confirmation.

👀 This can vary depending on the activity of the testnet

💡 Tip: Keep track of your address! It usually ends with a unique identifier (like "th6x").


Now, we go back to our project

Deploying our Smart Contract

Once we have our test MINA tokens, we can go back and deploy our contract.

We can run the following command

$ zk deploy <your_network_name>

If you follow along with me, your network name would be ‘testnet’ so the command would go like

$ deploy testnet

And this would take us through the deployment process

Understanding the deployment 🧐

When you deploy your smart contract, several important things happen:

  1. A verification key is generated from your smart contract code

  2. This key becomes part of your smart contract's identity on the network

    1. The verification key is how you identify an account to be a zkapp
  3. The contract becomes a verifier that can validate zero-knowledge proofs

The Prover-Verifier relationship

Your ZK application consists of two main parts:

  1. Verifier (on-chain): Your deployed smart contract

  2. Prover (local): Your interaction script running on your machine interact.tsfile

Interacting with our contract

Once we have our contract deployed, we can start interacting with it using a script, typically named interact.ts

First, let's start by creating our interaction script. We'll need some essential imports from the Mina Protocol libraries. Head over to the interact.ts file. You can delete the existing content, and we'll start fresh. I'll explain each part of the code so you can have a comprehensive understanding of what's happening.

Here's a step-by-step guide to creating your interaction script:

import {
    Mina,
    PrivateKey,
    PublicKey,
    fetchAccount
} from 'o1js';

And of course we import our contract, in our case

import { Add } from './add.js';  // Our smart contract

👀 Note: We use .js and not .ts because we are importing the files that are already compiled from the build folder.

Let's break down what each import does:

  • Mina: The main interface to interact with the Mina network

  • PrivateKey & PublicKey: Classes for handling cryptographic keys

  • fetchAccount: Function to retrieve account data from the blockchain

  • Add: Our smart contract class that we created previously

This would be the necessary packages we are going to use to interact with our contract

Connecting to the Mina Network 🌐

To interact with the Mina network, we need to set up our network configuration:

For that we write in our file the following code

const Network = Mina.Network('https://api.minascan.io/node/devnet/v1/graphql')
Mina.setActiveInstance(Network);

We pas as an argument the network that we previously us to deploy our smart contracts

💡 Tip: You can find your network URL in the config.json file that was created during project setup.

Creating a reference to our Smart Contract 📝

To interact with our deployed smart contracts, we need to create an instance of our deployed smart contract using its public key:

We need to convert the public key from base58 to a public key object

const zkAppKey = PublicKey.fromBase58('B62qph2VodgSo4LQ...');  // Your contract's public key

We need to pass a value, and that value is the public key of your smart contract

const zkApp = new Add(zkAppKey) our smart contract reference

You may be asking: "Where do I get this public key from?" You can find it in two places:

  1. The Mina Explorer after deployment

  2. In your project's keys directory under the deploy alias

Reading the state from the Blockchain 📊

To read the current state from our smart contract we can make the following:

// First fetch the account
await fetchAccount({ publicKey: appKey });

// Then read the state

const currentNum = zkApp.num.get().toString();

And then we display into the console,

console.log('Current state value:', currentNum);

This code:

  1. Fetches the latest account state from the blockchain

  2. Reads the num state variable from our contract

  3. Converts it to a string (required because Mina uses special field types for ZK proofs)

💡 Tip: Always fetch the account before trying to read state to ensure you have the latest data!

Basically what we are doing here is going to our smart contract and get the current value of the ‘num’ state

And then with that we can run

$ npm run build && node build/src/interact.js

And you can see we get the value of 1 (which is the default value that was set on the default smart contract code)

With that, we make our first interaction with the mina chain 😄

Understanding the GraphQL Interface 🔍

The Mina Protocol offers a robust GraphQL interface for network interactions. You can connect your GraphQL client to http://localhost:3085/graphql or open this URL in your browser to use the GraphQL IDE.

Please note that Mina APIs are still under construction, so these endpoints may change. Keep this in mind while developing your applications

You can read more about the mina protocol graphql api by going to the documentation 👉 https://docs.minaprotocol.com/node-developers/graphql-api

Key pairs 🗝️🔑

Before continuing, let's understand what we're trying to achieve. In a ZK app, we have two types of key pairs:

  1. The ZK app's own private and public keys

  2. User key pairs for interacting with the ZK app

Generating a Key Pair 🔑

First, let's create a key pair that we'll use to interact with our deployed ZK app. We'll create a new file called generate.ts:

import { PrivateKey } from 'o1js';
// Generate a random private key

const privateKey = PrivateKey.random();

// Derive the public key from the private key

const publicKey = privateKey.toPublicKey();

// Create an object with base58 encoded keys

const keyPair = {

privateKey: privateKey.toBase58(),

publicKey: publicKey.toBase58()

};

console.log(keyPair);

💡 Tip: Always store your private keys securely and never share them. The public key can always be derived from the private key, but not vice versa!

Creating the Interaction Script 🛠️

Let's create our interaction script that will communicate with the ZK app, we can still use our interact.ts file

import { PrivateKey, PublicKey, Mina } from 'o1js';
// …rest of the code

const accountPrivateKey = PrivateKey.fromBase58('your-private-key-here');

const accountPublicKey = accountPrivateKey.toPublicKey();

Await Add.compile()  // this is require to create ZKProof

// Create a transaction

const tx = await Mina.transaction({

sender: accountPublicKey, //who would pay the fee

fee: 0.1e9 // 0.1 MINA

}, async () => {

zkApp.update()

});

// Create the proof (this may take some time)

console.log('Proving...');

await tx.prove();

// Sign and send the transaction

const sentTx = await tx.sign([accountPrivateKey]).send();

// Get the transaction URL for the explorer

const txUrl = https://minascan.io/mainnet/berkeley/transaction/${sentTx.hash()};

console.log(`View transaction at: ${txUrl}`);

Understanding what happend 🧮

One of the fascinating aspects of ZK apps is how they handle code execution. When you update the state of a ZK app:

  1. The code executes locally on your machine, not on the blockchain

  2. A zero-knowledge proof is generated to prove the computation was correct

  3. The proof is sent to the network for verification

  4. If valid, the state updates; if invalid, the transaction fails

  • The network can verify your computation without redoing it

  • The actual computation remains private

  • The system is highly secure against tampering

💡 Tip: Any modification to the contract code after deployment will result in invalid proofs. If you need to change the logic, you'll need to deploy a new contract!

Deploying on local

Now, we are going to learn how to work with a local blockchain. If you've been struggling with long wait times during development or worried about spending real tokens while testing, this guide is exactly what you need!

Why use local Blockchain? 🤔

Before we jump into the code, let's understand why local blockchain development is so important:

  • Lightning-fast development cycles

  • No waiting for network confirmations

  • Complete control over test accounts

  • Generate test MINA tokens at will

  • No real costs involved

Setting up your local environment 🛠️

First, let's import all the necessary components from the Mina Protocol libraries:

import {
    Mina,
    PrivateKey,
    AccountUpdate,
    PublicKey
} from 'o1js';

// Import your smart contract

import { Add } from './Add';

Initializing the local Blockchain

Here's where the magic happens:

// Initialize local blockchain
const Local = Mina.LocalBlockchain({

proofsEnabled: true

});

Mina.setActiveInstance(Local);

This code creates a local instance of the Mina blockchain. The proofsEnabled flag determines whether zero-knowledge proofs will be generated - set it to false during early development to speed things up!

Setting up test accounts 👥

Now, here's something interesting - we need three different accounts:

// Deployer account
const {

privateKey: deployerKey,

publicKey: deployerAccount

} = Local.testAccounts[0];

// Sender account

const {

privateKey: senderKey,

publicKey: senderAccount

} = Local.testAccounts[1];

// ZkApp account

const zkAppPrivateKey = PrivateKey.random();

const zkAppAccount = zkAppPrivateKey.toPublicKey();

You might be wondering, "Why do we need three accounts?" Let me break it down:

  1. Deployer Account: Responsible for deploying the smart contract

  2. Sender Account: Will interact with the smart contract

  3. ZkApp Account: The actual address of your smart contract

💡 Tip Always use a separate account for deployment rather than using the ZkApp account itself. This follows the principle of separation of concerns!

Deploying our Smart Contract 📝

Let's deploy our contract:

// Create instance of our contract
const zkApp = new Add(zkAppAccount);

console.log('Compiling contract...');

await zkApp.compile();

// Create and send deploy transaction

const deployTxn = await Mina.transaction(deployerAccount, () => {

AccountUpdate.fundNewAccount(deployerAccount);

zkApp.deploy();

});

await deployTxn.sign([deployerKey, zkAppPrivateKey]).send();

Here's what's happening step by step:

  1. We create a new instance of our contract

  2. Compile it to generate verification keys

  3. Create a deployment transaction

  4. Fund the new account and deploy

  5. Sign with both the deployer and ZkApp keys

Interacting with our contract 🔄

Now that our contract is deployed, we can interact with it:

// Read the current state
let num = await zkApp.num.get();

console.log('Initial state:', num.toString());

// Create an update transaction

const txn = await Mina.transaction(senderAccount, () => {

zkApp.update();

});

console.log('Proving transaction...');

await txn.prove();

await txn.sign([senderKey]).send();

// Read updated state

num = await zkApp.num.get();

console.log('Updated state:', num.toString());

💡 Tip: Local blockchain transactions are instant - no need to wait for block confirmations!

Advanced tips and tricks 🎯

  1. Disable Proofs During Development:
const Local = Mina.LocalBlockchain({
proofsEnabled: false  // Speed up development!

});
  1. Generate Multiple Test Accounts:
const testAccount = Local.testAccounts[2];  // Get more pre-funded accounts
  1. Quick State Reset:
Mina.setActiveInstance(Local);  // Reset to clean state

Common Pitfalls to Avoid ⚠️

  1. Don't forget to compile before deployment

  2. Always sign transactions with the correct keys

  3. Remember to fund new accounts before using them

  4. Keep proof generation enabled for final testing

💡 Tip: Keep your local development environment separate from your testnet and mainnet configurations to avoid any confusion or accidental deployments!

Test your smart contract

Testing is a crucial part of developing zkApps on the Mina Protocol. In this section, we'll walk through everything you need to know about testing your zkApps locally, from setting up your testing environment to writing integration tests. Whether you're new to zkApp development or looking to improve your testing practices, this tutorial has got you covered!

Getting Started with Test Files

The zkApp CLI makes it super easy to create test files for your smart contracts. Let's start with the basics, see this example

$ zk file foo

This command generates two files:

  • foo.ts: Your main contract file

  • foo.test.ts: The corresponding test file

Tip 💡 Always organize your tests in a way that mirrors your smart contract's structure. This makes it easier to maintain and understand your test suite as your project grows.

Structuring your tests

All zkApp projects created using the zkApp CLI come with the Jest JavaScript testing framework. While Jest is a robust and popular choice, you're free to use any testing framework that suits your preferences and project needs. This flexibility allows you to tailor your testing environment to best fit your development workflow and ensure your zkApp performs flawlessly.

Let's look at how to organize your tests properly. The Jest testing framework uses describe blocks to group related tests together. Here's a basic structure:

describe('MyContract', () => {
    describe('initialization', () => {

        it('should deploy with correct initial state', () => {

        // Test deployment and initial state

        });

    });

    describe('state modifications', () => {

        it('should update state correctly', () => {

        // Test state updates

        });

    });

});

Best Practices 🎯

  • Group related tests together in describe blocks

  • Use clear, descriptive test names

  • Test one thing per test case

  • Follow the Arrange-Act-Assert pattern

Setting Up the Local Blockchain

One of the most powerful features for testing zkApps is the ability to simulate a local blockchain. Let's see how to set this up:

async function setupLocalBlockchain() {
// Create a local blockchain instance

    const Local = await Mina.LocalBlockchain();

    Mina.setActiveInstance(Local);

    // Access test accounts

    const feePayer = Local.testAccounts[0].key;

    // Create zkApp account

    const zkAppPrivateKey = PrivateKey.random();

    const zkAppAddress = zkAppPrivateKey.toPublicKey();

    return { Local, feePayer, zkAppPrivateKey, zkAppAddress };

}

Deploying our contract for Testing

Here's a complete example of deploying a contract in your test environment:

async function deployContract() {
    const { Local, feePayer, zkAppPrivateKey, zkAppAddress } = await setupLocalBlockchain();

    // Create contract instance

    const zkAppInstance = new MyContract(zkAppAddress);

    // Deploy transaction

    const txn = await Mina.transaction(feePayer, async () => {

        AccountUpdate.fundNewAccount(feePayer);
        await zkAppInstance.deploy({ zkappKey: zkAppPrivateKey });

    });

    await txn.prove();

    await txn.sign([feePayer, zkAppPrivateKey]).send();

    return { zkAppInstance, feePayer };
}

Writing Integration Tests

Here's a comprehensive example of an integration test that verifies contract behavior:

describe('MyContract Integration Tests', () => {
    let zkAppInstance: MyContract;

    let feePayer: PrivateKey;

    beforeAll(async () => {

        ({ zkAppInstance, feePayer } = await deployContract());

    });

    it('should initialize with correct state', async () => {

        const state = zkAppInstance.num.get();

        expect(state).toEqual(Field(1));

    });

    it('should update state correctly', async () => {

    // Prepare transaction

        const txn = await Mina.transaction(feePayer, async () => {

        await zkAppInstance.update();

    });

// Prove and send

    await txn.prove();

    await txn.sign([feePayer]).send();

    // Verify new state

    const newState = zkAppInstance.num.get();

    expect(newState).toEqual(Field(3));

    });

});

Running our tests 🏃‍♂️

Execute your tests using these npm commands:

# Run all tests
$ npm run test
# Run tests in watch mode
$ npm run testw
# Generate test coverage report
$ npm run coverage

Tip 💡Use watch mode during development for instant feedback as you make changes to your code!

Best Practices and Tips 🌟

  1. Test Coverage: Aim for high test coverage but focus on testing critical paths and edge cases.

  2. Error Cases: Don't forget to test failure scenarios and error conditions.

  3. Clean Setup: Use beforeEach or beforeAll to ensure a clean state for each test.

  4. Meaningful Assertions: Write assertions that verify the actual business logic, not just state changes.

  5. Documentation: Use test descriptions as documentation for your contract's behavior.

Common Questions and Tips

Q: "Do I need special hardware to develop ZK apps?" A: No! You can develop and test ZK apps on any standard development machine.

Q: "How do I interact with the Mina network? Won't that cost money?" A: For development and testing, you can use Mina's Berkeley testnet, which uses test tokens that are free. You only need real MINA tokens when deploying to mainnet.

Q: "What if I run into version compatibility issues?" A: Always use the zk system command to check your versions. If you encounter problems, try updating your tools:

Q: Is the smart contract code stored on-chain? A: No! This is one of the unique aspects of Mina Protocol. Only the verification key and state are stored on-chain. The actual contract logic remains local, and zero knowledge proofs are used to verify correct execution.

Tips to keep in mind 🧠

  1. Keep your development tools updated regularly

  2. Join the Mina Discord community for support

  3. Start with simple proofs before tackling complex applications

  4. Use TypeScript's type system to catch errors early

  5. Always test your proofs thoroughly before deployment

  6. Keep your private keys secure and never commit them to version control

  7. Monitor your transaction fees and manage them appropriately


That's all folks.

I hope this article was helpful for you.

If you have any comment or suggestions, please leave it in the comments section.

You can follow me on twitter @0bkevin and DM me, I’m always happy to talk and get to know more people in this amazing community.


Sources: 🥸

  1. Create Your Own ZK Smart Contract series by Mina protocol 👉https://www.youtube.com/playlist?list=PLItixFkgfjYEvV0SwNpguAu-sNQ4cjyZ0

  2. ZK10: o1js: The dawn of zk smart contracts - Gregor Mitscha-Baude 👉https://www.youtube.com/watch?v=D5ASDWd7Xto.

  3. Berkeley Testnet Faucet 👉https://faucet.minaprotocol.com/

  4. GraphQL api reference 👉 https://docs.minaprotocol.com/graphql-api

  5. Mina Protocol Documentation👉 docs.minaprotocol.com