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

A zkApp consists of a smart contract and a UI to interact with it. To allow users to interact with your smart contract in a web browser, you typically want to build a website UI.

You can write the UI with any framework like React, Vue, or Svelte, or with plain HTML and JavaScript.

Using one of the provided UI framework scaffolds

When you create a project using the zkApp CLI, you can choose a supported framework to be scaffolded as a part of your zkApp project. For example, Next.js, Sveltkit, or Nuxt.js.

Adding your smart contract as a dependency of the UI

You can use one of the provided scaffolding options and add your smart contract to an existing frontend, a different UI framework, or a plain HTML and JavaScript website.

Specify the smart contracts to import

The index.ts file is the entry point of your project that imports all smart contract classes you want access to and exports them to your smart contract. 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 you to use your smart contract within your UI project during local development without having to publish it to npm. This local use allows for rapid development. See the npm link reference docs.

  1. At the command line, change into your smart contract project directory using cd <your-project> and run the npm link command.
  2. At the command line, change into your UI project directory using cd <your-ui-project> and run the npm link <your-package-name> command. your-package-name is the name property used in your smart contract's package.json.
  3. Import the smart contracts into your UI project, using import { YourSmartContract } from 'your-package-name';.

After making changes, remember to run npm run build in your smart contract directory so that the changes are reflected in the smart contract consumed by your UI project.

Publish to npm for production

  1. Create an npm account. If you don't have an account yet, go to npm Sign Up.
  2. Login to npm. To sign in, run npm login on the command line. When prompted,enter your username, password, and email address.
  3. Publish your package. At the root of your smart contract project directory, run npm publish. An error occurs if the package name already exists. To change the package name, change the name property in your package.json.
tip

To check if a package name exists on npm, use the npm search command. To avoid naming collisions, npm allows you to publish scoped packages: @your-username/your-package-name. See Introduction to packages and modules in the npm reference docs.

Consuming your smart contract in your UI

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

  1. Install your smart contract package.
  • Run npm install your-package-name from the root of your UI project directory.
  • If you published a scoped npm package, run npm install @your-username/your-project-name.
  1. 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, render your UI before importing and loading your smart contract so the SnarkyJS wasm workers can perform initialization without blocking the UI.

For example, if your UI is built using React, instead of a top level import, load the smart contract in a useEffect to 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.

These headers enable SnarkyJS to use SharedArrayBuffer that SnarkyJS relies on to enable important WebAssembly (Wasm) features.

  • Set Cross-Origin-Opener-Policy to same-origin.
  • Set Cross-Origin-Embedder-Policy 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. Otherwise, set these headers in the server framework of your choice (for example, Express for JavaScript).

Set headers for Vercel

If your app will be hosted on Vercel, set the headers in vercel.json.

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

Set headers for Cloudflare Pages

To host your app on Cloudflare Pages, set the headers in a _headers file.

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

Connecting your zkApp with a user's wallet

The Mina community has created a variety of different wallets. Only the Auro Wallet for Chrome supports interactions with zkApps.

To interact with your zkApp, users of your zkApp must have the Auro Wallet installed:

  • window.mina is automatically available in the user's browser environment.
  • Your zkApp uses this object to interact with the wallet.
  1. Install the Chrome extension for Auro Wallet.

  2. Get accounts.

    To fetch a user's list of Mina accounts, use the requestAccounts() method:

    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);
    }

    It is useful to indicate if the user's wallet is successfully connected to your zkApp:

  3. Send a transaction.

    After your user interacts with your zkApp, you can sign and send the transaction using sendTransaction(). You receive a transaction ID as soon as the Mina network has received the proposed transaction. However, this does not guarantee that the transaction is accepted in the network 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);
    }

    A best practice is to show the error message in your UI.

info

For details about the Mina Provider API, see the Mina Provider API Reference docs.

Display assertion exceptions in your UI

If an assertion exception occurs while a user interacts with any of your smart contract methods, you 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. Add a matching case for each unique assertion within your method. To assist with this, 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, like:

    try {
    YourSmartContract.yourMethod();
    } catch (err) {
    let uiErrorMessage;
    switch (err.message) {
    // A custom error thrown within YourSmartContract.yourMethod()
    // when there is an insufficient balance.
    case 'INSUFFICIENT_BALANCE':
    // Set a helpful message to show the user in the UI.
    uiErrorMessage =
    'Your account has an insufficient balance for this transaction';
    break;
    // etc
    }
    }