Skip to main content

Creating a Fungible Token Contract

In this tutorial we'll implement a Fungible Token.

info

To know what a fungible (and a non-fungible) token is, we recommend you to checkout Weil Tokens

warning

The steps are slightly different depending on the language you are using, so make sure go chose the right language now.

You have chosen Rust!

Preparations

This tutorial assumes that you have completed the Cross-Contract tutorial.

To start, create a new project. We'll name our example token Yutaka, as an homage to Japanese mathematician Yutaka Taniyama.

cargo new yutaka --lib
cd yutaka

Contract Specification

The token is defined by the following WIDL interface.

yutaka.widl
interface Yutaka {
query func name() -> string;
query func symbol() -> string;
query func decimals() -> u8;
query func details() -> tuple<string, string, u8>;
query func total_supply() -> uint;
query func balance_for(addr: string) -> result<uint, string>;
mutate func transfer(to_addr: string, amount: uint) -> result<(), string>;
mutate func approve(spender: string, amount: uint);
mutate func transfer_from(from_addr: string, to_addr: string, amount: uint) -> result<(), string>;
query func allowance(owner: string, spender: string) -> uint
}

This is the minimal interface any Fungible Token must implement in a WeilChain to be compatible with our Wallet. You could extend it for your own purposes, though, but we will not do this in this tutorial.

Server-Side Bindings

As usual, we need to create the server-side bindings and the contract skeleton.

widl generate yutaka.widl server rust

This will have generated the bindings.rs file, with the Applet skeleton. Let's fill in the skeleton with the contract logic. First, lets move the file to its final location.

cat bindings.rs > src/lib.rs
rm bindings.rs

Filling in the Logic

Contract State

The Yutaka contract has an inner field of type Fungible token, to which most operations are delegated.

src/lib.rs
...
#[derive(Serialize, Deserialize, WeilType)]
pub struct YutakaContractState {
inner: FungibleToken,
}
...

Contract Initialization

The constructor is responsible for initializing the inner Fungible token. Initializing it requires 3 parameters:

  • name: the name of the token
  • symbol: the symbol of the token
  • total supply: the total number of this token for ever to exist.

For our example, we use "Yutaka" as name, "YTK" as symbol, and 100_000 as total supply.

src/lib.rs
    #[constructor]
fn new() -> Result<Self, String>
where
Self: Sized,
{
let total_supply = 100000;
let mut yutaka_token = YutakaContractState {
inner: FungibleToken::new("Yutaka".to_string(), "YTK".to_string(), total_supply),
};

yutaka_token.inner.mint().map_err(|err| err.to_string())?;

Ok(yutaka_token)
}

Basic Methods

The contract provides methods to retrieve data related to the inner Fungible Token, as follows:

src/lib.rs
   #[query]
async fn name(&self) -> String {
self.inner.name()
}

#[query]
async fn symbol(&self) -> String {
self.inner.symbol()
}

#[query]
async fn decimals(&self) -> u8 {
18
}

#[query]
async fn details(&self) -> (String, String, u8) {
(self.name().await, self.symbol().await, self.decimals().await)
}

#[query]
async fn total_supply(&self) -> usize {
self.inner.total_supply()
}

Query Methods

Other query methods are used to get retrieve information based on the current state of the contract.

src/lib.rs

#[query]
async fn balance_for(&self, addr: String) -> Result<usize, String> {
self.inner.balance_for(addr).map_err(|err| err.to_string())
}

#[query]
async fn allowance(&self, owner: String, spender: String) -> usize {
self.inner.allowance(owner, spender)
}

Mutation Methods

These methods are used to modify the current state of the contract.

src/lib.rs
    #[mutate]
async fn transfer(&mut self, to_addr: String, amount: usize) -> Result<(), String> {
self.inner
.transfer(to_addr, amount)
.map_err(|err| err.to_string())
}

#[mutate]
async fn approve(&mut self, spender: String, amount: usize) {
self.inner.approve(spender, amount)
}

#[mutate]
async fn transfer_from(
&mut self,
from_addr: String,
to_addr: String,
amount: usize,
) -> Result<(), String> {
self.inner
.transfer_from(from_addr, to_addr, amount)
.map_err(|err| err.to_string())
}

You now have all pieces in place. Go ahead, build the contract and deploy it. Then follow the WebWallet tutorial to see and operate your tokens.