Skip to main content

Please note that zkApp programmability is not yet available on Mina Mainnet, but zkApps can now be deployed to Berkeley Testnet.

How to Write a zkApp UI

To allow users to interact with your smart contract, you’ll typically want to build a website UI.

This UI can be written with any framework (e.g. React, Vue, Svelte, etc.) or plain HTML and JavaScript.

Using one of the provided UI framework scaffolds

When creating a new project with the zkApp CLI, you can directly choose one of three frameworks to be scaffolded as a part of your new zkApp project. At the moment, we support NuxtJS, Svelte Kit and NextJS.

You can do so by adding the --ui=<framework> flag to the zk project <myproj> command: zk project <myproj> --ui=<framework>, where <framework> stands for one of the three currently supported frameworks nuxt, svelte and next.

Adding your smart contract as a dependency of the UI

If you want to use one of the scaffolding options that we provide and you want to add your smart contract to an already existing frontend, a different UI framework or simply a plain HTML and JavaScript website, you can do so as well.


The index.ts file is the entry point of your project. This file only imports all smart contract classes you want access to, and exports them. This pattern allows you to specify which smart contracts are available to import when consuming your project from npm within your UI.

import { YourSmartContract } from './YourSmartContract.js';

export { YourSmartContract };

Local development

The npm link command allows to use your smart contract within your UI project during local development without needing to publish it npm. This allows for rapid development.

  1. Using a command line, change into your smart contract project directory using cd <your-project>, type npm link, and hit enter.
  2. Using a command line, change into your UI project directory using cd <your-ui-project>, type npm link <your-package-name>, and hit enter. your-package-name is the name property used within your smart contract's package.json.
  3. Import into your UI project like normal, using import { YourSmartContract } from ‘your-package-name’;.

Remember to run npm run build in your smart contract directory after making any changes in order for changes to be reflected in the smart contract consumed by your UI project.


For additional details about npm link, see the full npm reference docs.

Publish to npm for production

  1. Create an npm account: Create one here if you don't have one yet.
  2. Login: To sign in, enter npm login on the command line. You will be prompted to enter your username, password, and email address.
  3. Publish: To publish your package, enter npm publish on the command line at the root of your smart contract project directory. If the package name already exists on npm, you will get an error. You can change the package name by changing the name property in your package.json.

You can check if a package name already exists on npm using the npm search terminal command. To avoid naming collisions, npm allows to publish scoped packages: @your-username/your-package-name. For additional details about publishing packages including scoped packages, see the full npm reference docs.

Consuming your smart contract in your UI

Once you have published your smart contract to npm, you can easily add it to any UI framework of your choosing by importing the package.

  1. Install your smart contract package: To install your package, run the following npm command: npm install your-package-name from the root of your UI project directory. Or if you published a scoped npm package, run npm install @your-username/your-project-name.
  2. Import your smart contract package into the UI using: import { YourSmartContract } from ‘your-package-name’; , where YourSmartContract is the named export that you chose in your smart contract.

For a more performant UI, you may want to render your UI before importing and loading your smart contract. This allows the SnarkyJS wasm workers to perform initialization without blocking the UI.

For example, if your UI is built using React, loading the smart contract in a useEffect, instead of a top level import, will give the UI time to render its components before loading SnarkyJS.

Loading your contract with React

useEffect(() => {
(async () => {
const { YourSmartContract } = await import('your-package-name');
}, []);

Loading your contract with Svelte

onMount(async () => {
const { YourSmartContract } = await import('your-package-name');

Loading your contract with Vue

onMounted(async () => {
const { YourSmartContract } = await import('your-package-name');

Enabling COOP and COEP headers

To load SnarkyJS code in your UI, you must set the COOP and COEP headers as described below. These enable SnarkyJS' use of SharedArrayBuffer, which SnarkyJS relies on to enable important WebAssembly features.

  • Cross-Origin-Opener-Policy must be set to same-origin.
  • Cross-Origin-Embedder-Policy must be set to require-corp.

You can enable these headers a number of different ways. If you deploy your UI to a host such as Vercel or Cloudflare Pages, you can set these headers in a custom configuration file (see below). Otherwise, you can set these headers in the server framework of your choice (e.g. Express for JavaScript).


If your app will be hosted on Vercel, you can set these headers via vercel.json.

"headers": [
"source": "/(.*)",
"headers": [
{ "key": "Cross-Origin-Opener-Policy", "value": "same-origin" },
{ "key": "Cross-Origin-Embedder-Policy", "value": "require-corp" }

Cloudflare Pages

If your app will be hosted on Cloudflare Pages, you can set these headers via a _headers file.

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Connecting your zkApp with a user's wallet


Currently, only Auro Wallet's Chrome extension supports zkApp transactions. We anticipate other wallets to add support over time, which they can do by using the mina-provider from NPM within their browser-extension wallet.

  1. Install Auro Wallet for Chrome. Users of your zkApp will need to have this wallet installed in order to interact with your zkApp. Once installed, window.mina will automatically become available in the user's browser environment. Your zkApp will then use this object to interact with the wallet.

  2. Get accounts - To fetch a user's list of Mina accounts, you can use the requestAccounts() method. It can be useful to indicate if the user's wallet is successfully connected to your zkApp, such as in the screenshot below.

let accounts;

try {
// Accounts is an array of string Mina addresses.
accounts = await window.mina.requestAccounts();

// Show first 6 and last 4 characters of user's Mina account.
const display = `${accounts[0].slice(0, 6)}...${accounts[0].slice(-4)}`;
} catch (err) {
// If the user has a wallet installed but has not created an account, an
// exception will be thrown. Consider showing "not connected" in your UI.
  1. Sending a transaction - After your user interacts with your zkApp, you can sign and send the transaction using sendTransaction(). You will receive a transaction ID as soon as the Mina network has received the proposed transaction. But keep in mind that this does not guarantee that the network will accept the transaction into an upcoming block.
try {
// This is the public key of the deployed zkapp you want to interact with.
const zkAppAddress = 'B62qq8sm7JdsED6VuDKNWKLAi1Tvz1jrnffuud5gXMq3mgtd';

const tx = await Mina.transaction(() => {
const YourSmartContractInstance = new YourSmartContract(zkAppAddress);;

await tx.prove();

const { hash } = await window.mina.sendTransaction({
transaction: tx.toJSON(),
feePayer: {
fee: '',
memo: 'zk',

} catch (err) {
// You may want to show the error message in your UI to the user if the transaction fails.

If an error occurs when sending the transaction, you may want to show the error message in your UI to the user.


For additional details about the Mina Provider, see the full reference docs.

Displaying assertion exceptions in your UI

If an assertion exception occurs while a user interacts with any of your smart contract methods, you will want to capture this and display a helpful message for the user in your UI.

  1. Use a try-catch statement to catch exceptions when a user invokes a method on your smart contract.
  2. Use a switch-case statement to identify which exception was thrown. You will want a matching case for each unique assertion within your method. To assist with this, you may consider setting custom error messages for your assertions while writing the smart contract--for example: INSUFFICIENT_BALANCE.
  3. Display a helpful error message for the user within your UI.
try {
} catch (err) {
let uiErrorMessage;
switch (err.message) {
// A custom error thrown within YourSmartContract.yourMethod()
// when there is an insufficient balance.
// Set a helpful message to show the user in the UI.
uiErrorMessage =
'Your account has an insufficient balance for this transaction';
// etc