Skip to main content
info

zkApp programmability is not yet available on the Mina Mainnet, but zkApps can now be deployed on Berkeley Testnet.

Tutorial 8: Custom Tokens

In this tutorial, you learn to create custom tokens.

Mina comes with native support for custom tokens. Each account on Mina can also have tokens associated with it.

To create a new token, one creates a smart contract, which becomes the manager for the token, and uses that contract to set the rules around how the token can be mint, burned, and sent.

The manager account may also set a token symbol for its token, such as in this example, MYTKN. Uniqueness is not enforced for token names. Instead the public key of the manager account is used to identify tokens.

In this tutorial, you review smart contract code that creates and manages new tokens.

The full example code is provided in the 08-custom-tokens/src/ example files.

For reference, a more extensive example, including all the ways to interact with token smart contracts, is provided in token.test.ts.

Prerequisites

  • Make sure you have the latest version of the zkApp CLI installed:

    $ npm install -g zkapp-cli
  • Ensure your environment meets the Prerequisites for zkApp Developer Tutorials.

This tutorial has been tested with:

Create the project

  1. Create or change to a directory where you have write privileges.

  2. Create a project by using the zk project command:

    $ zk project 08-custom-tokens

    The zk project command has the ability to scaffold the UI for your project. For this tutorial, select none:

    ? Create an accompanying UI project too? …
    next
    svelte
    nuxt
    empty
    ❯ none

Prepare the project

  1. Change to the project directory, delete the existing files, and create a new src/BasicTokenContract smart contract, and a index.ts file:

    $ cd 08-custom-tokens
    $ rm src/Add.ts
    $ rm src/Add.test.ts
    $ rm src/interact.ts
    $ zk file src/BasicTokenContract
    $ touch src/index.ts
  2. Edit index.ts to import and export your new smart contract:

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

    export { BasicTokenContract };

Basic Token Example

To create a token manager smart contract, create a normal smart contract whose methods call special functions that manipulate tokens.

A full copy of the BasicTokenContract.ts is provided.

Imports and smart contract structure

First, bring in imports and set up the structure for the smart contract.

The single state variable totalAmountInCirculation tracks how many tokens exist.

import {
SmartContract,
state,
State,
method,
DeployArgs,
Permissions,
UInt64,
PublicKey,
Signature,
} from 'o1js';

const tokenSymbol = 'MYTKN';

export class BasicTokenContract extends SmartContract {
@state(UInt64) totalAmountInCirculation = State<UInt64>();

deploy(args: DeployArgs) {
super.deploy(args);

const permissionToEdit = Permissions.proof();

this.account.permissions.set({
...Permissions.default(),
editState: permissionToEdit,
setTokenSymbol: permissionToEdit,
send: permissionToEdit,
receive: permissionToEdit,
});
}

init() and mint() methods

Next, add an init method and a method that mints tokens.

  • Set the token symbol (MYTKN) in the init() method.

  • To start tracking the amount in circulation, set it to zero.

  • Write a function to mint new tokens and send them to a recipient.

    This function checks that a signature has been provided by the zkApp account, so that only the zkApp account can call the mint() method.

  • In the mint() method, track how many tokens are in existence.

  @method async init() {
super.init();
this.account.tokenSymbol.set(tokenSymbol);
this.totalAmountInCirculation.set(UInt64.zero);
}

@method async mint(
receiverAddress: PublicKey,
amount: UInt64,
adminSignature: Signature
) {
let totalAmountInCirculation = this.totalAmountInCirculation.get();
this.totalAmountInCirculation.requireEquals(totalAmountInCirculation);

let newTotalAmountInCirculation = totalAmountInCirculation.add(amount);

adminSignature
.verify(
this.address,
amount.toFields().concat(receiverAddress.toFields())
)
.assertTrue();

this.token.mint({
address: receiverAddress,
amount,
});

this.totalAmountInCirculation.set(newTotalAmountInCirculation);
}

Send function

Finally, write a send function.

  • Holders of the MYTKN token call the sendTokens() method to send tokens to other Mina accounts.
  @method async sendTokens(
senderAddress: PublicKey,
receiverAddress: PublicKey,
amount: UInt64
) {
this.token.send({
from: senderAddress,
to: receiverAddress,
amount,
});
}
}

That completes a review of a basic token.

Examples

To see an example of interacting with this contract, see main.ts.

To see an example of putting rules around a token, see this example of a token with whitelist gating so that public keys can interact with it.

Building zkApps that interact with Tokens

With zkApps, you can also build smart contracts that interact with tokens. For example, swapping one token for another or taking deposits of Mina tokens.

For now, see this example of a zkApp implementing an AMM-based DEX.

Conclusion

You have finished reviewing the steps to build a smart contract to manage a token. You learned how to build a smart contract that places custom rules over tokens.

To learn more, see Custom Token API.

Check out Tutorial 9: Recursion to learn how to use recursive ZKPs with o1js, to implement zkRollups, large computations, and more.