Skip to content

Commit

Permalink
initial docs (#34)
Browse files Browse the repository at this point in the history
Co-authored-by: i <[email protected]>
  • Loading branch information
qwadratic and i authored Apr 3, 2024
1 parent be57714 commit 840f302
Show file tree
Hide file tree
Showing 6 changed files with 327 additions and 1 deletion.
2 changes: 1 addition & 1 deletion FungibleToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import type { FungibleTokenLike } from "./FungibleTokenLike.js"
export interface FungibleTokenDeployProps extends Exclude<DeployArgs, undefined> {
/** The initial administrator of the token contract. */
owner: PublicKey
/** The initial supply of the token. */
/** The max supply of the token. */
supply: UInt64
/** The token symbol. */
symbol: string
Expand Down
56 changes: 56 additions & 0 deletions docs/01-concepts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Mina Custom Token mechanics

Mina comes with native support for custom tokens ([see MIP-4](https://github.com/MinaProtocol/MIPs/blob/main/MIPS/mip-zkapps.md#token-mechanics) for more details). 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 minted, burned, and sent.

The manager account may also set a token symbol for its token. Uniqueness is not enforced for token names. Instead the public key of the manager account is used to identify tokens.

The full token contract code is provided in the [FungibleToken.ts](https://github.com/MinaFoundation/mina-fungible-token/blob/main/FungibleToken.ts) file.

For reference, all the ways to interact with token smart contract are provided in [examples/e2e.eg.ts](https://github.com/MinaFoundation/mina-fungible-token/blob/main/examples/e2e.eg.ts).

To reuse `FungibleToken` contract in your project, simply add 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. Any account can initiate a transfer, and the transfer must be approved by a Token Manager 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.

> [!IMPORTANT]
> 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 Token Manager zkApp. Use `deriveTokenId()` function to get id of a token.

### 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 or constructing `AccountUpdate`s for sending tokens manually, don't forget to call `approveBase()`.

93 changes: 93 additions & 0 deletions docs/02-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# FungibleTokenBase API overview

The token standard implementation is a Token Manager zkApp that is splitted in 2 parts: low-level and high-level one.

Check warning on line 3 in docs/02-api.md

View workflow job for this annotation

GitHub Actions / Ensure correct spelling

Unknown word (splitted)

The low-level implementation is included in `o1js` library `TokenContract` abstract class. See the overview in the o1js [Custom Tokens tutorial](https://docs.minaprotocol.com/zkapps/o1js/custom-tokens)

> [!WARNING]
> Please note that this is a beta release. The implementation will change soon. The API may also change in future.
The high-level part inherts from the `TokenContract` class and has following user-facing features:

Check warning on line 10 in docs/02-api.md

View workflow job for this annotation

GitHub Actions / Ensure correct spelling

Unknown word (inherts)

## On-chain State, `decimals` and deploy arguments

The on-chain state is defined as follows:

```ts
@state(PublicKey) public owner = State<PublicKey>();
@state(UInt64) public supply = State<UInt64>();
@state(UInt64) public circulating = State<UInt64>();
```

- `owner` is set on deployment, and some of token functionality requires an admin signature.

If you want to implement admin-only method, just call `this.ensureOwnerSignature()` helper in the method you want to protect.

- `supply` defines a maximum amount of tokens to exist. It is set on deployment and can be modified with `setSupply()` function (can be called by admin only)

- `circulating` tracks the total amount in circulation. When new tokens are minted, the `circulating` increases by an amount minted.

- The `decimals` is a constant, that defines where to place the decimal comma in the token amounts.

- The `deploy()` function requires `owner` and `supply` to be passed as parameters.

- Along with state variables initial values, the `deploy()` function also takes `symbol` (to set `account.tokenSymbol`) and `src` (to set `account.zkappUri`)

## Methods

Methods that can be called only by admin are:

```ts
mint(address: PublicKey, amount: UInt64)
setTotalSupply(amount: UInt64)
setOwner(owner: PublicKey)
```

Transfer and burn functionality is available by following methods:

```ts
transfer(from: PublicKey, to: PublicKey, amount: UInt64)
burn(from: PublicKey, amount: UInt64)
```

Helper methods for reading state variables and account balance

```ts
getBalanceOf(address: PublicKey)
getSupply()
getCirculating()
getDecimals()
```

## Events

On each token operation, the event is emitted.
The events are declared as follows:

```ts
events = {
SetOwner: PublicKey,
Mint: MintEvent,
SetSupply: UInt64,
Burn: BurnEvent,
Transfer: TransferEvent,
}

class MintEvent extends Struct({

Check warning on line 76 in docs/02-api.md

View workflow job for this annotation

GitHub Actions / Ensure correct spelling

Unknown word (Struct)
recipient: PublicKey,
amount: UInt64,
}) {}

class BurnEvent extends Struct({

Check warning on line 81 in docs/02-api.md

View workflow job for this annotation

GitHub Actions / Ensure correct spelling

Unknown word (Struct)
from: PublicKey,
amount: UInt64,
}) {}

class TransferEvent extends Struct({

Check warning on line 86 in docs/02-api.md

View workflow job for this annotation

GitHub Actions / Ensure correct spelling

Unknown word (Struct)
from: PublicKey,
to: PublicKey,
amount: UInt64,
}) {}
```

That completes a review of a fungible token.
52 changes: 52 additions & 0 deletions docs/03-deploy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Create and deploy a custom token

To create a token manager smart contract, inherit your smart contract from base custom token implementation, or use the `FungibleToken` directly

```ts
import {
FungibleToken
} from 'mina-fungible-token';

class MyToken extends FungibleToken {}
```

> [!NOTE]
> If you inherit from `FungibleToken` to override some functionality, you will need to compile both parent and child contracts to be able to prove code for both of them
To deploy a token manager contract, create and compile the token contract instance, then create, prove and sign the deploy transaction:

```ts
await FungibleToken.compile()
await MyToken.compile()

const {
privateKey: tokenKey,
publicKey: tokenAddress
} = PrivateKey.randomKeypair()
const token = new MyToken(tokenAddress)

// paste the private key of the deployer and admin account here
const deployerKey = PrivateKey.fromBase58('...')
const ownerKey = PrivateKey.fromBase58('...')
const owner = PublicKey.fromPrivateKey(ownerKey)
const deployer = PublicKey.fromPrivateKey(deployerKey)

const supply = UInt64.from(21_000_000)
const symbol = 'MYTKN'

Check warning on line 35 in docs/03-deploy.md

View workflow job for this annotation

GitHub Actions / Ensure correct spelling

Unknown word (MYTKN)
const src = "https://github.com/MinaFoundation/mina-fungible-token/blob/main/FungibleToken.ts"

const fee = 1e8

const tx = await Mina.transaction({sender: deployer, fee}, () => {
AccountUpdate.fundNewAccount(deployer, 1)
token.deploy(owner, supply, symbol, src)
})

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.

Refer to [examples/e2e.eg.ts](https://github.com/MinaFoundation/mina-fungible-token/blob/main/examples/e2e.eg.ts) to see executable end to end example.
80 changes: 80 additions & 0 deletions docs/04-token-operations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Token Operations

In this section, we will explore the various token operations represented by the standard, which include:

- Minting
- Burning
- Transferring between users

## 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({sender: owner, fee}, () => {
// comment this line if a receiver already has token account
AccountUpdate.fundNewAccount(owner, 1)
token.mint(mintTo, mintAmount)
})

tx.sign([tokenAdminKey])
await tx.prove()
await tx.send()
```

> [!IMPORTANT]
> 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({sender: burnFrom, fee}, () => {
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({sender: sendFrom, fee}, () => {
token.transfer(sendFrom, sendTo, sendAmount)
})
tx.sign([sendFromKey])
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)
```

Refer to [examples/e2e.eg.ts](https://github.com/MinaFoundation/mina-fungible-token/blob/main/examples/e2e.eg.ts) to see executable end to end example.
45 changes: 45 additions & 0 deletions docs/05-use-in-zkapp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# 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.

## Escrow contract code

Interacting with tokens from a zkApp is as simple as writing off-chain code (same code like in previous chapter is executed from within zkApp methods):


```ts
export class TokenEscrow extends SmartContract {
@state(PublicKey)
tokenAddress = State<PublicKey>()
@state(UInt64)
total = State<UInt64>()

deploy(args: DeployArgs & { tokenAddress: PublicKey }) {
super.deploy(args)

this.tokenAddress.set(args.tokenAddress)
this.total.set(UInt64.zero)
}

@method
deposit(from: PublicKey, amount: UInt64) {
const token = new FungibleToken(this.tokenAddress.getAndRequireEquals())
token.transfer(from, this.address, amount)
const total = this.total.getAndRequireEquals()
this.total.set(total.add(amount))
}

@method
withdraw(to: PublicKey, amount: UInt64) {
const token = new FungibleToken(this.tokenAddress.getAndRequireEquals())
const total = this.total.getAndRequireEquals()
total.greaterThanOrEqual(amount)
this.total.set(total.sub(amount))
token.transfer(this.address, to, amount)
}
}
```

## Interacting with token escrow

Refer to [examples/escrow.eg.ts](https://github.com/MinaFoundation/mina-fungible-token/blob/main/examples/escrow.eg.ts) to see executable `TokenEscrow` example.

0 comments on commit 840f302

Please sign in to comment.