Fungible Assets


This is the tutorial to read if you want to learn about fungible assets, or the ERC-20 equivalent on Aptos.


Overview

A fungible asset is a type of asset that is interchangeable and indistinguishable from other assets of the same kind, often representing assets like currency or stocks.

Coins vs Fungible Assets

Aptos has 2 ERC-20 like standards: Coin (legacy) and Fungible Asset. As of 04/2024, we are in the migration phase from Coin to Fungible Asset. Fungible Asset is based on Object and provides more flexibility and efficiency than Coin.

DEXes on Aptos today only support Coin now, but we are working on making them support Fungible Asset soon, targeting mid 2024.

When to Use Fungible Assets or Coin?

In our example, the food for Aptogotchi is something we want to create many identical copies to buy, transfer, and eat. This would be a perfect use case for the Fungible Asset in the Aptos Token Standard.


Aptogotchi Example

Source Code

Create Fungible Assets


const FOOD_FA_OBJECT_SEED: vector<u8> = b"APTOGOTCHI_FOOD";
struct FoodController has key {
/// Used to mint fungible assets.
fungible_asset_mint_ref: MintRef,
/// Used to burn fungible assets.
fungible_asset_burn_ref: BurnRef,
}
fun create_food_token(creator: &signer) {
// Creates an object, because in a nutshell a fungible asset is an object with fungibility.
let constructor_ref = &object::create_named_object(account, FOOD_FA_OBJECT_SEED);
let food_fa_signer = &object::generate_signer(constructor_ref);
// Creates the fungible asset using the object.
primary_fungible_store::create_primary_store_enabled_fungible_asset(
constructor_ref,
option::none(),
string::utf8(FOOD_NAME),
string::utf8(FOOD_SYMBOL),
0,
string::utf8(FOOD_URI),
string::utf8(PROJECT_URI),
);
let fungible_asset_mint_ref = fungible_asset::generate_mint_ref(constructor_ref);
let fungible_asset_burn_ref = fungible_asset::generate_burn_ref(constructor_ref);
// Store the FA refs as a FoodController resource under the food object.
move_to(food_fa_signer, FoodController {
fungible_asset_mint_ref,
fungible_asset_burn_ref,
});
}


Reference

References (refs) are the means to implement granular permission control across different standards in Aptos. In FA standard, we have three kinds of references:

  • MintRef - One can mint more assets with this ref.
  • TransferRef - Flip frozen in FungibleStore holding FA of the same metadata in the TransferRef. If it is true, the store is "frozen" so nobody can deposit to or withdraw from this store without using the ref.
  • BurnRef - One can burn assets with this ref.

Mint/Burn Fungible Tokens

A user can only perform minting and burning when they hold the MintRef and BurnRef. In the Aptogotchi app, we call these two functions for the purchase food and eat food features.


public(friend) fun mint_food(user: &signer, amount: u64) acquires FoodToken {
let food_token = borrow_global<FoodToken>(get_food_token_address());
let fungible_asset_mint_ref = &food_token.fungible_asset_mint_ref;
primary_fungible_store::deposit(
address_of(user),
fungible_asset::mint(fungible_asset_mint_ref, amount),
);
}
public(friend) fun burn_food(user: &signer, amount: u64) acquires FoodToken {
let food_token = borrow_global<FoodToken>(get_food_token_address());
primary_fungible_store::burn(&food_token.fungible_asset_burn_ref, address_of(user), amount);
}


Check Balance

With the input of owner address and the token type, we can call primary_fungible_store::balance directly to get the asset balance.


#[view]
/// Returns the balance of the food token of the owner
public fun get_food_balance(owner_addr: address): u64 {
let food_token = object::address_to_object<FoodToken>(get_food_token_address());
primary_fungible_store::balance(owner_addr, food_token)
}


Add a Price to Your Fungible Token

We can set a price for the fungible tokens and initiate the coin transfer from user to the contract (or a separated fee collector).

Note that the buy_food function is defined in the main module, not the food module. To make sure only main module can call mint_food and burn_food, we mark these 2 functions as public (friend) and declare main as the friend of food module.


module aptogotchi::food {
friend aptogotchi::main;
public(friend) fun mint_food(user: &signer, amount: u64) acquires FoodToken {
// ...
}
public(friend) fun burn_food(user: &signer, amount: u64) acquires FoodToken {
// ...
}
}
module aptogotchi::main{
use aptogotchi::food;
public entry fun buy_food(owner: &signer, amount: u64) {
// charge price for food
coin::transfer<AptosCoin>(owner, @aptogotchi, UNIT_PRICE * amount);
food::mint_food(owner, amount);
}
}



More Resources

Read more about the friend syntax here.

Link