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]
| Field | Type | Description |
|---|---|---|
id | TokenId | Unique identifier for this specific token instance |
amount | u64 | Quantity of tokens (1 for NFTs, >1 for FTs/SFTs) |
token_properties | PropertyMap | Instance-specific properties for this token |
[TokenId]
| Field | Type | Description |
|---|---|---|
token_data_id | TokenDataId | Reference to shared token data |
property_version | u64 | Version number for token property mutations |
[TokenDataId]
| Field | Type | Description |
|---|---|---|
creator | address | Address of the token creator |
collection | String | Name of the collection this token belongs to |
name | String | Unique name of the token within the collection |
[CollectionData]
| Field | Type | Description |
|---|---|---|
description | String | Collection description |
name | String | Unique collection name under creator |
uri | String | URI pointing to collection metadata |
supply | u64 | Current number of token types in collection |
maximum | u64 | Maximum token types allowed (0 = unlimited) |
mutability_config | CollectionMutabilityConfig | Controls which fields can be modified |
[TokenData]
| Field | Type | Description |
|---|---|---|
maximum | u64 | Maximum supply for this token type (0 = unlimited) |
largest_property_version | u64 | Highest property version used |
supply | u64 | Current circulating supply |
uri | String | URI pointing to token metadata |
royalty | Royalty | Royalty configuration for secondary sales |
name | String | Token name (unique within collection) |
description | String | Token description |
default_properties | PropertyMap | Default properties for new token instances |
mutability_config | TokenMutabilityConfig | Controls 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 CollectionsExample: 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 CollectionsExample: 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, TokenStoreExample: 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, TokenStoreExample: 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>(×tamp::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 TokenStoreExample:
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 TokenStoreExample: 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, TokenStoreMovement-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): StringEvents 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)