Back to all stories
Highlighted Stories
Technical Blogs
Technical Insights
Move for Solidity Developers: Token Standard II — Advanced Fungible Token Extensions
6/12/2025
Move for Solidity Developers: Token Standard II — Advanced Fungible Token Extensions

“Move for Solidity Developers” is a series created for experienced Solidity developers who already know a bit about Move. It helps you shift from writing Solidity contracts to developing on Move-based blockchains, such as Aptos and Sui.

Building on our previous analysis of basic token functionalities across Solidity, Sui Move, and Aptos Move, this report focuses on the advanced features of fungible tokens. We specifically explore how these platforms implement fungible token standards, with extensions such as whitelisting/blacklisting, fee mechanisms, pausing, and whitelisting/blacklisting.

Solidity

Solidity’s longevity in the smart contract space has fostered a mature ecosystem, particularly for token standards like ERC-20. This maturity is characterized by well-established patterns and the availability of robust libraries, which significantly shape how developers extend basic token functionalities.

The nature of Solidity, especially its support for inheritance, is fundamental to extending token contracts. Developers typically start with a base ERC-20 contract and then inherit from other contracts that provide specific additional features. Esteemed libraries such as OpenZeppelin, Solmate or Solady play a crucial role by offering standardized, audited, and battle-tested implementations of common extensions. For instance, OpenZeppelin's ERC20.sol contract provides the core ERC-20 implementation, while extensions like ERC20Pausable.sol or ERC20Burnable.sol can be inherited to augment the token’s capabilities. This approach reduces boilerplate code, minimizes the risk of introducing vulnerabilities, and promotes interoperability. The developer experience is thus streamlined, allowing for a focus on application-specific logic rather than re-implementing standard features.

1 Code

The sample code basically overrides ERC20Pausable and ERC20Blocklist to complete the pausable and blacklist feature. Both maintain a flag or map for corresponding functionalities. If users must change the fee during a transfer, they can add logic in the _update function.

Token Extensions in Sui Move: A Dual System of Regulated Coins and Programmable Token Policies

Now, let’s dive into Sui Move to see how it maintains a similar extension like what we see on Solidity. Sui Move offers a distinct approach to fungible tokens and their extensions, primarily through two modules: sui::coin for "regulated coins" with built-in compliance features, and sui::token for "closed-loop" tokens that allow for highly customizable, programmable policies.

The sui::coin Module: Built-in Regulation for Fungible Assets

The sui::coin module is designed for fungible assets that may require regulatory features, such as stablecoins or other tokens needing access control.

  1. Creating Regulated Coins: Referring to the Regulated Coin Example, the primary function for instantiating such tokens is coin::create_regulated_currency_v2. This function is significant because, in addition to returning the standard TreasuryCap (which grants capabilities to mint and burn the coin), it also produces a DenyCapV2 object. This DenyCapV2 is a capability object—a core concept in Move representing a unique, unforgeable right—that grants its bearer administrative control over the deny list and global pause features for that specific coin type. This capability-based access control is a notable departure from Solidity's common address-centric pattern.

  2. Deny List (Blacklisting) Management: A key feature of regulated coins is the deny list. Unlike Solidity, where a blacklist is typically a mapping within the token contract itself, Sui's deny list for sui::coin is a system-level, singleton shared object residing at the fixed address @0x403. The bearer of the DenyCapV2 can interact with this system object using functions like coin::deny_list_v2_add and coin::deny_list_v2_remove to manage the list of blocked addresses for their specific coin type.

The effects of these actions are subject to Sui's epoch-based timing (epochs are approximately 24 hours):

  • Adding to Deny List: An address added to the deny list is immediately prevented from sending the regulated coin. However, it can still receive the coin until the beginning of the next epoch. After the next epoch starts, the address can neither send nor receive the coin.
  • Removing from Deny List: An address removed from the deny list must wait until the start of the next epoch before it can send the coin.

This system-level deny list and its epoch-based propagation of state changes are fundamental differences from the immediate, contract-contained state updates familiar to Solidity developers.

  1. Global Pause Functionality: Regulated coins can also implement a global pause feature. This is enabled by setting an allow_global_pause boolean flag to true during the call to coin::create_regulated_currency_v2. If enabled, the holder of the DenyCapV2 can use coin::deny_list_v2_enable_global_pause to halt all activity for that coin type and coin::deny_list_v2_disable_global_pause to resume it.

The effects of the global pause also have immediate and next-epoch components:

  • Enable Pause: The coin type can still mint. At the start of the next epoch, addresses are also disallowed from receiving that coin type.
  • Disable Pause: Addresses can only begin sending/receiving the coin type again, starting from the next epoch.

Similar to the deny list, this global pause is managed via a capability, and its full enforcement across all aspects of interaction is subject to epoch progression.

  1. Fee Mechanisms: In its standard form for regulated coins, the sui::coin module does not natively include logic for implementing fee-on-transfer mechanisms. Developers seeking such functionality would need to consider alternative approaches, such as building custom coin logic from scratch (which sacrifices some of the benefits of the standard module) or utilizing the more flexible sui::token module, discussed next.

  2. Enforcement Points: Checks related to the deny list are performed at multiple stages within the Sui system. For instance, they occur during transaction signing (as seen in authority.rs references for V1 and V2 deny lists) and, for V2, additional checks can occur during transaction execution (as per context.rs references). This signifies that certain regulatory enforcements are embedded at a deeper layer of the Sui protocol, not solely within the developer-written smart contract code.

The sui::token Module: Customizable Policies with the Hot-Potato Pattern

For scenarios demanding more granular control and custom rules beyond the scope of sui::coin, Sui provides the sui::token module. This is often used for "closed-loop" tokens, such as loyalty points, in-game currencies, or other assets whose utility might be primarily within a specific application or ecosystem.

  1. Concept of Closed-Loop Tokens and TokenPolicy: The sui::token module allows developers to define a TokenPolicy<T> object for a specific token type T. This policy object encapsulates the custom rules governing the token's behavior. Administrative control over this policy is granted by a TokenPolicyCap, another capability object. The policy can dictate rules for various actions, including transfers, spends, and conversions between tokens and coins.

  2. The Hot-Potato Pattern and ActionRequest: A critical Move pattern utilized by the sui::token module is the "hot-potato" pattern. An ActionRequest<T> that has no abilities (like store or drop). This means that, if a function returns an ActionRequest<T>, that ActionRequest<T> object cannot be simply discarded or stored in an account's state; it must be consumed by another function within the same transaction.

2 Chart

Protected actions on tokens, such as token::transfer, token::spend, token::to_coin, and token::from_coin, are designed to return an ActionRequest<T>. This ActionRequest<T> must then be passed to and processed by a function associated with the token's TokenPolicy. This mechanism effectively forces all interactions with the token to pass through and adhere to the rules defined in its policy, ensuring that policy checks cannot be inadvertently or maliciously bypassed.

  1. Implementing Extensions via Custom Rules: The TokenPolicy provides the framework for implementing various extensions:

    • Whitelisting/Blacklisting: The rules within a TokenPolicy can check sender or receiver addresses against custom lists. These lists could be stored, for example, in a Bag or Table object associated with the policy. For example, in simple_token, the rule governing the transfer_action would consult this list and approve or deny the action accordingly based on sender or receiver.

3 Code

  • Pausable Functionality: A simple boolean flag within the TokenPolicy object can serve as a pause switch. Rules handling ActionRequests for various operations would first check this flag and proceed only if the token is not paused.
  • Fee-on-Transfer: A rule designed to handle the transfer_action (or a custom transfer function that generates an ActionRequest) can implement fee logic. Upon receiving an ActionRequest for a transfer, the rule could split the token amount, forwarding a portion to a designated fee collector address and the remainder to the intended recipient. The coffee.move contract example, while not direct fee-on-transfer implementations, demonstrates mechanisms for collecting payments or managing value flows that are conceptually similar to how fees could be managed within a TokenPolicy.

4 Code

This approach offers high flexibility, but also places the responsibility for designing and securely implementing these rules squarely on the developer.

Token Extensions in Aptos Move: The Fungible Asset (FA) Standard and Dispatchable Logic

Aptos Move has also evolved its approach to tokens, moving toward a more flexible and extensible model with its Fungible Asset (FA) standard. This standard is designed to overcome limitations of earlier token models and provide a robust foundation for advanced functionalities. Initially, Aptos provided a coin module, similar in concept to Sui's basic coin functionalities. However, to address the need for greater extensibility and overcome the rigidity of the original struct-based coin, Aptos introduced the Fungible Asset (FA) standard at AIP-21. The FA standard leverages Move Objects, which are inherently more customizable and allow for richer interactions than simple structs. This shift is part of a broader effort to unify token handling on Aptos and provide a more future-proof framework for diverse use cases.

Core Components of the Fungible Asset Standard

The FA standard is built around several key components:

  • Metadata Object: Each distinct type of fungible asset is defined by a Metadata object. This object, typically created to be non-deletable, stores essential information like the asset's name, symbol, decimals, and potentially a URI for an icon. This Metadata object serves as the unique identifier for the token type.
  • Capability Refs: The FA standard utilizes special reference types, or Refs, to manage permissions and control critical operations. These are generated, often starting from a ConstructorRef obtained during the FA's creation:
    • ConstructorRef: Used during the initialization of the fungible asset to create other capability Refs.
    • MintRef: Grants the capability to mint new units of the fungible asset.
    • BurnRef: Grants the capability to burn (destroy) units of the fungible asset.
    • TransferRef: A particularly relevant capability for extensions, as it allows an authorized entity to freeze accounts from transferring the specific FA, or to bypass such freezes. This is crucial for implementing regulatory compliance features like pausing or restricting transfers for certain accounts.

These Refs embody a capability-based security model, where holding a specific Ref grants the authority to perform the associated action.

Implementing Extensions via Dispatchable Functions (Hooks)

A cornerstone of the FA standard's extensibility is its support for dispatchable functions, or "hooks." Token issuers can register custom Move functions that the Aptos Framework will automatically invoke during specific FA operations, such as deposits or withdrawals. This allows developers to inject custom logic without altering the core FA module.

  • Mechanism: Functions like object::register_dispatch_functions (or the more specific fungible_asset::register_derive_supply_dispatch_function for supply-related hooks) are used to associate these custom functions with the FA's metadata. When a standard operation like a transfer (which involves a withdrawal from the sender and a deposit to the recipient) occurs, the framework checks for registered hooks. If found, the custom hook is executed instead of, or in addition to, the default logic. The underlying mechanism for this is referred to as native_dispatch within the Aptos framework sources.

5 Chart

  • Supported Overrides: The primary operations that can be hooked are deposit and withdraw. Hooks for balance and supply can also be registered to customize how these values are derived or reported.

6 Chart

This hook system provides a structured way to introduce custom behavior, somewhat analogous to Solidity's internal function overrides (like _update), but more explicitly tied to predefined actions within the token lifecycle.

Implementing Specific Extensions

Using the FA standard's capabilities and dispatch hooks, developers can implement various advanced features:

  1. Pausing/Freezing Accounts: The TransferRef capability is the primary tool for implementing pausing or freezing functionality for a specific fungible asset. The entity holding the TransferRef for an FA can invoke functions to freeze an account, preventing that account from transferring units of that particular FA. Developers can also take advantage of hooks for deposit and withdraw to globally pause the functionality.

  2. Whitelisting/Blacklisting: Whitelisting or blacklisting can be implemented by embedding custom logic within the dispatch hooks for deposit and withdraw operations. For example, a blacklist: the withdraw hook could check if the sender is on a blacklist, and the deposit hook could check if the recipient is on a blacklist.

  3. Fee-on-Transfer: Fee-on-transfer mechanisms can also be built using the deposit and withdraw dispatch hooks.

    • A fee could be deducted from the sender during the withdraw hook. The hook logic would calculate the fee, transfer the fee amount to a designated treasury account, and then allow the withdrawal of the original amount minus the fee.
    • Alternatively, a fee could be assessed on the recipient during the deposit hook, or a combination of both. The custom logic within the hook would handle the fee calculation and redirection.

For example:

7 Code

Security Considerations

There are several security considerations when working with Fungible Assets:

  • Metadata Validation: It is critical to always validate the Metadata object address of an FA before accepting it or using it in any contract logic. This prevents spoofing, where a malicious actor might try to pass off a fake token as a legitimate one.
  • Capability Ref Integrity: Ensure that any capability Ref (e.g., MintRef or TransferRef) being used corresponds to the expected Metadata of the FA it purports to control.
  • Consistency During Transfers: Verify that FA Metadata remains consistent during transfers, especially between wallets or contracts, to avoid unauthorized usage or misuse.

Summary of Approaches

  • Solidity: Relies on contract inheritance and the overriding of internal functions (e.g., _update). The ecosystem is heavily supported by mature libraries like OpenZeppelin, which provide standard implementations for extensions like pausing and blocklisting. Fee mechanisms are typically custom-built within these overrides.
  • Sui Move: Presents a dual system. The sui::coin module offers built-in regulatory features (deny list, global pause) for "regulated coins," managed via the DenyCapV2 capability, with some operations subject to epoch-based latency. The sui::token module allows for highly customizable "closed-loop" tokens through programmable TokenPolicy objects, governed by a TokenPolicyCap and enforced using the hot-potato pattern with ActionRequest structs.
  • Aptos Move: Centers on the Fungible Asset (FA) standard, which uses Move Objects for greater extensibility. Customization is achieved through dispatchable functions (hooks) for operations like deposit and withdraw, with administrative control managed by various Ref capabilities (e.g., TransferRef for freezing).

Transitioning to Move-based platforms requires an investment in understanding their core principles: the resource model, ownership, capabilities, and the specific object models of Sui and Aptos. While the learning curve exists, Move languages potentially offer advantages in building secure and robust token systems. By grasping these foundational concepts, Solidity developers can effectively leverage the advanced token extension features available on Sui and Aptos, enabling the creation of innovative and sophisticated decentralized applications.

References

Largest Blockchain Security Auditor
Ready to take the next step? Connect with our sales team to request your free quote and secure your project today!
Client Testimonials