Back to Hub

PTBs & Advanced Object Patterns

Sui Hub Workshop — 30 Minutes

TypeScript SDK • Move Contracts • Object-Centric Design

1 / 16

Agenda (30 min)

10mPTBs with TypeScript SDK — Building atomic transactions in code
12mAdvanced Object Patterns — Capabilities, Hot Potato, Wrapping, Dynamic Fields
5mSecurity Pitfalls — Real bugs from Sui audits
3mQ&A & Resources
2 / 16

What are PTBs?

Programmable Transaction Blocks = Sui's atomic execution unit.

  • One transaction can contain up to1,024 operations
  • Chain splits → merges → transfers → Move calls in a single block
  • All-or-nothing: if one step fails,everything rolls back
  • Unlike traditional chains: one tx ≠ one action. One tx = a whole program.
3 / 16

PTB vs. Traditional TX

Traditional Chain

1 tx = 1 action

Split coin → tx 1
Swap on DEX → tx 2
Deposit LP → tx 3

3 gas fees, 3 confirmation waits, race condition risk

Sui PTB (SDK)

1 tx = 1,024 operations

Split → Swap → Deposit
→ Transfer change

1 gas fee, 1 confirmation, atomic guarantee

4 / 16

PTB Basics — TypeScript SDK

import { Transaction }from'@mysten/sui/transactions';const tx =newTransaction();// Split gas coinconst [coin] = tx.splitCoins(tx.gas, [tx.pure.u64(100_000_000)]);// Transfer to an addresstx.transferObjects([coin], tx.pure.address(recipient));// Sign and sendawait client.signAndExecuteTransaction({ signer, transaction: tx });
5 / 16

Chaining in PTB — SDK

const tx =newTransaction();// 1. Split gas into paymentconst [payment] = tx.splitCoins(tx.gas, [tx.pure.u64(5_000_000_000)]);// 2. Call Move function — output becomes input automaticallyconst [nft, change] = tx.moveCall({ target: `${pkg}::shop::buy`, arguments: [tx.object(shopId), payment] });// 3. Transfer resultstx.transferObjects([nft], tx.pure.address(buyer)); tx.transferObjects([change], tx.pure.address(buyer));// All 3 steps = 1 atomic transactionawait client.signAndExecuteTransaction({ signer, transaction: tx });
Key: SDK handlesNestedResult(0, 0) automatically — just pass the variable.
6 / 16

PTB Golden Rules

Rule 1 — Chaining: Output of step 0 becomes input of step 1. The SDK wires this for you.
Rule 2 — Consumption: Every non-dropvalue must be transferred, destroyed, or used before the PTB ends. No dangling objects.
Rule 3 — Atomicity: All steps succeed or all fail. No partial state.
7 / 16

Advanced Object Patterns

Move contracts — design secure, composable objects

8 / 16

Capability Pattern

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) {let Receipt {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 structHero has key {id: UID, sword:Sword,// nested object}// Optional wrappingpublic structHero has key {id: UID, sword:Option<Sword>, // may or may not have one}// Vector wrapping (inventory)public structHero has 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.

use sui::dynamic_field;// Adddynamic_field::add(&mut obj.id,b"level",5_u64);// Readlet lvl = dynamic_field::borrow<vector<u8>,u64>(&obj.id,b"level");// Removelet lvl = dynamic_field::remove<vector<u8>,u64>(&mutobj.id, b"level");

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.

14 / 16

Security Pitfalls

BugFix
public(package) entry bypassAlways verify tx_context::sender(ctx)
Phantom role escalationConstrain generic <R> to specific role types
Table key collisionCheck table::contains before add
Excessive abilities on hot potatoRemove store/drop from receipts
Shared AdminCapTransfer to deployer, never share_object
Unvalidated object relationshipsVerify parent/child IDs match
15 / 16

Questions?

PTBs & Advanced Object Patterns

SDK @mysten/sui/transactions

DOCSdocs.sui.io/develop/transactions/ptbs

COURSEgithub.com/sui-foundation/sui-move-intro-course

Thanks for joining — happy building on Sui!

16 / 16