Design Patterns
A capability is an object/resource whose possession grants permission.
31.1 Capability pattern
A capability is an object/resource whose possession grants permission.
Capabilities are transferable (if store), can be scoped to a specific object (cap.pool_id == object::id(pool)), and are easy to audit.
// Sui
module shop::shop;
public struct AdminCap has key, store { id: UID }
public struct Shop has key { id: UID, price: u64 }
fun init(ctx: &mut TxContext) {
transfer::transfer(AdminCap { id: object::new(ctx) }, ctx.sender());
transfer::share_object(Shop { id: object::new(ctx), price: 100 });
}
public fun set_price(_: &AdminCap, shop: &mut Shop, price: u64) {
shop.price = price; // only AdminCap holders can call
}// Aptos: capability stored as a resource under the admin's account
struct AdminCap has key {}
fun init_module(admin: &signer) { move_to(admin, AdminCap {}) }
public entry fun set_price(admin: &signer, price: u64) acquires Shop {
assert!(exists<AdminCap>(std::signer::address_of(admin)), E_NOT_ADMIN);
borrow_global_mut<Shop>(@my_addr).price = price;
}31.2 Witness pattern
A witness is an instance of a type that only its defining module can create, used to prove "this call is authorized by module X". Generic framework functions take witness: T to bind actions to the type's module:
module my_coin::gold;
public struct GOLD has drop {} // only this module can construct GOLD {}
// A generic registry elsewhere might require: fun register<T: drop>(_w: T, ...)
// Only `gold` can call register<GOLD>(GOLD {}, ...).31.3 One-time witness (OTW) — Sui
A one-time witness is a special witness guaranteed to be created exactly once, at publish time:
Struct named after the module in UPPERCASE (module gold → GOLD).
Only the drop ability, no fields (or a single bool field in older versions).
Passed by the runtime as the first argument of init.
Used by coin::create_currency, package::claim, and other "do this once per type" APIs.
module my_coin::gold;
public struct GOLD has drop {}
fun init(otw: GOLD, ctx: &mut TxContext) {
assert!(sui::types::is_one_time_witness(&otw), 0);
// use otw to create a currency, claim a Publisher, etc.
}31.4 Hot potato pattern
A struct with no abilities cannot be stored, copied, or dropped — so it must be consumed by a function of its module within the same transaction. Perfect for flash loans and enforced multi-step flows:
Within one PTB: borrow → use the coin (arbitrage) → repay. If the user does not call repay, the transaction cannot complete because Receipt cannot be dropped. Kiosk's TransferRequest and many "request" APIs use the same idea.
module lend::flash;
use sui::balance::{Self, Balance};
use sui::coin::{Self, Coin};
use sui::sui::SUI;
public struct Pool has key { id: UID, funds: Balance<SUI>, fee_bps: u64 }
/// Hot potato: no abilities.
public struct Receipt { pool_id: ID, amount: u64 }
const EWrongPool: u64 = 0;
const ERepayTooLow: u64 = 1;
public fun borrow(pool: &mut Pool, amount: u64, ctx: &mut TxContext): (Coin<SUI>, Receipt) {
let coin = coin::take(&mut pool.funds, amount, ctx);
(coin, Receipt { pool_id: object::id(pool), amount })
}
public fun repay(pool: &mut Pool, payment: Coin<SUI>, receipt: Receipt) {
let Receipt { pool_id, amount } = receipt;
assert!(pool_id == object::id(pool), EWrongPool);
let due = amount + amount * pool.fee_bps / 10_000;
assert!(payment.value() >= due, ERepayTooLow);
coin::put(&mut pool.funds, payment);
}31.5 Other patterns
Request / Receipt Hot potato collected stamps (rules) before being confirmed (Kiosk transfer policies) Object wrapper / escrow
Wrap an asset in a shared object until conditions are met Versioned shared object
Store a version field; functions check it, enabling safe upgrades (Chapter 37)
Shared object holding a Table of entries Owned-object fast path
Model per-user state as owned objects to avoid consensus Aptos refs as capabilities
Store MintRef, TransferRef, ExtendRef inside resources only the module accesses Witness-gated generics
fun register<T: drop>(_: T) so only T's module can register T