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 acquires clause 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 ResourceType to 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 of object::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_addr resolves to correct address during deployment
-
For object deployment, use aptos move deploy-object not publish
-
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? → Use object::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
- 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); }
- Simplify Code
Break complex expressions into simple steps with debug prints between each step.
- Test Incrementally
Write separate tests for each step of complex logic.
- 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.yaml or .env to debug issues — these contain private keys; use aptos account list to verify account config instead
-
Never run echo $VITE_MODULE_PUBLISHER_ACCOUNT_PRIVATE_KEY or env | 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 errors
-
generate-tests
-
Test for errors proactively
-
security-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.