If you’ve been building on Ethereum or other EVM chains for a while, chances are you’ve heard more and more about Sui. An ecosystem where there’s fast execution, parallelism, strong safety guarantees, and a new language called Move.

At first glance, Move can feel alien. It doesn’t look like Solidity, it doesn’t behave like Solidity, and many of the assumptions you’ve internalized over the years suddenly stop applying. That can be intimidating, but it can also be refreshing.

This blog post serves not as a reference manual, but as a guided walk from a Solidity mental model into the Sui way of thinking. We’ll start with the big idea, and then gradually layer in the details so everything has a place to land.

We’ll cover the foundations of developing in Sui, from the Object Model to Assets as Resources, security, parallelism, developer tooling and more.

OpenZeppelin and Sui are building smart contract libraries, and starter apps to act as the foundation for future apps on Sui. This guide provides a jump-off point before getting into building with the new smart contract libraries, and more.

1. The First Big Shift: Where State Lives

When you write Solidity, there’s an assumption so fundamental you rarely question it:

State lives inside contracts.

Balances, ownership, permissions. Everything is ultimately some variable in contract storage. Users don’t really ownassets directly; contracts do, and they keep track of who owns what.

From Accounts to the Object Model

On Sui, state lives in objects.

Instead of asking a contract “what is Alice’s balance?”, Alice literally owns an object that represents her balance. If she wants to transfer it, she transfers the object itself.

There’s no global storage to peek into. While Sui maintains a global set of objects, Move code cannot discover or fetch them dynamically. Every object a transaction may read or modify must be explicitly passed in as an input, which lets validators know upfront which pieces of state a transaction will affect. This leads to safer execution, fewer hidden dependencies, and better parallelism, all without sacrificing composability.

Once this clicks, most of Move and Sui suddenly feel much more coherent.

2. Objects: The Building Blocks of Everything

Once you accept that state lives in objects, the next question is obvious:

What exactly is an object on Sui?

At a high level, an object is just a piece of structured data that the blockchain knows how to track, version, and assign ownership to. But unlike Solidity, where anything in contract storage can behave like state, Sui is much more explicit about what is, and isn’t, an object.

The key Ability: What Makes Something an Object

In Move, not every struct becomes an on-chain object by default. To be stored, owned, and transferred on Sui, a struct must declare the key ability.

Abilities in Move are compile-time markers that describe what a type is allowed to do. In this case, key tells the system that values of this type can exist as on-chain objects with identity and ownership.

/// A simple NFT object.
///
/// `key` means each instance can live on-chain as an
/// object with a unique identity (`UID`) and
/// can be owned, transferred, shared, or frozen.
public struct SimpleNFT has key, store {
    id: UID, // Every object must have a unique ID as its first field
    name: String,
    description: String,
    url: Url,
}

Adding has key tells the system: instances of this struct are real, on-chain objects. For now, it’s enough to know that the store ability allows this object to be embedded as a field inside other objects, rather than existing only at the top level.

There are two important rules here:

  • Objects must have the key ability
  • Objects must include a UID field as their first member, named id, which uniquely identifies the object on-chain

That UID is not something you invent yourself. It’s created by the network using the transaction context. You don’t need to worry about the specifics of the syntax yet. What matters is that object transfers are handled by the transfer helpers provided by the Sui framework, and that the transaction context is passed in explicitly.

This is why object creation always requires a TxContext: it allows Sui to generate unique object IDs and track ownership changes. Unlike Solidity’s msg.sender or other implicit globals, Move makes transaction context an explicit input to the transaction. We’ll come back to the details later.

/// Create and transfer an NFT to `recipient`.
public fun mint(
    name: vector,
    description: vector,
    url: vector,
    recipient: address,
    ctx: &mut TxContext
) {
    let nft = SimpleNFT {
        id: object::new(ctx), // Generate a fresh UID for this object
        name: name.to_string(),
        description: description.to_string(),
        url: url::new_unsafe_from_bytes(url),
    };

    // Ownership is updated by transferring the object to the recipient.
    transfer::transfer(nft, recipient);
}

This might feel verbose at first, but it’s deliberate: nothing appears out of thin air.

Objects Are Not Storage Slots

One subtle but important distinction: defining a key struct does not create storage by itself.

The struct definition is just a type. Storage only exists once an actual object is created and owned by someone. This reinforces the idea that storage is not ambient or global-it's always tied to concrete objects that exist independently of any single module.

With this foundation in place, we can move to the most important property of objects on Sui: ownership.

3. Ownership: Who Can Touch What (and Why It Matters)

Now that we know what objects are, the next natural question is:

Who is allowed to use them?

On Sui, ownership is not an abstract idea or a convention enforced by code. It’s a first-class concept that the system itself understands and enforces. Every object on Sui has a clear ownership model, and that model determines who can include the object in a transaction, whether transactions involving the object can conflict with others, and how much parallelism the system can safely exploit during execution.

At a high level, Sui objects fall into three fundamental ownership categories:

  • Owned objects: controlled by a single address. Only the owner can use them in transactions.
  • Shared objects: accessible by anyone, with updates coordinated to ensure correctness under concurrency.
  • Immutable objects: frozen forever and readable by all, with no possibility of change.

In addition to these, you may encounter some derived ownership patterns:

  • Wrapped objects: objects that are members of another object, allowing complex state to be composed and moved as a unit.
  • Dynamic field (child) objects: objects logically attached to a parent object and accessed through it rather than directly.
  • Party objects: a form of shared object whose access is restricted to a predefined set of participants, enabling coordinated updates without opening the object to everyone.

These patterns are built on top of the same ownership primitives. This is very different from Solidity, where ownership is usually implemented as logic inside contracts.

For simplicity, this guide focuses on the three core ownership types, which are enough to understand most Sui programs and performance characteristics.

A Quick Note on Addresses in Sui

Before going further, it helps to clarify what an address means on Sui. If you come from Ethereum, you can mostly think of account addresses the same way: they represent users or accounts, they’re controlled by private keys, and wallets manage them. These are the addresses that own objects and authorize transactions.

However, Sui also uses the same address format to represent object IDs. In other words, not every address you see corresponds to a user account; some addresses uniquely identify objects on-chain. In this section, when we talk about addresses owning objects, we are specifically referring to account addresses, not object IDs.

This distinction is important conceptually, but in practice it reinforces the same idea: ownership on Sui is always explicit and unambiguous. An object is either owned by an address, shared, or immutable, and the system enforces that directly.

It’s also worth noting that, because object IDs are addresses, an object address can receive other objects via transfer-to-object (TTO). Those objects are later accessed explicitly through the transfer::receive interface. This is a powerful composition mechanism, but it’s out of scope for this guide, which focuses on the core ownership model.

Owned Objects: The Default Case

The most common form of ownership on Sui is address-owned objects. An owned object belongs to exactly one address, and only that address is allowed to use it as an input to a transaction.

/// `key` makes `Sword` a first-class, on-chain object with its own identity,
/// while `store` allows it to be embedded as a field inside other objects.
public struct Sword has key, store {
    id: UID,
    strength: u64
}

If Alice owns a Sword, only Alice can:

  • include it in a transaction
  • transfer it to someone else
  • mutate it (if the rules allow)

No access checks are needed in the code at this point. If Alice doesn’t own the object, the transaction simply won’t pass validation.

This model is simple, easy to reason about, and extremely fast. Transactions that touch only owned objects do not require global ordering and can be verified and executed independently and in parallel.

For many use cases, such as user balances, NFTs, inventory items, this is all you need.

Shared Objects: When Multiple Users Need Access

Sometimes, a single owner isn’t enough. You may want multiple users to interact with the same piece of state: a liquidity pool, a game board, or a registry. For those cases, Sui provides shared objects.

A shared object is explicitly marked as shared and can be read or modified by anyone. Sharing an object is irreversible; once shared, it cannot be reverted to a single-owner object.

// Share the object using the Sui framework's `transfer` module
transfer::share_object(board);

Shared objects:

  • can be read and modified by anyone,
  • may require coordination when multiple transactions try to update them,
  • can become contended if heavily accessed.

It’s important to note that shared objects are not inherently more expensive than owned objects. However, because they are open to access by anyone, they are more likely to experience congestion. When many transactions attempt to touch the same shared object, ordering becomes necessary and costs can increase as a result.

For this reason, shared objects should be used intentionally, only for parts of the system that truly require shared access from multiple parties.

Immutable Objects: Write Once, Read Forever

The final core ownership model is immutable objects.

Once an object is made immutable, it can never be modified again.

// Make the object immutable using the Sui framework's `transfer` module,
transfer::freeze_object(rules);

Immutable objects:

  • have no owner
  • can be read by anyone
  • are guaranteed never to change

They’re ideal for configuration data, rules, constants, or reference information. Because immutable objects can’t be modified, they are as fast and conflict-free as address-owned objects, and often even simpler to reason about.

A Brief Detour: What “Consensus” Means on Sui

If you’re coming from Ethereum, even a simple ETH transfer competes for the same global execution order as everything else. Sui takes a different approach. Consensus only needs to order transactions when they touch the same objects.

Here’s the key idea:

  • Every transaction explicitly declares the objects it will read or modify.
  • If two transactions do not overlap on inputs, they do not need to be ordered relative to each other.
  • Validators can verify and execute those transactions independently and in parallel, even though they are part of the same consensus flow.

Ownership is what makes this possible.

Transactions that touch only address-owned or immutable objects are conflict-free by construction. There is no ambiguity about who is allowed to mutate the object, and no risk of competing updates. As a result, these transactions can be verified and executed in parallel across the network.

When a transaction involves a shared object, ordering may become necessary, because multiple parties may attempt to update the same state. In those cases, consensus establishes a single agreed-upon order of updates to ensure correctness.

Earlier versions of Sui described a separate “fast path” for owned-object transactions. That distinction no longer exists. Instead, all transactions go through consensus, and still leverage ownership and input visibility to maximize parallelism and eliminate entire classes of conflicts, such as object equivocation.

The key takeaway is this:

On Sui, consensus is universal, but ordering is conditional.

As a developer, this gives you a powerful lever. By choosing how you model ownership, you’re shaping when transactions can proceed independently and how much parallelism your application can unlock.

4. Assets and Resources: Safety by Construction

By now, we’ve seen that on Sui:

  • state lives in objects
  • objects have explicit ownership
  • ownership determines who can touch what, and when ordering is required in consensus
The next natural question may be:

Ok, but how do assets work? How do balances, tokens, and NFTs fit into this model?

This is where Move’s design really starts to pay off.

How Assets Are Usually Modelled in Solidity

In Solidity, assets are almost always represented indirectly.

A typical ERC-20 token keeps balances like this:

mapping(address => uint256) public balanceOf;

From the compiler’s point of view, this is just data:

  • balances are numbers in storage
  • transfers are arithmetic updates
  • correctness depends entirely on the surrounding logic

Over time, the ecosystem has developed strong conventions and patterns to make this safe. These included checks-effects-interactions, SafeMath (historically), careful audits, and more, but the language itself doesn’t really know that these numbers represent scarce assets.

They’re just integers.

Assets as Resources in Move

Move takes a different approach.

On Sui, assets are modeled as resources: values with special rules enforced by the type system itself. Therefore Sui assets:

  • cannot be copied
  • cannot be implicitly destroyed
  • must always be moved somewhere

These rules aren’t conventions or best practices. They’re enforced at compile time and at the VM level. If you forget to handle a resource, the code simply won’t compile.

This is a subtle shift, but an important one:

  • assets are no longer accounted for, but instead **they are handled directly.

Coins Are Objects You Own

Let’s look at how coins work on Sui.

Instead of a contract keeping an internal mapping of balances, each coin is an object, owned by an address. If you have a balance, it’s because you own one or more coin objects.

Minting new coins looks like this:

/// Mint new coins and transfer them to a recipient.
///
/// To call this function, the caller must possess the `TreasuryCap`
/// for the given token type. This is how minting authority is enforced.
public fun mint(
    treasury_cap: &mut TreasuryCap, // Capability proving minting rights
    amount: u64,                              // Amount of coins to mint
    recipient: address,                      // Who will own the newly minted coin
    ctx: &mut TxContext                      // Transaction context (used to create objects)
) {
    // Create a new coin object with the given amount.
    // This does not update any global balance mapping.
    let coin = coin::mint(treasury_cap, amount, ctx);

    // Explicitly transfer ownership of the coin object to the recipient.
    // To use public_transfer the object must implement the store ability,
    // which coin does.
    transfer::public_transfer(coin, recipient);
}

A few important things are happening here:

  • There is no global balance mapping being updated
  • A real coin object is created by the system
  • Ownership of that object is explicitly transferred to the recipient

At the time of writing, Sui is actively exploring a more ergonomic model called Address Balances, which introduces a canonical balance per (address, currency) pair while preserving Sui’s object-centric execution model and parallelism.

For more details, see SIP-58: Sui Address Balances:

The contract does not own user balances. Users do.

The only thing the contract controls is who is allowed to mint, which is enforced through possession of the TreasuryCap. This is a special object called a capability, that represents the minting authority for a token. Whoever owns this object is allowed to mint new coins of that type.

We’ll come back to capabilities in more depth later. For now it’s enough to recognize them as one of the core building blocks behind Sui’s security model.

Why This Is a Big Deal

Modelling assets as resources has some very practical consequences:

  • Assets can’t be silently dropped or lost
  • Transfers must be explicit and complete
  • Invariants are easier to reason about

Many bugs that are common in Solidity simply don’t have a place to exist here. Not because developers are more careful, but because the language refuses to express those mistakes.

For Solidity developers, this can feel restrictive at first. But once you get used to it, you realize that many “best practices” you’ve internalized are now guarantees by default.

A Subtle but Important Shift

In the EVM world, contracts are responsible for:

  • tracking balances
  • enforcing invariants
  • preventing misuse

On Sui, contracts (or rather, modules) mostly define rules over objects:

  • how they can be created
  • how they can be transformed
  • who is allowed to perform certain actions

The data itself lives independently, owned by users or shared explicitly.

Once you see assets this way, as objects and resources rather than entries in a mapping, a lot of the Sui design starts to feel less “different” and more inevitable.

Capabilities Instead of Permissions

Let’s come back now to capabilities. If you’re used to Solidity, access control probably looks something like this:

require(msg.sender == owner);

Here, authority is inferred from an address, and enforced through conditional logic inside the contract.

Move takes a different approach.

In Move and Sui, privileged actions are typically guarded using capabilities. A capability is a special object that represents the right to perform a specific action. If you have the capability, you can perform the action. If you don’t, you can’t. There’s no fallback logic or alternative path.

The TreasuryCap we saw earlier is a concrete example of this idea.

To mint coins, you must possess the corresponding TreasuryCap object. If you don’t have it, you simply cannot call the minting function. There is:

  • no address check to forget
  • no modifier to bypass
  • no way to “fake” authorization

The transaction will fail before any code executes.

This pattern is known as capability-based access control, and it shows up throughout Sui:

  • minting and burning assets
  • admin or upgrade authority
  • protocol configuration
  • one-time or limited privileges

Instead of checking who you are, the system checks what you hold.

For Solidity developers, a helpful way to think about this is:

Capabilities are like unforgeable keys represented as objects.

They can be transferred, shared, or destroyed intentionally, but they can’t be duplicated or guessed.

There are scenarios where address checking may still make sense alongside capability-based gating.

While object ownership is often sufficient for access control, certain designs require additional guarantees. For example, when multiple capability instances exist and each governs access to a different resource, the capability may carry metadata (such as an address or ID) that should be validated explicitly.

Whether such checks are required depends on the intended business logic. Capability possession alone is not always the full security boundary.

With assets now grounded in the object and ownership model, you now have the foundation needed to understand what development looks like on Sui; a growing blockchain known for its speed, security, and developer experience.

Next, we will round out this pre-cursor to building in the Sui ecosystem by covering security considerations, explicit context instead of implicit globals, the point of the ‘strict’ feeling of Move, and more.

5. Security Side Effects: What Disappears, What Remains

At this point, we haven’t talked much about “security” directly, and that’s intentional.

In Solidity, security is usually something you add on top of a design: modifiers, patterns, audits, defensive coding. On Sui, many security properties emerge naturally from the object and ownership model you’ve already seen.

Let’s look at what that means in practice.

Reentrancy: Why the EVM Threat Model Does Not Apply

If you’ve written Solidity for any length of time, you’ve almost certainly worried about reentrancy. Typically when we talk about it, the issue comes from the fact that you can end up with a “sandwiched” call stack, where:

  • a contract performs some checks,
  • then makes an external call or hook that invalidates those checks,
  • and later resumes execution assuming those checks still hold.

That pattern relies on something very specific: the ability to make an external call into code you don’t control, which can execute arbitrary logic and call back into the original function before the first execution frame finishes. This is the core of classic EVM reentrancy.

On Sui, this execution shape cannot be expressed.

Move does not support dynamic dispatch in the way Solidity does. Calls are statically resolved to known modules and functions at compile time. There is no mechanism to invoke user-provided code, function pointers, or arbitrary contract addresses as callbacks.

You may notice that Move also restricts which modules can call each other: a module can only call into itself or its explicitly declared dependencies, and package dependency graphs are acyclic. While this reinforces predictability, it is not the primary reason reentrancy is avoided. Even with an acyclic call graph, reentrancy would still be possible in a language that allows runtime-selected callbacks.

The real constraint is simpler and stronger:

There is no general way to hand control to untrusted code mid-execution.

This also rules out more subtle shapes you might initially worry about, such as calling back into an older version of the same package via a different package ID.

All call targets are explicit, statically known, and resolved ahead of execution.

Fewer Hidden Side Effects

If reentrancy disappears as a default threat, the next question becomes: what kinds of mistakes are still possible? Another common source of bugs in Solidity is implicit state access.

A function might:

  • read from a mapping you forgot about
  • update shared storage in an unexpected way
  • affect users you didn’t anticipate

On Sui, this is much harder to do accidentally.

Because all objects a transaction may touch must be passed in explicitly:

  • there are no hidden reads
  • there are no surprise writes
  • dependencies are visible upfront

This makes code easier to reason about, easier to audit, and easier to review.

Approvals, Allowances, and Why They’re Less Central

In Solidity, approvals and allowances exist because users don’t directly control assets; contracts do.

On Sui, users own assets directly. To give someone else the ability to act on an asset, you usually:

  • transfer a capability
  • transfer ownership of an object
  • or interact with a shared object by design

This doesn’t mean approvals disappear entirely, but they’re no longer a core primitive required for most workflows. Many patterns that rely on allowances in the EVM world are replaced by explicit ownership transfer or capability delegation.

What Still Requires Care

None of this means that security is solved. What changes on Sui is not the need to reason carefully, but what you are reasoning about.

Once reentrancy and hidden callbacks are off the table, the remaining risks come from explicit state transitions that you can see and trace through the code.

On Sui, any function you call can mutate an object if it is passed as a mutable reference. Ownership alone does not protect you here. If a value is passed as &mut, it can be changed regardless of whether it is owned, shared, or nested inside another object.

This means you still need to be deliberate about:

  • Business logic correctnessEnsuring that state transitions match the intended rules of your application.
  • Invariant management across callsIf you check a condition, call another function, and then continue execution, you must assume that any object passed as &mut may have changed during that call.
  • Sequencing and compositionComplex flows that involve multiple object mutations must be designed so that intermediate states are either valid or unobservable.
  • Authority and upgrade boundariesCapabilities, admin objects, and upgrade rights remain powerful tools, and misuse can still lead to serious failures.
  • Denial-of-service through contentionWhile not a correctness issue, concentrating too many operations on the same objects can degrade performance or availability.

What’s important is that these risks are explicit and local.

There are no invisible reads, no surprise writes, and no execution paths that appear only at runtime. When something can change, it is visible in the function signature. When authority exists, it is represented as an object. When sequencing matters, it is encoded directly in the call order.

In other words, the remaining challenges are not about defending against unknown behavior, but about correctly modeling known behavior.

Security as a Property of the Model

A useful way to summarize the shift is this:

  • In Solidity, security is largely about defensive coding
  • In Sui, security is largely about choosing the right ownership and object model

Once that choice is made correctly, many entire classes of bugs become impossible. This is not because developers are perfect, but because the language and runtime won’t let you express them.

With security framed this way, we can now zoom out and look at the bigger picture: how all of this affects execution, performance, and parallelism at the system level.

6. Execution and Parallelism as a Design Tool

By now, you’ve probably noticed a recurring theme:

On Sui, many properties that are implicit in the EVM are made explicit.

This is especially true when it comes to execution.

The EVM World: Everything Competes With Everything

In the EVM, all transactions effectively compete for the same global execution lane. Even when two transactions touch completely unrelated contracts, operate on different users, and have no logical interaction at all, they still need to be totally ordered and executed one after another.

From the validator’s perspective, there is no safe way to assume independence ahead of time.

Storage is global and implicitly shared, so dependencies are only discovered during execution.

This is why parallel execution in EVM-based systems is so difficult. The system cannot confidently execute transactions in parallel because it does not know in advance which pieces of state will be affected.

Sui’s Approach: Make Dependencies Obvious

Sui flips this model around by making dependencies explicit from the start. State lives in objects, objects must be passed in as transaction inputs, and ownership is unambiguous. As a result, validators can see upfront exactly which pieces of state a transaction will touch.

This makes something possible that is largely out of reach in the EVM. Transactions that operate on disjoint sets of owned or immutable objects do not need to be ordered relative to each other. They can be verified, executed, and finalized independently, allowing safe parallel execution without sacrificing correctness.

Consensus and Conditional Ordering

Sui does not eliminate consensus. Instead, it narrows its role.

All transactions are processed under a single consensus system, but ordering is only required when transactions actually contend on the same state. Because transactions must declare the objects they read or modify up front, the system can distinguish between cases where ordering matters and cases where it does not.

When transactions operate on disjoint sets of owned or immutable objects, there is no ambiguity. These transactions do not need to be ordered relative to each other, and they can be verified and executed independently.

Ordering becomes necessary only when multiple transactions attempt to mutate the same objects. In those cases, consensus establishes a single, agreed-upon sequence to ensure correctness.

Parallelism Is Not an Optimization, It’s a Design Choice

In most blockchain systems, performance tuning happens after the design is already set. Developers reach for gas optimizations, batching tricks, or off-chain workarounds to make systems scale once bottlenecks start to appear.

On Sui, performance starts much earlier, at the level of the data model itself.

When you decide how state is represented, such as which objects are owned, which are shared, and which are immutable, you are simultaneously deciding how the system will execute. Those choices determine how much parallelism is possible, where contention will naturally arise, and how the application behaves as load increases.

This is a subtle but powerful shift. Instead of fighting the execution model with increasingly complex optimizations, you work with it. Parallelism is no longer something you hope the runtime can recover after the fact. It is something you design into the system from the beginning.

Thinking in Terms of Contention

A useful mental habit when designing on Sui is to ask a simple question:

Which objects will many users want to touch at the same time?

Those objects naturally define the pressure points of your system. They are where contention will show up first, where ordering constraints become necessary, and where performance can degrade under load. Identifying them early makes it much easier to reason about how your application will scale.

In many cases, the right response is not to optimize harder, but to change ownership boundaries. Moving per-user state into owned objects, keeping shared objects small and focused, and pushing immutable data out of hot paths often has a larger impact than any low-level optimization.

This kind of thinking is much harder in a global storage model, where everything is implicitly shared and contention only becomes visible at runtime. On Sui, ownership makes those boundaries explicit, which turns contention from a surprise into a design choice.

Execution Predictability as a Feature

One of the less obvious benefits of Sui’s execution model is predictability.

In the EVM, it is often difficult to know ahead of time which transactions will conflict, how load will affect latency, or where performance cliffs might appear. Many of these effects only become visible once a system is under real usage, and even then they can be hard to reason about from the code alone.

On Sui, those questions are largely answered by design. Transactions that operate on owned objects scale naturally, because they do not contend with other users. Immutable objects are always cheap to read and never introduce conflicts. When contention does exist, it is explicit and localized, typically around a small set of shared objects.

This makes it easier to reason not only about correctness, but also about how a system will behave under real-world conditions.

With execution and parallelism now grounded in the object and ownership model, the next step is to look at how transactions themselves are structured, and how Sui draws clear boundaries around what can and cannot be called.

7. Transactions, Entry Functions, and Explicit Context

Up to now, we’ve talked a lot about what code can do on Sui in terms of objects, ownership, assets, and execution.

The next topic is about how execution is entered, and how Sui lets you be explicit about which functions are meant to be triggered directly by transactions.

Entry Functions: An Explicit Transaction Boundary

If you come from Solidity, you’re used to a very flexible model. Any public or external function can be called:

  • directly by users,
  • by other contracts,
  • by wrappers, proxies, or intermediaries.

This flexibility is powerful, but it also blurs an important distinction: is a function intended to be part of a contract’s internal logic, or is it meant to be a user-facing transaction entry point?

On Sui, that distinction can be made explicit.

Move supports entry functions, which are functions that are explicitly marked as callable directly from a transaction.

entry fun claim_reward(
    reward: Reward,
    ctx: &mut TxContext
) {
	// logic here
}

An entry function:

  • can be called directly from a transaction (wallet, CLI, frontend),
  • cannot be invoked by other Move modules,
  • defines a clear boundary for where transaction execution begins.

It’s important to note that entry is not mandatory. Move modules can still expose public functions, and those behave much closer to Solidity’s public or external functions in terms of composability and reuse.

The key difference is that entry lets you opt in to a stricter model when you want one.

Why This Matters

For Solidity developers, this may feel restrictive at first. After all, flexibility and composability are familiar and often desirable.

The value of entry is not that it replaces public functions, but that it gives you a tool to clearly separate:

  • transaction-facing APIs that users are meant to call directly, from
  • internal or composable logic that is meant to be reused by other modules.

This makes it easier to reason about:

  • which actions can be triggered directly by users,
  • which flows are purely internal,
  • where transaction execution begins and ends.

This is especially useful for cases where you do not want a function to be callable programmatically by other contracts, such as randomness consumption, privileged actions, or flows that assume a single transaction boundary.

In Solidity, enforcing this separation usually requires additional guards or careful conventions. On Sui, the distinction can be expressed directly in the function signature and enforced by the language.

The result is not less flexibility, but more explicit intent. You choose when a function is part of the public transaction surface, and when it is just reusable logic.

Explicit Context Instead of Implicit Globals

Another difference you’ve probably noticed is the absence of things like msg.sender.

In Move, there are no implicit global variables available to your code. If a function needs transaction-related information, it must receive it explicitly.

That’s what TxContext is for.

ctx: &mut TxContext

The transaction context contains things like:

  • the sender’s address
  • information needed to create new object IDs
  • transaction metadata

But crucially, it’s not something you can access magically. You have to ask for it in the function signature.

This design reinforces the same principle you’ve seen throughout Sui:

All inputs and dependencies are explicit.

Why Explicit Context Is a Feature

You can look at a function signature and immediately see:

  • whether it creates objects
  • whether it depends on the sender
  • whether it is meant to be an entry point

Nothing is hidden.

A Cleaner Mental Model

A helpful way to think about this is:

  • Entry functions define what users are allowed to do in a single transaction
  • Non-entry functions define reusable logic within the module
  • TxContext is explicit proof that a function depends on transaction-level information

This clear separation reduces ambiguity and removes many edge cases that Solidity developers have learned to work around.

With transaction boundaries now clearly defined, the next piece of the puzzle is how contracts are initialized and bootstrapped. This is particularly important since Sui doesn’t have constructors in the Solidity sense.

8. Initialization Without Constructors

If you’re coming from Solidity, you’re used to:

  • constructor runs
  • initial state is set as part of deployment

Sui doesn’t have constructors in that sense. Instead, Sui Move supports a module initializer named init.

What Is init?

An init function is a special function that runs exactly once, automatically, when the package is published. The Sui runtime calls the init function for each module during the publishing process.

Its role is not to “set variables inside a contract”, but to:

  • create initial objects
  • mint and distribute capability objects
  • optionally share or freeze objects as part of setup

Here’s a simple example:

module my_module;

/// Capability object granting admin rights.
public struct AdminCap has key {
    id: UID
}

/// Runs once, right after the module is published.
fun init(ctx: &mut TxContext) {
    let cap = AdminCap {
        id: object::new(ctx),
    };

    // Transfer admin capability to the publisher.
    transfer::transfer(cap, ctx.sender());
}

This looks superficially similar to a Solidity constructor, but the semantics are quite different.

No Arbitrary Constructor Arguments

One important difference from Solidity is that init does not accept arbitrary user-provided arguments.

You can’t pass custom parameters the way you would with a Solidity constructor. Initialization is intentionally constrained to be deterministic and self-contained.

There are advanced patterns to parameterize initialization, most notably through one-time witnesses, but those are outside the scope of this guide. For now, it’s enough to know that init is designed for bootstrapping objects and capabilities, not for general configuration.

A Useful Mental Model

A helpful way to think about it is:

  • Solidity constructor: initialize contract storage with arguments
  • Sui init: create the first objects and hand out authority

Once initialization is done, everything else follows the same rules you’ve already seen: ownership is explicit, access is enforced by capabilities, and state lives in objects.

With initialization covered, we’ve now completed the lifecycle of a Sui application from publish to execution. The remaining topics are less about mechanics and more about how it all feels to build with Move.

9. Move’s Type System: Why It Feels Strict (and Why That’s the Point)

If you’re coming from Solidity, one of your first reactions to Move is probably something like:

Why is the language so strict? Why do I have to be so explicit about everything?

That reaction is completely normal.

Move is deliberately designed to make certain mistakes impossible to express, even if that means writing a bit more code or thinking a bit harder upfront.

Solidity Feels Flexible, Until It Isn’t

Solidity gives you a lot of freedom:

  • you can copy values freely
  • you can drop things implicitly
  • you can read and write storage from almost anywhere

That flexibility is powerful, but it also means the compiler has very little understanding of intent. From its perspective, a balance, a counter, and a random number are all just integers.

As a result, many important rules like “don’t double-spend”, “don’t forget to update both sides”, “don’t lose funds,” live only in developer discipline and review processes.

Move Is Opinionated About Scarcity

Move starts from a different premise:

Some values represent scarce resources, and the language should treat them differently.

This is why Move has concepts like:

  • abilities (copy, drop, store, key)
  • linear resources
  • explicit ownership and moves

Instead of assuming everything can be copied and discarded, Move forces you to declare what a type is allowed to do.

For example:

  • a coin can’t be copied
  • a capability can’t be silently dropped
  • an object can’t be mutated unless you own it

These rules are enforced by the compiler, not by convention.

Abilities as Intent, Not Syntax

Earlier, you saw abilities like key and store. While they might look like syntax noise at first, they serve an important purpose:

Abilities tell the compiler how a value is meant to be used.

They encode intent directly into the type system:

  • Is this value meant to be an on-chain object?
  • Can it be stored inside other objects?
  • Is it safe to copy?
  • Is it allowed to disappear?

Once you internalize this, Move code starts to read less like “ceremony” and more like documentation enforced by the compiler.

Explicitness as a Feature

The same philosophy shows up everywhere:

  • transaction context is explicit
  • object ownership is explicit
  • state dependencies are explicit
  • entry points are explicit

This can feel verbose at first, especially if you’re used to Solidity’s implicit globals. It has a payoff, though, where when you read a function signature, you can usually tell exactly what it does and what it depends on.

Very little happens “by accident”.

A useful way to think about Move is not as “a stricter Solidity”, but as something closer to:

A systems language for digital assets.

Much like Rust forces you to think about memory ownership to avoid whole classes of bugs, Move forces you to think about asset ownership to avoid whole classes of financial and security bugs.

The learning curve is real, but it’s front-loaded. Once the mental model clicks, many patterns become simpler, not harder.

Overall, we’ve covered the full conceptual arc: from where state lives, to objects and ownership, to how assets, security, execution, transactions, and initialization all fit together, and finally the language philosophy that ties everything together.

All that’s left is to zoom out and talk briefly about developer experience and tooling, and then wrap things up.

10. Developer Experience and Tooling

At this point, the natural concern for many Solidity developers is no longer conceptual, but practical:

Is the tooling there yet? What does it actually feel like to build on Sui?

It’s true that the Ethereum ecosystem has had many years to mature, and the breadth of tooling around Solidity is hard to match. But Sui’s tooling is intentionally focused on the things that matter most for its execution model.

A CLI-Centered Workflow

Sui puts a lot of emphasis on its command-line tooling. The sui CLI is the primary entry point for most development workflows, and it covers a surprisingly wide surface area:

  • creating and managing accounts
  • running local networks
  • publishing and upgrading packages
  • inspecting objects and ownership
  • submitting and simulating transactions

Because state lives in objects, being able to inspect objects directly from the CLI turns out to be extremely valuable. You’re not just querying storage; you’re looking at concrete pieces of state, with clear ownership and history.

Local Development Feels Familiar

Running a local Sui network and iterating on Move code feels closer to traditional development than you might expect:

  • publish a package
  • interact with it via transactions
  • inspect the resulting objects
  • iterate and upgrade

There’s no need to set up complex indexers or custom scripts just to understand what’s happening. The object model makes state visible and tangible in a way that’s often harder to achieve in EVM-based systems.

Fewer Tools, More Intentional Ones

The Sui ecosystem doesn’t yet have the sheer volume of third-party tooling that Solidity enjoys, but it does include many essential capabilities directly into the platform and its standard tooling.

Instead of relying heavily on external frameworks to paper over language limitations, many concerns are addressed directly by:

  • the Move type system
  • explicit ownership and state access
  • built-in transaction simulation
  • clear execution boundaries

For many developers, this ends up feeling like less tooling overall, but also less glue code and fewer sharp edges.

A Different Kind of Productivity

Building on Sui often feels slower at the very beginning, while you’re adjusting to the mental model. But once that adjustment happens, productivity tends to increase. This is not because you write less code, but because you spend less time debugging invisible state, access control mistakes, or unintended interactions.

In that sense, Sui’s developer experience is less about speed of writing code, and more about speed of confidence.

With tooling covered, we can now wrap things up by stepping back and summarizing the shift from Solidity to Sui as a whole; not in terms of features, but in terms of mindset.

That’s where we’ll finish.

11. Wrapping Up: A Shift in Mindset, Not Just a New Stack

Moving from Solidity to Sui isn’t really about learning new syntax or memorizing new APIs. It’s about adopting a different way of thinking about how blockchain applications are built.

In the EVM world, you’re used to:

  • contracts owning state
  • global storage being implicitly available
  • security enforced through patterns and discipline
  • performance as something you optimize around

On Sui, those assumptions are flipped:

  • state lives in objects
  • ownership is explicit and enforced by the system
  • assets are resources, not numbers
  • consensus and ordering are shaped by ownership and contention, not a constant tax
  • parallelism is something you design for, not something you hope for

What initially feels restrictive: explicit context, explicit ownership, explicit inputs, turns out to be the thing that makes systems easier to reason about, easier to scale, and harder to break.

For Solidity developers, this can be a refreshing shift. Many of the “best practices” you’ve internalized over the years don’t disappear, they become properties of the model itself. Instead of defending against entire classes of bugs, you simply can’t express them in the first place.

Sui doesn’t try to be “Ethereum, but faster.” It makes different tradeoffs, and those tradeoffs show up everywhere: in the language, the execution model, the security guarantees, and the developer experience.

If you’re curious about where this path leads, the best next step is to experiment:

  • build a small module
  • create and move some objects
  • model ownership intentionally

Once the mental model clicks, Sui stops feeling alien, and starts feeling surprisingly natural.

12. What’s Next?

Sui is gaining momentum as the go-to blockchain for speed, security, and developer experience that all teams want. Be sure to follow both Sui and OpenZeppelin for more developments on libraries and starter Apps that will be released over the coming months that will make it even easier for developers to build onchain applications for the future of finance.