Back to all stories
Blogs
Tech & Dev
Move for Solidity Developers: Token Standard I
5/14/2025
Move for Solidity Developers: Token Standard I

“Move for Solidity Developers” is a series created for experienced Solidity developers with a basic understanding of 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 access control and storage model differences among Solidity, Sui Move, and Aptos Move, this second installment shifts focus toward a practical application of those foundational concepts. We now examine the most common token (ERC20 on Solidity), which is an essential component of value transfer and functionality across the DeFi ecosystem.

In this post, we particularly discuss how basic token standards are implemented across these platforms, focusing solely on minting, burning, and transferring functionalities. Advanced features such as pausing, whitelisting, and freezing will be discussed in detail in a subsequent series.

Fungible Token: Solidity vs. Sui Move vs. Aptos Move

Token Creation

To understand the unified token standard across Solidity and Move (Aptos and Sui), let's begin by outlining the creation process.

Solidity

In Solidity, developers can easily create ERC20 tokens by importing OpenZeppelin’s ERC20.sol library. The following is a basic ERC20 token contract that extends OpenZeppelin’s standard ERC20 implementation:

Code 1

Sui Move

Unlike Solidity, Sui Move adopts an object-based storage model. Sui provides a standardized module, sui::coin, for the creation and management of fungible tokens. The following example demonstrates the basic process of creating a custom coin in Sui Move.

Code 2

Creating a token in Sui Move involves several key components:

  1. Coin Package Import: The standard coin module from the Sui framework must be imported to access core coin functionalities.
  2. Initialization (init): The init() function acts like a constructor, executed at module deployment. It registers the custom coin type and initializes its associated metadata.
  3. One-Time Witness: A one-time witness ensures that a specific coin type T (e.g., MY_COIN) can be registered only once on-chain, enforcing type uniqueness.
  4. Capability Management: Upon coin creation, two essential objects are emitted:
  • TreasuryCap<T>: Grants mint and burn privileges for the token type T. This is usually held by the token issuer or an authorized module.
  • CoinMetadata: Contains descriptive metadata such as name, symbol, decimals, description, and optional icon URL.

Best Practices for TreasuryCap and CoinMetadata Management in Initialization

  • TreasuryCap<T> should not be shared, as it enables unrestricted minting. If shared, it should be controlled by well defined delegate logic to avoid public mint/burn.
  • TreasuryCap<T> should not be frozen in order to preserve authorized minting and burning capabilities.
  • CoinMetadata should be frozen in order to prevent tampering with token attributes, ensuring consistency and trust.

Aptos Move

Aptos' Move package provides the coin module, similar to Sui, for basic coin creation. managed_coin encapsulates the functionalities of the basic coin module, providing essential scripts to initialize, mint, burn, and transfer tokens. The managed_coin::initialize method is designed for basic token functionality only. The following example demonstrates a basic implementation of a coin named Moon Coin:

Code 3

managed_coin::initialize in Aptos Move calls coin::initialize to set the coin metadata and transfers the burn_cap, freeze_cap, and mint_cap to the contract deployer (sender).

Mint/Burn

Minting and burning are core mechanisms for managing token supply. Though functionally similar, their implementations in Solidity and Move differ in how they enforce security and authority. This section examines these differences.

Solidity

OpenZeppelin’s ERC20 token contract includes internal _mint and _burn functions, allowing developers to design custom public functions within their extended contracts to suit different use cases and business logic. Burning is usually controlled by privileged roles or permitted for users to destroy tokens from their own accounts. The contract should enforce strict access control on mint and burn functions to prevent unauthorized token issuance or destruction.

Sui Move

The sui::coin package provides built-in functions for minting and burning tokens:

  • coin::mint_and_transfer
  • coin::burn

However, both operations are strictly permissioned and can only be invoked by accounts or modules that hold access to the corresponding TreasuryCap<T> object. When burning occurs, the function destroys the coin object and updates the total supply counter.

Code 4

Notably, once TreasuryCap<T> is frozen, both minting and burning operations become impossible, as both mint_and_transfer and burn require a &mut TreasuryCap<T> (a mutable reference), consequently affecting other functions that depend on these core capabilities.

It is worth noting that, in contrast to Solidity, Move does not support direct inheritance. Users or administrators in Move could interact directly with the native coin module, rather than through intermediary modules or contracts like my_coin.

Aptos Move

The managed_coin package provides entry functions for minting and burning tokens: managed_coin::mint and managed_coin::burn. Similar to the design in Sui Move, these functions are strictly governed by the mint_cap and burn_cap, and perform functionality through the coin module, which enforces access control.

Unlike Sui Coin, we can see that the mint_cap and burn_cap are wrapped inside Capabilities. Due to the feature of the move language, the unwrapped operation can only be performed in the managed_coin module, which declares Capabilities. As a result, coins can only be minted through the managed_coin with the resource holder as the signer.

Code 5

This means that the admin cannot bypass contract constraints because they need both the signer authority and the capability resource, which is account-bound rather than being a transferable object as in Sui.

Token Transfers and Account Balance Queries

DeFi relies on token transfers and balance queries as fundamental operations. This section contrasts the Solidity and Move approaches to these core functionalities, highlighting differences in their design principles, security implications, and execution mechanisms.

Solidity

In the standard ERC20 contract, all token holder balances are centrally recorded and managed in the mapping _balances within the contract itself.

Code 6

The ERC20 standard allows token holders to transfer tokens from their own accounts using the transfer() function. Additionally, holders can authorize other addresses to operate on their behalf by calling the approve() function. Once approved, the designated address can transfer tokens from the owner’s account using the transferFrom() function.

Sui Move

Sui Move uses the Coin<T> object to represent token balances. Each Coin<T> instance contains a unique object ID (UID) and a Balance<T> field that encapsulates the actual token amount. Each token type is uniquely identified by the type parameter T (e.g., SUI, USDC).

Code 7

To transfer tokens to a specific account, the sui::pay::split_and_transfer function is typically used. This function allows a Coin<T> object to be split into a specified amount and transferred directly to a recipient.

Code 8

Transferring tokens results in the creation of a new Coin<T> object. The transferred amount can be verified on-chain by querying the UID of the newly created Coin<T> object. For example, when Alice transfers 50 SUI to Bob, a new Coin<T> object is created with a unique id and balance of 50 SUI, and transferred to B's ownership. The original sender retains the remaining balance (if any) in a separate Coin<T>. Rather than maintaining balances in a centralized ledger, user holdings are represented as collections of individual Coin<T> objects. A single account may hold multiple Coin<T> objects, each with its own UID and balance.

Diagram

Due to Sui Move’s object-based model, the approve() and transferFrom() pattern commonly used in ERC20 is not supported. In Sui Move, only the object owner has the authority to access or transfer the resources contained within a Coin<T> object.

Aptos Move

Aptos Move uses CoinStore<T> and Coin<T> to store user’s coin balance and account status:

Code 9

Historically, Aptos required an account to explicitly register to receive a custom token type by creating a CoinStore for it. AIP-13 shifted this from an explicit opt-in to an implicit opt-out model. The aptos_account::transfer_coins function now typically handles the automatic creation of a CoinStore for the recipient if one doesn't already exist for that CoinType, simplifying the user experience. This function encapsulates the underlying withdraw and deposit operations implemented within the coin package.

Code 10

Users can still choose to opt-out of receiving direct coin transfers via aptos_account::set_allow_direct_coin_transfers(false).

Comparison: ERC20(Solidity) v.s. Sui Move Coin v.s. Aptos Move Coin

FeatureERC20 (Solidity)Sui Move CoinAptos Move Coin
Balance RepresentationUses global state variable _balances mapping(address => uint256) to track balancesCoin<T> owned by the userCoinStore<T> and Coin<T> owned by the user
Minting/Burning AuthorityControlled via onlyOwner or role-based access with additional extensionsControlled by exclusive access to TreasuryCap<T>Controlled by exclusive access to MintRef, BurnRef, TransferRef
TransferUpdate _balances mappingCreate new object and transfer the object owner to the coin receiverCreate a CoinStore for receivers if they do not have one, update the balance field of CoinStore

This article presented the foundational aspects of the Coin standard, including core interfaces, transfer logic, and module interactions. In the next article, we will cover advanced features for regulated coin and fungible assets, including:

  • Pause and freeze: Restrict transfers globally or per account.
  • Denylist: Block specific addresses from receiving and transferring assets.
  • Secure function overrides: Extend base functions like mint() with custom logic, while preventing unauthorized bypasses through direct module access.
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