Claude-skill-registry aptos-framework

Expert on Aptos Framework (0x1 standard library) - account, coin, fungible_asset, object, timestamp, table, event, vector, string, option, error, and other core modules. Triggers on keywords aptos framework, 0x1, account module, table, smarttable, event, timestamp, randomness, aggregator, resource account

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/framework" ~/.claude/skills/majiayu000-claude-skill-registry-aptos-framework && rm -rf "$T"
manifest: skills/data/framework/SKILL.md
source content

Aptos Framework Expert

Purpose

Provide comprehensive guidance on the Aptos Framework (0x1 address) - the standard library of core modules that power Aptos blockchain. These modules provide fundamental functionality for accounts, storage, events, randomness, and more.

When to Use

Auto-invoke when users mention:

  • Framework Modules - 0x1::, aptos_framework::, standard library
  • Account Management - account creation, auth keys, rotation
  • Storage - Table, SimpleMap, SmartTable, efficient data structures
  • Events - event emission, event handles, indexing
  • Randomness - VRF, secure random numbers
  • Time - timestamp, block time access
  • Resources - resource accounts, deterministic addresses
  • Aggregator - parallel execution primitives

Framework Architecture

Core Framework Modules (0x1::)

aptos_framework/
├── account.move           - Account management
├── aptos_account.move     - High-level account operations
├── aptos_coin.move        - Native APT token
├── aptos_governance.move  - On-chain governance
├── coin.move              - Fungible token standard (v1)
├── fungible_asset.move    - Fungible asset standard (v2)
├── object.move            - Object model primitives
├── timestamp.move         - Block timestamp access
├── table.move             - Key-value storage
├── smart_table.move       - Auto-split table
├── event.move             - Event emission
├── randomness.move        - Secure randomness
├── aggregator.move        - Parallel execution
├── aggregator_v2.move     - Improved aggregator
├── resource_account.move  - Deterministic deployment
├── transaction_fee.move   - Fee collection
└── staking_contract.move  - Validator staking

Standard Library (std::)

move-stdlib/
├── vector.move      - Dynamic arrays
├── option.move      - Optional values
├── string.move      - UTF8 strings
├── signer.move      - Signer operations
├── error.move       - Error codes
├── bcs.move         - Binary serialization
├── hash.move        - Cryptographic hashing
└── fixed_point64.move - Fixed-point math

account.move - Account Management

Core Functions

use aptos_framework::account;

// Create new account at address
public fun create_account(new_address: address) {
    account::create_account(new_address);
}

// Get account's sequence number
public fun get_sequence_number(addr: address): u64 {
    account::get_sequence_number(addr)
}

// Get authentication key
public fun get_authentication_key(addr: address): vector<u8> {
    account::get_authentication_key(addr)
}

// Check if account exists
public fun exists_at(addr: address): bool {
    account::exists_at(addr)
}

Account Rotation

// Rotate authentication key
public entry fun rotate_authentication_key(
    account: &signer,
    new_auth_key: vector<u8>
) {
    account::rotate_authentication_key(account, new_auth_key);
}

// Offer rotation capability to another address
public entry fun offer_rotation_capability(
    account: &signer,
    rotation_capability_offerer: address
) {
    account::offer_rotation_capability(
        account,
        rotation_capability_offerer,
        vector::empty()
    );
}

SignerCapability Pattern

use aptos_framework::account::{Self, SignerCapability};

struct ModuleData has key {
    signer_cap: SignerCapability
}

public fun initialize(deployer: &signer) {
    let (resource_signer, signer_cap) = account::create_resource_account(
        deployer,
        b"SEED"
    );

    move_to(&resource_signer, ModuleData { signer_cap });
}

public fun use_resource_account() acquires ModuleData {
    let module_data = borrow_global<ModuleData>(@my_module);
    let resource_signer = account::create_signer_with_capability(&module_data.signer_cap);

    // Use resource_signer for operations
}

table.move - Scalable Key-Value Storage

Basic Table Operations

use aptos_framework::table::{Self, Table};

struct Registry has key {
    data: Table<address, UserData>
}

public fun initialize(account: &signer) {
    move_to(account, Registry {
        data: table::new()
    });
}

public fun add_user(
    registry_addr: address,
    user_addr: address,
    user_data: UserData
) acquires Registry {
    let registry = borrow_global_mut<Registry>(registry_addr);
    table::add(&mut registry.data, user_addr, user_data);
}

public fun get_user(
    registry_addr: address,
    user_addr: address
): &UserData acquires Registry {
    let registry = borrow_global<Registry>(registry_addr);
    table::borrow(&registry.data, user_addr)
}

public fun update_user(
    registry_addr: address,
    user_addr: address
): &mut UserData acquires Registry {
    let registry = borrow_global_mut<Registry>(registry_addr);
    table::borrow_mut(&mut registry.data, user_addr)
}

public fun remove_user(
    registry_addr: address,
    user_addr: address
): UserData acquires Registry {
    let registry = borrow_global_mut<Registry>(registry_addr);
    table::remove(&mut registry.data, user_addr)
}

public fun has_user(
    registry_addr: address,
    user_addr: address
): bool acquires Registry {
    let registry = borrow_global<Registry>(registry_addr);
    table::contains(&registry.data, user_addr)
}

Table vs SimpleMap vs SmartTable

FeatureVectorSimpleMapTableSmartTable
Max size~1000~1000UnlimitedUnlimited
Gas cost (read)O(n)O(n)O(1)O(1)
Gas cost (write)O(n)O(n)O(1)O(1)
StorageOn-chainOn-chainGlobal storageGlobal + auto-split
Iteration✅ Easy✅ Easy❌ Not supported⚠️ Complex
Best forSmall listsSmall mapsLarge mapsVery large maps

SmartTable (Auto-Splitting)

use aptos_framework::smart_table::{Self, SmartTable};

struct LargeRegistry has key {
    data: SmartTable<address, UserData>
}

public fun initialize(account: &signer) {
    move_to(account, LargeRegistry {
        data: smart_table::new()
    });
}

// Same API as Table
public fun add_user(addr: address, data: UserData) acquires LargeRegistry {
    let registry = borrow_global_mut<LargeRegistry>(@my_module);
    smart_table::add(&mut registry.data, addr, data);
}

// SmartTable automatically splits when buckets get large
// Better for very large datasets (100k+ entries)

event.move - Event Emission

Event Handles (V1)

use aptos_framework::event::{Self, EventHandle};

struct TransferEvent has drop, store {
    from: address,
    to: address,
    amount: u64,
}

struct Events has key {
    transfer_events: EventHandle<TransferEvent>
}

public fun initialize(account: &signer) {
    move_to(account, Events {
        transfer_events: account::new_event_handle<TransferEvent>(account)
    });
}

public fun emit_transfer(
    from: address,
    to: address,
    amount: u64
) acquires Events {
    let events = borrow_global_mut<Events>(@my_module);
    event::emit_event(&mut events.transfer_events, TransferEvent {
        from,
        to,
        amount,
    });
}

Event API (V2 - Recommended)

use aptos_framework::event;

#[event]
struct TransferEvent has drop, store {
    from: address,
    to: address,
    amount: u64,
}

public fun transfer(from: address, to: address, amount: u64) {
    // Direct emission (no EventHandle needed!)
    event::emit(TransferEvent { from, to, amount });
}

Event V2 Advantages:

  • No EventHandle management
  • Cleaner code
  • Better indexing support
  • Automatic event routing

timestamp.move - Block Time

use aptos_framework::timestamp;

public fun get_current_time(): u64 {
    timestamp::now_seconds()
}

public fun get_current_time_microseconds(): u64 {
    timestamp::now_microseconds()
}

// Time-based logic
public fun is_expired(deadline: u64): bool {
    timestamp::now_seconds() >= deadline
}

public fun create_with_deadline(duration: u64): u64 {
    timestamp::now_seconds() + duration
}

Important: Block timestamp is set by validators, can have small drift.

randomness.move - Secure Randomness

VRF-based Random Numbers

use aptos_framework::randomness;

#[randomness]
public entry fun random_mint(user: &signer) {
    let random_value = randomness::u64_integer();

    let rarity = if (random_value % 100 < 1) {
        // 1% chance - legendary
        3
    } else if (random_value % 100 < 10) {
        // 9% chance - rare
        2
    } else {
        // 90% chance - common
        1
    };

    mint_nft(user, rarity);
}

// Random in range
#[randomness]
public entry fun random_reward(user: &signer) {
    let amount = randomness::u64_range(100, 1000); // 100 to 999
    transfer_reward(user, amount);
}

Requirements:

  • Must use
    #[randomness]
    attribute
  • Must be entry function
  • Only works on-chain (not in view functions)

Random Bytes

#[randomness]
public entry fun random_selection() {
    let random_bytes = randomness::bytes(32); // 32 random bytes
    // Use for cryptographic purposes
}

resource_account.move - Deterministic Deployment

Creating Resource Accounts

use aptos_framework::resource_account;
use aptos_framework::account;

public fun create_resource_acct(deployer: &signer) {
    let seed = b"MY_RESOURCE";

    // Create resource account
    let (resource_signer, signer_cap) = account::create_resource_account(
        deployer,
        seed
    );

    // Resource account address is deterministic:
    // hash(deployer_address, seed)
    let resource_addr = signer::address_of(&resource_signer);

    // Store signer capability to use later
    move_to(&resource_signer, ResourceData {
        signer_cap
    });
}

Use Cases for Resource Accounts

  1. Module Storage - Store module data at predictable address
  2. Liquidity Pools - Each pool at deterministic address
  3. Protocol Treasuries - Controlled programmatically
  4. Registry Systems - Well-known addresses
// Example: Liquidity Pool at deterministic address
public fun create_pool<X, Y>(deployer: &signer) {
    let seed = b"POOL_";
    vector::append(&mut seed, type_name<X>());
    vector::append(&mut seed, b"_");
    vector::append(&mut seed, type_name<Y>());

    let (pool_signer, signer_cap) = account::create_resource_account(
        deployer,
        seed
    );

    move_to(&pool_signer, Pool<X, Y> {
        reserve_x: 0,
        reserve_y: 0,
        signer_cap,
    });
}

aggregator_v2.move - Parallel Execution

Aggregators for Concurrent Modification

use aptos_framework::aggregator_v2::{Self, Aggregator};

struct Stats has key {
    total_users: Aggregator<u64>,
    total_volume: Aggregator<u64>,
}

public fun initialize(account: &signer) {
    move_to(account, Stats {
        total_users: aggregator_v2::create_aggregator(0),
        total_volume: aggregator_v2::create_aggregator(0),
    });
}

public fun increment_users() acquires Stats {
    let stats = borrow_global_mut<Stats>(@my_module);
    aggregator_v2::add(&mut stats.total_users, 1);
}

public fun add_volume(amount: u64) acquires Stats {
    let stats = borrow_global_mut<Stats>(@my_module);
    aggregator_v2::add(&mut stats.total_volume, amount);
}

public fun get_total_users(): u64 acquires Stats {
    let stats = borrow_global<Stats>(@my_module);
    aggregator_v2::read(&stats.total_users)
}

Why Use Aggregators:

  • Enable parallel transaction execution
  • Multiple transactions can increment same aggregator concurrently
  • No conflicts/retries like regular u64 fields
  • Critical for high-throughput protocols

option.move - Optional Values

use std::option::{Self, Option};

struct Profile has key {
    name: String,
    bio: Option<String>,  // Optional field
}

public fun create_profile(account: &signer, name: String) {
    move_to(account, Profile {
        name,
        bio: option::none()  // No bio initially
    });
}

public fun set_bio(account: &signer, bio: String) acquires Profile {
    let addr = signer::address_of(account);
    let profile = borrow_global_mut<Profile>(addr);

    if (option::is_some(&profile.bio)) {
        // Update existing bio
        *option::borrow_mut(&mut profile.bio) = bio;
    } else {
        // Set bio for first time
        option::fill(&mut profile.bio, bio);
    }
}

public fun get_bio(addr: address): Option<String> acquires Profile {
    let profile = borrow_global<Profile>(addr);
    option::clone(&profile.bio)
}

// Using option value
public fun print_bio(addr: address) acquires Profile {
    let bio_opt = get_bio(addr);
    if (option::is_some(&bio_opt)) {
        let bio = option::extract(&mut bio_opt);
        // Use bio
    } else {
        // No bio set
    }
}

string.move - UTF8 Strings

use std::string::{Self, String};

public fun create_message(): String {
    string::utf8(b"Hello, Aptos!")
}

public fun concatenate(s1: String, s2: String): String {
    let mut result = s1;
    string::append(&mut result, s2);
    result
}

public fun substring(s: &String, start: u64, end: u64): String {
    string::sub_string(s, start, end)
}

public fun string_length(s: &String): u64 {
    string::length(s)
}

// String to bytes
public fun to_bytes(s: &String): vector<u8> {
    *string::bytes(s)
}

vector.move - Dynamic Arrays

use std::vector;

public fun vector_operations() {
    let mut v = vector::empty<u64>();

    // Add elements
    vector::push_back(&mut v, 10);
    vector::push_back(&mut v, 20);
    vector::push_back(&mut v, 30);

    // Get length
    let len = vector::length(&v);  // 3

    // Access elements
    let first = *vector::borrow(&v, 0);  // 10

    // Modify elements
    let second = vector::borrow_mut(&mut v, 1);
    *second = 25;

    // Remove element
    let last = vector::pop_back(&mut v);  // 30

    // Check if contains
    let has_ten = vector::contains(&v, &10);  // true

    // Find index
    let (found, index) = vector::index_of(&v, &25);

    // Reverse
    vector::reverse(&mut v);

    // Append another vector
    vector::append(&mut v, vector[40, 50]);

    // Remove and return element
    let removed = vector::remove(&mut v, 0);

    // Swap elements
    vector::swap(&mut v, 0, 1);
}

Common Patterns

Pattern 1: Registry with Table

use aptos_framework::table::{Self, Table};

struct Registry<K: copy + drop, V: store> has key {
    data: Table<K, V>,
    count: u64,
}

public fun initialize<K: copy + drop, V: store>(account: &signer) {
    move_to(account, Registry<K, V> {
        data: table::new(),
        count: 0,
    });
}

public fun register<K: copy + drop, V: store>(
    registry_addr: address,
    key: K,
    value: V
) acquires Registry {
    let registry = borrow_global_mut<Registry<K, V>>(registry_addr);
    assert!(!table::contains(&registry.data, key), ERROR_ALREADY_EXISTS);

    table::add(&mut registry.data, key, value);
    registry.count = registry.count + 1;
}

Pattern 2: Event-Driven State Changes

#[event]
struct StateChanged has drop, store {
    old_state: u8,
    new_state: u8,
    timestamp: u64,
}

public fun change_state(new_state: u8) acquires State {
    let state = borrow_global_mut<State>(@my_module);
    let old = state.value;

    state.value = new_state;

    event::emit(StateChanged {
        old_state: old,
        new_state,
        timestamp: timestamp::now_seconds(),
    });
}

Pattern 3: Time-Locked Operations

struct TimeLock has key {
    unlock_time: u64,
    amount: u64,
}

public fun create_timelock(
    account: &signer,
    amount: u64,
    lock_duration: u64
) {
    let unlock_time = timestamp::now_seconds() + lock_duration;

    move_to(account, TimeLock {
        unlock_time,
        amount,
    });
}

public fun withdraw(account: &signer) acquires TimeLock {
    let addr = signer::address_of(account);
    let timelock = move_from<TimeLock>(addr);

    assert!(
        timestamp::now_seconds() >= timelock.unlock_time,
        ERROR_STILL_LOCKED
    );

    let TimeLock { unlock_time: _, amount } = timelock;
    // Transfer amount to user
}

Pattern 4: Resource Account Pool

struct Pool<phantom X, phantom Y> has key {
    reserve_x: u64,
    reserve_y: u64,
    signer_cap: SignerCapability,
}

public fun create_pool<X, Y>(creator: &signer) {
    let seed = b"POOL";
    let (pool_signer, signer_cap) = account::create_resource_account(
        creator,
        seed
    );

    coin::register<X>(&pool_signer);
    coin::register<Y>(&pool_signer);

    move_to(&pool_signer, Pool<X, Y> {
        reserve_x: 0,
        reserve_y: 0,
        signer_cap,
    });
}

public fun swap<X, Y>(amount_in: u64): u64 acquires Pool {
    let pool_addr = account::create_resource_address(&@my_module, b"POOL");
    let pool = borrow_global_mut<Pool<X, Y>>(pool_addr);

    // Swap logic using signer_cap for transfers
    let pool_signer = account::create_signer_with_capability(&pool.signer_cap);
    // ...
}

Framework Module Reference

Quick Reference Table

ModuleKey FunctionsUse Case
accountcreate_account, rotate_authentication_keyAccount management
cointransfer, balance, registerFungible tokens
fungible_assetmint, burn, transferAdvanced tokens
objectcreate_object, transferObject model
tableadd, borrow, removeLarge key-value stores
smart_tableadd, borrow, removeVery large stores
eventemit, emit_eventEvent emission
timestampnow_secondsTime access
randomnessu64_integer, bytesSecure randomness
aggregator_v2create, add, readParallel execution
resource_accountcreate_resource_accountDeterministic addresses

Best Practices

✅ Do

  • Use SmartTable for large datasets - Better than Table for 100k+ entries
  • Use Event V2 API - Simpler than EventHandle
  • Use Aggregator for counters - Enables parallel execution
  • Use resource accounts for protocols - Deterministic addresses
  • Check timestamp carefully - Validator-set, can have drift
  • Use randomness for fair selection - VRF-based security

❌ Avoid

  • Don't iterate over Tables - Not supported, use vector/map if needed
  • Don't trust timestamp for exact timing - Block-level granularity
  • Don't use randomness in view functions - Not supported
  • Don't forget to handle Option::none - Check before unwrapping
  • Don't create too many event handles - Use Event V2 instead

Response Style

  • Module-focused - Reference specific framework modules
  • Pattern-driven - Show common framework usage patterns
  • Performance-aware - Mention gas implications
  • Practical - Real-world examples with framework modules
  • Reference docs - Link to specific module documentation

Follow-up Suggestions

After helping with framework modules, suggest:

  • Gas optimization for storage structures
  • Event indexing strategies
  • Parallel execution with aggregators
  • Resource account architectures
  • Time-based protocol designs
  • Random number generation patterns