home / skills / raintree-technology / claude-starter / object-model
This skill helps you model and manage transferable on-chain objects with ownership, composability, and lifecycle using ObjectCore, Object<T>, and refs.
npx playbooks add skill raintree-technology/claude-starter --skill object-modelReview the files below or copy the command above to add this skill to your agents.
---
name: aptos-object-model
description: Expert on Aptos Object Model for composable, transferable assets. Covers ObjectCore, Object<T> wrapper, ConstructorRef, ExtendRef, DeleteRef, TransferRef capabilities, object ownership, named vs generated objects, and composability patterns.
allowed-tools: Read, Write, Edit, Grep, Glob, Bash
model: sonnet
license: MIT
metadata:
author: raintree
version: "1.0"
---
# Aptos Object Model Expert
Expert on the Aptos Object Model for building composable, transferable on-chain assets.
## Triggers
- object model, objectcore, Object<T>
- constructorref, extendref, deleteref, transferref
- named object, generated object
- object ownership, composable object
- soul-bound, nesting
## Core Concepts
The Object Model enables:
- **Transferable resources** - Objects can move between accounts
- **Composability** - Objects can own other objects
- **Lifecycle management** - Create, extend, delete via refs
- **Ownership separation** - Owner address != object address
## ObjectCore
Every object has an `ObjectCore`:
```move
struct ObjectCore has key {
owner: address,
allow_ungated_transfer: bool,
}
```
## Object<T> Wrapper
```move
struct Object<phantom T> has copy, drop, store {
inner: address // Pointer to object
}
```
## Object Creation
### Named Objects (Deterministic)
```move
let constructor_ref = object::create_named_object(creator, b"SEED");
// Address = hash(creator_address, seed)
```
### Generated Objects (Random)
```move
let constructor_ref = object::create_object(creator);
// Non-deterministic address
```
### Sticky Objects (Cannot Delete)
```move
let constructor_ref = object::create_sticky_object(creator);
```
## References (Capabilities)
### ConstructorRef - Master Key (Creation Only)
```move
let constructor_ref = object::create_object(creator);
// Generate all other refs during creation
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);
let object_signer = object::generate_signer(&constructor_ref);
// Store refs at object address
move_to(&object_signer, Refs { extend_ref, transfer_ref, delete_ref });
```
### ExtendRef - Access Later
```move
// Get signer after creation
let object_signer = object::generate_signer_for_extending(&refs.extend_ref);
```
### TransferRef - Control Transfers
```move
// Disable transfers (soul-bound)
object::disable_ungated_transfer(&refs.transfer_ref);
// Enable transfers
object::enable_ungated_transfer(&refs.transfer_ref);
// Force transfer
object::transfer_with_ref(&refs.transfer_ref, new_owner);
```
### DeleteRef - Destroy Objects
```move
// Remove all resources first, then delete
let MyResource { value: _ } = move_from<MyResource>(object_addr);
object::delete(delete_ref);
```
## Ownership & Transfer
```move
// Get object info
let object_addr = object::object_address(&obj);
let owner = object::owner(obj);
let transferable = object::ungated_transfer_allowed(obj);
// User transfer (if allowed)
object::transfer(owner, obj, new_owner);
```
## Common Patterns
### Soul-Bound Token
```move
public fun create_sbt(creator: &signer, recipient: address) {
let constructor_ref = token::create_named_token(...);
let transfer_ref = object::generate_transfer_ref(&constructor_ref);
object::disable_ungated_transfer(&transfer_ref);
// One-time transfer to recipient
let linear_ref = object::generate_linear_transfer_ref(&transfer_ref);
object::transfer_with_ref(linear_ref, recipient);
// Don't store transfer_ref - permanently non-transferable
}
```
### Composable NFT (Nesting)
```move
// Create parent
let parent_ref = token::create_named_token(...);
let parent_addr = object::address_from_constructor_ref(&parent_ref);
// Create child owned by parent
let child_ref = token::create_named_token(...);
let transfer_ref = object::generate_transfer_ref(&child_ref);
let linear_ref = object::generate_linear_transfer_ref(&transfer_ref);
object::transfer_with_ref(linear_ref, parent_addr);
```
### Singleton/Registry
```move
public fun get_or_create_registry(creator: &signer): address {
let seed = b"REGISTRY";
let addr = object::create_object_address(&signer::address_of(creator), seed);
if (!object::object_exists<Registry>(addr)) {
let ref = object::create_named_object(creator, seed);
let signer = object::generate_signer(&ref);
move_to(&signer, Registry { items: simple_map::create() });
};
addr
}
```
### Upgradeable Module Data
```move
struct DataRefs has key { extend_ref: ExtendRef }
public fun upgrade_config(new_config: Config) acquires DataRefs {
let refs = borrow_global<DataRefs>(data_addr);
let signer = object::generate_signer_for_extending(&refs.extend_ref);
// Modify resources at data_addr
}
```
## Best Practices
- Store refs at object address, not creator address
- Generate all needed refs during creation (ConstructorRef is ephemeral)
- Use named objects for singletons, generated for collections
- Disable ungated transfer for soul-bound tokens
- Clean up all resources before deletion
- Document object ownership hierarchy
## Common Errors
### "Object does not exist"
```move
assert!(object::object_exists<T>(addr), ERROR);
```
### "Ungated transfer not allowed"
```move
// Use TransferRef for forced transfer
object::transfer_with_ref(&refs.transfer_ref, recipient);
```
### "Object already exists"
```move
// Check existence before creating named object
if (!object::object_exists<ObjectCore>(addr)) {
object::create_named_object(creator, seed);
}
```
This skill is an expert guide to the Aptos Object Model for building composable, transferable on-chain assets. It explains ObjectCore, the Object<T> wrapper, ConstructorRef/ExtendRef/TransferRef/DeleteRef capabilities, ownership models, named vs generated objects, and composability patterns. Practical examples show common patterns like soul-bound tokens, nesting, registries, and upgradeable data.
The skill inspects how objects are created, addressed, and managed via constructor references and derived capability refs. It explains deterministic named objects versus non-deterministic generated objects, how refs are stored at the object address, and how transfer/delete/extend operations are gated by specific refs. It also covers lifecycle flows: create, extend, transfer, and delete while preserving ownership separation between owner address and object address.
Why store refs at the object address instead of the creator?
Storing refs at the object address ensures capability ownership follows the object lifecycle and prevents creator-only control after transfer.
When should I use named vs generated objects?
Use named objects for deterministic singletons or registries keyed by seed; use generated objects for unique, non-deterministic assets or large collections.
How do I make an object permanently non-transferable?
Disable ungated transfer via the TransferRef and avoid storing any transferable refs; for one-time assignment, use a linear transfer ref and then discard it.