An object that proves you have permission to do something.
// Soulbound: key but NO store → can't be transferred!public structAdminCaphas key { id:UID }public entry funmint( _: &AdminCap, collection: &mutCollection, ctx: &mutTxContext) {// Only the AdminCap holder can call this}
Transferable Cap
Has key, store Can be sold / traded
Soulbound Cap
Has key only Bound to owner forever
9 / 16
Hot Potato Pattern
A struct with no abilities — can't be dropped, stored, or copied.
Forces the caller to consume it in the same transaction. Classic for flash loans.
// NO abilities! This is intentional.public structReceipt { amount:u64 }public funborrow(pool: &mutPool, amount: u64, ctx: &mutTxContext): (Coin<SUI>,Receipt) {let coin = balance::split(&mut pool.balance, amount);let receipt =Receipt { amount }; (coin::from_balance(coin, ctx), receipt) }// Must be called in same PTB to destroy the receiptpublic funrepay(pool: &mutPool, coin:Coin<SUI>, receipt: Receipt) {letReceipt {amount } = receipt;// destructure = destroyassert!(coin::value(&coin) == amount,0); balance::join(&mut pool.balance, coin::into_balance(coin)); }
10 / 16
Object Wrapping
One object owns another. The wrapped object is invisible at the top level.
// Direct wrappingpublic structHerohas key {id: UID, sword:Sword,// nested object}// Optional wrappingpublic structHerohas key {id: UID, sword:Option<Sword>, // may or may not have one}// Vector wrapping (inventory)public structHerohas key {id: UID, items:vector<Item>, // many items}
Rule: Wrapped objects can't be accessed directly via their ID — only through the parent.
11 / 16
Dynamic Fields
Attach data to objects at runtimewithout changing the struct.
Perfect for: extensible NFTs, game items with variable stats, user-defined metadata.
12 / 16
Shared vs. Owned Objects
Owned Object
• Single owner • Parallel execution • No consensus overhead • Best for: user wallets, caps, personal items
Shared Object
• Multi-writer • Requires consensus • Slower, more expensive • Best for: pools, markets, registries
// Owned — transferred to an addresstransfer::transfer(obj, recipient);// Shared — anyone can accesstransfer::share_object(obj);// Immutable — no owner, forevertransfer::freeze_object(obj);
13 / 16
Transfer-to-Object
Transfer an object so it becomesowned by another object (not an address).
// Make `child` owned by `parent` objecttransfer::transfer(child, object::id(parent));// Now child is in parent's "inventory"// Parent can manage it via dynamic fields or wrapping
Enables object hierarchies: a Hero owns aSword which owns a Gem.