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:
Object<Metadata>
- This represents details about the fungible asset itself, including information such as thename
,symbol
, anddecimals
.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 oneFungibleStore
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’ FungibleStore
s to indicate which FA is being tracked:

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:
- Creating a non-deletable Object to own the newly created Fungible Asset
Metadata
. - Generating
Ref
s to enable any desired permissions. - 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
. Ref
s 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 Ref
s 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:
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 Ref
s. In addition to the usual Object Refs, FAs define three additional permissions you can generate:
MintRef
offers the capability to mint new FA units.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).BurnRef
offers the capability to burn or delete FA units.
Note: All Ref
s 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:
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:
public fun mint(ref: &MintRef, amount:u64): FungibleAsset
Or burn FA units using the BurnRef
like so:
public fun burn(ref: &BurnRef, fa: FungibleAsset)
At this point, you can mint, transfer, and burn Fungible Assets using the Ref
s 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:
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:
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:
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 FungibleStore
s.
Check Balance
To check the balance of a primary store, call:
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:
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:
Deposit
: Emitted when FAs are deposited into a store.
struct Deposit has drop, store {
store: address,
amount: u64,
}
Withdraw
: Emitted when FAs are withdrawn from a store.
struct Withdraw has drop, store {
store: address,
amount: u64,
}
Frozen
: Emitted when the frozen status of a fungible store is updated.
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:
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 offungible_asset::deposit
. - Use
fungible_asset::withdraw_with_ref
instead offungible_asset::withdraw
.
- Use
- Avoid calling functions defined in
dispatchable_fungible_asset
andprimary_fungible_store
, except for inline functions, to prevent errors during transfers. - Note that calling
fungible_asset::withdraw
andfungible_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.
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:
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:
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:
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:
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:
- Create an Object to get its
ConstructorRef
. - Call:
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:
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:
#[view]
public fun paired_metadata<CoinType>(): Option<Object<Metadata>>
Similarly, a function exists for reverse lookup:
#[view]
public fun paired_coin(metadata: Object<Metadata>): Option<TypeInfo>
Off-chain Migration
There are two changes needed for off-chain services:
- Balances should reflect that a user may have both a
coin
balance and a paired FA balance. - 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 as0x1::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
andConcurrentFungibleBalance
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
:
- Call
paired_metadata<CoinType>()
to obtain the paired FA metadata object address (the address is immutable). - 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:
-
Vanilla FA: Tokens that primarily manage simple balance updates.
-
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:
-
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.
-
#[view] function primary_fungible_store::balance<T: key>(account: address, metadata: Object<T>): u64
-
#[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.