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