- The Hot Potato pattern forces a guaranteed workflow inside a single transaction — the foundation of flash loans on Sui.
- Dynamic Fields let objects grow to any size without hitting struct size limits.
- Choosing between shared and owned objects directly controls transaction speed and consensus cost.
- A One-Time Witness (OTW) ensures a critical setup step runs exactly once — required to mint a custom coin.
- Package Upgrades let you ship new code to the same address using
UpgradeCap. - Programmable Transaction Blocks (PTBs) chain up to 1,024 Move calls into one atomic transaction without publishing a new package.
This sui move tutorial advanced guide picks up where the basics stop. You will learn the six architecture patterns that serious Sui developers use every day: the Hot Potato pattern, Dynamic Fields, object ownership strategy, the One-Time Witness, capability-based access control, Package Upgrades, and Programmable Transaction Blocks. Each section includes working code examples and a plain-English explanation so you know not just what to write, but why it works that way.
“For smart contracts, you want a language that gives you the necessary abstractions around ownership and scarcity, just like in the physical world. You want those basic safety guarantees — we wanted to design Move around providing these primitives so programmers can write code safely and efficiently and not have to reinvent the wheel every time they want to write some code.”
— Sam Blackshear, Creator of Move & Co-founder of Mysten Labs5
Before reading this article, you should complete Sui Move Tutorial: Master the Object Model From Scratch. That guide covers structs, abilities, object ownership basics, and the init function. This article builds on all of those concepts.
Why Advanced Sui Move Patterns Matter
When you first learn Sui Move, you practice simple struct definitions, single object transfers, and the init function. That knowledge gets you to a working contract. But it does not get you to a production-ready contract. Real protocols on Sui — decentralized exchanges, lending platforms, NFT marketplaces, and games — depend on a handful of advanced patterns that the basic tutorials never cover. Understanding these patterns is the difference between code that works in a test and code that handles real users, real money, and real upgrades over time.8
Sui Move is also an object-centric language by design. Every asset is an on-chain object with a unique ID. That sounds simple, but it changes how you think about storage, access control, and parallel execution. Mastering advanced Sui Move means mastering the object model at a deeper level — learning how objects interact, how they grow, and how they move safely between modules and users.
The Hot Potato Pattern: Forced Workflows in a Single Transaction
The Hot Potato pattern is one of the most creative ideas in all of Sui Move. A “hot potato” is a struct that has no abilities at all — no copy, no drop, no store, no key. Because it has no drop ability, Move cannot destroy it automatically. Because it has no store, you cannot save it inside another object. The only thing you can do with it is pass it to a function that knows how to consume it. That consuming function must live in the same module that created the struct.1
This enforces a guaranteed multi-step workflow. You call function A, it gives you a hot potato. The transaction cannot end until you call function B to consume it. If you forget function B, the entire transaction fails. There is no way around it — the type system enforces the rule at compile time.
Flash Loan Example
The most common use case is a flash loan. A user borrows tokens from a pool. The borrow function returns both the tokens and a Receipt hot potato. The user can do whatever they want with the tokens — arbitrage, liquidation, a DeFi operation — but before the transaction ends, they must call the repay function, which is the only function that can consume the Receipt. If repayment is short, the assertion fails and the whole transaction reverts. The loan is atomic by construction, not by trust.1
module example::flash_loan {
use sui::coin::Coin;
use sui::sui::SUI;
// No abilities = hot potato. Must be consumed in same transaction.
public struct Receipt {
amount: u64
}
public fun borrow(
pool: &mut Pool,
amount: u64,
ctx: &mut TxContext
): (Coin<SUI>, Receipt) {
let coin = pool.withdraw(amount);
(coin, Receipt { amount })
}
public fun repay(
pool: &mut Pool,
payment: Coin<SUI>,
receipt: Receipt
) {
let Receipt { amount } = receipt; // deconstruct (consume) the potato
assert!(coin::value(&payment) >= amount, 0);
pool.deposit(payment);
}
}
The Sui Kiosk — the standard marketplace protocol on Sui — uses this same Hot Potato idea internally. When you take an item from a Kiosk for inspection, you receive a TransferRequest hot potato. The marketplace rules can only be satisfied (royalties paid, policies checked) before that potato is consumed. No custom code, no workarounds — the type system does the policing.6
Dynamic Fields: Objects That Can Grow Indefinitely
A normal Sui struct has a fixed set of fields. Once you publish the package, you cannot add new fields to an existing struct without a breaking upgrade. More importantly, large structs become expensive to load from storage because the entire object must be read every time it is touched. Dynamic Fields solve both problems.
With Dynamic Fields, you attach key-value pairs to any object at runtime. The key can be any type with copy + drop + store abilities. The value can be any type with store. The field is stored separately from the parent object and is only loaded from storage when you explicitly access it. This keeps the parent object lightweight no matter how many dynamic fields you attach.8
use sui::dynamic_field;
public fun add_stat(
hero: &mut Hero,
stat_name: vector<u8>,
value: u64
) {
dynamic_field::add(&mut hero.id, stat_name, value);
}
public fun get_stat(hero: &Hero, stat_name: vector<u8>): u64 {
*dynamic_field::borrow(&hero.id, stat_name)
}
Table and Bag: Higher-Level Collections
The Sui framework also provides two higher-level collection types built on Dynamic Fields. Table<K, V> is a homogeneous map — all keys are the same type, all values are the same type. It is the right choice when you need a predictable, typed collection, such as a leaderboard or a registry. Bag is a heterogeneous map — different keys can point to values of different types. Both types grow indefinitely because each entry is stored as a separate on-chain object. A Table with one million entries costs no more to read (per entry) than a Table with ten entries.8
Dynamic Fields are also the key to safe package upgrades. By keeping the core object simple and adding configuration as a dynamic field, you can change the configuration structure in future upgrades without altering the public type signature of the parent object. Sui’s own system state at address 0x5 uses this versioned wrapper pattern internally.
Quick Reference: Which Pattern Solves Your Problem?
| Your Goal | Pattern to Use | Key Module / Type |
|---|---|---|
| Enforce multi-step logic (e.g., repay a loan) in one transaction | Hot Potato | Struct with no abilities |
| Attach unlimited data to an object at runtime | Dynamic Fields | sui::dynamic_field |
| Store a typed, growing key-value map | Table | sui::table::Table<K,V> |
| Store a mixed-type map | Bag | sui::bag::Bag |
| Allow any user to interact with an object (e.g., a DEX pool) | Shared Object | transfer::share_object |
| Restrict access to one owner (e.g., a personal NFT) | Owned Object | transfer::transfer |
| Create a custom token exactly once | One-Time Witness | sui::coin::create_currency |
| Restrict admin-only functions without address checks | Capability Object | Custom struct with key, store |
| Push new code to the same on-chain address | Package Upgrade | sui::package::UpgradeCap |
| Chain many Move calls without a new module | Programmable Transaction Block | TypeScript SDK Transaction |
Shared vs. Owned Objects: The Performance Decision
One of the biggest performance decisions in a Sui Move advanced design is whether an object should be owned or shared. This choice does not just affect who can touch the object. It directly controls whether a transaction goes through Sui’s fast path or the slower consensus path.
An owned object belongs to a single address (or to another object). When a transaction only touches owned objects, Sui can finalize it using a simple two-phase commit — no global ordering, no BFT consensus round. This is Sui’s “fastpath,” and it is part of why owned-object transactions can reach finality in under half a second on well-connected nodes.8
A shared object can be touched by any user. Because multiple transactions might try to modify it at the same time, Sui must serialize those transactions through the Mysticeti consensus protocol. That adds latency. Shared objects are the right tool for DEX pools, governance contracts, and any resource that genuinely needs multi-user write access. But if you make every object shared out of habit, you pay a consensus tax on every transaction.2
Practical Rule
Ask yourself: does more than one user need to write to this object in the same time window? If yes, use transfer::share_object. If no — if only the owner ever mutates it — keep it owned. A common mistake is sharing a config object that only an admin updates. That admin update now incurs consensus overhead on every call, for no gain. Keep admin-only objects owned by the admin’s address and pass them as arguments to privileged functions.
One-Time Witness: Safe Custom Coin Creation
The One-Time Witness (OTW) is a special struct that Move guarantees can only ever be created once — at the moment a package is published, inside the init function. The struct must have the same name as the module in all-caps, and it must have the drop ability and no fields. Move’s bytecode verifier enforces these rules at publish time.
The OTW’s only job is to prove to the sui::coin framework that you are calling create_currency for the first time in this module’s lifetime. Once the witness is consumed, it is gone forever. This prevents anyone — including the original developer — from calling create_currency a second time to create a second treasury cap for the same token type. The OTW pattern is the on-chain proof-of-uniqueness for custom currencies.
module mytoken::mycoin {
use sui::coin;
use sui::transfer;
use sui::tx_context::TxContext;
// Must match module name in all-caps. Has `drop` only.
public struct MYCOIN has drop {}
// `init` is called once at publish. Move passes the OTW automatically.
fun init(witness: MYCOIN, ctx: &mut TxContext) {
let (treasury, metadata) = coin::create_currency(
witness,
9, // decimals
b"MYC", // symbol
b"My Coin", // name
b"", // description
option::none(),
ctx
);
transfer::public_freeze_object(metadata);
transfer::public_transfer(treasury, ctx.sender());
}
}
The returned TreasuryCap is the capability object that lets the holder mint and burn tokens. Whoever owns the TreasuryCap controls the token supply. The metadata is frozen so the symbol and decimals cannot change after launch. This is the exact same pattern used by every USDC, wrapped asset, and protocol token on Sui mainnet.4
Capability-Based Access Control: Safer Than Address Checks
Many developers coming from Solidity write access control like this: assert!(tx_context::sender(ctx) == ADMIN_ADDRESS, 0);. That works, but it is brittle. The admin address is hard-coded or stored in the contract state. Transferring admin rights means a contract upgrade or a state change. There is also no way to give someone partial rights — it is all or nothing.
Sui Move offers a better model: capability objects. You create a struct — usually called AdminCap, MintCap, or similar — that carries key and store abilities so it can be owned and transferred. Privileged functions require this cap as an argument. Only the current holder of the cap can call the function. Transferring admin rights is as simple as transferring the cap object to a new address.6
public struct AdminCap has key, store { id: UID }
// Created once in `init` and transferred to deployer
fun init(ctx: &mut TxContext) {
transfer::transfer(AdminCap { id: object::new(ctx) }, ctx.sender());
}
// Callable only by whoever holds AdminCap
public fun update_fee(
_: &AdminCap,
config: &mut Config,
new_fee: u64
) {
config.fee_bps = new_fee;
}
You can also create role-based access by issuing different cap types. A MintCap only allows minting. An AdminCap allows config changes. A PauseCap can freeze the protocol in an emergency. Each capability is an independent on-chain object with its own owner. This composability is one of the places where Move’s type system goes far beyond what Solidity’s modifiers can express.
Package Upgrades: Shipping New Code Without Changing the Address
On most blockchains, deploying a new version of a contract means deploying to a new address, migrating state, and asking every integration to update their pointers. Sui handles this differently. When you first publish a package, Sui gives you an UpgradeCap object. As long as you hold that cap, you can publish compatible upgrades to the same package address.
Package upgrades on Sui allow you to add new functions, fix bugs, and add new types — without breaking any existing integrations. There are rules: you cannot remove public functions, change existing function signatures, or remove public struct types. The upgrade is forward-compatible by enforcement, not by convention.3
The UpgradeCap and UpgradeTicket
The upgrade flow is itself a great example of the Hot Potato pattern in action. To upgrade, you call package::authorize_upgrade on your UpgradeCap. This issues an UpgradeTicket — a hot potato. The UpgradeCap can only issue one ticket at a time, which prevents race conditions between concurrent upgrade attempts. The ticket must be consumed by the actual upgrade command in the same transaction, which then returns an UpgradeReceipt — also a hot potato — that must be used to commit the upgrade back to the cap. The entire upgrade is atomic and ordered.4
A best practice for upgradeable contracts is to keep the core object’s type signature minimal and store configuration in a dynamic field. That way you can change the configuration layout in a future upgrade without touching the public type that other packages depend on. The Move Book calls this the “anchor pattern.”3
Programmable Transaction Blocks: Chain Many Calls Into One
Programmable Transaction Blocks (PTBs) are one of Sui’s most powerful features, and they live mostly outside the Move code itself — in the TypeScript SDK or CLI that builds the transaction. A PTB lets you sequence up to 1,024 Move function calls, coin operations, and object transfers inside a single atomic transaction without writing a new wrapper module.2
Each command in a PTB can use the output of any previous command as its input. Split a coin, pass the split coin into a Move call, take the returned object, pass it into a second Move call, and transfer the final result — all in one transaction. If any step fails, the entire block reverts. There are no re-entrancy risks because PTBs are not recursive. They compose at the transaction level, not at the contract level.
// TypeScript SDK example
import { Transaction } from '@mysten/sui/transactions';
const tx = new Transaction();
// Split 1000 MIST from the gas coin
const [coin] = tx.splitCoins(tx.gas, [tx.pure.u64(1000)]);
// Pass to a Move function
const [nft] = tx.moveCall({
target: '0xPACKAGE::minter::mint',
arguments: [coin, tx.pure.string('My NFT')]
});
// Transfer the returned NFT to a recipient
tx.transferObjects([nft], tx.pure.address(recipientAddress));
await client.signAndExecuteTransaction({ signer: keypair, transaction: tx });
For miners and early developers transitioning to Sui, PTBs are the equivalent of a batch-processing script. Instead of sending ten separate transactions with ten gas fees, you bundle the operations and pay once. This is especially powerful for airdrop contracts, automated rebalancing, and game state updates where many objects must change in one consistent step.9
Sui Move Advanced: Shared Objects vs. Owned Objects Side by Side
| Feature | Owned Object | Shared Object |
|---|---|---|
| Who can write to it | Only the current owner | Any address (per contract rules) |
| Transaction path | Fastpath (no consensus round) | Consensus (ordered execution) |
| Typical finality | < 0.5 seconds (well-connected nodes) | ~0.5–1 second (Mysticeti v2) |
| Concurrent access | Not possible — only one owner | Yes — serialized by consensus |
| Best use cases | Personal wallets, admin caps, user NFTs | DEX pools, DAO contracts, shared registries |
| How to create | transfer::transfer(obj, address) | transfer::share_object(obj) |
| Can it become owned later? | Yes — transfer to any address | No — once shared, always shared (or deleted) |
How These Patterns Connect to Chialisp and Other Smart Contract Languages
If you have been building on Chia Network with Chialisp, many of these patterns will feel familiar in structure even if the syntax is different. In Chialisp, every spend is a puzzle-and-solution pair. The puzzle defines the rules; the solution provides the values. Sui Move’s capability pattern works on the same principle — the function signature is the “puzzle,” and passing an AdminCap object is the “solution” that unlocks execution.
The Hot Potato pattern also parallels Chialisp’s concept of announcement-based coin communication. In Chialisp, one coin can announce a condition and another coin must assert it in the same block — creating a guaranteed, multi-coin workflow. The Hot Potato enforces the same idea within a single Sui transaction: create a struct, and you must resolve it. Both languages use the type system or condition system to enforce workflow rules that the runtime cannot circumvent. If you want to go deeper on how Chialisp encodes this kind of programmable logic, this deep-dive on Chialisp transforming blockchain smart contracts covers the puzzle model in detail.
Case Study: Bucket Protocol’s Flash Loan on Sui Mainnet
Bucket Protocol, a DeFi lending platform live on Sui mainnet, uses the Hot Potato pattern at the core of its flash loan implementation. Their FlashReceipt<T> struct carries no abilities, meaning it must be consumed by the flash_repay function before the transaction closes — a real production deployment proving that ability-less structs are not just academic examples but the foundation of mainnet DeFi logic on Sui.
Move 2024: What Advanced Developers Need to Know
Move 2024 (edition 2024.beta) introduced several quality-of-life improvements that affect advanced code. The most useful for daily development is method syntax: you can now write obj.method() instead of module::method(&obj) for any function where the object is the first argument. This makes long chains of operations far easier to read. Structs must now be marked public explicitly. Mutable variables require the mut keyword. The sui move migrate command automates most of the update.7
For new projects, set edition = "2024.beta" in your Move.toml. For existing projects, run sui move migrate in the project root and review the diff before committing. The beta edition is Mysten Labs’ recommended default as of 2025.
Conclusion
This sui move tutorial advanced guide has walked you through the six patterns that separate beginner contracts from production-grade ones. The Hot Potato enforces guaranteed workflows. Dynamic Fields let your objects grow without limits. Choosing shared versus owned objects controls your transaction speed. The One-Time Witness secures your token launch. Capability objects replace fragile address checks with portable, transferable permissions. Package Upgrades keep your code evolvable. And PTBs let you compose complex operations without deploying extra modules. Each of these patterns is used in production on Sui mainnet today. Start by implementing one in a test project — the Hot Potato flash loan or a capability-gated minter are both great first projects — and you will quickly see how the Sui object model snaps into place. The Move Book and the Sui Foundation Intro Course (Units 3–5) are your next stops for going even deeper.
Sui Move Tutorial Advanced FAQs
What is the Hot Potato pattern in a sui move tutorial advanced guide?
In a sui move tutorial advanced context, the Hot Potato pattern is a struct with no abilities — no copy, drop, store, or key. Because it cannot be dropped automatically, any function that creates it must pair with a consuming function in the same transaction, enforcing a guaranteed multi-step workflow like a flash loan repayment.
What is the difference between Dynamic Fields and regular struct fields in Sui Move?
Regular struct fields are fixed at publish time and load the entire struct from storage every time the object is accessed. Dynamic Fields are attached at runtime as separate on-chain objects and are only loaded when explicitly accessed, so large collections stay cheap regardless of how many entries they contain.
Why do I need a One-Time Witness to create a coin in Sui Move?
The One-Time Witness proves to the sui::coin framework that create_currency is being called for the first time in a module’s lifetime. Since the OTW struct is automatically passed by the runtime only at publish, it can never be forged or replayed — guaranteeing exactly one TreasuryCap per token type.
Does this sui move tutorial advanced apply to Move 2024?
Yes — this sui move tutorial advanced guide reflects patterns that work in both the classic and Move 2024 editions. The main Move 2024 change you will notice in advanced code is method syntax (obj.method()), mandatory public on shared structs, and the mut keyword for mutable variables; the architectural patterns themselves are unchanged.
When should I use Programmable Transaction Blocks instead of writing a new Move module?
Use a PTB when you need to chain existing Move functions together in a single atomic transaction without writing new on-chain logic — for example, splitting coins, calling a DEX swap, and depositing the result into a lending pool in one step. Write a new module only when the operation requires new on-chain rules that PTBs cannot express, such as loops or custom validation logic.
Sui Move Tutorial Advanced Citations
- Sui Move Intro Course — Hot Potato Design Pattern
- Sui Documentation — Programmable Transaction Blocks
- The Move Book — Upgradeability Practices
- Sui Documentation — Module sui::package (UpgradeCap)
- Sui.io — Move: The Language of Sui (Sam Blackshear quote)
- Sui Documentation — Move Conventions
- Sui Documentation — Migrating to Move 2024
- Mysten Labs Tech Blog — Why We Created Sui Move
- Sui Documentation — Building Programmable Transaction Blocks
