Movement Labs LogoMovement Docs
Token Standard/Legacy Standard

Token (Legacy)

Learn about the legacy Token standard for multi-token development on Movement

Token (Legacy Standard)

The Token standard provides a comprehensive framework for creating and managing collections of tokens on Movement. This standard supports fungible tokens (FTs), non-fungible tokens (NFTs), and semi-fungible tokens (SFTs) within a unified system, enabling creators to build complex token ecosystems with rich metadata and customizable properties.

The Token standard is available in the Movement framework at aptos_token::token.

This is the legacy token standard. For new projects on Movement, consider using the updated Digital Asset Standard for NFTs or Fungible Asset Standard for fungible tokens, which offer enhanced features and better composability.

Core Architecture

Multi-Token Framework

The Token standard uses a hierarchical structure to organize tokens:

module aptos_token::token {
    /// Core token structure supporting all token types
    struct Token has store {
        id: TokenId,
        amount: u64,
        token_properties: PropertyMap,
    }
    
    /// Global unique identifier for tokens
    struct TokenId has store, copy, drop {
        token_data_id: TokenDataId,
        property_version: u64,
    }
    
    /// Identifier for shared token data
    struct TokenDataId has copy, drop, store {
        creator: address,
        collection: String,
        name: String,
    }
}

Key Benefits:

  • Unified Standard: Single framework handles all token types (FT, NFT, SFT)
  • Efficient Storage: Shared token data reduces on-chain storage requirements
  • Version Control: Property versioning enables token evolution and customization
  • Creator Control: Comprehensive mutability and capability management

Collection Structure

Collections organize related tokens under a common framework:

module aptos_token::token {
    /// Collection metadata and configuration
    struct CollectionData has store {
        description: String,
        name: String,
        uri: String,
        supply: u64,
        maximum: u64,
        mutability_config: CollectionMutabilityConfig,
    }
    
    /// Controls which collection fields can be modified
    struct CollectionMutabilityConfig has copy, store, drop {
        description: bool,
        uri: bool,
        maximum: bool,
    }
}

Token Data and Metadata

Shared token information is stored in TokenData:

module aptos_token::token {
    /// Shared data for tokens with the same TokenDataId
    struct TokenData has store {
        maximum: u64,
        largest_property_version: u64,
        supply: u64,
        uri: String,
        royalty: Royalty,
        name: String,
        description: String,
        default_properties: PropertyMap,
        mutability_config: TokenMutabilityConfig,
    }
    
    /// Royalty configuration for creator economics
    struct Royalty has copy, drop, store {
        royalty_points_numerator: u64,
        royalty_points_denominator: u64,
        payee_address: address,
    }
}

Movement Framework Structures

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

[Token]

FieldTypeDescription
idTokenIdUnique identifier for this specific token instance
amountu64Quantity of tokens (1 for NFTs, >1 for FTs/SFTs)
token_propertiesPropertyMapInstance-specific properties for this token

[TokenId]

FieldTypeDescription
token_data_idTokenDataIdReference to shared token data
property_versionu64Version number for token property mutations

[TokenDataId]

FieldTypeDescription
creatoraddressAddress of the token creator
collectionStringName of the collection this token belongs to
nameStringUnique name of the token within the collection

[CollectionData]

FieldTypeDescription
descriptionStringCollection description
nameStringUnique collection name under creator
uriStringURI pointing to collection metadata
supplyu64Current number of token types in collection
maximumu64Maximum token types allowed (0 = unlimited)
mutability_configCollectionMutabilityConfigControls which fields can be modified

[TokenData]

FieldTypeDescription
maximumu64Maximum supply for this token type (0 = unlimited)
largest_property_versionu64Highest property version used
supplyu64Current circulating supply
uriStringURI pointing to token metadata
royaltyRoyaltyRoyalty configuration for secondary sales
nameStringToken name (unique within collection)
descriptionStringToken description
default_propertiesPropertyMapDefault properties for new token instances
mutability_configTokenMutabilityConfigControls which fields can be modified

Token Operations

Movement's token standard provides comprehensive functionality for creators and users.

For Collection Creators

Create a Collection

Collections serve as containers for organizing related tokens under a common framework. Before creating any tokens, you must first create a collection. The mutate_setting parameter controls which collection properties (description, URI, maximum) can be modified after creation.

public fun create_collection(
    creator: &signer,
    name: String,
    description: String,
    uri: String,
    maximum: u64,
    mutate_setting: vector<bool>
) acquires Collections

Example: Creating a Movement NFT Collection

public entry fun create_movement_nft_collection(creator: &signer) {
    let mutate_setting = vector<bool>[
        true,  // description mutable
        true,  // uri mutable  
        false  // maximum immutable
    ];
    
    token::create_collection(
        creator,
        string::utf8(b"Movement Pioneers"),
        string::utf8(b"Exclusive NFT collection for Movement ecosystem pioneers"),
        string::utf8(b"https://movement.xyz/metadata/pioneers"),
        1000, // Maximum 1000 NFTs
        mutate_setting
    );
}

Create Token Data

Token data defines the template for tokens within a collection, including metadata, royalty configuration, and default properties. Each token data entry represents a unique token type that can have multiple instances (for FTs/SFTs) or a single instance (for NFTs). Properties are stored as key-value pairs with explicit type information.

public fun create_tokendata(
    account: &signer,
    collection: String,
    name: String,
    description: String,
    maximum: u64,
    uri: String,
    royalty_payee_address: address,
    royalty_points_denominator: u64,
    royalty_points_numerator: u64,
    token_mutate_config: TokenMutabilityConfig,
    property_keys: vector<String>,
    property_values: vector<vector<u8>>,
    property_types: vector<String>
): TokenDataId acquires Collections

Example: Creating an NFT with Properties

public fun create_pioneer_nft(
    creator: &signer,
    pioneer_id: u64,
    rarity: String,
    special_ability: String
): TokenDataId {
    let property_keys = vector<String>[
        string::utf8(b"pioneer_id"),
        string::utf8(b"rarity"),
        string::utf8(b"special_ability"),
        string::utf8(b"TOKEN_BURNABLE_BY_CREATOR")
    ];
    
    let property_values = vector<vector<u8>>[
        bcs::to_bytes<u64>(&pioneer_id),
        bcs::to_bytes<String>(&rarity),
        bcs::to_bytes<String>(&special_ability),
        bcs::to_bytes<bool>(&true)
    ];
    
    let property_types = vector<String>[
        string::utf8(b"u64"),
        string::utf8(b"0x1::string::String"),
        string::utf8(b"0x1::string::String"),
        string::utf8(b"bool")
    ];
    
    let mutate_config = token::create_token_mutability_config(
        &vector<bool>[false, true, true, true, true] // Only maximum immutable
    );
    
    token::create_tokendata(
        creator,
        string::utf8(b"Movement Pioneers"),
        string::utf8(b"Pioneer #") + to_string(pioneer_id),
        string::utf8(b"A unique Movement ecosystem pioneer NFT"),
        1, // NFT - maximum supply of 1
        string::utf8(b"https://movement.xyz/metadata/pioneer/") + to_string(pioneer_id),
        signer::address_of(creator),
        100, // 10% royalty (10/100)
        10,
        mutate_config,
        property_keys,
        property_values,
        property_types
    )
}

Mint Tokens

Minting creates actual token instances from previously defined token data. The amount parameter determines how many tokens to create (1 for NFTs, more for FTs/SFTs). Minted tokens are initially held by the creator and can then be transferred to recipients.

public fun mint_token(
    account: &signer,
    token_data_id: TokenDataId,
    amount: u64,
): TokenId acquires Collections, TokenStore

Example: Minting and Distributing NFTs

public fun mint_and_distribute_pioneer(
    creator: &signer,
    recipient: address,
    token_data_id: TokenDataId
) {
    // Mint the NFT
    let token_id = token::mint_token(creator, token_data_id, 1);
    
    // Transfer to recipient if they've opted in
    if (token::is_opt_in_direct_transfer(recipient)) {
        let token = token::withdraw_token(creator, token_id, 1);
        token::direct_deposit(recipient, token);
    }
}

Token Property Management

The legacy token standard supports mutable properties, allowing tokens to evolve over time. Property mutations create new property versions, enabling features like character leveling, equipment upgrades, or dynamic NFT attributes. Only properties marked as mutable during token data creation can be modified.

public fun mutate_token_properties(
    account: &signer,
    token_owner: address,
    creator: address,
    collection_name: String,
    token_name: String,
    token_property_version: u64,
    amount: u64,
    keys: vector<String>,
    values: vector<vector<u8>>,
    types: vector<String>,
) acquires Collections, TokenStore

Example: Upgrading Token Properties

public fun upgrade_pioneer_abilities(
    creator: &signer,
    token_owner: address,
    token_id: TokenId,
    new_special_ability: String,
    power_level: u64
) {
    let keys = vector<String>[
        string::utf8(b"special_ability"),
        string::utf8(b"power_level"),
        string::utf8(b"last_upgraded")
    ];
    
    let values = vector<vector<u8>>[
        bcs::to_bytes<String>(&new_special_ability),
        bcs::to_bytes<u64>(&power_level),
        bcs::to_bytes<u64>(&timestamp::now_seconds())
    ];
    
    let types = vector<String>[
        string::utf8(b"0x1::string::String"),
        string::utf8(b"u64"),
        string::utf8(b"u64")
    ];
    
    token::mutate_token_properties(
        creator,
        token_owner,
        token_id.token_data_id.creator,
        token_id.token_data_id.collection,
        token_id.token_data_id.name,
        token_id.property_version,
        1,
        keys,
        values,
        types
    );
}

For Token Users

Opt-in for Direct Transfer

Unlike the newer Digital Asset standard, the legacy token standard requires recipients to explicitly opt-in before they can receive direct token transfers. This provides security against unwanted token spam but adds friction to the transfer process.

public entry fun opt_in_direct_transfer(account: &signer, opt_in: bool) acquires TokenStore

Example:

public entry fun enable_token_transfers(user: &signer) {
    token::opt_in_direct_transfer(user, true);
}

Transfer Tokens

The transfer_with_opt_in function transfers tokens between accounts when the recipient has enabled direct transfers. The function requires specifying the full token identification (creator, collection, name, property version) to uniquely identify the token being transferred.

public entry fun transfer_with_opt_in(
    from: &signer,
    creator: address,
    collection_name: String,
    token_name: String,
    token_property_version: u64,
    to: address,
    amount: u64,
) acquires TokenStore

Example: Trading NFTs

public entry fun trade_pioneer_nft(
    seller: &signer,
    buyer_address: address,
    creator: address,
    pioneer_id: u64
) {
    let collection_name = string::utf8(b"Movement Pioneers");
    let token_name = string::utf8(b"Pioneer #") + to_string(pioneer_id);
    
    token::transfer_with_opt_in(
        seller,
        creator,
        collection_name,
        token_name,
        0, // Original property version
        buyer_address,
        1  // Transfer 1 NFT
    );
}

Burn Tokens

Burning permanently destroys tokens, removing them from circulation. Tokens can only be burned if the TOKEN_BURNABLE_BY_CREATOR or TOKEN_BURNABLE_BY_OWNER properties were set during token data creation. This is useful for redemption mechanics, upgrade systems, or cleaning up unwanted tokens.

public entry fun burn(
    owner: &signer,
    creators_address: address,
    collection: String,
    name: String,
    property_version: u64,
    amount: u64
) acquires Collections, TokenStore

Movement-Specific Features

Enhanced Property System

The legacy token standard includes a flexible property system that stores arbitrary key-value pairs with type information. Properties can be set during token creation and optionally mutated later, enabling dynamic NFTs and evolving game assets.

// Custom property types supported
struct PropertyMap has store, drop, copy {
    map: SimpleMap<String, PropertyValue>
}

// Flexible property values
struct PropertyValue has store, drop, copy {
    value: vector<u8>,
    type: String,
}

Benefits:

  • Type Safety: Strongly typed property values with runtime validation
  • Extensibility: Support for custom property types and complex data structures
  • Versioning: Property mutations create new versions for NFT evolution
  • Gas Efficiency: Optimized storage and access patterns

Creator Economics

The legacy token standard includes a built-in royalty system that defines creator fees for secondary sales. Royalties are configured during token data creation and are expressed as a fraction (numerator/denominator) to support precise percentage calculations.

struct Royalty has copy, drop, store {
    royalty_points_numerator: u64,
    royalty_points_denominator: u64,
    payee_address: address,
}

Features:

  • Flexible Rates: Configurable royalty percentages
  • Multi-Recipient: Support for shared royalty distribution
  • Marketplace Integration: Standard interface for marketplace compliance
  • Immutable Options: Lock royalty settings for creator guarantees

Advanced Token Types

Semi-Fungible Tokens (SFTs)

Semi-fungible tokens combine properties of both fungible and non-fungible tokens. They can have multiple identical copies (like FTs) but also support unique per-instance properties (like NFTs). This is ideal for game items, event tickets, or any asset that needs both quantity and uniqueness.

// Create SFT with limited supply
public fun create_game_item_sft(
    creator: &signer,
    item_name: String,
    max_supply: u64,
    base_stats: vector<u64>
): TokenDataId {
    // Implementation for game items that can have multiple copies
    // but maintain unique properties per instance
}

Dynamic NFTs

Dynamic NFTs leverage the property mutation system to create tokens that evolve based on external events, user actions, or time. Property mutations create new versions, preserving the history of changes while updating the token's current state.

// NFT that can level up and gain new abilities
public fun level_up_character(
    owner: &signer,
    character_token_id: TokenId,
    new_level: u64,
    new_abilities: vector<String>
) {
    // Update character properties based on game progression
}

Collection Management

Batch Operations

Batch operations enable efficient processing of multiple tokens in a single transaction, reducing gas costs and ensuring atomic execution. This is essential for large-scale minting events, airdrops, or collection-wide updates.

public fun batch_mint_tokens(
    creator: &signer,
    token_data_ids: vector<TokenDataId>,
    amounts: vector<u64>,
    recipients: vector<address>
) {
    // Batch mint multiple tokens efficiently
}

Collection Analytics

Query functions provide insights into collection performance, including current supply, maximum capacity, and minting history. These are useful for building dashboards, enforcing supply limits, and monitoring collection growth.

public fun get_collection_stats(
    creator: address,
    collection_name: String
): (u64, u64, u64) {
    // Returns (current_supply, maximum, total_minted)
}

Query Functions

The legacy token standard provides a comprehensive query interface for retrieving token ownership, balances, metadata, and collection information. These functions are essential for building user interfaces, validating ownership, and integrating with marketplaces.

// Token ownership and balance
public fun balance_of(owner: address, token_id: TokenId): u64
public fun get_token_amount(token: &Token): u64

// Token metadata
public fun get_token_id(token: &Token): &TokenId
public fun get_property_map(owner: address, token_id: TokenId): PropertyMap

// Collection information
public fun get_collection_supply(creator: address, collection_name: String): u64
public fun get_collection_maximum(creator: address, collection_name: String): u64

// Token data queries
public fun get_tokendata_maximum(token_data_id: TokenDataId): u64
public fun get_tokendata_supply(token_data_id: TokenDataId): u64
public fun get_tokendata_uri(creator: address, token_data_id: TokenDataId): String

Events and Monitoring

The legacy token standard emits comprehensive events for tracking the complete token lifecycle. These events enable indexers, analytics platforms, and user interfaces to track token creation, transfers, burns, and property mutations in real-time.

// Token lifecycle events
struct CreateTokenDataEvent has drop, store { /* ... */ }
struct MintTokenEvent has drop, store { /* ... */ }
struct BurnTokenEvent has drop, store { /* ... */ }

// Transfer events
struct DepositEvent has drop, store { /* ... */ }
struct WithdrawEvent has drop, store { /* ... */ }

// Property mutation events
struct MutateTokenPropertyMapEvent has drop, store { /* ... */ }

Movement Labs provides migration tools and documentation for projects transitioning from legacy standards. The Token standard will continue to be supported for existing projects while new development is encouraged to use modern standards.

Summary

  • Unified framework supporting FTs, NFTs, and SFTs through hierarchical structures
  • Collection-based organization with built-in royalty systems for creator economics
  • Mutable properties with versioning support for dynamic NFTs and evolving assets
  • Opt-in transfers requiring recipient approval for enhanced security
  • Flexible burning when configured during token creation for redemption mechanics
  • Migration recommended to Digital Asset Standard (NFTs) or Fungible Asset Standard (FTs)