Skip to content
🎉 Welcome to the new Aptos Docs! Click here to submit an issue.
BuildSmart Contracts (Move)Fungible Asset (FA)

Aptos Fungible Asset (FA) Standard

The Aptos Fungible Asset Standard (also known as “Fungible Asset” or “FA”) provides a standard, type-safe way to define fungible assets in the Move ecosystem. It is a modern replacement for the coin module that allows for seamless minting, transfer, and customization of fungible assets for any use case.

This standard is important because it allows fungible assets on Aptos (such as Currencies and Real World Assets (RWAs)) to represent and transfer ownership in a consistent way dApps can recognize. This standard also allows for more extensive customization than the coin module did by leveraging Move Objects to represent fungible asset data.

The FA standard provides all the functionality you need to create, mint, transfer, and burn fungible assets (as well as automatically allowing recipients of the fungible asset to store and manage any fungible assets they receive).

It does so by using two Move Objects:

  1. Object<Metadata> - This represents details about the fungible asset itself, including information such as the name, symbol, and decimals.
  2. Object<FungibleStore> - This stores a count of fungible asset units owned by this account. Fungible assets are interchangeable with any other fungible asset that has the same metadata. An account may own more than one FungibleStore for a single Fungible Asset, but that is only for advanced use cases.

The diagram below shows the relationship between these Objects. The Metadata Object is owned by the Fungible Asset creator, then referenced in FA holders’ FungibleStores to indicate which FA is being tracked:

FA Object Relationship

This implementation is an improvement on the coin Standard because Move Objects are more customizable and extensible via smart contract. See the advanced guides on writing Move Objects for more details. The FA standard also automatically handles tracking how much of a fungible asset an account owns, as opposed to requiring the recipient to register a CoinStore resource separate from the transfer.

Creating a new Fungible Asset (FA)

At a high level, this is done by:

  1. Creating a non-deletable Object to own the newly created Fungible Asset Metadata.
  2. Generating Refs to enable any desired permissions.
  3. Minting Fungible Assets and transferring them to any account you want to.

To start with, the Fungible Asset standard is implemented using Move Objects. Objects by default are transferable, can own multiple resources, and can be customized via smart contract. For full details on Objects and how they work, please read this guide.

To create an FA, first you need to create a non-deletable Object since destroying the metadata for a Fungible Asset while there are active balances would not make sense. You can do that by either calling object::create_named_object(caller_address, NAME) or object::create_sticky_object(caller_address) to create the Object on-chain.

When you call these functions, they will return a ConstructorRef. Refs allow Objects to be customized immediately after they are created. You can use the ConstructorRef to generate other permissions that may be needed based on your use case.

Note that the ConstructorRef cannot be stored and is destroyed by the end of the transaction used to create this Object, so any Refs must be generated during Object creation.

One use for the ConstructorRef is to generate the FA Metadata Object. The standard provides a generator function called primary_fungible_store::create_primary_store_enabled_fungible_asset which will allow your fungible asset to be transferred to any account. This method makes it so the primary FungibleStore for recipients is automatically created or re-used so you don’t need to create or index the store directly.

This is what create_primary_store_enabled_fungible_asset looks like:

example.move
public fun create_primary_store_enabled_fungible_asset(
    constructor_ref: &ConstructorRef,
    // This ensures total supply does not surpass this limit - however,
    // Setting this will prevent any parallel execution of mint and burn.
    maximum_supply: Option<u128>,
    // The fields below here are purely metadata and have no impact on-chain.
    name: String,
    symbol: String,
    decimals: u8,
    icon_uri: String,
    project_uri: String,
)

Alternatively, you can use add_fungibility which uses the same parameters, but requires recipients to keep track of their FungibleStore addresses to keep track of how many units of your FA they have.

Once you have created the Metadata, you can also use the ConstructorRef to generate additional Refs. In addition to the usual Object Refs, FAs define three additional permissions you can generate:

  1. MintRef offers the capability to mint new FA units.
  2. TransferRef offers the capability to freeze accounts from transferring this FA, or to bypass an existing freeze. (This can be important when trying to be compliant with some regulations).
  3. BurnRef offers the capability to burn or delete FA units.
⚠️

Note: All Refs must be generated when the Object is created as that is the only time you can generate an Object’s ConstructorRef.

To generate an Object with all FA permissions, you could deploy a contract like this:

example.move
module my_addr::my_fungible_asset_example {
    use aptos_framework::fungible_asset::{Self, MintRef, TransferRef, BurnRef, Metadata, FungibleAsset};
    use aptos_framework::object::{Self, Object};
    use aptos_framework::primary_fungible_store;
    use std::error;
    use std::signer;
    use std::string::utf8;
    use std::option;
 
  const ASSET_SYMBOL: vector<u8> = b"FA";
 
	// Make sure the `signer` you pass in is an address you own.
	// Otherwise you will lose access to the Fungible Asset after creation.
  entry fun initialize(admin: &signer) {
    // Creates a non-deletable object with a named address based on our ASSET_SYMBOL
    let constructor_ref = &object::create_named_object(admin, ASSET_SYMBOL);
 
    // Create the FA's Metadata with your name, symbol, icon, etc.
    primary_fungible_store::create_primary_store_enabled_fungible_asset(
        constructor_ref,
        option::none(),
        utf8(b"FA Coin"), /* name */
        utf8(ASSET_SYMBOL), /* symbol */
        8, /* decimals */
        utf8(b"http://example.com/favicon.ico"), /* icon */
        utf8(b"http://example.com"), /* project */
    );
 
    // Generate the MintRef for this object
    // Used by fungible_asset::mint() and fungible_asset::mint_to()
    let mint_ref = fungible_asset::generate_mint_ref(constructor_ref);
 
    // Generate the TransferRef for this object
    // Used by fungible_asset::set_frozen_flag(), fungible_asset::withdraw_with_ref(),
    // fungible_asset::deposit_with_ref(), and fungible_asset::transfer_with_ref().
    let transfer_ref = fungible_asset::generate_transfer_ref(constructor_ref);
 
    // Generate the BurnRef for this object
    // Used by fungible_asset::burn() and fungible_asset::burn_from()
    let burn_ref = fungible_asset::generate_burn_ref(constructor_ref);
 
    // Add any other logic required for your use case.
    // ...
  }
}

For a full example of how to create your own Fungible Asset module, please see fa_coin.move. Alternatively, you can explore the collection of FA example code here.

Now you can use the MintRef to mint via:

example.move
public fun mint(ref: &MintRef, amount:u64): FungibleAsset

Or burn FA units using the BurnRef like so:

example.move
public fun burn(ref: &BurnRef, fa: FungibleAsset)

At this point, you can mint, transfer, and burn Fungible Assets using the Refs you generated. See the above example Move scripts for how to implement those entry functions.

Reference Docs

You can find the complete set of functions that the Fungible Asset Standard provides here.

The basic features owners of Fungible Assets can use include the following.

Withdraw

An owner can withdraw FA from their primary store by calling:

withdraw
public fun primary_fungible_store::withdraw<T: key>(owner: &signer, metadata: Object<T>, amount:u64): FungibleAsset

This function will emit a WithdrawEvent.

Deposit

An owner can deposit FA to their primary store by calling:

deposit
public fun primary_fungible_store::deposit(owner: address, fa: FungibleAsset)

This function will emit a DepositEvent.

Transfer

An owner can deposit FA from their primary store to that of another account by calling:

transfer
public entry fun primary_fungible_store::transfer<T: key>(sender: &signer, metadata: Object<T>, recipient: address, amount:u64)

This will emit both WithdrawEvent and DepositEvent on the respective FungibleStores.

Check Balance

To check the balance of a primary store, call:

check_balances
public fun primary_fungible_store::balance<T: key>(account: address, metadata: Object<T>): u64

Check Frozen Status

To check whether the given account’s primary store is frozen, call:

is_frozen
public primary_fungible_store::fun is_frozen<T: key>(account: address, metadata: Object<T>): bool

Events

FAs have three events emitted from the above basic functions:

  1. Deposit: Emitted when FAs are deposited into a store.
deposit_event
struct Deposit has drop, store {
    store: address,
    amount: u64,
}
  1. Withdraw: Emitted when FAs are withdrawn from a store.
withdraw_event
struct Withdraw has drop, store {
    store: address,
    amount: u64,
}
  1. Frozen: Emitted when the frozen status of a fungible store is updated.
frozen_event
struct Frozen has drop, store {
    store: address,
    frozen: bool,
}

Dispatchable Fungible Asset (Advanced)

Aside from the default managed fungible asset functionality provided by the Aptos Framework, fungible asset issuers can implement their own deposit/withdraw logic using the dispatchable fungible asset standard. This is done by registering custom hook functions to be invoked at withdrawal/deposit time. These hook functions are stored in the metadata of a fungible asset class, and the Aptos Framework will automatically invoke them instead of the default logic. This allows issuers to implement complex logic, such as customized access control. For more details, refer to the corresponding AIP.

Implementing the Hook Function

To implement a custom hook function, build a module with functions that have the following signature:

example.move
module my_addr::my_fungible_asset_example {
    // ...
    public fun withdraw<T: key>(
        store: Object<T>,
        amount: u64,
        transfer_ref: &TransferRef,
    ): FungibleAsset {
        // Your custom logic here
    }
 
    public fun deposit<T: key>(
        store: Object<T>,
        fa: FungibleAsset,
        transfer_ref: &TransferRef,
    ) {
        // Your custom logic here
    }
    // ...
}

Limitations

  • Reentrancy Prevention: Only call with_ref APIs in your custom hooks for deposit/withdraw operations.
    • Use fungible_asset::deposit_with_ref instead of fungible_asset::deposit.
    • Use fungible_asset::withdraw_with_ref instead of fungible_asset::withdraw.
  • Avoid calling functions defined in dispatchable_fungible_asset and primary_fungible_store, except for inline functions, to prevent errors during transfers.
  • Note that calling fungible_asset::withdraw and fungible_asset::deposit will NOT work for assets with registered hooks. See more information in Interacting with dispatchable fungible asset.

For more details on these limitations, refer to the AIP.

Registering the Hook Function

Once the functions are implemented, use the API defined in dispatchable_fungible_asset::register_dispatch_functions to bind the functions with your fungible asset.

register_dispatch
module 0x1::dispatchable_fungible_asset {
    public fun register_dispatch_functions(
        constructor_ref: &ConstructorRef,
        withdraw_function: Option<FunctionInfo>,
        deposit_function: Option<FunctionInfo>,
        derived_balance_function: Option<FunctionInfo>,
    )
}

The register_dispatch_functions function takes Option<FunctionInfo> as input to specify whether to use custom or default logic for withdraw/deposit/balance operations. If option::none() is passed, the default logic is used. A FunctionInfo identifies the function to be used as a custom hook. The constructor_ref is a reference for the metadata object of your fungible asset.

To construct a FunctionInfo, use either of:

new_function_info
module 0x1::function_info {
    public fun new_function_info(module_signer: &signer, module_name: String, function_name: String): FunctionInfo
    // or if you do not have the signer:
    public fun new_function_info_from_address(module_address: address, module_name: String, function_name: String): FunctionInfo
}

The registration can look like this:

example.move
module my_addr::my_fungible_asset_example {
    use aptos_framework::string;
    use aptos_framework::object;
    use aptos_framework::primary_fungible_store;
    use aptos_framework::dispatchable_fungible_asset;
    use aptos_framework::function_info;
 
    fun create_fungible_asset(module_signer: &signer, /* ... */) {
        // Make the deposit override function info
        let deposit_override = function_info::new_function_info(
            module_signer,
            string::utf8(b"my_fungible_asset_example"),
            string::utf8("deposit")
        );
 
        // Create the fungible asset
        let constructor_ref = object::create_sticky_object( /* ... */);
 
        primary_fungible_store::create_primary_store_enabled_fungible_asset(&constructor_ref, ...);
        // or below if not having primary stores
        // fungible_asset::add_fungibility(&constructor_ref, /* ... */);
 
        // Override default functionality for deposit
        dispatchable_fungible_asset::register_dispatch_functions(
            &constructor_ref,
            option::none(),
            option::some(deposit_override),
            option::none()
        );
 
        // ...
    }
 
    // ...
}

Interacting with dispatchable fungible asset

For users using primary_fungible_store to manage assets, no changes are required to interact with assets with dispatchable hooks. The Aptos Framework automatically invokes the dispatch logic if a hook is set up.

For users using secondary FungibleStore to interact with assets, use dispatchable_fungible_asset::withdraw/deposit instead of fungible_asset::withdraw/deposit to handle assets with registered hooks.

The dispatchable_fungible_asset::withdraw/deposit functions are replacements, and also work with functions that do not have hooks registered.

Managing Stores (Advanced)

Behind the scenes, the Fungible Asset Standard needs to manage how the asset balances are stored on each account. In the vast majority of circumstances, users will store all FA balances in their Primary FungibleStore. Sometimes though, additional “Secondary Stores” can be created for advanced DeFi applications.

  • Each account owns only one non-deletable primary store for each type of FA, the address of which is derived in a deterministic manner from the account address and metadata object address. If primary store does not exist, it will be created if FA is going to be deposited by calling functions defined in primary_fungible_store.move
  • Secondary stores do not have deterministic addresses and are deletable when empty. Users are able to create as many secondary stores as they want using the provided functions but there is a caveat that addressing secondary stores on-chain may be more complex.

You can look up a primary store via the following function:

primary_store
public fun primary_store<T: key>(owner: address, metadata: Object<T>): Object<FungibleStore>

And if you want to create a primary store manually you can use this function:

create_primary_store
public fun create_primary_store<T: key>(owner_addr: address, metadata: Object<T>): Object<FungibleStore>

The primary reason to use a secondary store is for assets managed by a smart contract. For example, an asset pool may have to manage multiple fungible stores for one or more types of FA.

To create a secondary store, you must:

  1. Create an Object to get its ConstructorRef.
  2. Call:
create_store
public fun create_store<T: key>(constructor_ref: &ConstructorRef, metadata: Object<T>): Object<FungibleStore>

This will create a secondary store owned by the newly created Object. Sometimes an object can be reused as a store. For example, a metadata object can also be a store to hold some FA of its own type or a liquidity pool object can be a store of the issued liquidity pool’s coin.

It is crucial to set the correct owner of a FungibleStore object for managing the FA stored inside. By default, the owner of a newly created object is the creator whose signer is passed into the creation function. For FungibleStore objects managed by smart contract itself, the owner should usually be an Object address controlled by the contract. For those cases, those objects should keep their ExtendRef at the proper place to create signer as needed to modify the FungibleStore via contract logic.

Migration from Coin to the Fungible Asset Standard

Smart Contract Migration

Projects utilizing the coin module do not need to modify their contracts. The coin module has been enhanced to handle migration automatically. Whenever a paired FA is required for a coin, it will be automatically created if it doesn’t already exist. The mapping between coins and FAs is immutable and stored in an on-chain table:

coin_conversion
struct CoinConversionMap has key {
    coin_to_fungible_asset_map: Table<TypeInfo, address>,
}

A #[view] function is available to retrieve metadata for the paired FA if it exists:

paired_metadata
#[view]
public fun paired_metadata<CoinType>(): Option<Object<Metadata>>

Similarly, a function exists for reverse lookup:

paired_coin
#[view]
public fun paired_coin(metadata: Object<Metadata>): Option<TypeInfo>

Off-chain Migration

There are two changes needed for off-chain services:

  1. Balances should reflect that a user may have both a coin balance and a paired FA balance.
  2. Event listeners should listen for both coin and FA events.

Since a user may possess both a coin balance and a paired FA balance, off-chain applications should be updated to reflect the sum of both the coin balance and its paired FA balance.

  • For Aptos Indexer users, you may utilize the table called current_fungible_asset_balances to obtain the latest sum of coin balance and FA balance representing the same asset type. If the FA has a paired coin type, the asset type would be set to the coin type, such as 0x1::aptos_coin::AptosCoin. Otherwise, for FA not paired from a coin, the asset type would be the metadata address. Users could filter by this field to get the FA balance of their interest.
  • For users employing Node API or other customized indexing, they should add the balance of the paired FA in users’ FungibleStore and ConcurrentFungibleBalance if any of them exist to the coin balance.

To retrieve the balance of the PrimaryFungibleStore for a paired FA to an existing coin of type CoinType:

  1. Call paired_metadata<CoinType>() to obtain the paired FA metadata object address (the address is immutable).
  2. Retrieve the balance of the paired FA:
    • Call getCurrentFungibleAssetBalances.
    • Alternatively, determine the address of the primary FungibleStore, which can be deterministically calculated with the following formula:
      • sha3_256(32-byte account address | 32-byte metadata object address | 0xFC)
    • Then use that address to obtain the FungibleStore resource to fetch the balance.
      • If the balance is non-zero, this is the final balance of this FA.
      • Otherwise, try to get ConcurrentFungibleBalance resource at the same address and get the balance there instead.
      • If neither exist, the FA balance for this account is 0.

Post-migration, both coin events and FA events could be emitted for an activity, depending on whether the user has migrated or not. Dapps relying on events should update their business logic accordingly.

Migration FAQs

What is the Aptos Fungible Asset (FA) standard?

The FA standard introduces a new way to represent fungible tokens as Move objects, replacing the legacy Coin resource model. Fungible Assets are more composable and developer-friendly compared to legacy Coins.

APT will be migrated starting on June 30, 2025.

How exactly does the new FA standard differ from legacy Coins?

With legacy Coins, each account directly holds a CoinStore\<CoinType> resource that tracks balances (in u64), includes flags like “frozen,” and emits basic events on deposits or withdrawals. Transfers, mints, and burns are performed via 0x1::coin module functions.

Under the FA Standard, token balances are held in FungibleStore objects (instead of each account directly holding a CoinStore resource). Each asset has metadata that defines its properties (name, symbol, etc.). For any account that owns that token, the balance lives in a FungibleStore object belonging to that account and linked to the Metadata object.

The primary way an account holds a fungible asset is via a primary fungible store; the address of this object is deterministically derived from the user’s account address and the token’s metadata address.

FAs come in two flavors:

  1. Vanilla FA: Tokens that primarily manage simple balance updates.

  2. Dispatchable FA (DFA): Tokens that embed custom Move logic automatically executed upon transfers.

How will this migration impact me?

As an end user, you don’t need to do anything. Your tokens remain safe, exactly where they should be in their new form. The migration does not affect ownership or usability in any way.

If you’re a developer, your existing smart contract code remains functional, but you should immediately start using the FA SDK for all new work. Existing coin API calls will continue working by silently routing to FA. After the migration, the coin module will be kept as it is, with minimal maintenance. Please note that you will no longer be able to look up coin balance by resource. Move to the 0x1::coin::balance view function, or the balance REST API instead.

What is the migration timeline?

All tokens on Aptos will begin migrating automatically from Coin v1 to the FA standard. All the coins except APT will be migrated from June 23 to 30. APT will transition from June 30 to July 8, 2025. The process involves continuously running batched transactions until every valid CoinStore has been fully converted into the new FungibleStore.

Why is the upgrade to the Fungible Asset standard necessary?

Short answer: It unlocks powerful functionalities that the legacy Coin module simply cannot support.

Modern DeFi and RWA apps increasingly demand sophisticated features like automated yield claims, custom fee structures, and built-in compliance checks. These are difficult to implement on legacy Coins. Attempting to bolt these capabilities onto the old standard rapidly creates composability issues, integration headaches and rising complexity.

Beyond functionality, builders can also use a unified asset standard across all tokens, including stablecoins. Imagine designing a payment kiosk: if it accepts only digital payments, you avoid the complexity of cash slots, coin dispensers, and change mechanisms altogether. Similarly, adopting a single streamlined token standard reduces complexity in platform development. It improves developer productivity and delivers more consistent user experience.

In short, the FA standard is clean and elegant. Developers can launch tokens that immediately integrate seamlessly across wallets, explorers, and DeFi applications from day one.

What are some new and unique functionalities I can build with Fungible Assets?

Fungible Assets open the door to a range of advanced features that weren’t possible with the legacy Coin model. Some notable capabilities include:

  • Tokens that automatically collect fees (like a percentage charge on transfers).

  • Interest-bearing tokens that accrue yield directly to the holders without manual claims.

  • Tokens with built-in vesting or time-locks that automatically release funds when certain conditions are met, a la escrow.

  • Tokens that dispense loyalty points on-chain when they’re spent.

  • Tokens that can dynamically adjust supply; burning or minting based on usage patterns.

The possibilities are endless.

A great real-world example is xLPT from Thala Labs, which uses built-in DFA hooks to automate staking & unstaking LP tokens, updating positions and rewards automatically upon each transfer, without any user intervention.

We know there is always a paired FA of a coin type. How can we query the supply and balance of this asset after the migration?

After the migration, querying resource 0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin> at account address will be unavailable. Instead, you can query the account balance by the following three ways:

  1. Balance Node API, the asset_type can be either coin_type, such as 0x1::aptos_coin::AptosCoin or FA metadata address such as 0xa, either way should return the same value.

  2. #[view] function primary_fungible_store::balance<T: key>(account: address, metadata: Object<T>): u64

  3. #[view] function balance<CoinType>(owner: address): u64. This method is deprecated and not applicable to pure FA tokens (e.g., USDt); it applies only to migrated coins such as APT. Due to its limitations and higher gas costs, it is not recommended.

Before migration, I could query all the assets a user has by getting all the resources at their address using API. How do I do it after migration?

You can use the Indexer API to query all the fungible assets the user owns.

Querying raw resources to get the asset balance and types is not recommended and will not be well-supported by fullnode API.

What if I have more questions?

Join the Aptos Discord! Aptos Labs engineers will be available throughout migration week to answer questions and offer support.

OSZAR »