Movement Labs LogoMovement Docs
Token Standard/Current Standard

Fungible Asset

Learn about the Fungible Asset standard for creating and managing fungible tokens on Movement

Fungible Asset (FA) Standard

The Movement Fungible Asset Standard provides a modern, type-safe framework for creating and managing fungible tokens on Movement. Built on the foundation of Move Objects, this standard offers enhanced security, composability, and customization capabilities that surpass traditional token implementations.

The Fungible Asset standard is available in the Movement framework at aptos_framework::fungible_asset and represents the current standard for fungible token development.

Overview

Movement's Fungible Asset standard enables developers to:

  • Create Sophisticated Tokens: Build tokens with custom logic, automated behaviors, and advanced features
  • Enhanced Security: Leverage Move's type safety and object-based architecture for secure token operations
  • Seamless Integration: Automatic compatibility with Movement ecosystem applications and wallets
  • Advanced Customization: Implement custom transfer logic, fee mechanisms, and compliance features

Core Architecture

Object-Based Design

The FA standard uses two primary Move Objects to represent fungible assets:

module aptos_framework::fungible_asset {
    /// Metadata object containing token information
    struct Metadata has key, copy, drop {
        name: String,
        symbol: String,
        decimals: u8,
        icon_uri: String,
        project_uri: String
    }
    
    /// Store object holding token balances
    struct FungibleStore has key {
        metadata: Object<Metadata>,
        balance: u64,
        frozen: bool
    }
    
    /// Ephemeral token representation for transfers
    struct FungibleAsset {
        metadata: Object<Metadata>,
        amount: u64
    }
}

Architecture Benefits:

  • Unified Standard: Single framework for all fungible token types
  • Composability: Objects can be extended and customized via smart contracts
  • Automatic Management: Recipients automatically receive storage capabilities
  • Gas Efficiency: Optimized storage patterns reduce transaction costs

Movement Framework Structures

The following tables describe the core structures in Movement's FA implementation.

[Metadata]

FieldTypeDescription
nameStringFull name of the fungible asset
symbolStringShort symbol (e.g., "MOVE", "USDT")
decimalsu8Number of decimal places for display
icon_uriStringURI pointing to token icon image
project_uriStringURI pointing to project website

[FungibleStore]

FieldTypeDescription
metadataObject<Metadata>Reference to the token's metadata object
balanceu64Current token balance in this store
frozenboolWhether transfers are frozen for this store

[FungibleAsset]

FieldTypeDescription
metadataObject<Metadata>Reference to the token's metadata object
amountu64Amount of tokens in this asset instance

Creating Fungible Assets on Movement

Basic Token Creation

The simplest way to create a fungible asset is using primary_fungible_store::create_primary_store_enabled_fungible_asset. This function creates a token with automatic store management, meaning recipients don't need to manually create storage for the token. The function returns capability references (MintRef, TransferRef, BurnRef) that must be stored securely for later token management operations.

module movement_addr::movement_token {
    use aptos_framework::fungible_asset::{Self, MintRef, TransferRef, BurnRef};
    use aptos_framework::object::{Self, Object};
    use aptos_framework::primary_fungible_store;
    use std::string;
    use std::option;
    use std::signer;

    /// Movement ecosystem token
    struct MovementToken has key {}

    /// Initialize a new Movement token
    public entry fun create_movement_token(creator: &signer) {
        // Create non-deletable object for token metadata
        let constructor_ref = &object::create_named_object(
            creator, 
            b"MOVEMENT_TOKEN"
        );

        // Create the fungible asset with Movement branding
        primary_fungible_store::create_primary_store_enabled_fungible_asset(
            constructor_ref,
            option::some(1000000000), // 1B max supply
            string::utf8(b"Movement Token"),
            string::utf8(b"MOVE"),
            8, // 8 decimals
            string::utf8(b"https://movement.xyz/assets/move-token.png"),
            string::utf8(b"https://movement.xyz")
        );

        // Generate capability references
        let mint_ref = fungible_asset::generate_mint_ref(constructor_ref);
        let transfer_ref = fungible_asset::generate_transfer_ref(constructor_ref);
        let burn_ref = fungible_asset::generate_burn_ref(constructor_ref);

        // Store capabilities in a resource
        move_to(creator, MovementTokenRefs {
            mint_ref,
            transfer_ref,
            burn_ref,
        });
    }

    /// Resource to store token capabilities
    struct MovementTokenRefs has key {
        mint_ref: MintRef,
        transfer_ref: TransferRef,
        burn_ref: BurnRef,
    }
}

Advanced Token with Custom Features

For more sophisticated use cases, you can create tokens with custom logic such as fee collection, time-based restrictions, or integration with other protocols. This example demonstrates a DeFi token with automatic fee collection on minting operations. The token configuration stores additional state like fee rates and launch timestamps.

module movement_addr::advanced_movement_token {
    use aptos_framework::fungible_asset::{Self, MintRef, TransferRef, BurnRef, FungibleAsset};
    use aptos_framework::primary_fungible_store;
    use aptos_framework::object::{Self, Object};
    use aptos_framework::event;
    use aptos_framework::timestamp;
    use std::string;
    use std::option;
    use std::signer;

    /// Advanced Movement DeFi token
    struct MovementDeFiToken has key {}

    /// Token configuration and state
    struct TokenConfig has key {
        mint_ref: MintRef,
        transfer_ref: TransferRef,
        burn_ref: BurnRef,
        fee_rate: u64,           // Fee rate in basis points (100 = 1%)
        fee_collector: address,   // Address that receives fees
        total_fees_collected: u64,
        launch_time: u64,
    }

    /// Fee collection event
    #[event]
    struct FeeCollected has drop, store {
        amount: u64,
        from_transfer: u64,
        collector: address,
    }

    /// Create advanced Movement DeFi token
    public entry fun create_advanced_token(
        creator: &signer,
        fee_rate: u64,
        fee_collector: address
    ) {
        assert!(fee_rate <= 1000, 1); // Max 10% fee
        
        let constructor_ref = &object::create_named_object(
            creator,
            b"MOVEMENT_DEFI_TOKEN"
        );

        // Create fungible asset with Movement DeFi branding
        primary_fungible_store::create_primary_store_enabled_fungible_asset(
            constructor_ref,
            option::none(), // Unlimited supply for DeFi use cases
            string::utf8(b"Movement DeFi Token"),
            string::utf8(b"MOVEFI"),
            18, // High precision for DeFi
            string::utf8(b"https://movement.xyz/assets/movefi-token.png"),
            string::utf8(b"https://defi.movement.xyz")
        );

        // Generate capabilities
        let mint_ref = fungible_asset::generate_mint_ref(constructor_ref);
        let transfer_ref = fungible_asset::generate_transfer_ref(constructor_ref);
        let burn_ref = fungible_asset::generate_burn_ref(constructor_ref);

        // Store configuration
        move_to(creator, TokenConfig {
            mint_ref,
            transfer_ref,
            burn_ref,
            fee_rate,
            fee_collector,
            total_fees_collected: 0,
            launch_time: timestamp::now_seconds(),
        });
    }

    /// Mint tokens with automatic fee collection
    public entry fun mint_with_fee(
        creator: &signer,
        recipient: address,
        amount: u64
    ) acquires TokenConfig {
        let config = borrow_global_mut<TokenConfig>(signer::address_of(creator));
        
        // Calculate fee
        let fee_amount = (amount * config.fee_rate) / 10000;
        let net_amount = amount - fee_amount;

        // Mint tokens
        let tokens = fungible_asset::mint(&config.mint_ref, amount);
        
        // Split for fee collection
        if (fee_amount > 0) {
            let fee_tokens = fungible_asset::extract(&mut tokens, fee_amount);
            primary_fungible_store::deposit(config.fee_collector, fee_tokens);
            
            config.total_fees_collected = config.total_fees_collected + fee_amount;
            
            event::emit(FeeCollected {
                amount: fee_amount,
                from_transfer: amount,
                collector: config.fee_collector,
            });
        }

        // Deposit remaining tokens to recipient
        primary_fungible_store::deposit(recipient, tokens);
    }
}

Token Operations

For Token Creators

Minting Tokens

Minting creates new token instances and adds them to circulation. The MintRef capability is required to mint tokens, ensuring only authorized accounts can increase the token supply. After minting, tokens must be deposited to a recipient's store using primary_fungible_store::deposit.

public entry fun mint_tokens(
    creator: &signer,
    recipient: address,
    amount: u64
) acquires MovementTokenRefs {
    let refs = borrow_global<MovementTokenRefs>(signer::address_of(creator));
    let tokens = fungible_asset::mint(&refs.mint_ref, amount);
    primary_fungible_store::deposit(recipient, tokens);
}

Batch Minting for Airdrops

For token distributions like airdrops, batch minting allows you to efficiently send tokens to multiple recipients in a single transaction. This reduces gas costs compared to individual transfers and ensures atomic execution where all transfers succeed or none do.

public entry fun airdrop_tokens(
    creator: &signer,
    recipients: vector<address>,
    amounts: vector<u64>
) acquires MovementTokenRefs {
    let refs = borrow_global<MovementTokenRefs>(signer::address_of(creator));
    let len = vector::length(&recipients);
    assert!(len == vector::length(&amounts), 1);

    let i = 0;
    while (i < len) {
        let recipient = *vector::borrow(&recipients, i);
        let amount = *vector::borrow(&amounts, i);
        
        let tokens = fungible_asset::mint(&refs.mint_ref, amount);
        primary_fungible_store::deposit(recipient, tokens);
        
        i = i + 1;
    };
}

Freezing and Unfreezing Accounts

The freeze functionality allows token creators to restrict transfers for specific accounts. This is useful for compliance requirements, security incidents, or implementing vesting schedules. The TransferRef capability is required to freeze or unfreeze accounts.

public entry fun freeze_account(
    creator: &signer,
    account: address,
    metadata: Object<Metadata>
) acquires MovementTokenRefs {
    let refs = borrow_global<MovementTokenRefs>(signer::address_of(creator));
    let store = primary_fungible_store::primary_store(account, metadata);
    fungible_asset::set_frozen_flag(&refs.transfer_ref, store, true);
}

public entry fun unfreeze_account(
    creator: &signer,
    account: address,
    metadata: Object<Metadata>
) acquires MovementTokenRefs {
    let refs = borrow_global<MovementTokenRefs>(signer::address_of(creator));
    let store = primary_fungible_store::primary_store(account, metadata);
    fungible_asset::set_frozen_flag(&refs.transfer_ref, store, false);
}

For Token Users

Basic Transfer Operations

Users can transfer tokens using primary_fungible_store::transfer, which handles withdrawing from the sender's store and depositing to the recipient's store in a single atomic operation. The recipient's store is automatically created if it doesn't exist.

public entry fun transfer_tokens(
    sender: &signer,
    metadata: Object<Metadata>,
    recipient: address,
    amount: u64
) {
    primary_fungible_store::transfer(sender, metadata, recipient, amount);
}

Batch Transfers

Batch transfers allow sending tokens to multiple recipients in a single transaction, reducing gas costs and ensuring atomic execution. This is useful for payroll systems, reward distributions, or any scenario requiring multiple simultaneous transfers.

public entry fun batch_transfer(
    sender: &signer,
    metadata: Object<Metadata>,
    recipients: vector<address>,
    amounts: vector<u64>
) {
    let len = vector::length(&recipients);
    assert!(len == vector::length(&amounts), 1);

    let i = 0;
    while (i < len) {
        let recipient = *vector::borrow(&recipients, i);
        let amount = *vector::borrow(&amounts, i);
        
        primary_fungible_store::transfer(sender, metadata, recipient, amount);
        i = i + 1;
    };
}

Conditional Transfers

Conditional transfers add validation logic before executing a transfer. This example ensures the sender maintains a minimum balance after the transfer, which is useful for preventing users from accidentally depleting their entire balance or for implementing reserve requirements.

public entry fun conditional_transfer(
    sender: &signer,
    metadata: Object<Metadata>,
    recipient: address,
    amount: u64,
    min_balance_required: u64
) {
    // Check sender has sufficient balance beyond minimum
    let sender_balance = primary_fungible_store::balance(
        signer::address_of(sender), 
        metadata
    );
    assert!(
        sender_balance >= amount + min_balance_required, 
        1
    );

    primary_fungible_store::transfer(sender, metadata, recipient, amount);
}

Dispatchable Fungible Assets (Advanced)

Movement supports Dispatchable Fungible Assets (DFA) that execute custom logic during transfers.

Custom Transfer Logic

Dispatchable Fungible Assets allow you to intercept and customize withdraw and deposit operations. This enables advanced features like automatic fee collection, transfer taxes, bonus rewards, or compliance checks. The custom logic is registered during token creation and executes automatically on every transfer.

module movement_addr::fee_collecting_token {
    use aptos_framework::fungible_asset::{Self, FungibleAsset, TransferRef};
    use aptos_framework::object::{Self, Object};
    use aptos_framework::primary_fungible_store;
    use aptos_framework::dispatchable_fungible_asset;
    use aptos_framework::function_info;
    use std::string;
    use std::option;

    /// Custom withdraw with fee collection
    public fun withdraw<T: key>(
        store: Object<T>,
        amount: u64,
        transfer_ref: &TransferRef,
    ): FungibleAsset {
        // Calculate fee (1% of transfer)
        let fee_amount = amount / 100;
        let net_amount = amount - fee_amount;

        // Withdraw full amount
        let fa = fungible_asset::withdraw_with_ref(transfer_ref, store, amount);
        
        // Extract fee portion
        let fee_fa = fungible_asset::extract(&mut fa, fee_amount);
        
        // Deposit fee to protocol treasury
        let treasury = get_treasury_address();
        primary_fungible_store::deposit(treasury, fee_fa);

        fa // Return net amount
    }

    /// Custom deposit with bonus rewards
    public fun deposit<T: key>(
        store: Object<T>,
        fa: FungibleAsset,
        transfer_ref: &TransferRef,
    ) {
        let amount = fungible_asset::amount(&fa);
        
        // Calculate bonus (0.1% of deposit)
        let bonus_amount = amount / 1000;
        
        // Deposit original amount
        fungible_asset::deposit_with_ref(transfer_ref, store, fa);
        
        // Mint and deposit bonus
        if (bonus_amount > 0) {
            let mint_ref = get_mint_ref();
            let bonus_fa = fungible_asset::mint(mint_ref, bonus_amount);
            fungible_asset::deposit_with_ref(transfer_ref, store, bonus_fa);
        }
    }

    /// Register dispatch functions
    public entry fun setup_dispatchable_token(creator: &signer) {
        let constructor_ref = &object::create_named_object(
            creator,
            b"MOVEMENT_FEE_TOKEN"
        );

        // Create the fungible asset
        primary_fungible_store::create_primary_store_enabled_fungible_asset(
            constructor_ref,
            option::some(10000000000), // 10B max supply
            string::utf8(b"Movement Fee Token"),
            string::utf8(b"MOVEFEE"),
            8,
            string::utf8(b"https://movement.xyz/assets/fee-token.png"),
            string::utf8(b"https://movement.xyz")
        );

        // Create function info for dispatch
        let withdraw_function = function_info::new_function_info(
            creator,
            string::utf8(b"fee_collecting_token"),
            string::utf8(b"withdraw")
        );

        let deposit_function = function_info::new_function_info(
            creator,
            string::utf8(b"fee_collecting_token"),
            string::utf8(b"deposit")
        );

        // Register dispatch functions
        dispatchable_fungible_asset::register_dispatch_functions(
            constructor_ref,
            option::some(withdraw_function),
            option::some(deposit_function),
            option::none()
        );
    }

    // Helper functions
    fun get_treasury_address(): address { @movement_treasury }
    fun get_mint_ref(): &fungible_asset::MintRef { 
        // Implementation to retrieve stored mint ref
        abort 0
    }
}

Query Functions

The Fungible Asset standard provides a comprehensive query interface for retrieving token metadata, supply information, balances, and store status. These functions enable applications to display token information, verify balances before transfers, and check account states.

// Token metadata queries
public fun name<T: key>(metadata: Object<T>): String
public fun symbol<T: key>(metadata: Object<T>): String  
public fun decimals<T: key>(metadata: Object<T>): u8
public fun icon_uri<T: key>(metadata: Object<T>): String
public fun project_uri<T: key>(metadata: Object<T>): String

// Supply information
public fun supply<T: key>(metadata: Object<T>): Option<u128>
public fun maximum<T: key>(metadata: Object<T>): Option<u128>

// Balance queries
public fun balance<T: key>(store: Object<T>): u64
public fun primary_store_balance<T: key>(account: address, metadata: Object<T>): u64

// Store status
public fun is_frozen<T: key>(store: Object<T>): bool
public fun store_exists(store: address): bool

Events and Monitoring

Movement's FA standard emits comprehensive events for tracking:

// Core transfer events
#[event]
struct Deposit has drop, store {
    store: address,
    amount: u64,
}

#[event]
struct Withdraw has drop, store {
    store: address,
    amount: u64,
}

#[event]
struct Frozen has drop, store {
    store: address,
    frozen: bool,
}

// Creation and management events
#[event]
struct Creation has drop, store {
    metadata: address,
    name: String,
    symbol: String,
    decimals: u8,
    maximum_supply: Option<u128>,
}

Summary

  • Modern framework built on Move Objects with automatic store management for recipients
  • Capability-based access through MintRef, TransferRef, and BurnRef for secure operations
  • Advanced features including dispatchable tokens with custom transfer logic
  • Batch operations for efficient airdrops and multi-recipient transfers
  • Enhanced security with protected capability refs and automatic storage creation
  • Recommended standard for new fungible token projects, replacing legacy Coin