SuiMoveSmart ContractsBlockchain DevelopmentWeb3 Security

What is Sui Move? A Developer's Guide to Sui's Smart Contract Language

Learn the Sui Move programming language from the ground up. This comprehensive guide covers Move's origins, core concepts, security advantages, and why leading crypto security tools like Kairo Guard build on Sui.

Kairo TeamJanuary 27, 202525 min read

What is Sui Move? A Developer's Guide to Sui's Smart Contract Language

If you're exploring blockchain development in 2025, you've likely heard about the Sui Move programming language—a smart contract language that's fundamentally changing how we think about on-chain security and asset management. Whether you're a Solidity veteran looking to expand your toolkit or a newcomer to Web3 development, understanding Sui Move is becoming essential.

In this comprehensive guide, we'll take you from zero to confident with Sui Move. You'll learn where it came from, why it matters, and how to write your first smart contracts. We'll also explore why security-focused projects like Kairo Guard chose Sui as their foundation for protecting user assets.

Let's dive in.


Table of Contents

  1. Introduction to Sui Blockchain
  2. The Origins of Move Language
  3. Sui Move vs Original Move (Aptos Move)
  4. Core Concepts of Sui Move
  5. Why Move is More Secure Than Solidity
  6. Understanding Sui's Object-Centric Model
  7. Code Examples
  8. Development Tools and Environment
  9. Why Kairo Guard Chose Sui
  10. Getting Started: Your First Sui Move Project
  11. FAQ

Introduction to Sui Blockchain

Before we explore the Sui Move programming language, let's understand the blockchain it powers.

Sui is a Layer 1 blockchain developed by Mysten Labs, founded by former Meta (Facebook) engineers who worked on the Diem blockchain project. Launched in 2023, Sui was built from the ground up to solve the scalability, latency, and user experience problems that plague older blockchains.

What Makes Sui Different?

Parallel Transaction Execution: Unlike Ethereum, where transactions execute sequentially, Sui can process independent transactions simultaneously. This means the network doesn't slow down when activity spikes—a game-changer for real-world applications.

Sub-Second Finality: Most Sui transactions achieve finality in under 500 milliseconds. Compare this to Ethereum's 12+ seconds or Bitcoin's 10+ minutes, and you'll see why developers building responsive applications gravitate toward Sui.

Low, Predictable Gas Fees: Sui's gas mechanism is designed for stability. Users can estimate costs accurately, and fees remain low even during high network usage.

Object-Centric Architecture: This is where Sui truly innovates. Instead of the account-based model used by Ethereum, Sui treats everything as objects with clear ownership. This architectural choice has profound implications for both performance and security.

Why Sui Matters for Developers

The blockchain trilemma—the notion that you can only optimize for two of decentralization, security, and scalability—has constrained blockchain design for years. Sui's innovations in consensus (Narwhal and Bullshark) and its object model challenge this assumption.

For developers, this translates to:

  • Better user experiences (fast, predictable transactions)
  • Lower costs (efficient execution, reasonable fees)
  • Enhanced security (Move's type system catches bugs at compile time)
  • Simpler mental models (objects are intuitive)

Now let's explore the language that makes this all possible.


The Origins of Move Language

The Sui Move programming language didn't appear out of nowhere. Its history is intertwined with one of the most ambitious corporate blockchain projects ever attempted.

From Facebook's Libra to Diem

In 2019, Facebook announced Libra—a global cryptocurrency project that would let billions of people send money as easily as sending a message. The project needed a smart contract language that prioritized safety above all else. After all, a bug in code handling billions of dollars could be catastrophic.

Facebook's blockchain team created Move, a language designed specifically for safe resource management. The name "Move" reflects its core principle: digital assets are moved between owners, never accidentally copied or destroyed.

The Diem Legacy

When regulatory pressure forced Facebook to abandon the project (which had been renamed to Diem), the engineers didn't let their work disappear. Several teams spun out to build new blockchains using Move:

  • Aptos: Uses a version close to the original Diem Move
  • Sui: Uses an evolved version called Sui Move
  • Movement: A newer entrant also leveraging Move

This lineage matters because it means Move has been battle-tested and refined by some of the best engineers in the industry, backed by years of research into formal verification and type theory.

Move's Design Philosophy

Move was created with specific goals that distinguish it from earlier smart contract languages:

  1. First-class resources: Digital assets are treated as special types that cannot be copied or implicitly discarded
  2. Safety and verifiability: The type system catches common bugs at compile time
  3. Flexibility: Modules can define custom resource types with fine-grained control
  4. Formal verification support: Move was designed to be analyzable by mathematical proof tools

These principles carry forward into the Sui Move programming language, with enhancements that make it even more powerful.


Sui Move vs Original Move (Aptos Move) {#sui-move-vs-original-move}

While Sui Move shares DNA with the original Move language (now often called "Core Move" or "Aptos Move"), Mysten Labs made significant modifications. Understanding these differences is crucial if you're coming from Aptos or studying Move resources from different ecosystems.

The Fundamental Shift: Objects vs Global Storage

Aptos Move uses a global storage model. Resources are stored in accounts, and you access them through global storage operations like move_to, move_from, borrow_global, and borrow_global_mut.

// Aptos Move: Account-based storage
public fun transfer(sender: &signer, recipient: address, amount: u64) {
    let coin = withdraw(sender, amount);
    deposit(recipient, coin);
}

Sui Move eliminates global storage entirely. Instead, everything is an object that's passed explicitly as a function parameter. Objects have unique IDs, clear ownership, and are manipulated directly.

// Sui Move: Object-based approach
public fun transfer(coin: Coin<SUI>, recipient: address) {
    transfer::public_transfer(coin, recipient);
}

This might seem like a small change, but it has enormous implications:

| Aspect | Aptos Move | Sui Move | |--------|-----------|----------| | Storage Model | Global, account-based | Object-centric | | Asset Access | Via global operations | Explicit parameters | | Parallel Execution | Limited | Native support | | Ownership | Implicit in accounts | Explicit object property | | Composability | Module-controlled | Object-controlled |

No Global Storage Operators

In Sui Move, you won't find these familiar Aptos/Diem operations:

  • move_to<T>()
  • move_from<T>()
  • borrow_global<T>()
  • borrow_global_mut<T>()
  • exists<T>()

Instead, objects are created with object::new() and transferred with functions from the transfer module.

Object Abilities in Sui

Sui introduces the key ability with specific semantics. An object with key must have id: UID as its first field:

struct MyObject has key {
    id: UID,           // Required first field
    value: u64,
    name: String,
}

Shared vs Owned Objects

Sui introduces a distinction that doesn't exist in Aptos:

  • Owned objects: Belong to a specific address; only that address can use them in transactions
  • Shared objects: Accessible by anyone; require consensus for concurrent access
  • Immutable objects: Frozen forever; anyone can read but no one can modify

This ownership model enables Sui's parallel execution—owned objects that don't interact can be processed simultaneously.

Entry Functions and Transactions

Sui uses entry functions as transaction entry points:

public entry fun mint_and_transfer(ctx: &mut TxContext) {
    let nft = NFT {
        id: object::new(ctx),
        name: string::utf8(b"My NFT"),
    };
    transfer::transfer(nft, tx_context::sender(ctx));
}

The TxContext parameter provides transaction metadata and is required for creating new objects.


Core Concepts of Sui Move

Let's explore the fundamental concepts you need to master the Sui Move programming language.

Objects: The Building Blocks

In Sui, everything is an object. Tokens, NFTs, game items, configuration data—all represented as objects with unique identifiers.

Every object has:

  • A unique ID (UID) that never changes
  • An owner (address, another object, or "shared")
  • A type defined by a Move struct
  • Data stored in the struct's fields
struct Sword has key, store {
    id: UID,
    damage: u64,
    durability: u64,
    enchantment: Option<String>,
}

The Ownership Model

Sui's ownership model is elegant and powerful:

Single Owner (Address)

// Create and transfer to sender
let sword = Sword { id: object::new(ctx), damage: 100, durability: 50, enchantment: option::none() };
transfer::transfer(sword, tx_context::sender(ctx));

Shared Object

// Make accessible to everyone
transfer::share_object(game_state);

Immutable Object

// Freeze forever
transfer::freeze_object(config);

Object-Owned Object

// One object owns another
transfer::public_transfer(gem, object::uid_to_address(&sword.id));

Abilities: copy, drop, store, key

Move uses abilities to control what you can do with types. Think of them as capabilities or permissions.

copy

Values with copy can be duplicated. Primitive types like u64 and bool have this by default.

struct Point has copy, drop {
    x: u64,
    y: u64,
}

let p1 = Point { x: 10, y: 20 };
let p2 = p1; // p1 is copied, both are valid

drop

Values with drop can be discarded (go out of scope without explicit handling). Without drop, you must explicitly destroy or transfer the value.

struct Ticket has drop {
    event_id: u64,
}
// Ticket can simply go out of scope

store

Values with store can be stored inside other structs. This is required for any type that will be a field in an object.

struct Metadata has store {
    created_at: u64,
    creator: address,
}

struct NFT has key, store {
    id: UID,
    metadata: Metadata, // Metadata needs 'store'
}

key

The key ability marks a type as an object that can be stored in Sui's global object storage. Objects with key must have id: UID as their first field.

struct GameCharacter has key {
    id: UID,  // Required for 'key' ability
    name: String,
    level: u64,
}

Combining Abilities

Most objects you create will have key (to be a Sui object) and often store (to be transferable and storable):

struct Coin has key, store {
    id: UID,
    value: u64,
}

Digital assets typically don't have copy or drop—this ensures they can't be duplicated or accidentally destroyed, which is exactly what you want for valuable assets.


Why Move is More Secure Than Solidity

Security is where the Sui Move programming language truly shines. Let's examine why projects focused on protecting user assets—like Kairo Guard—choose Move over Solidity.

Resource Safety: No Accidental Duplication or Loss

In Solidity, token balances are just numbers in a mapping:

// Solidity: Balances are just numbers
mapping(address => uint256) public balances;

function transfer(address to, uint256 amount) public {
    balances[msg.sender] -= amount;
    balances[to] += amount;
}

Bugs in arithmetic, missing checks, or reentrancy can lead to tokens being created from nothing or destroyed unexpectedly.

In Move, tokens are resources that must be explicitly handled:

// Move: Coins are actual resources
public fun transfer(coin: Coin<SUI>, recipient: address) {
    // The coin MUST go somewhere - compiler enforces this
    transfer::public_transfer(coin, recipient);
}

The compiler ensures resources are never:

  • Copied (unless explicitly allowed)
  • Dropped (unless explicitly allowed)
  • Left hanging (must be used, stored, or destroyed)

No Reentrancy Vulnerabilities

The DAO hack that split Ethereum was a reentrancy attack. In Solidity, external calls can call back into your contract before state updates complete:

// Vulnerable Solidity code
function withdraw() public {
    uint256 amount = balances[msg.sender];
    (bool success, ) = msg.sender.call{value: amount}("");  // External call
    require(success);
    balances[msg.sender] = 0;  // State update AFTER external call - vulnerable!
}

Move's design makes reentrancy structurally impossible:

  1. No dynamic dispatch: You can't call arbitrary code through interfaces
  2. Linear resource handling: Resources move through functions in predictable ways
  3. No callbacks: External calls don't exist in the same way

Type Safety and the Borrow Checker

Move borrowed concepts from Rust, including a sophisticated borrow checker:

public fun modify_and_read(obj: &mut MyObject): u64 {
    obj.value = obj.value + 1;
    obj.value  // Return the new value
}

The compiler tracks:

  • Who has mutable vs immutable references
  • That references don't outlive their data
  • That you can't have mutable and immutable references simultaneously

This catches entire categories of bugs at compile time.

Comparison Table: Move vs Solidity Security

| Vulnerability | Solidity | Move | |--------------|----------|------| | Reentrancy | Common, requires careful coding | Structurally impossible | | Integer overflow | Possible (mitigated in 0.8+) | Checked by default | | Asset duplication | Logic bugs can cause this | Compiler prevents | | Asset destruction | Possible through bugs | Must be explicit | | Access control bugs | Common | Type system helps | | Uninitialized storage | Possible | Not applicable |

Formal Verification

Move was designed with formal verification in mind. The Move Prover can mathematically verify that your code satisfies specified properties:

spec transfer {
    // Formal specification
    ensures global<Balance>(sender).value == old(global<Balance>(sender).value) - amount;
    ensures global<Balance>(recipient).value == old(global<Balance>(recipient).value) + amount;
}

This level of verification is possible because Move's semantics are clean and well-defined.


Understanding Sui's Object-Centric Model

The object-centric model is Sui's most distinctive feature. Let's compare it to Ethereum's account-based approach to understand why it matters.

Ethereum's Account-Based Model

In Ethereum, the blockchain tracks accounts:

  • Externally Owned Accounts (EOAs): Controlled by private keys
  • Contract Accounts: Controlled by code

Tokens exist as entries in contract storage (mappings). When you "have" 100 USDC, there's an entry in the USDC contract's storage: balances[your_address] = 100.

Ethereum State:
├── Account 0x123...
│   ├── Balance: 1.5 ETH
│   └── Nonce: 42
└── USDC Contract 0xA0b...
    └── Storage:
        └── balances mapping
            ├── 0x123... → 100
            └── 0x456... → 250

Sui's Object-Centric Model

In Sui, the blockchain tracks objects:

Sui State:
├── Object 0xAAA (Coin<SUI>)
│   ├── Owner: 0x123...
│   └── Value: 1500000000
├── Object 0xBBB (Coin<USDC>)
│   ├── Owner: 0x123...
│   └── Value: 100
├── Object 0xCCC (NFT)
│   ├── Owner: 0x123...
│   └── Metadata: {...}
└── Object 0xDDD (Coin<USDC>)
    ├── Owner: 0x456...
    └── Value: 250

Each asset is a distinct object with its own ID and explicit owner.

Why Object-Centric is Better

1. Natural Parallelization

Since objects are independent, transactions touching different objects can execute in parallel:

Transaction 1: Transfer Object 0xAAA from Alice to Bob
Transaction 2: Transfer Object 0xDDD from Carol to Dave

→ These execute simultaneously (different objects)

2. Clear Ownership Semantics

Ownership is explicit and verifiable:

// This function can ONLY be called by the object's owner
public entry fun use_my_object(obj: MyObject, ctx: &TxContext) {
    // Only works if tx_context::sender(ctx) owns obj
}

3. Simplified Reasoning

When you have an object, you HAVE it. No need to query balances or worry about state changes elsewhere:

public fun combine_coins(coin1: Coin<SUI>, coin2: Coin<SUI>): Coin<SUI> {
    // You definitely have both coins - the type system guarantees it
    coin::join(coin1, coin2)
}

4. Better Composability

Objects can be passed between modules freely:

// Module A creates an object
let reward = module_a::claim_reward(ctx);

// Module B accepts it
module_b::stake_reward(reward);

// No approvals, no callbacks, no trust assumptions

Code Examples: Learning by Doing

Let's write real Sui Move code. These examples progress from simple to more complex, giving you practical patterns you can use.

Example 1: Simple Fungible Token

module examples::simple_coin {
    use sui::coin::{Self, Coin, TreasuryCap};
    use sui::url;

    /// The type identifier for our coin
    public struct SIMPLE_COIN has drop {}

    /// Initialize the coin (called once on module publish)
    fun init(witness: SIMPLE_COIN, ctx: &mut TxContext) {
        let (treasury_cap, metadata) = coin::create_currency<SIMPLE_COIN>(
            witness,
            9,                  // decimals
            b"SIMPLE",          // symbol
            b"Simple Coin",     // name
            b"A simple example coin", // description
            option::some(url::new_unsafe_from_bytes(b"https://example.com/icon.png")),
            ctx
        );
        
        // Transfer treasury cap to deployer
        transfer::public_transfer(treasury_cap, tx_context::sender(ctx));
        // Freeze metadata (make immutable)
        transfer::public_freeze_object(metadata);
    }

    /// Mint new coins (only treasury cap holder can call)
    public entry fun mint(
        treasury_cap: &mut TreasuryCap<SIMPLE_COIN>,
        amount: u64,
        recipient: address,
        ctx: &mut TxContext
    ) {
        let coin = coin::mint(treasury_cap, amount, ctx);
        transfer::public_transfer(coin, recipient);
    }

    /// Burn coins
    public entry fun burn(
        treasury_cap: &mut TreasuryCap<SIMPLE_COIN>,
        coin: Coin<SIMPLE_COIN>
    ) {
        coin::burn(treasury_cap, coin);
    }
}

Example 2: NFT Collection

module examples::nft_collection {
    use std::string::{Self, String};
    use sui::event;
    use sui::package;
    use sui::display;

    /// The NFT type
    public struct GameItem has key, store {
        id: UID,
        name: String,
        description: String,
        image_url: String,
        attributes: Attributes,
    }

    public struct Attributes has store {
        power: u64,
        rarity: String,
        level: u64,
    }

    /// One-time witness for publisher
    public struct NFT_COLLECTION has drop {}

    /// Event emitted when NFT is minted
    public struct NFTMinted has copy, drop {
        object_id: ID,
        creator: address,
        name: String,
    }

    /// Initialize display settings
    fun init(otw: NFT_COLLECTION, ctx: &mut TxContext) {
        let keys = vector[
            string::utf8(b"name"),
            string::utf8(b"description"),
            string::utf8(b"image_url"),
            string::utf8(b"power"),
            string::utf8(b"rarity"),
        ];
        
        let values = vector[
            string::utf8(b"{name}"),
            string::utf8(b"{description}"),
            string::utf8(b"{image_url}"),
            string::utf8(b"{attributes.power}"),
            string::utf8(b"{attributes.rarity}"),
        ];

        let publisher = package::claim(otw, ctx);
        let mut display = display::new_with_fields<GameItem>(
            &publisher, keys, values, ctx
        );
        
        display::update_version(&mut display);
        
        transfer::public_transfer(publisher, tx_context::sender(ctx));
        transfer::public_transfer(display, tx_context::sender(ctx));
    }

    /// Mint a new game item
    public entry fun mint(
        name: vector<u8>,
        description: vector<u8>,
        image_url: vector<u8>,
        power: u64,
        rarity: vector<u8>,
        recipient: address,
        ctx: &mut TxContext
    ) {
        let nft = GameItem {
            id: object::new(ctx),
            name: string::utf8(name),
            description: string::utf8(description),
            image_url: string::utf8(image_url),
            attributes: Attributes {
                power,
                rarity: string::utf8(rarity),
                level: 1,
            },
        };

        event::emit(NFTMinted {
            object_id: object::id(&nft),
            creator: tx_context::sender(ctx),
            name: nft.name,
        });

        transfer::public_transfer(nft, recipient);
    }

    /// Level up an item
    public entry fun level_up(item: &mut GameItem) {
        item.attributes.level = item.attributes.level + 1;
        item.attributes.power = item.attributes.power + 10;
    }

    /// Burn (destroy) an item
    public entry fun burn(item: GameItem) {
        let GameItem { id, name: _, description: _, image_url: _, attributes: _ } = item;
        object::delete(id);
    }
}

Example 3: Simple DeFi - Liquidity Pool

module examples::simple_pool {
    use sui::coin::{Self, Coin};
    use sui::balance::{Self, Balance};

    /// Pool holding two token types
    public struct Pool<phantom A, phantom B> has key {
        id: UID,
        reserve_a: Balance<A>,
        reserve_b: Balance<B>,
        lp_supply: u64,
    }

    /// LP token representing pool share
    public struct LPToken<phantom A, phantom B> has key, store {
        id: UID,
        amount: u64,
    }

    /// Create a new liquidity pool
    public entry fun create_pool<A, B>(ctx: &mut TxContext) {
        let pool = Pool<A, B> {
            id: object::new(ctx),
            reserve_a: balance::zero<A>(),
            reserve_b: balance::zero<B>(),
            lp_supply: 0,
        };
        transfer::share_object(pool);
    }

    /// Add liquidity to the pool
    public fun add_liquidity<A, B>(
        pool: &mut Pool<A, B>,
        coin_a: Coin<A>,
        coin_b: Coin<B>,
        ctx: &mut TxContext
    ): LPToken<A, B> {
        let amount_a = coin::value(&coin_a);
        let amount_b = coin::value(&coin_b);
        
        // Add to reserves
        balance::join(&mut pool.reserve_a, coin::into_balance(coin_a));
        balance::join(&mut pool.reserve_b, coin::into_balance(coin_b));
        
        // Calculate LP tokens (simplified: geometric mean)
        let lp_amount = sqrt(amount_a * amount_b);
        pool.lp_supply = pool.lp_supply + lp_amount;
        
        LPToken<A, B> {
            id: object::new(ctx),
            amount: lp_amount,
        }
    }

    /// Swap token A for token B
    public fun swap_a_for_b<A, B>(
        pool: &mut Pool<A, B>,
        coin_in: Coin<A>,
        ctx: &mut TxContext
    ): Coin<B> {
        let amount_in = coin::value(&coin_in);
        let reserve_a = balance::value(&pool.reserve_a);
        let reserve_b = balance::value(&pool.reserve_b);
        
        // Constant product formula: x * y = k
        // amount_out = (amount_in * reserve_b) / (reserve_a + amount_in)
        let amount_out = (amount_in * reserve_b) / (reserve_a + amount_in);
        
        // Update reserves
        balance::join(&mut pool.reserve_a, coin::into_balance(coin_in));
        let out_balance = balance::split(&mut pool.reserve_b, amount_out);
        
        coin::from_balance(out_balance, ctx)
    }

    /// Integer square root helper
    fun sqrt(x: u64): u64 {
        if (x == 0) return 0;
        let mut z = (x + 1) / 2;
        let mut y = x;
        while (z < y) {
            y = z;
            z = (x / z + z) / 2;
        };
        y
    }
}

Development Tools and Environment

Building with the Sui Move programming language requires familiarity with several tools.

Sui CLI

The Sui CLI is your primary interface for development:

# Install Sui CLI
cargo install --locked --git https://github.com/MystenLabs/sui.git --branch mainnet sui

# Or use pre-built binaries
# Download from https://github.com/MystenLabs/sui/releases

# Check installation
sui --version

Common commands:

# Create a new Move project
sui move new my_project

# Build your project
sui move build

# Run tests
sui move test

# Publish to network
sui client publish --gas-budget 100000000

# Call a function
sui client call --package <PACKAGE_ID> --module <MODULE> --function <FUNCTION> --args <ARGS>

Move Analyzer (IDE Support)

For VSCode, install the Move Analyzer extension:

  1. Install the extension from VSCode marketplace
  2. Install the language server: cargo install --git https://github.com/move-language/move move-analyzer
  3. Configure path in VSCode settings

Features include:

  • Syntax highlighting
  • Go to definition
  • Type information on hover
  • Error diagnostics
  • Auto-completion

Testing Framework

Sui Move includes a powerful testing framework:

#[test_only]
module examples::pool_tests {
    use sui::test_scenario::{Self as ts, Scenario};
    use sui::coin;
    use sui::sui::SUI;
    use examples::simple_pool::{Self, Pool};

    #[test]
    fun test_create_pool() {
        let mut scenario = ts::begin(@0x1);
        
        // Create pool
        ts::next_tx(&mut scenario, @0x1);
        {
            simple_pool::create_pool<SUI, SUI>(ts::ctx(&mut scenario));
        };
        
        // Verify pool exists
        ts::next_tx(&mut scenario, @0x1);
        {
            let pool = ts::take_shared<Pool<SUI, SUI>>(&scenario);
            // Assert pool state...
            ts::return_shared(pool);
        };
        
        ts::end(scenario);
    }

    #[test]
    #[expected_failure(abort_code = 1)]
    fun test_swap_empty_pool_fails() {
        // Test that swapping in empty pool fails
        // ...
    }
}

Run tests with:

sui move test
sui move test --filter test_create  # Run specific tests
sui move test --coverage            # Generate coverage report

Local Network

For development, run a local Sui network:

# Start local network
sui start --with-faucet --force-regenesis

# In another terminal, configure CLI for local
sui client new-env --alias local --rpc http://127.0.0.1:9000
sui client switch --env local

# Get test tokens
sui client faucet

Sui Explorer

Use the Sui Explorer to inspect transactions, objects, and packages:

  • Mainnet: https://suiexplorer.com
  • Testnet: https://suiexplorer.com/?network=testnet
  • Local: https://suiexplorer.com/?network=local

Why Kairo Guard Chose Sui

At Kairo, we evaluated multiple blockchains before building our policy enforcement system. Sui's architecture aligned perfectly with our security requirements. Here's why:

Parallel Execution for Real-Time Protection

When a user initiates a transaction, Kairo needs to evaluate it against their security policies instantly. Sui's parallel execution means our policy checks don't create bottlenecks:

/// Kairo policy evaluation happens in parallel with the transaction
public fun evaluate_transaction(
    policy: &Policy,
    tx_details: &TransactionDetails,
): bool {
    // Check spending limits
    if (tx_details.amount > policy.daily_limit) {
        return false
    };
    
    // Check recipient allowlist
    if (!vector::contains(&policy.allowed_recipients, &tx_details.recipient)) {
        return false
    };
    
    true
}

Object Ownership for Policy Enforcement

Sui's ownership model lets us create policies that are cryptographically bound to user assets:

/// A Kairo security policy
public struct SecurityPolicy has key {
    id: UID,
    owner: address,
    daily_limit: u64,
    allowed_contracts: vector<address>,
    require_2fa_above: u64,
    cooldown_period: u64,
}

/// Policy-protected asset wrapper
public struct ProtectedAsset<T: key + store> has key {
    id: UID,
    asset: T,
    policy_id: ID,
}

Because objects have clear ownership, we can enforce that policy checks happen before any asset movement.

Move's Security Guarantees

The Sui Move programming language's type system helps us prevent the vulnerabilities that plague other security tools:

  1. No reentrancy: Our policy checks can't be bypassed through callback attacks
  2. Resource safety: Protected assets can't be duplicated or extracted without policy approval
  3. Compile-time verification: Many potential bugs are caught before deployment

Immutable Audit Trail

Every policy decision is recorded as an on-chain event:

public struct PolicyDecision has copy, drop {
    policy_id: ID,
    transaction_id: ID,
    decision: bool,
    timestamp: u64,
    reason: String,
}

This creates an immutable audit trail that users can verify independently.

Composability with DeFi

Sui's object model means Kairo-protected assets can still participate in DeFi:

/// Use a protected asset in DeFi while maintaining policy enforcement
public fun stake_with_policy<T: key + store>(
    protected: &mut ProtectedAsset<T>,
    policy: &SecurityPolicy,
    pool: &mut StakingPool<T>,
    ctx: &mut TxContext
) {
    // Verify policy allows staking
    assert!(policy_allows_staking(policy, object::id(pool)), EStakingNotAllowed);
    
    // Perform the stake with policy oversight
    // ...
}

Users get security without sacrificing functionality.


Getting Started: Your First Sui Move Project

Ready to write your first Sui Move code? Let's set up your environment and build a simple project.

Step 1: Install Prerequisites

macOS/Linux:

# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Install Sui
cargo install --locked --git https://github.com/MystenLabs/sui.git --branch mainnet sui

Windows:

# Install Rust from https://rustup.rs
# Then in PowerShell:
cargo install --locked --git https://github.com/MystenLabs/sui.git --branch mainnet sui

Verify installation:

sui --version
# sui 1.x.x

Step 2: Create Your Project

sui move new hello_sui
cd hello_sui

This creates:

hello_sui/
├── Move.toml          # Project manifest
├── sources/           # Your Move code goes here
└── tests/             # Test files

Step 3: Write Your First Module

Edit sources/hello.move:

module hello_sui::greeting {
    use std::string::{Self, String};

    /// A greeting card object
    public struct GreetingCard has key, store {
        id: UID,
        message: String,
        from: address,
        to: address,
    }

    /// Create and send a greeting card
    public entry fun send_greeting(
        message: vector<u8>,
        recipient: address,
        ctx: &mut TxContext
    ) {
        let card = GreetingCard {
            id: object::new(ctx),
            message: string::utf8(message),
            from: tx_context::sender(ctx),
            to: recipient,
        };
        transfer::transfer(card, recipient);
    }

    /// Read the message (view function)
    public fun read_message(card: &GreetingCard): &String {
        &card.message
    }

    /// Burn (delete) a greeting card
    public entry fun burn_card(card: GreetingCard) {
        let GreetingCard { id, message: _, from: _, to: _ } = card;
        object::delete(id);
    }
}

Step 4: Build and Test

# Build
sui move build

# Add a test

Create tests/greeting_tests.move:

#[test_only]
module hello_sui::greeting_tests {
    use sui::test_scenario as ts;
    use hello_sui::greeting::{Self, GreetingCard};
    use std::string;

    #[test]
    fun test_send_greeting() {
        let sender = @0xA;
        let recipient = @0xB;
        
        let mut scenario = ts::begin(sender);
        
        // Send a greeting
        ts::next_tx(&mut scenario, sender);
        {
            greeting::send_greeting(
                b"Hello, Sui!",
                recipient,
                ts::ctx(&mut scenario)
            );
        };
        
        // Recipient receives it
        ts::next_tx(&mut scenario, recipient);
        {
            let card = ts::take_from_sender<GreetingCard>(&scenario);
            assert!(greeting::read_message(&card) == &string::utf8(b"Hello, Sui!"), 0);
            ts::return_to_sender(&scenario, card);
        };
        
        ts::end(scenario);
    }
}

Run tests:

sui move test

Step 5: Deploy to Testnet

# Connect to testnet
sui client new-env --alias testnet --rpc https://fullnode.testnet.sui.io:443
sui client switch --env testnet

# Get test SUI tokens
sui client faucet

# Deploy
sui client publish --gas-budget 100000000

Save the package ID from the output—you'll need it to interact with your contract.

Step 6: Interact with Your Contract

# Send a greeting
sui client call \
    --package <PACKAGE_ID> \
    --module greeting \
    --function send_greeting \
    --args "Hello from the CLI!" <RECIPIENT_ADDRESS> \
    --gas-budget 10000000

Congratulations! You've deployed your first Sui Move smart contract.


Frequently Asked Questions

What is the Sui Move programming language?

Sui Move is a smart contract programming language used on the Sui blockchain. It's derived from the Move language originally created at Facebook for the Diem (formerly Libra) project. Sui Move features an object-centric programming model, strong type safety, and built-in protection against common smart contract vulnerabilities like reentrancy attacks.

How is Sui Move different from Solidity?

The main differences are:

  1. Resource safety: Move treats digital assets as resources that can't be copied or accidentally destroyed
  2. No reentrancy: Move's architecture makes reentrancy attacks impossible
  3. Object model: Sui uses objects instead of account-based storage
  4. Type system: Move has a more expressive type system with abilities (copy, drop, store, key)
  5. Parallel execution: Sui Move enables parallel transaction processing

Is Move difficult to learn for Solidity developers?

There's a learning curve, but many concepts transfer. The biggest adjustments are:

  • Thinking in objects instead of contract storage
  • Understanding the ability system
  • Getting used to explicit resource handling

Most Solidity developers become productive in Sui Move within a few weeks.

What are the "abilities" in Move?

Abilities are permissions that control what you can do with a type:

  • copy: Value can be duplicated
  • drop: Value can be discarded
  • store: Value can be stored in other structs
  • key: Value is a Sui object with a UID

Can I use Sui Move for DeFi applications?

Absolutely. Sui's parallel execution and low latency make it excellent for DeFi. Major protocols like Cetus, Turbos, and Scallop are built on Sui. The object model actually simplifies many DeFi patterns.

How does Sui's gas model work?

Sui uses a storage-based gas model:

  • Computation: Fees for CPU cycles
  • Storage: Fees for storing data on-chain (rebated when deleted)
  • Gas budget: Maximum you're willing to pay

Gas prices are stable and predictable compared to Ethereum's auction model.

What IDEs support Sui Move development?

  • VSCode with Move Analyzer extension (recommended)
  • IntelliJ with Move plugin
  • Sui Playground (browser-based)

How do I debug Sui Move code?

  1. Unit tests: Use the test framework extensively
  2. Print debugging: Use std::debug::print in tests
  3. Event logging: Emit events to trace execution
  4. Sui Explorer: Inspect transaction results on testnet/mainnet

Is Sui Move production-ready?

Yes. Sui mainnet has been running since May 2023, processing millions of transactions. Major applications with significant TVL are built on Sui Move. The language and tooling are mature.

Where can I learn more about Sui Move?

  • Official docs: https://docs.sui.io
  • Move book: https://move-book.com
  • Sui examples: https://github.com/MystenLabs/sui/tree/main/examples
  • Kairo blog: More tutorials coming soon

Conclusion

The Sui Move programming language represents a genuine evolution in smart contract development. Its resource-oriented design, object-centric model, and strong safety guarantees address the fundamental security challenges that have plagued blockchain development for years.

For developers, Move offers a refreshing paradigm—one where the type system works with you to prevent bugs rather than letting them slip through to production. The learning curve is real, but the payoff in security and developer experience is worth it.

At Kairo Guard, we chose Sui Move precisely because security isn't optional in our domain. When you're building tools that protect user assets, you need a foundation that makes the right thing easy and the wrong thing hard. Move delivers that.

Whether you're building DeFi protocols, NFT platforms, gaming applications, or security infrastructure, we encourage you to explore what Sui Move can offer. The ecosystem is growing rapidly, the tooling is maturing, and the community is welcoming to newcomers.

Ready to dive deeper? Set up your development environment, deploy the examples in this guide, and start building. The future of secure smart contracts is being written in Move.


Kairo Guard is a crypto security browser extension built on Sui that helps protect your digital assets through customizable security policies. Learn more about how we're using Sui Move to keep your crypto safe.

Ready to secure your crypto?

Kairo Guard brings 2PC-MPC security and policy-gated transactions to your existing wallet. No seed phrases, no single points of failure.

Get Early Access

© 2026 Kairo Guard. All rights reserved.