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
Create or change to a directory where you have write privileges.
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, selectnone
:? Create an accompanying UI project too? …
next
svelte
nuxt
empty
> none
Prepare the project
Change to the project directory, delete the existing files, and create a new
src/BasicTokenContract
smart contract, and aindex.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.tsEdit
index.ts
to import and export your new smart contract:import { BasicTokenContract } from './BasicTokenContract.js';
export { BasicTokenContract };
Create the project
Create or change to a directory where you have write privileges.
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, selectnone
:? Create an accompanying UI project too? …
next
svelte
nuxt
empty
❯ none
Prepare the project
Change to the project directory, delete the existing files, and create a new
src/BasicTokenContract
smart contract, and aindex.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.tsEdit
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.