$npx -y skills add aptos-labs/aptos-agent-skills --skill write-contractsGenerates secure Aptos Move V2 smart contracts with Object model, Digital Asset integration, security patterns, and storage type guidance. Includes comprehensive storage decision framework for optimal data structure selection. Triggers on: 'write contract', 'create NFT collection
| 1 | # Write Contracts Skill |
| 2 | |
| 3 | ## Core Rules |
| 4 | |
| 5 | ### Digital Assets (NFTs) ⭐ CRITICAL |
| 6 | |
| 7 | 1. **ALWAYS use Digital Asset (DA) standard** for ALL NFT-related contracts (collections, marketplaces, minting) |
| 8 | 2. **ALWAYS import** `aptos_token_objects::collection` and `aptos_token_objects::token` modules |
| 9 | 3. **ALWAYS use** `Object<AptosToken>` for NFT references (NOT generic `Object<T>`) |
| 10 | 4. **NEVER use legacy TokenV1** standard or `aptos_token::token` module (deprecated) |
| 11 | 5. See `../../../patterns/move/DIGITAL_ASSETS.md` for complete NFT patterns |
| 12 | |
| 13 | ### Object Model |
| 14 | |
| 15 | 6. **ALWAYS use** `Object<T>` for all object references (NEVER raw addresses) |
| 16 | 7. **Generate all refs** (TransferRef, DeleteRef) in constructor before ConstructorRef destroyed |
| 17 | 8. **Return** `Object<T>` from constructors (NEVER return ConstructorRef) |
| 18 | 9. **Verify ownership** with `object::owner(obj) == signer::address_of(user)` |
| 19 | 10. **Use** `object::generate_signer(&constructor_ref)` for object signers |
| 20 | 11. **Use named objects** for singletons: `object::create_named_object(creator, seed)` |
| 21 | |
| 22 | ### Security |
| 23 | |
| 24 | 12. **ALWAYS verify signer authority** in entry functions: |
| 25 | `assert!(signer::address_of(user) == expected, E_UNAUTHORIZED)` |
| 26 | 13. **ALWAYS validate inputs**: non-zero amounts, address validation, string length checks |
| 27 | 14. **NEVER expose** `&mut` references in public functions |
| 28 | 15. **NEVER skip** signer verification in entry functions |
| 29 | |
| 30 | ### Modern Syntax |
| 31 | |
| 32 | 16. **Use inline functions** and lambdas for iteration |
| 33 | 17. **Use receiver-style** method calls: `obj.is_owner(user)` (define first param as `self`) |
| 34 | 18. **Use vector indexing**: `vector[index]` instead of `vector::borrow()` |
| 35 | 19. **Use direct named addresses**: `@marketplace_addr` (NOT helper functions) |
| 36 | |
| 37 | ### Required Patterns |
| 38 | |
| 39 | 20. **Use init_module** for contract initialization on deployment |
| 40 | 21. **Emit events** for ALL significant activities (create, transfer, update, delete) |
| 41 | 22. **Define clear error constants** with descriptive names (E_NOT_OWNER, E_INSUFFICIENT_BALANCE) |
| 42 | |
| 43 | ### Testability |
| 44 | |
| 45 | 23. **Add accessor functions** for struct fields - tests in separate modules cannot access struct fields directly |
| 46 | 24. **Use `#[view]` annotation** for read-only accessor functions |
| 47 | 25. **Return tuples** from accessors for multi-field access: `(seller, price, timestamp)` |
| 48 | 26. **Place `#[view]` BEFORE doc comments** - `/// comment` before `#[view]` causes compiler warnings. Write `#[view]` |
| 49 | first, then `///` |
| 50 | |
| 51 | ## Quick Workflow |
| 52 | |
| 53 | 1. **Create module structure** → Define structs, events, constants, init_module |
| 54 | 2. **Implement object creation** → Use proper constructor pattern with all refs generated upfront |
| 55 | 3. **Add access control** → Verify ownership and validate all inputs |
| 56 | 4. **Security check** → Use `security-audit` skill before deployment |
| 57 | |
| 58 | ## Key Example: Object Creation Pattern |
| 59 | |
| 60 | ```move |
| 61 | struct MyObject has key { |
| 62 | name: String, |
| 63 | transfer_ref: object::TransferRef, |
| 64 | delete_ref: object::DeleteRef, |
| 65 | } |
| 66 | |
| 67 | // Error constants |
| 68 | const E_NOT_OWNER: u64 = 1; |
| 69 | const E_EMPTY_STRING: u64 = 2; |
| 70 | const E_NAME_TOO_LONG: u64 = 3; |
| 71 | |
| 72 | // Configuration constants |
| 73 | const MAX_NAME_LENGTH: u64 = 100; |
| 74 | |
| 75 | /// Create object with proper pattern |
| 76 | public fun create_my_object(creator: &signer, name: String): Object<MyObject> { |
| 77 | // 1. Create object |
| 78 | let constructor_ref = object::create_object(signer::address_of(creator)); |
| 79 | |
| 80 | // 2. Generate ALL refs you'll need BEFORE constructor_ref is destroyed |
| 81 | let transfer_ref = object::generate_transfer_ref(&constructor_ref); |
| 82 | let delete_ref = object::generate_delete_ref(&constructor_ref); |
| 83 | |
| 84 | // 3. Get object signer |
| 85 | let object_signer = object::generate_signer(&constructor_ref); |
| 86 | |
| 87 | // 4. Store data in object |
| 88 | move_to(&object_signer, MyObject { |
| 89 | name, |
| 90 | transfer_ref, |
| 91 | delete_ref, |
| 92 | }); |
| 93 | |
| 94 | // 5. Return typed object reference (ConstructorRef automatically destroyed) |
| 95 | object::object_from_constructor_ref<MyObject>(&constructor_ref) |
| 96 | } |
| 97 | |
| 98 | /// Update with ownership verification |
| 99 | public entry fun update_object( |
| 100 | owner: &signer, |
| 101 | obj: Object<MyObject>, |
| 102 | new_name: String |
| 103 | ) acquires MyObject { |
| 104 | // ✅ ALWAYS: Verify ownership |
| 105 | assert!(object::owner(obj) == signer::address_of(owner), E_NOT_OWNER); |
| 106 | |
| 107 | // ✅ ALWAYS: Validate inputs |
| 108 | assert!(string::length(&new_name) > 0, E_EMPTY_STRING); |