From 3bf87df6b22ab5ffabb7a5bf7cf6f8bd342d24e7 Mon Sep 17 00:00:00 2001 From: i Date: Mon, 25 Mar 2024 17:44:34 +0100 Subject: [PATCH 1/4] initial draft --- docs/zkapps/tutorials/08-custom-tokens.mdx | 252 +++++++++++++++++++++ sidebars.js | 1 + 2 files changed, 253 insertions(+) create mode 100644 docs/zkapps/tutorials/08-custom-tokens.mdx diff --git a/docs/zkapps/tutorials/08-custom-tokens.mdx b/docs/zkapps/tutorials/08-custom-tokens.mdx new file mode 100644 index 000000000..ff749eab4 --- /dev/null +++ b/docs/zkapps/tutorials/08-custom-tokens.mdx @@ -0,0 +1,252 @@ +--- +title: 'Tutorial 8: Custom Tokens' +hide_title: true +sidebar_label: 'Tutorial 8: Custom Tokens' +description: Smart contract code to create and manage new tokens. Mina has native support for custom tokens. +keywords: + - smart contracts + - zkapps + - custom tokens + - zero knowledge proof programming + - zk proof + - zk + - blockchain + - mina + - tokens +--- + +:::info + +zkApp programmability is not yet available on the Mina Mainnet. You can get started now by deploying zkApps to the 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/](https://github.com/o1-labs/docs2/tree/main/examples/zkapps/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](https://github.com/o1-labs/o1js/blob/main/src/lib/token.test.ts). + +## Prerequisites + +- Ensure your environment meets the [Prerequisites](/zkapps/tutorials#prerequisites) for zkApp Developer Tutorials. + +This tutorial has been tested with: + +- [Mina zkApp CLI](https://github.com/o1-labs/zkapp-cli) version 0.17.2 +- [o1js](https://www.npmjs.com/package/o1js) version 0.17.0 + +## Create the project + +1. Create or change to a directory where you have write privileges. +1. Create a project by using the `zk project` command: + + ```sh + $ 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: + + ```sh + $ 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 + ``` + +1. Edit `index.ts` to import and export your new smart contract: + + ```ts + import { BasicTokenContract } from './BasicTokenContract.js'; + + export { BasicTokenContract }; + ``` + +1. Add the official token standard implementation to your project dependencies + + ```sh + $ npm i mina-fungible-token + ``` + +## Concepts + +As mentioned above, Mina comes with custom token mechanism built-in. + +Let's pause to explore terms and ideas, that are essential for understanding how this mechanism is implemented in Mina. + +### Token Manager + +The token manager account is a zkApp that can: + +- Set a token symbol (also called token name) for its token. Uniqueness is not enforced for token names because the public key of the manager account is used to derive a unique identifier for each token. +- Mint new tokens. The zkApp updates an account's balance by adding the newly created tokens to it. You can send minted tokens to any existing account in the network. +- Burn tokens (the opposite of minting). Burning tokens deducts the balance of a certain address by the specified amount. A zkApp cannot burn more tokens than the specified account has. +- Send tokens between two accounts. This must be approved by a zkApp (see [Approval mechanism](#approval-mechanism)). + +### Token Account + +Token accounts are like regular accounts, but they hold a balance of a specific custom token instead of MINA. A token account is created from an existing account and is specified by a public key _and_ a token id. + +Token accounts are specific for each type of custom token, so a single public key can have many different token accounts. + +A token account is automatically created for a public key whenever an existing account receives a transaction denoted with a custom token. + +:::note + +When a token account is created for the first time, an account creation fee must be paid the same as creating a new standard account. + +::: + +### Token ID + +Token ids are unique identifiers that distinguish between different types of custom tokens. Custom token identifiers are globally unique across the entire network. + +Token ids are derived from a zkApp. To check the token id of a zkApp, use the `this.deriveTokenId()` function. + +### Approval mechanism + +Sending tokens between two accounts must be approved by a Token Manager zkApp. This can be done with `approveBase()` method of the custom token standard reference implementation. + +If you customize the `transfer()` function, don't forget to call `approveBase()`. + +### Upgradeability + +As opposet to most blockchains, where you deploy a bytecode, on Mina, when you deploy a smart contract you generate a verification key from the contract source code. The verification key is stored on-chain and used to verify proofs that belong to that smart contract. + +That means, that upgradeability is achieved by changing the verification key. In the Customn Token Standard implementation this can be done with `setVerificationKey()` method. + +## FungibleTokenBase implementation overview + +The token standard implementation is a Token Manager zkApp that is splitted in 2 parts: low-level and high-level logic. + +The low-level implementation is included in `o1js` library `TokenContract` abstract class. See the overview in the o1js [Custom Tokens tutorial](../o1js/custom-tokens) + +The high-level part inherts from the `TokenContract` class and has following user-facing features: + +### On-chain State, `decimals` and deploy arguments + +The on-chain state is defined as follows: + +```ts +@state(PublicKey) public adminAccount = State(); +@state(UInt64) public totalSupply = State(); +@state(UInt64) public circulatingSupply = State(); +``` + +- The `adminAccount` is set on deployment, and some of token functionality requires an admin signature. + + If you want to implement admin-only method, just call `this.requireAdminSignature()` helper in the method you want to protect. + +- The `totalSupply` defines a maximum amount of tokens to exist. It is set on deployment and can be modified with `setTotalSupply()` function (can be called by admin only) + +- The `circulatingSupply` tracks the total amount in circulation. When new tokens are minted, the `circulatingSupply` changes by an amount minted. + +- The `decimals` is a constant, that defines where to place the decimal comma in the token amounts. It is exposed in `getDecimals()` method. + +- The `.deploy()` function requires `adminAccount` and `totalSupply` to be passed as parameters. + +### Methods + +Transfer and burn functionality is available by following methods: + +```ts +transfer(from: PublicKey | AccountUpdate, to: PublicKey | AccountUpdate, amount: UInt64 | number | bigint) +burn(from: PublicKey, amount: UInt64) +``` + +Methods that can be called only by admin are: + +```ts +mint(address: PublicKey, amount: UInt64) +setVerificationKey(verificationKey: VerificationKey) +setTotalSupply(amount: UInt64) +``` + +Helper methods for reading state and fetching account balance + +```ts +getBalanceOf(address: PublicKey) +getTotalSupply() +getCirculatingSupply() +getDecimals() +``` + +That completes a review of a basic token. + +## Create and deploy a custom token + +To create a token manager smart contract, inherit your smart contract from base custom token implementation + +```ts +import { + FungibleToken +} from 'mina-fungible-token'; + +class MyToken extends FungibleToken {} +``` + +A full copy of the [MyToken.ts](https://github.com/o1-labs/docs2/blob/main/examples/zkapps/08-custom-tokens/src/BasicTokenContract.ts) is provided. + + +## Token Operations + +### Mint tokens + +### Burn tokens + +### Transfer tokens between user accounts + +### Build zkApp that interact with tokens + +#### Implement a smart contract that use tokens + +With zkApps, you can also build smart contracts that interact with tokens. For example, a simple escrow contract, where tokens can be deposited to and withdrawn from. + +#### Transfer from user to smart contract + +#### Transfer from contract to user + +#### Transfer from contract to contract + +### Implement custom mechanics + +Overriding default burn functionality + + + +To see an example of interacting with this contract, see [main.ts](https://github.com/o1-labs/docs2/blob/main/examples/zkapps/08-custom-tokens/src/main.ts). + +To see an example of putting rules around a token, see this [example](https://github.com/o1-labs/docs2/blob/main/examples/zkapps/08-custom-tokens/src/WhitelistedTokenContract.ts) of a token with whitelist gating so that public keys can interact with it. + + +## 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 [Fungible token standard](/zkapps/o1js/custom-tokens). + +Check out [Tutorial 9: Recursion](/zkapps/tutorials/recursion) to learn how to use recursive ZKPs with o1js, to implement zkRollups, large computations, and more. \ No newline at end of file diff --git a/sidebars.js b/sidebars.js index 154870142..8d17f0c35 100644 --- a/sidebars.js +++ b/sidebars.js @@ -85,6 +85,7 @@ module.exports = { 'zkapps/tutorials/common-types-and-functions', 'zkapps/tutorials/offchain-storage', 'zkapps/tutorials/oracle', + 'zkapps/tutorials/custom-tokens', 'zkapps/tutorials/recursion', 'zkapps/tutorials/account-updates', 'zkapps/tutorials/advanced-account-updates', From 021c3234930b5be277334a77b139b5eb5adde7e0 Mon Sep 17 00:00:00 2001 From: i Date: Tue, 26 Mar 2024 14:21:37 +0100 Subject: [PATCH 2/4] code samples for token Operations --- docs/zkapps/tutorials/08-custom-tokens.mdx | 110 ++++++++++++++++++--- 1 file changed, 98 insertions(+), 12 deletions(-) diff --git a/docs/zkapps/tutorials/08-custom-tokens.mdx b/docs/zkapps/tutorials/08-custom-tokens.mdx index ff749eab4..8ed02ec18 100644 --- a/docs/zkapps/tutorials/08-custom-tokens.mdx +++ b/docs/zkapps/tutorials/08-custom-tokens.mdx @@ -105,7 +105,7 @@ The token manager account is a zkApp that can: - Set a token symbol (also called token name) for its token. Uniqueness is not enforced for token names because the public key of the manager account is used to derive a unique identifier for each token. - Mint new tokens. The zkApp updates an account's balance by adding the newly created tokens to it. You can send minted tokens to any existing account in the network. - Burn tokens (the opposite of minting). Burning tokens deducts the balance of a certain address by the specified amount. A zkApp cannot burn more tokens than the specified account has. -- Send tokens between two accounts. This must be approved by a zkApp (see [Approval mechanism](#approval-mechanism)). +- Send tokens between two accounts. Any account can initiate a transfer, and the transfer must be approved by a Token Manager zkApp (see [Approval mechanism](#approval-mechanism)). ### Token Account @@ -135,13 +135,13 @@ If you customize the `transfer()` function, don't forget to call `approveBase()` ### Upgradeability -As opposet to most blockchains, where you deploy a bytecode, on Mina, when you deploy a smart contract you generate a verification key from the contract source code. The verification key is stored on-chain and used to verify proofs that belong to that smart contract. +As opposed to most blockchains, where you deploy a bytecode, on Mina, when you deploy a smart contract you generate a verification key from the contract source code. The verification key is stored on-chain and used to verify proofs that belong to that smart contract. -That means, that upgradeability is achieved by changing the verification key. In the Customn Token Standard implementation this can be done with `setVerificationKey()` method. +That means, the upgradeability is achieved by changing the verification key. In the Customn Token Standard implementation this can be done with `setVerificationKey()` method. ## FungibleTokenBase implementation overview -The token standard implementation is a Token Manager zkApp that is splitted in 2 parts: low-level and high-level logic. +The token standard implementation is a Token Manager zkApp that is splitted in 2 parts: low-level and high-level one. The low-level implementation is included in `o1js` library `TokenContract` abstract class. See the overview in the o1js [Custom Tokens tutorial](../o1js/custom-tokens) @@ -209,19 +209,113 @@ import { class MyToken extends FungibleToken {} ``` +To deploy a token manager contract, create and compile the token contract instance, then create, prove and sign the deploy transaction: + +```ts +const { + privateKey: tokenKey, + publicKey: tokenAccount +} = PrivateKey.randomKeypair(); +const token = new MyToken(tokenAccount); + +// paste the private key of the admin account here +const tokenAdminKey = PrivateKey.fromBase58('...'); +const tokenAdminAccount = PublicKey.fromPrivateKey(tokenAdminKey); + +const totalSupply = UInt64.from(21000000); +const tokenSymbol = 'MYTKN'; + +const tx = await Mina.transaction(deployerAccount, () => { + token.deploy(tokenAdminAccount, totalSupply, tokenSymbol); +}); + +tx.sign([deployerKey, tokenKey]); +await tx.prove(); +await tx.send(); +``` + +For this and following samples to work, make sure you have enough funds on deployer and admin accounts. + A full copy of the [MyToken.ts](https://github.com/o1-labs/docs2/blob/main/examples/zkapps/08-custom-tokens/src/BasicTokenContract.ts) is provided. ## Token Operations +In this section, we will explore the various token operations represented by the standard, which include: + +- Minting +- Burning +- Transferring between users +- Building zkApps that interact with tokens + ### Mint tokens +To mint tokens to some address: + +```ts +// paste the address where you want to mint tokens to +const mintTo = PublicKey.fromBase58(''); + +const mintAmount = UInt64.from(1000); + +const tx = await Mina.transaction(tokenAdminAccount, () => { + token.mint(mintTo, mintAmount); +}); + +tx.sign([tokenAdminKey]); +await tx.prove(); +await tx.send(); +``` + +:::note + +When a token account is created for the first time, an account creation fee must be paid the same as creating a new standard account. + +::: + ### Burn tokens +To burn tokens owned by some address: + +```ts +// paste the address where you want to burn tokens from +const burnFrom = PublicKey.fromBase58(''); + +const burnAmount = UInt64.from(1000); + +const tx = await Mina.transaction(burnFrom, () => { + token.burn(burnFrom, burnAmount); +}); + +tx.sign([burnFromKey]); +await tx.prove(); +await tx.send(); +``` + + ### Transfer tokens between user accounts +To transfer tokens between two user accounts: + +```ts +/// paste the private key of the sender and the address of the receiver +const sendFrom = PublicKey.fromBase58('...'); +const sendFromKey = Private.fromPublicKey(sendFrom); +const sendTo = PublicKey.fromBase58('...'); + +const sendAmount = UInt64.from(1); + +const tx = await Mina.transaction(sendFrom, () => { + token.transfer(sendFrom, sendTo, sendAmount); +}); +tx.sign([sendFromKey]); +await tx.prove(); +await tx.send(); +``` + ### Build zkApp that interact with tokens + #### Implement a smart contract that use tokens With zkApps, you can also build smart contracts that interact with tokens. For example, a simple escrow contract, where tokens can be deposited to and withdrawn from. @@ -234,14 +328,6 @@ With zkApps, you can also build smart contracts that interact with tokens. For e ### Implement custom mechanics -Overriding default burn functionality - - - -To see an example of interacting with this contract, see [main.ts](https://github.com/o1-labs/docs2/blob/main/examples/zkapps/08-custom-tokens/src/main.ts). - -To see an example of putting rules around a token, see this [example](https://github.com/o1-labs/docs2/blob/main/examples/zkapps/08-custom-tokens/src/WhitelistedTokenContract.ts) of a token with whitelist gating so that public keys can interact with it. - ## Conclusion From c57062f7a845ac71b27da20240a8882516cc778c Mon Sep 17 00:00:00 2001 From: i Date: Tue, 26 Mar 2024 14:24:51 +0100 Subject: [PATCH 3/4] getBalanceOf --- docs/zkapps/tutorials/08-custom-tokens.mdx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/zkapps/tutorials/08-custom-tokens.mdx b/docs/zkapps/tutorials/08-custom-tokens.mdx index 8ed02ec18..618861721 100644 --- a/docs/zkapps/tutorials/08-custom-tokens.mdx +++ b/docs/zkapps/tutorials/08-custom-tokens.mdx @@ -292,7 +292,6 @@ await tx.prove(); await tx.send(); ``` - ### Transfer tokens between user accounts To transfer tokens between two user accounts: @@ -313,6 +312,16 @@ await tx.prove(); await tx.send(); ``` +### Fetch token balance of the account + +To get token balance of some account: + +```ts +// paste the address of the account you want to read balance of +const anyAccount = PublicKey.fromBase58('...'); +const balance = token.getBalanceOf(anyAccount); +``` + ### Build zkApp that interact with tokens From f1dc635d106b8f6d85ab012d44a280eba2d1db1c Mon Sep 17 00:00:00 2001 From: i Date: Tue, 26 Mar 2024 17:09:37 +0100 Subject: [PATCH 4/4] rm verificationkey and upgradeability --- docs/zkapps/tutorials/08-custom-tokens.mdx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/docs/zkapps/tutorials/08-custom-tokens.mdx b/docs/zkapps/tutorials/08-custom-tokens.mdx index 618861721..a01d2798e 100644 --- a/docs/zkapps/tutorials/08-custom-tokens.mdx +++ b/docs/zkapps/tutorials/08-custom-tokens.mdx @@ -133,12 +133,6 @@ Sending tokens between two accounts must be approved by a Token Manager zkApp. T If you customize the `transfer()` function, don't forget to call `approveBase()`. -### Upgradeability - -As opposed to most blockchains, where you deploy a bytecode, on Mina, when you deploy a smart contract you generate a verification key from the contract source code. The verification key is stored on-chain and used to verify proofs that belong to that smart contract. - -That means, the upgradeability is achieved by changing the verification key. In the Customn Token Standard implementation this can be done with `setVerificationKey()` method. - ## FungibleTokenBase implementation overview The token standard implementation is a Token Manager zkApp that is splitted in 2 parts: low-level and high-level one. @@ -182,7 +176,6 @@ Methods that can be called only by admin are: ```ts mint(address: PublicKey, amount: UInt64) -setVerificationKey(verificationKey: VerificationKey) setTotalSupply(amount: UInt64) ```