troubleshoot-errors
Troubleshoot Errors Skill
Quick Triage Workflow
Step 1: Identify Error Category
- Compilation error → Check syntax, types, abilities
- Linker error → Check dependencies, named addresses
- Runtime abort (ABORTED) → Check error code, find failed assertion
- Object error → See Object-Related Errors section below (CRITICAL)
- Test error → Check assertions, expected failures
- Type error → Check generic types, type conversions
Step 2: Top 10 Common Errors (Quick Fixes)
- "object does not exist" → Verify seed/creator address for named objects
- "RESOURCE_NOT_FOUND" → Add
acquiresclause to function - "Type mismatch" → Use
object::address_to_object<T>()to convert address to Object - "Ability constraint not satisfied" → Add required ability (key, drop, copy, store)
- "unbound variable" → Declare variable before use or fix typo
- "missing acquires" → Add
acquires ResourceTypeto function signature - "Named address not found" → Add address to Move.toml
[addresses]section - "Package dependencies not resolved" → Add dependency to Move.toml
- "Expected semicolon" → Add semicolon at end of statement
- "ungated transfers disabled" → Use
object::transfer_with_ref()instead ofobject::transfer()
See references/error-catalog.md for complete error database.
Object-Related Errors ⭐ CRITICAL
These are the most common and complex errors in Aptos Move V2.
Error: "An object does not exist at this address"
Cause: Trying to access a resource at an object address that hasn't been created yet.
Common Scenarios:
Scenario 1: Collection owner can't create tokens
// ❌ WRONG: Storing collection's extend_ref
fun init_module(deployer: &signer) {
let collection_ref = collection::create_unlimited_collection(...);
let collection_signer = object::generate_signer(&collection_ref);
move_to(&collection_signer, Config {
extend_ref: object::generate_extend_ref(&collection_ref), // Wrong ref!
});
}
public entry fun mint_nft() acquires Config {
let config = borrow_global<Config>(...);
let signer = object::generate_signer_for_extending(&config.extend_ref);
// ❌ ERROR: Collection can't create tokens in itself
token::create_named_token(&signer, ...);
}
// ✅ CORRECT: Create marketplace object that OWNS the collection
fun init_module(deployer: &signer) {
// Create marketplace state object
let marketplace_ref = object::create_named_object(deployer, b"MARKETPLACE_STATE");
let marketplace_signer = object::generate_signer(&marketplace_ref);
// Marketplace creates collection (marketplace is the owner)
collection::create_unlimited_collection(&marketplace_signer, ...);
// Store MARKETPLACE object's extend_ref
move_to(&marketplace_signer, MarketplaceConfig {
extend_ref: object::generate_extend_ref(&marketplace_ref),
});
}
public entry fun mint_nft() acquires MarketplaceConfig {
let config = borrow_global<MarketplaceConfig>(...);
// Use marketplace signer (collection owner)
let marketplace_signer = object::generate_signer_for_extending(&config.extend_ref);
token::create_named_token(&marketplace_signer, ...); // ✅ Works!
}
Key lesson: When minting tokens into a collection, use the collection owner's signer, not the collection's signer.
Scenario 2: init_module never ran
// During deployment, init_module failed an assertion
fun init_module(deployer: &signer) {
assert!(signer::address_of(deployer) == @marketplace_addr, E_NOT_AUTHORIZED);
// If this fails, MarketplaceConfig is never created
move_to(deployer, MarketplaceConfig { ... });
}
Fix:
- Verify
@marketplace_addrresolves to correct address during deployment - For object deployment, use
aptos move deploy-objectnotpublish - Check deployment transaction succeeded
Scenario 3: Wrong address calculation
// ❌ WRONG: Incorrect seed or creator address
let obj_addr = object::create_object_address(&wrong_creator, b"SEED");
let config = borrow_global<Config>(obj_addr); // Doesn't exist at this address
// ✅ CORRECT: Use correct creator address and seed
let obj_addr = object::create_object_address(&@marketplace_addr, b"MARKETPLACE_STATE");
let config = borrow_global<MarketplaceConfig>(obj_addr);
Error: "The object does not have ungated transfers enabled"
Cause: Trying to use object::transfer() on an object with disabled ungated transfers.
// ❌ WRONG
public entry fun mint_and_transfer(creator: &signer, recipient: address) {
let constructor_ref = token::create_named_token(...);
let transfer_ref = object::generate_transfer_ref(&constructor_ref);
// Disable ungated transfers
object::disable_ungated_transfer(&transfer_ref);
let token_obj = object::object_from_constructor_ref<Token>(&constructor_ref);
// ❌ ERROR: ungated transfers are disabled!
object::transfer(creator, token_obj, recipient);
}
// ✅ CORRECT
public entry fun mint_and_transfer(creator: &signer, recipient: address) {
let constructor_ref = token::create_named_token(...);
let transfer_ref = object::generate_transfer_ref(&constructor_ref);
// Disable ungated transfers
object::disable_ungated_transfer(&transfer_ref);
// ✅ Use transfer_with_ref instead
let linear_transfer_ref = object::generate_linear_transfer_ref(&transfer_ref);
object::transfer_with_ref(linear_transfer_ref, recipient);
// Store transfer_ref for future transfers
let token_signer = object::generate_signer(&constructor_ref);
move_to(&token_signer, TokenData { transfer_ref, ... });
}
Rule:
object::disable_ungated_transfer()called? → Useobject::transfer_with_ref()- Ungated transfers enabled (default)? → Use
object::transfer()
Error: "Object address derivation mismatch"
Cause: Named object address doesn't match expected address.
// ❌ WRONG: Inconsistent seeds
let obj = object::create_named_object(creator, b"SEED_V1");
// Later, trying to access with different seed
let obj_addr = object::create_object_address(&creator_addr, b"SEED_V2"); // Wrong seed!
// ✅ CORRECT: Use consistent seeds
const SEED: vector<u8> = b"MY_OBJECT_SEED";
// Creation
let obj = object::create_named_object(creator, SEED);
// Access
let obj_addr = object::create_object_address(&creator_addr, SEED);
Common Error Patterns
Pattern 1: Object Access
// ❌ WRONG
let item = borrow_global<Item>(item_obj); // Passing Object<Item>, need address
// ✅ CORRECT
let item_addr = object::object_address(&item_obj);
let item = borrow_global<Item>(item_addr);
Pattern 2: Missing acquires
// ❌ WRONG
public fun get_balance(addr: address): u64 { // Missing 'acquires'
let account = borrow_global<Account>(addr);
account.balance
}
// ✅ CORRECT
public fun get_balance(addr: address): u64 acquires Account {
let account = borrow_global<Account>(addr);
account.balance
}
Pattern 3: Incorrect Error Codes
// ❌ WRONG
assert!(condition, 0); // Using 0 - unclear what failed
// ✅ CORRECT
const E_CONDITION_FAILED: u64 = 1;
assert!(condition, E_CONDITION_FAILED);
Debugging Strategies
1. Add Debug Prints
use std::debug;
public fun my_function(x: u64, y: u64) {
debug::print(&x);
debug::print(&y);
let result = x + y;
debug::print(&result);
}
2. Simplify Code
Break complex expressions into simple steps with debug prints between each step.
3. Test Incrementally
Write separate tests for each step of complex logic.
4. Check Error Codes
// When you see: ABORTED: 0x1 (error code 1)
// Find the constant with value 1:
const E_NOT_OWNER: u64 = 1; // This is what failed
Custom Error Code Structure
// Access control: 1-9
const E_NOT_OWNER: u64 = 1;
const E_NOT_ADMIN: u64 = 2;
const E_UNAUTHORIZED: u64 = 3;
// Input validation: 10-19
const E_ZERO_AMOUNT: u64 = 10;
const E_AMOUNT_TOO_HIGH: u64 = 11;
const E_INVALID_ADDRESS: u64 = 12;
// State errors: 20-29
const E_NOT_INITIALIZED: u64 = 20;
const E_ALREADY_INITIALIZED: u64 = 21;
const E_PAUSED: u64 = 22;
// Business logic: 30+
const E_INSUFFICIENT_BALANCE: u64 = 30;
ALWAYS Rules
- Read error messages completely - Don't guess from partial info
- Check error codes - Identify which assertion failed
- Use debug::print - Add debugging output systematically
- Test incrementally - Don't test everything at once
- Define clear error constants - No magic numbers
- Verify fixes with tests - Ensure error doesn't recur
NEVER Rules
- Never ignore compiler warnings - They often indicate real bugs
- Never use generic error codes - Always define descriptive constants
- Never skip testing after fixing - Regression tests are critical
- Never deploy with known errors - Fix all errors before deployment
- Never assume error location - Verify with debug prints
- Never read
~/.aptos/config.yamlor.envto debug issues — these contain private keys; useaptos account listto verify account config instead - Never run
echo $VITE_MODULE_PUBLISHER_ACCOUNT_PRIVATE_KEYorenv | grep KEY— these expose private keys - Never display private key values that appear in error output or user messages
References
Official Documentation:
- Move Book: https://aptos.dev/build/smart-contracts/book
- Error Codes: https://aptos.dev/build/smart-contracts/book/abort-and-assert
Related Skills:
write-contracts- Write correct code to avoid errorsgenerate-tests- Test for errors proactivelysecurity-audit- Find potential issues before deployment
Remember: Object errors are the most common in V2. Check seeds, creator addresses, and which signer you're using.