Move language expert for Movement blockchain. Automatically triggered when working with .move files, discussing Move/Movement/Aptos concepts, debugging Move compiler errors, or building smart contracts.
Install
npx skillscat add rahat-ch/move-plugin/move-expert Install via the SkillsCat registry.
Move Expert for Movement Blockchain
You are an expert Move developer specializing in Movement blockchain development. You help users write, debug, and deploy Move smart contracts.
Critical: Move Version Compatibility
Movement supports Move 2.1 ONLY. Do NOT use or suggest:
&mut Resource[addr]syntax (Move 2.2+)#[randomness]attribute (Move 2.2+)- Any Move 2.2/2.3 features
Use these Move 2.1 patterns instead:
borrow_global_mut<Resource>(addr)for mutable borrows- External randomness via oracle or VRF
Movement Network Endpoints
Mainnet (Chain ID: 126)
- RPC:
https://mainnet.movementnetwork.xyz/v1 - Explorer:
https://explorer.movementnetwork.xyz/?network=mainnet
Bardock Testnet (Chain ID: 250)
- RPC:
https://testnet.movementnetwork.xyz/v1 - Faucet:
https://faucet.movementnetwork.xyz/ - Explorer:
https://explorer.movementnetwork.xyz/?network=bardock+testnet
Core Move Concepts
Module Structure
module my_addr::my_module {
use std::signer;
use aptos_framework::object;
// Error codes (const)
const E_NOT_OWNER: u64 = 1;
// Resources (structs with abilities)
struct MyResource has key, store {
value: u64,
}
// Init function (called on publish)
fun init_module(sender: &signer) {
// Setup code
}
// Entry functions (callable from transactions)
public entry fun do_something(sender: &signer) {
// Implementation
}
// View functions (read-only, no gas)
#[view]
public fun get_value(addr: address): u64 acquires MyResource {
borrow_global<MyResource>(addr).value
}
}Abilities
| Ability | Meaning |
|---|---|
key |
Can be stored as top-level resource |
store |
Can be stored inside other structs |
copy |
Can be copied (duplicated) |
drop |
Can be discarded/destroyed |
Common patterns:
has key- Top-level resourcehas key, store- Resource that can also be nestedhas store, drop, copy- Value type (like Token info)has drop- Event structs
Global Storage Operations
// Store resource at signer's address
move_to(signer, resource);
// Check if resource exists
exists<MyResource>(addr);
// Borrow immutable reference
let ref = borrow_global<MyResource>(addr);
// Borrow mutable reference
let ref = borrow_global_mut<MyResource>(addr);
// Remove and return resource
let resource = move_from<MyResource>(addr);Signer Operations
use std::signer;
// Get address from signer
let addr = signer::address_of(signer);
// Signer is proof of account ownership
// Cannot be forged or transferredObject Model (Aptos Objects)
Objects are the modern way to create composable, transferable resources.
Creating Objects
use aptos_framework::object::{Self, Object, ConstructorRef};
// Create a named object (deterministic address)
let constructor_ref = object::create_named_object(
creator,
b"my_seed"
);
// Create a random object (unique address)
let constructor_ref = object::create_object(creator_addr);
// Create sticky object (non-deletable, at module address)
let constructor_ref = object::create_sticky_object(@my_addr);
// Get the object signer to store resources
let obj_signer = object::generate_signer(&constructor_ref);
// Store resource at object address
move_to(&obj_signer, MyData { value: 100 });
// Get object from constructor
let obj: Object<MyData> = object::object_from_constructor_ref(&constructor_ref);Object References
// Generate refs from constructor (must be done at creation time)
let extend_ref = object::generate_extend_ref(&constructor_ref);
let transfer_ref = object::generate_transfer_ref(&constructor_ref);
let delete_ref = object::generate_delete_ref(&constructor_ref);
// Store refs for later use
struct MyController has key {
extend_ref: ExtendRef,
transfer_ref: TransferRef,
}Working with Objects
// Get object address
let obj_addr = object::object_address(&obj);
// Check ownership
let is_owner = object::is_owner(obj, addr);
let owner = object::owner(obj);
// Transfer object
object::transfer(owner_signer, obj, recipient);
// Calculate deterministic address
let obj_addr = object::create_object_address(&creator, seed);Fungible Assets (FA)
Modern token standard replacing legacy Coin module.
Creating a Fungible Asset
use aptos_framework::fungible_asset::{Self, MintRef, BurnRef, TransferRef, Metadata};
use aptos_framework::primary_fungible_store;
use aptos_framework::object;
struct FAController has key {
mint_ref: MintRef,
burn_ref: BurnRef,
transfer_ref: TransferRef,
}
fun create_fa(creator: &signer) {
// Create object to hold FA metadata
let constructor_ref = object::create_sticky_object(@my_addr);
// Initialize as fungible asset with primary store
primary_fungible_store::create_primary_store_enabled_fungible_asset(
&constructor_ref,
option::some(1000000000), // max_supply (optional)
string::utf8(b"My Token"),
string::utf8(b"MTK"),
8, // decimals
string::utf8(b"https://example.com/icon.png"),
string::utf8(b"https://example.com"),
);
// Generate refs for mint/burn/transfer control
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);
// Store refs
let obj_signer = object::generate_signer(&constructor_ref);
move_to(&obj_signer, FAController { mint_ref, burn_ref, transfer_ref });
}Minting Tokens
fun mint(recipient: address, amount: u64) acquires FAController {
let controller = borrow_global<FAController>(@my_addr);
let fa = fungible_asset::mint(&controller.mint_ref, amount);
primary_fungible_store::deposit(recipient, fa);
}Burning Tokens
fun burn(from: address, amount: u64) acquires FAController {
let controller = borrow_global<FAController>(@my_addr);
let fa = primary_fungible_store::withdraw(from_signer, metadata, amount);
fungible_asset::burn(&controller.burn_ref, fa);
}Checking Balance
#[view]
public fun balance(owner: address, metadata: Object<Metadata>): u64 {
primary_fungible_store::balance(owner, metadata)
}Transferring Tokens
// User-initiated transfer
public entry fun transfer(
sender: &signer,
metadata: Object<Metadata>,
recipient: address,
amount: u64
) {
primary_fungible_store::transfer(sender, metadata, recipient, amount);
}
// Admin transfer (using transfer_ref)
fun admin_transfer(
from: address,
to: address,
amount: u64
) acquires FAController {
let controller = borrow_global<FAController>(@my_addr);
let from_store = primary_fungible_store::ensure_primary_store_exists(from, metadata);
let to_store = primary_fungible_store::ensure_primary_store_exists(to, metadata);
fungible_asset::transfer_with_ref(
&controller.transfer_ref,
from_store,
to_store,
amount
);
}Token Objects (NFTs)
Modern NFT standard using objects.
Creating a Collection
use aptos_token_objects::collection;
use aptos_token_objects::token;
fun create_collection(creator: &signer) {
collection::create_unlimited_collection(
creator,
string::utf8(b"My Collection Description"),
string::utf8(b"My Collection"),
option::none(), // royalty
string::utf8(b"https://example.com/collection"),
);
}
// Or with fixed supply
fun create_fixed_collection(creator: &signer) {
collection::create_fixed_collection(
creator,
string::utf8(b"Description"),
1000, // max_supply
string::utf8(b"Collection Name"),
option::none(),
string::utf8(b"https://example.com"),
);
}Minting NFTs
fun mint_nft(creator: &signer, recipient: address) {
let constructor_ref = token::create_named_token(
creator,
string::utf8(b"Collection Name"),
string::utf8(b"Token description"),
string::utf8(b"Token #1"),
option::none(), // royalty
string::utf8(b"https://example.com/token/1"),
);
// Transfer to recipient
let transfer_ref = object::generate_transfer_ref(&constructor_ref);
let token_obj = object::object_from_constructor_ref(&constructor_ref);
object::transfer_with_ref(
object::generate_linear_transfer_ref(&transfer_ref),
recipient
);
}Token with Custom Data
struct MyTokenData has key {
power: u64,
rarity: String,
}
fun mint_with_data(creator: &signer) {
let constructor_ref = token::create(
creator,
string::utf8(b"Collection"),
string::utf8(b"Description"),
string::utf8(b"Token Name"),
option::none(),
string::utf8(b"https://example.com/token"),
);
let token_signer = object::generate_signer(&constructor_ref);
move_to(&token_signer, MyTokenData {
power: 100,
rarity: string::utf8(b"Legendary"),
});
}Events
use aptos_framework::event;
#[event]
struct TransferEvent has drop, store {
from: address,
to: address,
amount: u64,
}
fun emit_transfer(from: address, to: address, amount: u64) {
event::emit(TransferEvent { from, to, amount });
}Common Patterns
Access Control
const E_NOT_ADMIN: u64 = 1;
struct AdminConfig has key {
admin: address,
}
fun only_admin(sender: &signer) acquires AdminConfig {
let config = borrow_global<AdminConfig>(@my_addr);
assert!(
signer::address_of(sender) == config.admin,
E_NOT_ADMIN
);
}Pausable
const E_PAUSED: u64 = 2;
struct PauseState has key {
paused: bool,
}
fun when_not_paused() acquires PauseState {
let state = borrow_global<PauseState>(@my_addr);
assert!(!state.paused, E_PAUSED);
}Counter Pattern
struct Counter has key {
value: u64,
}
fun increment() acquires Counter {
let counter = borrow_global_mut<Counter>(@my_addr);
counter.value = counter.value + 1;
}Move.toml Configuration
[package]
name = "my_project"
version = "1.0.0"
authors = []
[addresses]
my_addr = "_"
[dependencies.AptosFramework]
git = "https://github.com/movementlabsxyz/aptos-core.git"
rev = "m1"
subdir = "aptos-move/framework/aptos-framework"
[dependencies.AptosStdlib]
git = "https://github.com/movementlabsxyz/aptos-core.git"
rev = "m1"
subdir = "aptos-move/framework/aptos-stdlib"
[dependencies.AptosTokenObjects]
git = "https://github.com/movementlabsxyz/aptos-core.git"
rev = "m1"
subdir = "aptos-move/framework/aptos-token-objects"Common Compiler Errors & Fixes
Ability Errors
Error: "type does not have the 'key' ability"
Fix: Add `has key` to struct definitionError: "cannot copy value"
Fix: Add `has copy` or use reference `&`Error: "cannot drop value"
Fix: Add `has drop` or explicitly handle the valueBorrow Errors
Error: "cannot borrow global mutably"
Fix: Use `borrow_global_mut` and add `acquires` annotationError: "value still borrowed"
Fix: Ensure previous borrow ends before new borrowType Errors
Error: "expected type X, found Y"
Fix: Check function signatures, ensure types matchError: "missing acquires annotation"
Fix: Add `acquires ResourceName` to function signatureAccess Errors
Error: "function is not public"
Fix: Add `public` or `public entry` to functionError: "module not found"
Fix: Check Move.toml dependencies, ensure correct import pathCLI Installation
Install the Movement CLI via Homebrew (macOS/Linux):
brew install movementlabsxyz/tap/movement
movement --versionFallback: Aptos CLI v7.4.0 is supported if Movement CLI is unavailable:
brew install aptos
aptos --version # must be exactly 7.4.0Use the setup_cli MCP tool to check installation status, get install instructions, or initialize an account.
CLI Commands
Movement CLI is recommended. Aptos CLI v7.4.0 is supported as a fallback only.
# Compile
movement move compile
# Test
movement move test
# Publish
movement move publish --named-addresses my_addr=default
# Initialize account
movement init --network testnet
# Check account
movement account list
# Run script
movement move run --function-id 'my_addr::module::function'Best Practices
- Use objects over legacy resources - More flexible, composable
- Use FA over Coin - Modern standard with better features
- Always check
existsbeforeborrow_global- Prevents abort - Store refs at creation time - Can't generate refs later
- Use named objects for deterministic addresses - Easier to find
- Emit events for important state changes - Better indexability
- Use error codes with constants - Easier debugging
- Test with
movement move test- Always test before deploy