Initialize a Fungible Asset
Summary
- Fungible assets are interchangeable and divisible digital tokens, similar to cryptocurrencies.
- The initialization process involves setting up token configuration, creating metadata, and implementing management structures.
- Essential steps include defining token properties (name, symbol, decimals), creating a metadata object, and generating management references.
- View functions are implemented to retrieve token metadata, check account balances, and verify frozen status.
- Best practices include proper error handling, optimization techniques, and comprehensive testing.
- Common pitfalls to avoid are related to initialization, view function implementation, and testing procedures.
Overview
Fungible assets are digital tokens that are interchangeable and divisible, such as cryptocurrencies. This guide outlines the process of initializing a fungible asset on a blockchain platform, focusing on:
- Setting up token configuration
- Creating metadata and management structures
- Implementing essential view functions
- Best practices for development and testing
The tutorial provides step-by-step instructions and code examples to help developers create a robust and functional fungible asset system.
1. Introduction
This article provides detailed instructions on how to:
- Initialize a Fungible Asset
- Set up management references
- Implement basic view functions
2. Token Configuration Structure
/// Token configuration constants
const TOKEN_NAME: vector<u8> = b"YourToken";
const TOKEN_SYMBOL: vector<u8> = b"YTK";
const TOKEN_DECIMALS: u8 = 6;
Components:
TOKEN_NAME
: Full name of the tokenTOKEN_SYMBOL
: Short symbol (3–5 characters)TOKEN_DECIMALS
: Number of decimal points (typically 6–8)
3. Token Initialization
3.1. Create Metadata Object
fun init_module(module_signer: &signer) {
let constructor_ref = &object::create_named_object(
module_signer,
TOKEN_SYMBOL,
);
Details:
module_signer
: Module signer (typically the deployer)constructor_ref
: Reference for creating related objectscreate_named_object
: Creates an object with a deterministic address
3.2. Initialize Fungible Asset
primary_fungible_store::create_primary_store_enabled_fungible_asset(
constructor_ref,
option::none(), // Maximum supply (none = unlimited)
string::utf8(TOKEN_NAME), // Token name
string::utf8(TOKEN_SYMBOL), // Token symbol
TOKEN_DECIMALS, // Decimals
string::utf8(b""), // Icon URI
string::utf8(b""), // Project URI
);
Parameters:
constructor_ref
: Reference from the previous stepmaximum_supply
: Supply limit (option::none() = unlimited)name
: Token namesymbol
: Token symboldecimals
: Number of decimalsicon_uri
: Token icon URLproject_uri
: Project URL
3.3. Create and Store Management References
// Generate refs
let mint_ref = fungible_asset::generate_mint_ref(constructor_ref);
let burn_ref = fungible_asset::generate_burn_ref(constructor_ref);
let transfer_ref = fungible_asset::generate_transfer_ref(constructor_ref);
// Store refs
let metadata_signer = &object::generate_signer(constructor_ref);
move_to(
metadata_signer,
TokenManagement {
mint_ref,
burn_ref,
transfer_ref,
}
);
References:
mint_ref
: Authority to create new tokensburn_ref
: Authority to destroy tokenstransfer_ref
: Authority to manage transfers and frozen status
4. Implement View Functions
4.1. Get Metadata Function
#[view]
public fun get_metadata(): object::Object<fungible_asset::Metadata> {
object::address_to_object(
object::create_object_address(&@module_addr, TOKEN_SYMBOL)
)
}
Function:
- Returns the token's metadata object
- Uses module address and token symbol to calculate the address
- Marked as a view function to optimize gas usage
4.2. Get Balance Function
#[view]
public fun get_balance(account: address): u64 {
if (primary_fungible_store::primary_store_exists(account, get_metadata())) {
primary_fungible_store::balance(account, get_metadata())
} else {
0
}
}
Flow:
- Check if the store exists
- If the store exists, return the balance
- If not, return 0
4.3. Check Frozen Status
#[view]
public fun is_frozen(account: address): bool {
if (primary_fungible_store::primary_store_exists(account, get_metadata())) {
primary_fungible_store::is_frozen(account, get_metadata())
} else {
false
}
}
Flow:
- Check if the store exists
- If the store exists, return the frozen status
- If not, return false
5. Usage Examples
Check Token Info
// Get token metadata
let metadata = get_metadata();
// Check account balance
let balance = get_balance(@user);
// Check if account is frozen
let is_frozen = is_frozen(@user);
6. Best Practices
6.1. Error Handling
const ESTORE_NOT_FOUND: u64 = 1;
const EINVALID_METADATA: u64 = 2;
public fun safe_get_balance(account: address): u64 {
assert!(primary_fungible_store::primary_store_exists(account, get_metadata()),
ESTORE_NOT_FOUND
);
get_balance(account)
}
6.2. Optimization
// Cache metadata object for frequent use
let cached_metadata = get_metadata();
let balance1 = get_balance(addr1);
let balance2 = get_balance(addr2);
6.3. Testing
#[test]
fun test_view_functions() {
// Initialize token
init_module(...)
// Test balance view
assert!(get_balance(@user) == 0, 1);
// Test frozen status
assert!(!is_frozen(@user), 2);
}
7. Common Pitfalls and Notes
- Initialization
- Should only be called once when deploying the module
- Refs cannot be recreated after initialization
- URIs can be empty but should be provided in production
- View Functions
- Always handle cases where the store doesn't exist
- Consider caching for improved performance
- Ensure appropriate error handling
- Testing
- Test all edge cases
- Verify initialization success
- Check view function accuracy
Full Code
module movement::fungible_token_tutorial {
use std::string;
use std::option;
use aptos_framework::object;
use aptos_framework::fungible_asset::{Self, MintRef, TransferRef, BurnRef};
use aptos_framework::primary_fungible_store;
// =================== Constants ===================
// Token configuration
const MOVEMENT_NAME: vector<u8> = b"Movement";
const MOVEMENT_SYMBOL: vector<u8> = b"MOVE";
const MOVEMENT_DECIMALS: u8 = 6;
// =================== Resources & Structs ===================
// Holds the refs for managing Movement
#[resource_group_member(group = aptos_framework::object::ObjectGroup)]
struct MovementManagement has key {
mint_ref: MintRef,
burn_ref: BurnRef,
transfer_ref: TransferRef,
}
// =================== Initialization ===================
// Initialize the Movement token
fun init_module(module_signer: &signer) {
// Create metadata object with deterministic address
let constructor_ref = &object::create_named_object(
module_signer,
MOVEMENT_SYMBOL,
);
// Create the fungible asset with support for primary stores
primary_fungible_store::create_primary_store_enabled_fungible_asset(
constructor_ref,
option::none(), // No maximum supply
string::utf8(MOVEMENT_NAME),
string::utf8(MOVEMENT_SYMBOL),
MOVEMENT_DECIMALS,
string::utf8(b""), // Empty icon URI
string::utf8(b""), // Empty project URI
);
// Generate management references
let mint_ref = fungible_asset::generate_mint_ref(constructor_ref);
let burn_ref = fungible_asset::generate_burn_ref(constructor_ref);
let transfer_ref = fungible_asset::generate_transfer_ref(constructor_ref);
// Store the management refs in metadata object
let metadata_signer = &object::generate_signer(constructor_ref);
move_to(
metadata_signer,
MovementManagement {
mint_ref,
burn_ref,
transfer_ref,
}
);
}
// =================== View Functions ===================
// Get the metadata object of Movement
#[view]
public fun get_metadata(): object::Object<fungible_asset::Metadata> {
object::address_to_object(
object::create_object_address(&@movement, MOVEMENT_SYMBOL)
)
}
// Get the balance of an account
#[view]
public fun get_balance(account: address): u64 {
if (primary_fungible_store::primary_store_exists(account, get_metadata())) {
primary_fungible_store::balance(account, get_metadata())
} else {
0
}
}
// Check if account store is frozen
#[view]
public fun is_frozen(account: address): bool {
if (primary_fungible_store::primary_store_exists(account, get_metadata())) {
primary_fungible_store::is_frozen(account, get_metadata())
} else {
false
}
}
}