Skip to main content
info

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.

Adding your smart contract as a dependency of the UI

Setup

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.

tip

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.
tip

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.
tip

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).

Vercel

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

info

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.
console.log(err.message);
}
  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);
YourSmartContractInstance.foo();
});

await tx.prove();

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

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

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

info

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

Displaying assertion exceptions in your UI

If an assertion error occurs while a user is interacting with your zkApp, you may want capture the assertion error and display a helpful message for the user in your UI.

  1. Capture errors that occur when a user interacts with a method on your smart contract in a try catch statement.
  2. Filter by the error message that was created.
  3. Display a helpful error message for the user in your UI based on what assertion exception was captured. This could be a modal or a message in your UI.
try {
YourSmartContract.yourMethod();
} catch (err) {
switch (err.message) {
case INSUFFICIENT_BALANCE:
// show a modal or a message telling the user they have insufficient balance for this
break;
// etc
}
}