Sui.

Publicación

Comparte tu conocimiento.

article banner.
OYETUNJI HELEN .
Aug 23, 2025
Artículo

Sui 102: Mental Model, Setup, and a Problem-Solver’s Onboarding Guide

  1. Why Sui is different (the mental model)

Sui is a Layer-1 blockchain built around an object-centric data model and the Move programming language (a resource-oriented language). Instead of thinking in terms of “accounts with balances,” think in terms of objects that have owners, types, fields, and versions. Transactions create, mutate, or transfer objects. This model enables Sui to parallelize many transactions safely: when two transactions touch disjoint sets of objects, they can execute concurrently, yielding high throughput and low latency.

Key mental shifts:

Objects, not global state. Most data you work with (coins, NFTs, game items, marketplace listings) are objects with an owner.

Owned vs. Shared objects.

Owned objects are controlled by a single address and can be mutated without global consensus (fast path).

Shared objects are visible to many users and require stronger coordination (consensus path).

Capabilities (witness pattern). Many privileged operations require a special object (“capability”) that proves the caller is allowed to do the action (e.g., minting, administration).

  1. Install and configure your environment

Install Sui CLI. Download binaries for your OS or build from source (official docs).

Create a keypair & address.

sui client new-address ed25519 sui client addresses

Set your active network (devnet/testnet/mainnet).

sui client switch --env testnet

Fund your address (dev/test). Use the official faucet to get test SUI (the coin used for gas and storage).

Verify connection.

sui client gas

You should see one or more gas coins (they are just coin objects) with balances.

Common setup problems & step-by-step fixes

Problem A: “GasBalanceTooLow” or “Insufficient gas”

Cause: Not enough SUI in your gas coin, or your gas budget is too low for the transaction.

Fix (step-by-step):

Request more test SUI (if on dev/test).

Increase gas budget in your command (e.g., --gas-budget 100000000).

If you have many small coins, merge them first:

sui client merge-coin --primary-coin <BIG_COIN_ID> --coin-to-merge <SMALL_COIN_ID> --gas-budget 50000000

Then use the larger coin as your gas coin in subsequent transactions.

Problem B: “Object version mismatch” or “Reference input is not the latest”

Cause: Another transaction updated the object after you fetched it.

Fix (step-by-step):

Re-fetch the object to get the latest version:

sui client object <OBJECT_ID>

Re-run your transaction using the updated object reference.

If this keeps happening, your workflow is likely racing with others use a fresh read right before submit, or switch to an owned object workflow where possible.

Problem C: “Module/Function not found” when calling Move functions

Cause: Wrong package ID, wrong module or function name, or the package wasn’t published successfully.

Fix (step-by-step):

Confirm publish success and copy the package ID from the CLI output.

List modules:

sui client package --id <PACKAGE_ID>

Double-check your function is public entry and that you’re passing the correct type parameters and arguments.

Problem D: “Capability not found” (e.g., TreasuryCap)

Cause: You’re calling a privileged function that requires a capability object your signer doesn’t own.

Fix (step-by-step):

Identify the capability type from the Move code (e.g., TreasuryCap for minting a custom coin).

Find the object:

sui client objects --address <YOUR_ADDRESS> | grep

If you don’t have it, get it from the deployer/owner or run the module initializer that issues it to your address.

  1. Understanding Sui coins, gas, and storage

SUI is a coin object. You can split, merge, and transfer it like any other object.

Gas fee is paid in SUI and depends on computation + storage.

Storage fund. On Sui, storage is accounted explicitly; deleting data can yield storage rebates.

Step-by-step: consolidate tiny coins before heavy ops

List your coins: sui client gas

Pick a primary coin with the largest balance.

Merge smaller coins into it (as shown above).

Use that primary coin as the --gas coin for your big transaction.

  1. When to use shared objects (and how to avoid pain)

Use shared objects only when many users must safely coordinate over the same state (e.g., a global order book). Otherwise, prefer owned objects for speed and simpler conflict management.

Typical shared-object problems & solutions

High contention / frequent conflicts. Solution: Shard your state into multiple shared objects, or redesign so each user mostly touches owned state.

Permission confusion. Solution: Wrap privileged actions behind capabilities that the admin or creator owns. Expose public read-only functions; isolate mutability.

  1. Debugging transactions like a pro

Inspect effects: After a transaction, review Created, Mutated, Deleted, Events in the CLI output.

Query objects: sui client object to verify current owner and version.

Query events: Filter by package or sender to trace complex flows.

Reproduce locally: Use a stable test script (CLI or SDK) to reproduce before/after states.

Checklist before you ask for help

Do you have the correct package ID and module/function names?

Are you using the latest object version?

Is your function marked public entry?

Do you own/hold the required capability?

Is your gas budget large enough and coin consolidated?

Article 2: Build & Ship Your First Sui Move dApp (End-to-End, With Fix-As-You-Go Tips)

  1. Create a new Move package sui move new hello_sui cd hello_sui

This scaffolds a Move package with a sources/ directory.

  1. Write a simple Counter module (owned object)

Create sources/counter.move:

module <YOUR_ADDR>::counter { use std::error; use std::option; use sui::object; use sui::transfer;

// A simple counter that is owned by a single address.
struct Counter has key {
    id: UID,
    value: u64,
    owner: address,
}

// Error codes
const ENotOwner: u64 = 1;

/// Initialize a counter owned by the signer; returns the created object.
public entry fun init(signer: &signer): Counter {
    let id = object::new();
    let c = Counter { id, value: 0, owner: signer::address_of(signer) };
    c
}

/// Increment the counter; only the owner may call.
public entry fun inc(counter: &mut Counter, signer: &signer) {
    let caller = signer::address_of(signer);
    if (counter.owner != caller) {
        abort ENotOwner
    };
    counter.value = counter.value + 1;
}

/// Transfer ownership to another address.
public entry fun transfer(counter: Counter, to: address) {
    transfer::transfer(counter, to);
}

/// Read-only getter (callable by anyone off-chain via RPC simulation or SDK).
public fun value(counter: &Counter): u64 {
    counter.value
}

}

Build & test locally sui move build sui move test

If build fails with dependency errors: confirm Move.toml has correct addresses and dependencies; run sui move build again after fixing.

If tests fail: ensure test functions use Move test framework patterns (in #[test] functions or similar).

  1. Publish your package sui client publish --gas-budget 100000000

Copy the Package ID from the output.

Common publish issues & solutions

“Unbound address / address mismatch.” Ensure the address in Move.toml matches your active signer address (or use the named address pattern recommended in the docs).

“Insufficient gas.” Merge coins, increase --gas-budget, or fund your account again (on testnets).

  1. Create & mutate your Counter

Init (create the object, returned as an Owned object):

sui client call
--package <PACKAGE_ID>
--module counter
--function init
--args
--gas-budget 50000000

The result will show a Created object of type <PACKAGE_ID>::counter::Counter.

Increment (mutate in place):

Fetch the object ID: sui client object <COUNTER_ID>

Call:

sui client call
--package <PACKAGE_ID>
--module counter
--function inc
--args <COUNTER_ID>
--gas-budget 50000000

Transfer to another address:

sui client call
--package <PACKAGE_ID>
--module counter
--function transfer
--args <COUNTER_ID> <RECIPIENT_ADDRESS>
--gas-budget 50000000

Troubleshooting the calls (step-by-step)

“Non-entry function” error: Ensure your callable functions are marked public entry.

“Type argument mismatch.” Some functions require explicit type parameters; check the signature and supply --type-args if needed (not used in this simple example).

“Object not found” or “No such ID.” You likely copied the wrong ID or the object was transferred/deleted. Re-query:

sui client objects --address <YOUR_ADDRESS> sui client object <COUNTER_ID>

“Sender not owner” (our custom error): You’re not the owner field of the object. Use the correct signer or transfer it to yourself first.

  1. Interact using the JavaScript/TypeScript SDK (pattern)

Install in your dApp:

npm install @mysten/sui.js

Example pattern (simplified):

import { Ed25519Keypair } from '@mysten/sui.js/keypairs/ed25519'; import { Connection, JsonRpcProvider } from '@mysten/sui.js'; import { TransactionBlock } from '@mysten/sui.js/transactions';

const provider = new JsonRpcProvider(new Connection({ fullnode: 'https://' })); // Load your keypair (NEVER hardcode secrets in production) const keypair = Ed25519Keypair.deriveKeypair("some test mnemonic"); const address = keypair.getPublicKey().toSuiAddress();

async function incCounter(packageId: string, counterId: string) { const tx = new TransactionBlock(); tx.moveCall({ target: ${packageId}::counter::inc, arguments: [tx.object(counterId), tx.pure.address(address)], }); tx.setGasBudget(50_000_000); // sign & execute with your signer (left out here for brevity) }

SDK gotchas & solutions

Serialization errors (BCS). Make sure argument types and order exactly match your Move function signature (e.g., object references vs. pure values).

Gas estimation too low. Explicitly set a larger gas budget during development to avoid surprises; fine-tune later.

  1. Evolving your design: when to go shared

If many users must update the same counter (e.g., a communal scoreboard), consider a shared object:

Create an admin-only initializer that produces a shared counter.

Add access control via capabilities to prevent arbitrary mutations.

Expect higher contention; design for it (sharding, batched updates, or per-user owned counters aggregated off-chain).

Step-by-step migration path

Start with owned counters per user (fast, parallel).

Emit events on increments.

Aggregate events off-chain to compute a global leaderboard.

If you truly need on-chain global state, switch to a shared leaderboard object behind capability checks.

Article 3: Gas, Storage, Object Design, and Security: A Practical Troubleshooting Playbook

  1. Gas & storage deep dive

Compute cost + storage cost = total gas burned.

Large objects and repeated writes increase storage cost.

Deleting data (or using smaller structs) can yield storage rebates.

Step-by-step to reduce gas/storage

Profile your transaction. Inspect the CLI’s transaction effects; note how many and which objects are mutated/created.

Minimize writes.

Prefer read-only patterns (return values) when possible.

Batch related state inside a single object to avoid touching multiple objects but don’t make one giant hot object.

Shrink data structures.

Use compact fields and avoid storing redundant data on-chain; emit events instead and index them off-chain.

Consolidate coins before heavy ops to reduce input overhead.

Avoid unnecessary shared objects. Shared writes are more expensive and more likely to conflict.

Common gas errors & fixes

“Computation cost exceeded gas budget.” Increase --gas-budget, simplify the Move code path, or split your operation into multiple smaller transactions.

“Insufficient gas object balance.” Merge small coins first, then retry with the consolidated coin.

  1. Object versioning & concurrency conflicts

Sui enforces that you mutate the latest version of an object. If two transactions try to mutate the same object concurrently, one will fail.

Step-by-step conflict mitigation

Re-read before write. Fetch the latest object version immediately before submitting the transaction.

Use per-user owned objects. Design so that typical actions only touch the caller’s objects.

Shard shared state. Instead of one global shared object, create multiple (e.g., one per category or per time window).

Queue & batch off-chain. For high-contention flows, batch intended updates and submit them in controlled bursts.

  1. Collections & dynamic data

When you need per-user collections (e.g., many NFTs/items), consider patterns such as:

Parent object + dynamic fields to map keys to child objects (lets you grow collections without rewriting the parent).

Event-driven indexing off-chain for search, pagination, or analytics.

Pitfalls & remedies

Huge parent object mutation cost. Switch to dynamic fields so the parent remains mostly static.

Complex lookups on-chain. Emit events and build off-chain indexes; keep on-chain lookups minimal.

  1. Access control with capabilities (security by construction)

Move’s resource model helps you encode permissions:

Admin/Owner capability: Only callers with the resource can invoke privileged actions.

Mint capability (e.g., TreasuryCap): Required to mint a custom coin.

Witness types: Phantom types that prove a condition at compile time.

Step-by-step to secure a privileged function

Define a capability struct (has key if it must be storable).

Issue it in an initializer to the creator/admin.

Make privileged functions accept &Capability to prove authorization.

Never expose a path for arbitrary users to obtain or forge the capability.

  1. Testing, simulation, and safe iteration

Unit tests (Move). Put tests in your package to cover error paths (abort codes) and invariants.

Local simulation. Use CLI or SDK to dry-run transactions before submitting on a public network.

Property tests / fuzzing (advanced). Build scripts that randomize inputs and assert invariants; catch edge cases early.

Step-by-step test checklist

Test happy path and failure (wrong owner, missing capability).

Test edge values (0, max u64).

Test idempotency where intended (repeated calls don’t corrupt state).

Test access control (only the rightful owner/admin can mutate).

Test upgrade path (if your package supports upgrades): ensure storage layout compatibility and proper capability migration.

  1. Observability: events and off-chain indexing

Emit events on meaningful state changes (created, updated, transferred).

Off-chain, subscribe to events via RPC and build a lightweight indexer for search and analytics.

Keep on-chain data lean; push aggregation and heavy querying off-chain.

Typical indexing problems & solutions

Missed events. Implement pagination & checkpointing in your indexer; re-scan ranges on restart.

Out-of-order reads. Use transaction timestamps/checkpoints to impose order in your database.

  1. Production readiness checklist

Access control via capabilities no backdoors.

Gas budgets tuned; large writes minimized; coins consolidated.

Contention mitigated (owned objects favored; shared sharded).

Monitoring: capture transaction results, events, and errors.

Rollout plan: publish package, run canary clients, monitor, then scale.

  • Architecture
0
Cuota
Comentarios
.
Kurosaki.ether.
Aug 23 2025, 15:10

What design patterns and strategies (e.g., sharding, per-user owned objects, batching, dynamic fields) can developers implement to reduce failed transactions in high-contention flows? How can event-driven off-chain aggregation complement on-chain shared object management to maintain both performance and consistency?

Dpodium.js.
Aug 23 2025, 15:19

What testing, simulation, and property-based approaches can ensure that only authorized users can invoke privileged actions, including during package upgrades or object migrations?