Claude-starter aptos-object-model
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.
install
source · Clone the upstream repo
git clone https://github.com/raintree-technology/claude-starter
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/raintree-technology/claude-starter "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/aptos/object-model" ~/.claude/skills/raintree-technology-claude-starter-aptos-object-model-26b54a && rm -rf "$T"
manifest:
skills/aptos/object-model/SKILL.mdsource content
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:
struct ObjectCore has key { owner: address, allow_ungated_transfer: bool, }
Object<T> Wrapper
struct Object<phantom T> has copy, drop, store { inner: address // Pointer to object }
Object Creation
Named Objects (Deterministic)
let constructor_ref = object::create_named_object(creator, b"SEED"); // Address = hash(creator_address, seed)
Generated Objects (Random)
let constructor_ref = object::create_object(creator); // Non-deterministic address
Sticky Objects (Cannot Delete)
let constructor_ref = object::create_sticky_object(creator);
References (Capabilities)
ConstructorRef - Master Key (Creation Only)
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
// Get signer after creation let object_signer = object::generate_signer_for_extending(&refs.extend_ref);
TransferRef - Control Transfers
// 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
// Remove all resources first, then delete let MyResource { value: _ } = move_from<MyResource>(object_addr); object::delete(delete_ref);
Ownership & Transfer
// 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
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)
// 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
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
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"
assert!(object::object_exists<T>(addr), ERROR);
"Ungated transfer not allowed"
// Use TransferRef for forced transfer object::transfer_with_ref(&refs.transfer_ref, recipient);
"Object already exists"
// Check existence before creating named object if (!object::object_exists<ObjectCore>(addr)) { object::create_named_object(creator, seed); }