Post
Share your knowledge.
Design Patterns and Concurrency Management for Shared Objects in Sui Move
I’m trying to wrap my head around the right architectural pattern when working with shared objects in Sui Move, especially when multiple users need to interact with them concurrently (e.g., in a game arena, DAO vote tracker, or lending vault).
I’ve read that shared objects can introduce synchronization issues, but I’m still unclear on:
- When exactly should I use a
shared
object vs.owned
? - How does Sui handle concurrent transaction attempts on the same shared object — do I need to manually queue/lock things?
- What’s the best practice for designing modules that support high concurrency but avoid frequent
ConflictTransaction
errors? - Should I be doing anything special (like splitting mutable fields into sub-objects) to reduce contention?
- Sui
- Architecture
- NFT Ecosystem
- Move
Answers
13When you're building a dApp on Sui where multiple users need to interact with the same state, like in a game arena, lending protocol, or DAO, you should use shared objects instead of owned ones. Shared objects are publicly accessible and mutable, meaning anyone can call entry functions on them as long as they’re in the global object graph. In contrast, owned objects are private to a user and are ideal for personal assets like wallets or inventory. So, you should use a shared object when state updates must be visible and usable by many users at once.
Sui handles concurrent transactions on shared objects through a mechanism called causal order validation, which requires you to explicitly provide the latest known version of a shared object. If two users try to modify the same shared object at the same time with outdated versions, one will succeed while the others fail with a ConflictTransaction
error. Sui doesn’t queue or lock shared objects natively, so you need to design your Move modules to minimize overlapping writes. One of the best ways to reduce conflict is to design for parallelism. For instance, instead of storing all mutable state in the root shared object, you can split high-contention fields into separate child objects. Each of those children can then be written independently, reducing the likelihood of transaction conflicts.
A great practice is to keep the main shared object as a directory and move granular, frequently updated state—like player scores, votes, or loan balances—into dynamic fields or smaller sub-objects. This way, users update only the specific part they need, and not the whole shared object. It’s also smart to separate read-heavy and write-heavy components. Moreover, you should build clear and deterministic transaction flows that always use the freshest version of shared objects and retry logic for clients to handle failed transactions.
You can learn more about shared object design and conflict mitigation in the Sui documentation here: https://docs.sui.io/concepts/objects#shared-objects.
If you're managing something like a vote tracker, here's a simplified transaction block example to reduce conflict:
const tx = new TransactionBlock();
tx.moveCall({
target: `${packageId}::vote_tracker::cast_vote`,
arguments: [
tx.sharedObject(voteTrackerId), // shared object reference
tx.pure(voterId), // owned object or public key
tx.pure(proposalId), // pure data
tx.pure(voteChoice)
]
});
In Sui Move, shared objects (marked with key + store
) are essential for multi-user interaction but require careful concurrency design. Use them when state must be globally mutable (e.g., games, DAOs), while owned/immutable objects suit single-user scenarios. Sui serializes transactions per shared object automatically, but frequent writes may trigger ConflictTransaction
errors—mitigate this by splitting hot fields into separate child objects (e.g., one per user) or batching updates. For high throughput, adopt event-sourcing patterns: store actions as immutable events and aggregate state periodically. Always minimize mutable shared fields and prefer atomic swaps via transfer::share_object
over direct mutations.
In Sui Move, handling shared objects and concurrency effectively requires careful design to avoid race conditions and high contention. Here's a breakdown of the best practices and architectural patterns:
1. When to Use Shared vs. Owned Objects:
- Shared Objects: Use when multiple users need to interact with the same object concurrently (e.g., shared game state, DAO vote tracker).
- Owned Objects: Use when the object is specific to a single user or account, avoiding shared state and reducing concurrency issues.
2. Concurrent Transaction Handling in Sui:
- Sui handles concurrency automatically by using Transaction Locks to prevent simultaneous modifications of the same object.
- You do not need to manually queue or lock shared objects, as Sui enforces a lock at the object level. However, you might get ConflictTransaction errors if multiple transactions conflict on the same object.
3. Best Practices for High Concurrency:
- Minimize Contention: Split mutable fields into separate sub-objects to allow parallel updates to different parts of the object.
- Avoid Hotspots: Distribute frequently modified state across multiple objects (e.g., instead of having a single shared ledger, split it into multiple buckets or accounts).
- Versioning: Use versioned objects or create new objects to track updates instead of mutating the same object repeatedly.
4. Managing Contention:
- Consider splitting mutable fields into smaller objects to reduce contention. This allows different parts of the object to be updated concurrently, reducing the chance of conflicts.
- Use atomic transactions to bundle related actions into one, ensuring consistency across related objects.
Example:
If you have a vote tracker for a DAO:
- Instead of storing all votes in a single shared object, split them into multiple objects (e.g., one per proposal), allowing parallel updates for each proposal while reducing contention.
Conclusion:
For high concurrency, minimize contention by distributing state, using sub-objects, and leveraging Sui’s automatic transaction handling. Ensure you understand your access patterns and structure the objects to minimize conflict points.
Understanding Design Patterns and Concurrency Management for Shared Objects in Sui Move
When dealing with shared objects in Sui Move, managing concurrency becomes critical, as multiple users may need to interact with the same object simultaneously. This is especially true for use cases like game arenas, DAO vote trackers, and lending vaults, where high concurrency is expected. The Sui Network provides some features to help manage concurrency, but it still requires careful design to avoid common pitfalls, like ConflictTransaction errors.
1. When to Use Shared Objects vs. Owned Objects
-
Owned Objects:
- An object is owned if it’s managed by a single address, which can transfer or modify it.
- Use owned objects when you expect a single entity to control and interact with an object. For example, if you have a wallet with coins, each wallet has ownership over its own coins.
-
Shared Objects:
-
Shared objects can be accessed and modified by multiple addresses simultaneously. Sui allows you to define objects as shared by making them mutable and providing access to multiple parties.
-
Use shared objects for scenarios where multiple users or addresses need to interact with the same object, such as:
- DAO vote trackers: Multiple users may vote on the same object.
- Lending vaults: Multiple users can lend and borrow from the same vault.
- Game arenas: Multiple users interact with the game state at the same time.
-
Key Consideration: Shared objects should be used carefully, as they can introduce contention — situations where multiple transactions are trying to access and modify the same object at once.
2. Concurrency and Transaction Attempts on Shared Objects
Sui ensures that concurrent transactions on the same shared object are handled correctly by enforcing transactional isolation. If multiple transactions attempt to mutate the same shared object at the same time, one of them will fail due to contention.
When a transaction attempts to modify a shared object that’s already being modified by another transaction, Sui will return a ConflictTransaction error. However, Sui handles most of the concurrency management internally, and you do not need to manually queue or lock objects. Instead, you need to design your contract to minimize contention and avoid situations where two transactions frequently conflict.
3. Best Practices for High Concurrency and Reducing ConflictTransaction Errors
To design modules that can handle high concurrency and avoid frequent ConflictTransaction errors, follow these best practices:
a. Minimize Contention Points:
- Limit mutable access: Keep the number of mutable fields in shared objects as small as possible. Each mutable field introduces a potential point of contention.
- Use owned objects for independent data: Instead of making everything shared, store independent data in owned objects. For instance, if you have user-specific data (like balance), store it in an owned object rather than a shared one.
b. Use the "State Partitioning" Pattern:
-
Split your shared object into multiple sub-objects to reduce contention. For example, instead of having one shared object that tracks the entire game state, you could split it into several sub-objects:
- One for each player's state.
- One for the game settings.
- One for leaderboard data.
-
This way, transactions related to different parts of the system can occur concurrently without blocking each other.
c. Batch Operations:
- When working with shared objects, batch your updates to reduce the number of transactions that need to interact with the object. This can help avoid frequent conflicts.
- Use collections like vectors or sets to manage multiple updates in a single transaction.
d. Use Atomic Operations:
- Whenever possible, design your modules to use atomic operations where possible. For example, instead of having separate transactions for checking a condition and then updating a field, bundle the checks and updates into a single atomic operation.
e. Retry Logic:
- While Sui handles retries internally, you can implement retry logic at the application level to handle occasional contention errors. For instance, if a
ConflictTransaction
error occurs, you can simply retry the transaction or queue it for later execution.
4. Reducing Contention by Splitting Mutable Fields into Sub-Objects
This is a highly recommended practice. When dealing with shared objects, the more fields you have in a shared object that are mutable, the higher the chance that two transactions will try to mutate the same field at the same time.
-
Example 1: If you have a shared object representing a lending vault, split it into sub-objects:
- One for deposits: Track individual deposits separately.
- One for loans: Track individual loans separately.
- One for vault state: Track general state info (e.g., total value, interest rate).
This way, a transaction that modifies one sub-object (e.g., the loan sub-object) does not conflict with a transaction that modifies another (e.g., the deposit sub-object).
-
Example 2: In a game arena:
- Split the game state into sub-objects for each player, game state, and battle statistics. This reduces the risk of conflicts when different players interact with their individual game states.
By splitting mutable fields into sub-objects, you reduce the likelihood that multiple users will try to modify the same object simultaneously.
5. Other Techniques to Manage Concurrency in Sui
-
Locking (Explicit Locks): While Sui doesn't require you to manually handle locks, in some cases, you may want to enforce explicit locks using locking objects (separate objects that ensure only one transaction can access a particular resource at a time).
-
Temporal Isolation: If your objects only change occasionally, consider introducing time-based isolation. For example, if you’re managing a vault or a voting system, set voting periods or transaction windows to limit interactions during certain times.
6. Example Code for Managing Concurrency with Sub-Objects
Here’s an example illustrating how to reduce contention by splitting a lending vault into smaller sub-objects:
module LendingVault {
struct Deposit has store {
amount: u64,
depositor: address,
}
struct Loan has store {
amount: u64,
borrower: address,
}
struct VaultState has store {
total_value: u64,
interest_rate: u64,
}
struct LendingVault has store {
deposits: vector<Deposit>,
loans: vector<Loan>,
vault_state: VaultState,
}
// Function to add a deposit
public fun add_deposit(vault: &mut LendingVault, depositor: address, amount: u64) {
let deposit = Deposit { amount, depositor };
Vector::push_back(&mut vault.deposits, deposit);
vault.vault_state.total_value = vault.vault_state.total_value + amount;
}
// Function to add a loan
public fun add_loan(vault: &mut LendingVault, borrower: address, amount: u64) {
let loan = Loan { amount, borrower };
Vector::push_back(&mut vault.loans, loan);
vault.vault_state.total_value = vault.vault_state.total_value - amount;
}
}
Summary
- Use shared objects when multiple users need to interact with the same object concurrently (e.g., DAO votes, game state, lending vaults).
- Minimize contention by splitting large shared objects into smaller sub-objects and reducing the number of mutable fields.
- Avoid frequent ConflictTransaction errors by following best practices such as state partitioning, batching operations, and using atomic operations.
- Retry logic and optimistic concurrency control can be used to handle conflicts gracefully.
- Sui's transaction model and conflict resolution are robust, but the design of your shared objects is key to ensuring efficient concurrent access.
By following these design principles, you'll be able to manage high concurrency in your Move-based modules while avoiding the typical pitfalls like conflicts and transaction errors.
Use shared objects for multi-writer access (e.g., games, DAOs). Key patterns:
-
Owned vs Shared
- Owned: Single-writer (faster).
- Shared: Concurrent writes (higher gas).
-
Concurrency Handling
- Sui sequences shared object TXs automatically (no manual locks).
- Split hot data into sub-objects (e.g., 1 per player) to reduce conflicts.
-
Conflict Mitigation
- Isolate high-traffic fields (e.g.,
Table<ID, PlayerData>
). - Use PTBs for atomic multi-object ops.
- Isolate high-traffic fields (e.g.,
Watch For:
ConflictTransaction
: Split data or batch updates.- Gas spikes on shared objects.
(Sui’s runtime handles conflicts—optimize via data sharding.)
Use shared objects (transfer::share_object
) when multiple users must mutate the same object (e.g., vault, DAO, game state). Use owned otherwise.
Sui handles concurrency via Narwhal & Tusk consensus: conflicting transactions on a shared object are serialized—only one succeeds per round; others fail with TransactionLockConflict
. No manual locking needed, but you must design for retries.
Best practices:
- Minimize mutable state on shared objects; split hotspots into separate sub-objects (e.g., per-user vault shares).
- Use
VecMap
orTable
for scalable mappings. - Avoid long-running computations in shared functions.
- Emit events instead of storing ephemeral state.
- For high-contention systems, use batching or commit-reveal schemes to reduce conflicts.
Shared objects are globally accessible but incur higher gas and contention—optimize for idempotency and retry logic in client code.
For shared objects in Sui Move, use these patterns to manage concurrency:
1. Shared vs. Owned?
-
Shared Object (
key + store
):- Use for global state (e.g., games, DAOs).
- Allows parallel writes (unlike EVM’s serialized TXs).
struct GameArena has key, store { id: UID, players: vector<address> }
-
Owned Object:
- Use for user-specific data (e.g., NFTs, wallets).
struct PlayerInventory has key { id: UID, items: vector<Item> }
2. Concurrency Handling
- No Manual Locks: Sui resolves conflicts automatically (unlike EVM).
- Split Contention: Break shared objects into smaller sub-objects (e.g., per-player or per-shard).
struct PlayerScore has key, store { id: UID, points: u64 } // Less contention
3. Reduce ConflictTransaction
Errors
- Isolate Writes: Update different fields in separate TXs.
- Use
&mut
Wisely: Avoid broad mutations.public entry fun update_score( arena: &mut GameArena, player: address, points: u64 ) { /* ... */ } // Only touches 1 player’s data
4. Best Practices
- Batch Updates: Group non-conflicting ops (e.g., votes in a DAO).
- Event-Driven: Emit events instead of mutating state where possible.
struct VoteEvent has copy, drop { voter: address, choice: u8 }
Use shared objects for multi-writer access (e.g., games, DAOs). Key patterns:
Owned vs Shared
Owned: Single-writer (faster). Shared: Concurrent writes (higher gas). Concurrency Handling
Sui sequences shared object TXs automatically (no manual locks). Split hot data into sub-objects (e.g., 1 per player) to reduce conflicts. Conflict Mitigation
Isolate high-traffic fields (e.g., Table<ID, PlayerData>). Use PTBs for atomic multi-object ops. Watch For:
ConflictTransaction: Split data or batch updates. Gas spikes on shared objects. (Sui’s runtime handles conflicts—optimize via data sharding.)
In Sui Move, shared objects (marked with key + store) are essential for multi-user interaction but require careful concurrency design. Use them when state must be globally mutable (e.g., games, DAOs), while owned/immutable objects suit single-user scenarios. Sui serializes transactions per shared object automatically, but frequent writes may trigger ConflictTransaction errors—mitigate this by splitting hot fields into separate child objects (e.g., one per user) or batching updates. For high throughput, adopt event-sourcing patterns: store actions as immutable events and aggregate state periodically. Always minimize mutable shared fields and prefer atomic swaps via transfer::share_object over direct mutations
Shared vs. Owned Objects: When to Use Each
Use shared objects when:
- Multiple users need to mutate the same state
- The object represents a public resource (like a game arena or DAO)
- You need permissionless access patterns
Use owned objects when:
- Only one address should control the object
- You want to leverage Sui's parallel execution fully
- Objects represent user-specific assets
Concurrency Management Strategies
1. Sub-object Pattern (Reduce Contention)
Split frequently-updated fields into separate objects:
struct GameArena has key {
id: UID,
// Immutable or rarely changed fields
config: ArenaConfig,
// Split mutable state
player_state: Table<address, PlayerState>,
leaderboard: Leaderboard
}
struct PlayerState has key, store {
id: UID,
health: u8,
score: u64
}
2. Batch Updates with Vectors/Tables
struct DAOVault has key {
id: UID,
// Store multiple pending actions
pending_votes: Table<ID, Vote>,
pending_deposits: Table<address, Deposit>
}
public entry fun batch_process(
vault: &mut DAOVault,
ctx: &mut TxContext
) {
// Process all pending items at once
let votes = table::remove_all(&mut vault.pending_votes);
process_votes(votes);
}
3. Epoch-based Processing
struct LendingPool has key {
id: UID,
current_epoch: u64,
next_epoch_data: EpochData,
current_epoch_data: EpochData
}
public entry fun advance_epoch(pool: &mut LendingPool) {
let finished = pool.current_epoch_data;
pool.current_epoch_data = pool.next_epoch_data;
pool.next_epoch_data = new_epoch_data();
process_finished_epoch(finished);
}
Conflict Avoidance Techniques
-
Field-level Partitioning:
struct HighTrafficObject has key { id: UID, // Split into separate objects per "hot" field stats_by_category: Table<u8, CategoryStats> }
-
Delayed Execution Queue:
struct ActionQueue has key { id: UID, pending: VecMap<u64, Action> } public entry fun execute_ready_actions( queue: &mut ActionQueue, ready_up_to: u64 ) { // Processes actions in batches }
-
Optimistic Concurrency with Versioning:
struct Versioned has key { id: UID, version: u64, data: vector<u8> } public entry fun update( obj: &mut Versioned, new_data: vector<u8>, expected_version: u64 ) { assert!(obj.version == expected_version, EBAD_VERSION); obj.data = new_data; obj.version = expected_version + 1; }
Practical CLI Examples
Checking object dependencies before submission:
sui client inspect-transaction --serialize-unsigned <TX_BYTES>
Simulating contention scenarios:
sui move test --path <YOUR_PACKAGE> --gas-budget 10000
Best Practices Summary
- Minimize Shared Object Mutations: Design for infrequent writes to shared objects
- Use Child Objects: Decompose state to reduce contention points
- Batch Operations: Group related changes into single transactions
- Consider Hybrid Models: Combine owned objects with occasional shared coordination
- Monitor Hotspots: Use
sui-tool replay
to analyze contention
Remember that Sui's runtime automatically handles some concurrency through its object model, but you still need to architect your data structures to minimize logical conflicts.
You should use a shared object in Sui Move when multiple users need to interact with the same piece of state, and those users aren't all under the same owner. Examples include a game arena open to all players, a DAO vote tracker that collects votes from different addresses, or a lending pool that anyone can deposit into. Shared objects are globally accessible and updatable through consensus, unlike owned objects, which are tied to a single address and modified without full consensus.
Sui handles concurrency on shared objects by requiring full consensus for any transaction that mutates them. When two users try to mutate the same shared object at nearly the same time, only one transaction succeeds—others get rejected with a ConflictTransaction
error. This makes shared objects powerful but also introduces contention issues when not designed carefully.
To support high-concurrency without running into frequent ConflictTransaction
errors, you should use design patterns that isolate mutable state. Instead of storing all mutable data in a single shared object, split it into multiple child objects—each with its own ownership or shared status. For example, in a game arena:
struct Arena has key, store {
game_id: u64,
players: vector<address>,
}
struct PlayerState has key, store {
hp: u64,
energy: u64,
}
Make Arena
shared, but give each PlayerState
its own shared object. That way, different players can update their state independently without conflicting.
Another pattern is the “index + bucket” strategy: keep a shared index object that points to individual buckets or entries. Only access the index to fetch the right entry, then operate on the entry itself. This reduces the amount of overlap between user transactions.
If you need strict ordering (e.g., for auctions), implement manual queuing using vectors or maps inside the shared object—but know that each mutation still needs consensus and could fail under load. In those cases, consider offloading ordering to the frontend or batching writes.
In summary:
- Use shared objects when state must be accessed by many independent users.
- Avoid putting all mutable fields into a single shared object—split them into sub-objects.
- Use shared + dynamic child objects to lower conflict rates.
- Expect
ConflictTransaction
errors under high contention and retry on the frontend. - Prefer append-only updates where possible, rather than in-place mutation.
When you design with shared objects in Sui Move, you’re essentially choosing between convenience for multi-user access and the risk of contention. Since Sui uses an object-centric and resource-oriented model, the right pattern depends on whether your use case demands collaborative access or isolated control. Here’s a structured way to think about it:
When to Use Shared vs. Owned Objects
- Owned objects are best when a single user needs full control over an asset (e.g., a wallet’s coins, a player’s inventory, or a unique NFT). Transactions involving them won’t conflict unless that same object is reused.
- Shared objects make sense when multiple users need to read or write to the same state (e.g., a DAO governance record, a game arena leaderboard, a lending pool vault). They allow global accessibility without needing permissioned transfers, but they come with concurrency challenges.
Concurrency Management in Sui
Sui’s runtime automatically serializes transactions that touch the same mutable shared object, meaning if two users try to update the same shared object, one will succeed and the other will get a ConflictTransaction
error. You don’t need to manually code locks or queues—conflict resolution is handled at the execution layer. However, frequent conflicts can degrade throughput, so your job is to design state layouts that minimize overlaps.
Best Practices to Avoid Frequent Conflicts
- Split mutable state: Instead of a single “mega-object,” break it down into smaller child objects. For instance, in a lending vault, separate each user’s position as its own owned or partially shared object, leaving the vault itself mostly static. This reduces write contention.
- Use immutable fields where possible: Store metadata or reference data in immutable sub-objects so transactions that only read don’t collide with those that write.
- Batch operations carefully: If your contract design requires multiple writes to the same shared object, consider batching them into a single transaction block to avoid multiple conflicts.
- Sharding patterns: For things like leaderboards or DAOs, shard state by group, round, or identifier so that not all transactions hit the exact same object path.
Pitfalls to Watch Out For
- Monolithic shared objects: Putting everything in one shared object causes high contention and constant conflicts under load.
- Overusing shared objects: If the data can be modeled as owned, prefer that route—it’s cheaper and less likely to bottleneck.
- Ignoring access patterns: If most interactions are reads with few writes, design around immutable snapshots rather than one mutable shared state.
Practical Example
Imagine building a DAO vote tracker:
- Bad pattern: A single shared object storing every vote, updated by all participants.
- Better pattern: Each voter creates an owned
Vote
object referencing the proposal, while the shared DAO object only aggregates results periodically. This way, parallel voting doesn’t collide.
Transaction Block
Inputs → Users submit transactions against shared or owned objects. Process → Sui’s runtime serializes transactions on the same mutable shared object. Result → Successful writes are committed, others get conflict errors; splitting objects lowers the chance of conflicts.
Great questions—this is exactly the crux of building “high-fanout” apps on Sui. Here’s a practical playbook you can follow.
🧭 When to use a shared object (vs owned)
Use a shared object only when multiple independent parties must mutate the same piece of state and you need one canonical result (e.g., AMM pool reserves, an arena’s match registry, a DAO proposal’s tally). Prefer owned objects when the state is per-user, per-position, or per-item and can evolve independently (inventories, user balances, open positions).
Rule of thumb: put the coordination surface in a shared object, and everything else in owned objects that can run in parallel.
⚙️ What Sui does with concurrent access
- Any tx that mutates a shared object goes through consensus and is totally ordered. Conflicting writes to the same fields are serialized; others proceed in parallel.
- You don’t manually lock; you design to avoid hotspots. Conflicts show up as
ConflictTransaction
when many txs try to bump the same version.
🏗️ High-concurrency design patterns (with Move tips)
1) Root-manager + owned positions
Keep a minimal shared root; put user/position state in owned objects so deposits/claims mostly touch owned data.
module example::vault {
use sui::tx_context::TxContext;
struct Vault has key { /* config only; no growing vectors */ }
struct Position has key { owner: address, /* state */ }
/// Shared root only needed to mint/close positions or checkpoint.
public entry fun open_position(v: &mut Vault, ctx: &mut TxContext): Position { /* ... */ }
/// Hot path touches owned Position only → parallel.
public entry fun deposit(pos: &mut Position, amount: u64) { /* ... */ }
}
Why it scales: most traffic avoids the shared root altogether.
2) Shard by key (dynamic fields / tables)
Instead of one big shared map, create N shard objects (shared) under a registry and route by hash(key) % N
. Each shard is a separate contention domain.
/// registry is shared; it stores IDs of shard objects
struct Registry has key { shards: vector<ID> }
/// choose shard once and keep it stable for that key
fun shard_for(reg: &Registry, key: &vector<u8>): ID { /* hash(key) % len */ }
Tip: don’t update the registry on every user op; mutate only the target shard.
3) Dynamic fields on a shared “anchor”, not growing vectors
Avoid vector.push
on a shared object (resizes = conflicts). Use dynamic fields (key → value) so each entry mutates independently.
SharedAnchor
└─ df[key=user] = tiny record (or pointer to owned object)
Move APIs: sui::dynamic_field::{add, borrow_mut, remove}
.
4) Two-phase “ticket” pattern
Have the shared object mint a ticket/capability (owned) that authorizes a later operation done off the hot path.
- Light touch on shared root → mint
Ticket
. - User completes heavy work by presenting the
Ticket
and mutating only owned/child objects.
Reduces churn on the shared root, and gives you idempotency (ticket can be one-time-use).
5) Append-only events, deferred aggregation
If you’re tempted to keep a total_*
counter on the shared root (a hotspot), emit events on each action and aggregate off-chain or in a periodic checkpoint entry that rolls up per-shard totals.
6) Split mutable fields into sub-objects
If one field changes much more frequently (e.g., queue head), peel it into its own child object (owned or shared as needed). Then updates don’t bump the root’s version.
🧪 Avoiding ConflictTransaction
in practice
- Keep the cut set small: Each entry function should touch as few objects as possible.
- No growing vectors on shared roots; use dynamic fields or sharded children.
- Stable routing: Deterministic key→shard mapping; never rehash live keys.
- Idempotency: Use tickets/nonces so retries don’t double-apply.
- Batch carefully: If you must touch multiple entries atomically, use a PTB—but keep it within a single shard when possible.
- Backoff & retry: On the client, exponential backoff for known conflict aborts.
🔍 Minimal conflict checklist
- Shared root is configuration + pointers only.
- Hot paths mutate owned positions or sharded children.
- Per-user/per-item state is not inside the root.
- No
vector.push
on shared; use dynamic fields. - Periodic checkpoint function consolidates, not every tx.
🛡️ Security & correctness notes
- Gate privileged ops with capabilities (e.g.,
AdminCap
,MintCap
); store caps in owned objects, not globals. - Validate invariants in shared entry points (sum of shard totals, proposal status transitions, etc.).
- Emit events for auditability; they are cheap and conflict-free.
Quick templates by use case
- Game arena: Shared
Arena
(config/match index) + ownedPlayer
/Loadout
. Match joins go toShard[hash(match_id)]
. - DAO voting: Shared
Proposal
(immutable metadata) + dynamic fields per voter or ownedBallot
tickets. Tally via periodic checkpoint. - Lending vault: Shared
Registry
+ ownedPosition
s; price/oracle reads are immutable; rebalances via sharded pools.
Do you know the answer?
Please log in and share it.
Sui is a Layer 1 protocol blockchain designed as the first internet-scale programmable blockchain platform.

- Why does BCS require exact field order for deserialization when Move structs have named fields?65
- Multiple Source Verification Errors" in Sui Move Module Publications - Automated Error Resolution55
- Sui Transaction Failing: Objects Reserved for Another Transaction49
- How to Maximize Profit Holding SUI: Sui Staking vs Liquid Staking313
- Sui Move Error - Unable to process transaction No valid gas coins found for the transaction315