Claude-starter aptos-token-standards

Expert on Aptos token standards including fungible tokens (Coin, Fungible Asset), non-fungible tokens (Digital Asset standard, Token V1/V2), collections, metadata, minting, burning, and transfer patterns. Triggers on keywords token, nft, fungible asset, coin, digital asset, collection, mint, burn, metadata, royalty

install
source · Clone the upstream repo
git clone https://github.com/raintree-technology/claude-starter
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/raintree-technology/claude-starter "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.claude/skills/aptos/token-standards" ~/.claude/skills/raintree-technology-claude-starter-aptos-token-standards-81b07b && rm -rf "$T"
manifest: .claude/skills/aptos/token-standards/skill.md
source content

Aptos Token Standards Expert

Purpose

Provide expert guidance on Aptos token standards, including fungible tokens (Coin and Fungible Asset frameworks), non-fungible tokens (Digital Asset standard, Token V1/V2), collections, metadata management, minting, burning, and transfer patterns.

When to Use

Auto-invoke when users mention:

  • Fungible Tokens - coin, fungible asset, FA, token standard, currency
  • NFTs - non-fungible token, digital asset, collection, NFT standard
  • Operations - mint, burn, transfer, freeze, metadata
  • Standards - Token V1, Token V2, Digital Asset, Coin framework
  • Collections - collection creation, NFT collections, series
  • Metadata - token URI, properties, attributes, royalties

Token Framework Overview

Aptos Token Frameworks

1. Coin Framework (0x1::coin)
   - Original fungible token standard
   - Simple, battle-tested
   - Used for APT and most tokens
   - Limited customization

2. Fungible Asset Framework (0x1::fungible_asset)
   - New fungible token standard
   - More flexible than Coin
   - Object-based architecture
   - Advanced features (pause, freeze, etc.)

3. Token V1 (0x3::token)
   - Original NFT standard (deprecated)
   - Simple but limited
   - Being phased out

4. Token V2 / Digital Asset (0x4::aptos_token, 0x4::token)
   - Current NFT standard
   - Object-based
   - Flexible, composable
   - Recommended for new projects

Process

When a user asks about tokens:

1. Identify Token Type

Fungible or Non-Fungible?
- Fungible: Use Coin or Fungible Asset
- Non-fungible: Use Digital Asset (Token V2)

Which standard to use?
- New projects: Fungible Asset / Digital Asset
- Existing integrations: Coin / Token V1 (if needed)
- Advanced features: Fungible Asset / Digital Asset

2. Determine Use Case

Common scenarios:
- Creating a new coin/token
- Minting NFTs in a collection
- Managing token metadata
- Implementing transfers/burns
- Setting up royalties
- Creating token utilities (staking, etc.)
- Multi-token management

3. Provide Implementation Guidance

Structure your response:

  • Standard choice - which framework to use and why
  • Code example - complete working implementation
  • Key concepts - important patterns and structures
  • Best practices - security and design recommendations
  • Common pitfalls - what to avoid
  • References - link to relevant docs

Fungible Tokens: Coin Framework

Creating a Coin

module my_addr::my_coin {
    use std::string;
    use aptos_framework::coin;

    struct MyCoin {}

    fun init_module(sender: &signer) {
        let (burn_cap, freeze_cap, mint_cap) = coin::initialize<MyCoin>(
            sender,
            string::utf8(b"My Coin"),
            string::utf8(b"MYC"),
            8, // decimals
            true, // monitor_supply
        );

        // Store capabilities
        move_to(sender, Capabilities {
            burn_cap,
            freeze_cap,
            mint_cap,
        });
    }

    struct Capabilities has key {
        burn_cap: coin::BurnCapability<MyCoin>,
        freeze_cap: coin::FreezeCapability<MyCoin>,
        mint_cap: coin::MintCapability<MyCoin>,
    }
}

Minting Coins

public entry fun mint(
    admin: &signer,
    recipient: address,
    amount: u64
) acquires Capabilities {
    let caps = borrow_global<Capabilities>(@my_addr);
    let coins = coin::mint(amount, &caps.mint_cap);
    coin::deposit(recipient, coins);
}

Burning Coins

public entry fun burn(
    account: &signer,
    amount: u64
) acquires Capabilities {
    let caps = borrow_global<Capabilities>(@my_addr);
    let coins = coin::withdraw<MyCoin>(account, amount);
    coin::burn(coins, &caps.burn_cap);
}

Coin Operations

// Transfer coins
public entry fun transfer(
    from: &signer,
    to: address,
    amount: u64
) {
    coin::transfer<MyCoin>(from, to, amount);
}

// Get balance
public fun balance(account: address): u64 {
    coin::balance<MyCoin>(account)
}

// Register to receive coins
public entry fun register(account: &signer) {
    coin::register<MyCoin>(account);
}

Fungible Tokens: Fungible Asset Framework

Creating a Fungible Asset

module my_addr::my_fa {
    use aptos_framework::fungible_asset::{Self, MintRef, BurnRef, TransferRef};
    use aptos_framework::object::{Self, Object};
    use aptos_framework::primary_fungible_store;
    use std::string;
    use std::option;

    struct MyFA {}

    struct Refs has key {
        mint_ref: MintRef,
        burn_ref: BurnRef,
        transfer_ref: TransferRef,
    }

    fun init_module(admin: &signer) {
        let constructor_ref = &object::create_named_object(admin, b"MY_FA");

        primary_fungible_store::create_primary_store_enabled_fungible_asset(
            constructor_ref,
            option::none(), // max_supply
            string::utf8(b"My Fungible Asset"),
            string::utf8(b"MFA"),
            8, // decimals
            string::utf8(b"https://myfa.com/icon.png"),
            string::utf8(b"https://myfa.com"),
        );

        let mint_ref = fungible_asset::generate_mint_ref(constructor_ref);
        let burn_ref = fungible_asset::generate_burn_ref(constructor_ref);
        let transfer_ref = fungible_asset::generate_transfer_ref(constructor_ref);

        move_to(admin, Refs { mint_ref, burn_ref, transfer_ref });
    }
}

Fungible Asset Operations

public entry fun mint(
    admin: &signer,
    recipient: address,
    amount: u64
) acquires Refs {
    let refs = borrow_global<Refs>(@my_addr);
    let fa = fungible_asset::mint(&refs.mint_ref, amount);
    primary_fungible_store::deposit(recipient, fa);
}

public entry fun burn(
    admin: &signer,
    holder: address,
    amount: u64
) acquires Refs {
    let refs = borrow_global<Refs>(@my_addr);
    let fa = primary_fungible_store::withdraw(holder, amount);
    fungible_asset::burn(&refs.burn_ref, fa);
}

// Freeze/unfreeze account
public entry fun freeze_account(
    admin: &signer,
    account: address
) acquires Refs {
    let refs = borrow_global<Refs>(@my_addr);
    fungible_asset::set_frozen_flag(&refs.transfer_ref, account, true);
}

Non-Fungible Tokens: Digital Asset (Token V2)

Creating a Collection

module my_addr::my_nft {
    use aptos_token_objects::collection;
    use std::string::{Self, String};
    use std::option;

    public entry fun create_collection(creator: &signer) {
        let description = string::utf8(b"My NFT Collection");
        let name = string::utf8(b"My NFTs");
        let uri = string::utf8(b"https://mynft.com/collection");

        collection::create_unlimited_collection(
            creator,
            description,
            name,
            option::none(), // royalty
            uri,
        );
    }

    // Or with fixed supply
    public entry fun create_fixed_collection(
        creator: &signer,
        max_supply: u64
    ) {
        collection::create_fixed_collection(
            creator,
            string::utf8(b"Description"),
            max_supply,
            string::utf8(b"Collection Name"),
            option::none(),
            string::utf8(b"https://uri.com"),
        );
    }
}

Minting NFTs

use aptos_token_objects::token;

public entry fun mint_nft(
    creator: &signer,
    collection_name: String,
    description: String,
    name: String,
    uri: String,
) {
    token::create_named_token(
        creator,
        collection_name,
        description,
        name,
        option::none(), // royalty
        uri,
    );
}

// Mint to specific recipient
public entry fun mint_to(
    creator: &signer,
    recipient: address,
    collection_name: String,
    token_name: String,
) {
    let constructor_ref = token::create_named_token(
        creator,
        collection_name,
        string::utf8(b"Description"),
        token_name,
        option::none(),
        string::utf8(b"https://uri.com"),
    );

    // Transfer to recipient
    let transfer_ref = object::generate_transfer_ref(&constructor_ref);
    let linear_transfer_ref = object::generate_linear_transfer_ref(&transfer_ref);
    object::transfer_with_ref(linear_transfer_ref, recipient);
}

NFT with Properties (Metadata)

use aptos_token_objects::property_map;

public entry fun mint_with_properties(
    creator: &signer,
    collection: String,
    name: String,
) {
    let constructor_ref = token::create_named_token(
        creator,
        collection,
        string::utf8(b"Description"),
        name,
        option::none(),
        string::utf8(b"https://uri.com"),
    );

    // Add properties
    let property_mutator_ref = property_map::generate_mutator_ref(&constructor_ref);

    property_map::add_typed(
        &property_mutator_ref,
        string::utf8(b"strength"),
        100u64
    );

    property_map::add_typed(
        &property_mutator_ref,
        string::utf8(b"rarity"),
        string::utf8(b"legendary")
    );

    // Store mutator ref if properties need to change later
    // move_to(creator, PropertyMutatorRef { property_mutator_ref });
}

Reading NFT Properties

public fun get_nft_property(
    token_address: address,
    property_name: String
): u64 {
    let property_map = property_map::borrow(token_address);
    property_map::read_u64(property_map, &property_name)
}

NFT Transfers

use aptos_framework::object;

// Standard transfer (if transferable)
public entry fun transfer_nft(
    owner: &signer,
    token_address: address,
    recipient: address
) {
    object::transfer(owner, object::address_to_object<token::Token>(token_address), recipient);
}

// For soul-bound tokens (non-transferable)
public entry fun make_soul_bound(creator: &signer, constructor_ref: &ConstructorRef) {
    let transfer_ref = object::generate_transfer_ref(constructor_ref);
    object::disable_ungated_transfer(&transfer_ref);
    // Don't store transfer_ref - token becomes non-transferable
}

Burning NFTs

public entry fun burn_nft(
    owner: &signer,
    token_address: address
) {
    let token_object = object::address_to_object<token::Token>(token_address);
    token::burn(owner, token_object);
}

Royalties

Setting Royalties on Collection

use aptos_token_objects::royalty;

public entry fun create_collection_with_royalty(
    creator: &signer,
    royalty_numerator: u64,
    royalty_denominator: u64,
    payee_address: address
) {
    let royalty = royalty::create(
        royalty_numerator,
        royalty_denominator,
        payee_address
    );

    collection::create_unlimited_collection(
        creator,
        string::utf8(b"Description"),
        string::utf8(b"Name"),
        option::some(royalty), // 👈 royalty here
        string::utf8(b"https://uri.com"),
    );
}

Reading Royalty Info

public fun get_royalty_info(collection_addr: address): (u64, u64, address) {
    let collection_obj = object::address_to_object<collection::Collection>(collection_addr);
    let royalty = royalty::get(collection_obj);

    (
        royalty::numerator(&royalty),
        royalty::denominator(&royalty),
        royalty::payee_address(&royalty)
    )
}

Advanced Patterns

Conditional Minting (Allowlist)

struct Allowlist has key {
    addresses: vector<address>
}

public entry fun mint_if_allowed(
    minter: &signer,
    collection: String,
    name: String
) acquires Allowlist {
    let allowlist = borrow_global<Allowlist>(@my_addr);
    let minter_addr = signer::address_of(minter);

    assert!(
        vector::contains(&allowlist.addresses, &minter_addr),
        ERROR_NOT_ALLOWED
    );

    token::create_named_token(/* ... */);
}

Evolving NFTs (Mutable Properties)

struct EvolutionRefs has key {
    mutator_refs: SimpleMap<address, PropertyMutatorRef>
}

public entry fun evolve_nft(
    token_addr: address,
    new_level: u64
) acquires EvolutionRefs {
    let refs = borrow_global<EvolutionRefs>(@my_addr);
    let mutator_ref = simple_map::borrow(&refs.mutator_refs, &token_addr);

    property_map::update_typed(
        mutator_ref,
        &string::utf8(b"level"),
        new_level
    );
}

Composable NFTs (Nesting)

// Create parent NFT
let parent_ref = token::create_named_token(/* parent */);
let parent_addr = object::address_from_constructor_ref(&parent_ref);

// Create child NFT owned by parent
let child_ref = token::create_named_token(/* child */);
let transfer_ref = object::generate_transfer_ref(&child_ref);
let linear_transfer_ref = object::generate_linear_transfer_ref(&transfer_ref);

object::transfer_with_ref(linear_transfer_ref, parent_addr);

Supply-Limited Minting

struct MintState has key {
    minted: u64,
    max_supply: u64,
}

public entry fun mint_limited(
    creator: &signer,
    collection: String,
    name: String
) acquires MintState {
    let state = borrow_global_mut<MintState>(@my_addr);

    assert!(state.minted < state.max_supply, ERROR_MAX_SUPPLY_REACHED);

    token::create_named_token(/* ... */);

    state.minted = state.minted + 1;
}

Token Standards Comparison

Coin vs Fungible Asset

FeatureCoinFungible Asset
Ease of use✅ Simple⚠️ More complex
Flexibility❌ Limited✅ Highly flexible
Freeze/Pause✅ Yes✅ Yes (more control)
Custom logic❌ Limited✅ Extensive
Gas cost✅ Lower⚠️ Slightly higher
Adoption✅ Wide🆕 Growing
RecommendationLegacy/SimpleNew projects

Token V1 vs Digital Asset (Token V2)

FeatureToken V1Digital Asset
Object model❌ No✅ Yes
Composability❌ Limited✅ High
Properties⚠️ Basic✅ Rich
Soul-bound❌ No✅ Yes
Royalties✅ Yes✅ Yes (better)
Status⚠️ Deprecated✅ Current
RecommendationLegacy onlyAll new projects

Common Patterns Summary

For Fungible Tokens

Use Coin when:
- Simple token needed
- Following existing integrations
- Maximum compatibility

Use Fungible Asset when:
- Need advanced features (pause, freeze)
- Building new protocol
- Want future-proof solution

For NFTs

Use Digital Asset (Token V2) for:
- All new NFT projects
- Collections with properties
- Composable/evolving NFTs
- Soul-bound tokens
- Advanced marketplace integration

Best Practices

✅ Security

  • Store capability refs securely (in resource under deployer account)
  • Validate inputs before minting/burning
  • Use proper access control for admin functions
  • Test thoroughly before mainnet deployment
  • Consider upgrade patterns early

✅ Design

  • Choose appropriate standard for use case
  • Design metadata schema carefully
  • Plan for future extensibility
  • Document token economics clearly
  • Consider gas optimization

✅ User Experience

  • Implement proper error messages
  • Auto-register users when needed
  • Show clear token information
  • Handle edge cases gracefully
  • Provide good metadata/images

❌ Avoid

  • Don't expose capability refs publicly
  • Don't hardcode addresses (use named objects)
  • Avoid unlimited minting without controls
  • Don't skip supply tracking
  • Avoid complex logic in entry functions

TypeScript SDK Integration

Coin Operations

import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk";

const aptos = new Aptos(new AptosConfig({ network: Network.TESTNET }));

// Get coin balance
const balance = await aptos.getAccountCoinAmount({
  accountAddress: "0x123...",
  coinType: "0x1::aptos_coin::AptosCoin"
});

// Transfer coins
const txn = await aptos.transaction.build.simple({
  sender: sender.accountAddress,
  data: {
    function: "0x1::coin::transfer",
    typeArguments: ["0x1::aptos_coin::AptosCoin"],
    functionArguments: [recipient, amount],
  },
});

NFT Operations

// Get owned tokens
const tokens = await aptos.getAccountOwnedTokens({
  accountAddress: "0x123..."
});

// Get token data
const tokenData = await aptos.getDigitalAssetData({
  digitalAssetAddress: "0xabc..."
});

Testing Token Standards

#[test(creator = @my_addr, user = @0x123)]
fun test_mint_and_transfer(creator: &signer, user: &signer) {
    // Initialize
    init_module(creator);

    // Register user
    coin::register<MyCoin>(user);

    // Mint to user
    mint(creator, signer::address_of(user), 1000);

    // Verify balance
    assert!(coin::balance<MyCoin>(signer::address_of(user)) == 1000, 0);
}

#[test(creator = @my_addr)]
#[expected_failure(abort_code = ERROR_MAX_SUPPLY)]
fun test_mint_exceeds_supply(creator: &signer) {
    init_module(creator);
    mint(creator, @0x123, MAX_SUPPLY + 1);
}

Response Style

  • Standard-first - Recommend appropriate framework immediately
  • Code examples - Show complete, working implementations
  • Compare options - Explain trade-offs between standards
  • Security-aware - Highlight security considerations
  • Practical - Focus on real-world use cases

Follow-up Suggestions

After helping with tokens, suggest:

  • Testing strategy for token operations
  • Frontend integration approach
  • Marketplace compatibility considerations
  • Token economics review
  • Upgrade/migration patterns
  • Gas optimization techniques