Sui.

Post

Share your knowledge.

article banner.
Elmicki.
Aug 13, 2025
Article

Mastering Sui’s Object Model & Ownership (Fixing Concurrency, Locking, and “Why Won’t My Call Work?

Problem this solves: New Sui devs often struggle with ownership kinds (owned, shared, immutable), why some calls are instant while others need consensus, and cryptic errors about locks or capabilities. This guide explains the model and shows how to fix the most common design mistakes.

The Mental Model

Everything is an object. Each has a unique ID and type.

Ownership types:

Owned: Exactly one owner (an address or another object). Mutations can often bypass consensus via fast-path because there’s no contention.

Shared: Many actors can access; updates require ordering via consensus.

Immutable: Frozen forever after creation; cheap to read, no writes allowed.

Concurrency superpower: Independent owned objects can be updated in parallel, letting Sui scale horizontally without global locks.

Step-by-Step: Designing with Ownership

Step 1 — Start Owned, Not Shared If you’re building a game inventory, marketplace listing draft, or personal profile, use owned objects first. You’ll get faster execution and fewer conflicts.

Step 2 — Promote to Shared Only When Needed Turn an object shared only if many unrelated users must mutate it concurrently (e.g., an orderbook, a public counter, an auction you want many bidders to touch). Expect to pay the consensus cost.

Step 3 — Use Immutables for Config & Code Protocol parameters, metadata templates, and published packages live best as immutable: once set, no accidental edits, and they’re cheap to reference everywhere.

Step 4 — Split & Merge Coins Correctly SUI coins are objects too. When you need exact gas or payments, use the CLI or SDK to split a coin into the desired amounts, pay, then merge dust back later. This avoids “too many small coin objects” clutter that confuses gas selection.

Split 10 SUI from a larger coin:

sui client split-coin --coin-id <COIN_ID> --amounts 10000000000 --gas-budget 30000000

Merge two coins:

sui client merge-coin --primary <COIN_A> --coin-to-merge <COIN_B> --gas-budget 30000000

Step 5 — Capabilities: Who’s Allowed to Mutate? Model authority explicitly. Give the creator an OwnerCap or AdminCap object that must be presented to mutate sensitive state. This removes guesswork and magic lists.

module myapp::caps { use sui::object; use sui::tx_context::{TxContext}; use sui::transfer;

public struct AdminCap has key, store { id: object::UID }

public entry fun mint_admin(ctx: &mut TxContext) {
    let cap = AdminCap { id: object::new(ctx) };
    transfer::transfer(cap, tx_context::sender(ctx));
}

// Later functions will require &AdminCap or its presence.

}

Step 6 — Avoid Hidden Shared Dependencies If an entry function touches a shared object anywhere in its call graph, the whole transaction follows the shared-object path (consensus). Keep hot loops on owned data only.

Step 7 — Deterministic IDs Where Appropriate When you need predictable references (e.g., one global registry per package), consider deriving IDs from seeds or creating them once and marking immutable. Avoid accidental duplicates by guarding creation with a capability.

Fixing the Top Ownership Errors

  1. ObjectNotFound

You’re calling with an object ID from a different network, or it was deleted, or your address doesn’t own it anymore.

Fix: Verify env; re-query the object; check its owner field.

  1. ObjectLocked or “object busy”

You attempted two transactions that both try to mutate the same owned object before the first finalized.

Fix: Wait for the first to finalize; use different owned objects for parallelism; batch operations in one programmable transaction if appropriate.

  1. Insufficient Permissions / capability required

You forgot to pass the cap object or the function signature requires a ref to it.

Fix: Redesign APIs to make capabilities explicit in parameters; ensure the signer actually owns the cap.

  1. MoveAbort with code related to EWrongOwner or ENotShared

Your function expects a shared object (or vice versa).

Fix: Ensure the object was converted to shared during initialization; document expected ownership in comments and types.

  1. Too many mutable references or borrow checker complaints

You’re trying to hold &mut to multiple objects in ways Move disallows for safety.

Fix: Refactor so you mutate one object at a time or pass by value where appropriate.

Patterns That Scale

Sharded State via Owned Buckets: Instead of one giant shared ledger, give each user a personal ledger (owned) and aggregate reads off-chain or via periodic reducers.

Event-Driven Analytics: Emit events rather than writing heavy on-chain aggregates; indexers consume events for dashboards.

Immutable Templates + Owned Instances: Store core logic or metadata immutable; users instantiate owned copies that they mutate freely.

Design Review Checklist

[ ] Can this be owned instead of shared?

[ ] Are capabilities explicit and passed as params?

[ ] Are we batching related writes to minimize lock contention?

[ ] Do we merge coin dust after complex flows?

[ ] Did we test under parallel load with multiple signers?

Locking down these habits eliminates 80% of early Sui headaches and lets you fully exploit Sui’s parallel execution.

  • Sui
0
Share
Comments
.