Set up your Move dev environment

Build Smart Contracts with Move works best as a sequence, not a scramble through settings. Do the minimum first: confirm compatibility, connect the core hardware, update only when needed, and test the result before adding optional features. That order keeps the task understandable and makes failures easier to isolate. After each step, pause long enough for the interface to finish syncing. Many setup problems are timing problems disguised as configuration problems. If the same step fails twice, record the exact error, restart the smallest affected piece, and retry before moving deeper.

move-based programming
1
Confirm prerequisites
Check compatibility, account access, firmware, network, and physical access before changing the Build Smart Contracts with Move setup.
move-based programming
2
Make one change at a time
Apply the setup steps in order so any connection, pairing, or permission failure is easy to isolate.
move-based programming
3
Verify the result
Test the final state from the app and from the physical device before adding automations or optional settings.

Write a basic Move module

Start by defining a module block that declares your package’s identity. Unlike Solidity, which bundles logic and state into contracts, Move structures code into modules that can be imported and composed. This modularity is the foundation of Move’s security model, allowing resources to be strictly typed and controlled.

Define a resource struct

Resources are Move’s primary innovation. They are first-class data types that cannot be copied or dropped accidentally. This prevents common vulnerabilities like reentrancy attacks or unintended token duplication. To define a resource, use the struct keyword with the key and store capabilities.

The key capability marks the struct as a unique identifier on-chain, while store determines if it can be held in other data structures. Without these capabilities, the struct behaves like a regular data type that can be copied or deleted freely.

MOVE
module my_package::my_module {
    use std::string;

    struct MyToken has key, store {
        id: UID,
        balance: u64,
        name: string::String,
    }
}

Implement a transfer function

Once you have a resource, you need a way to move it. Move enforces strict ownership rules: a resource can only be held by one account at a time unless explicitly transferred. Functions that handle resources must take them by mutable reference (&mut) or consume them entirely.

The following function demonstrates a simple transfer. It takes a mutable reference to the sender’s resource and a public key for the recipient. It then constructs a new resource for the recipient and destroys the original, ensuring the total supply remains constant.

MOVE
public fun transfer(
    sender: &signer,
    recipient: address,
    token: MyToken
) {
    // Transfer logic here
    // 1. Extract or modify token
    // 2. Create new instance for recipient
    // 3. Destroy original instance
}

Understand ownership and destruction

In Move, ownership is explicit. When you create a resource, you own it. When you pass it to a function, you give away that ownership. If you try to use a resource after it has been consumed, the compiler will throw an error. This "ownership model" eliminates entire classes of bugs found in other smart contract languages.

Resources must be explicitly destroyed when no longer needed. You cannot simply let them "fall out of scope." This requirement forces developers to think carefully about the lifecycle of their data, ensuring that value is either moved, stored, or intentionally burned.

Compile and verify

After writing your module, compile it using the Move CLI. The compiler checks for type safety, resource capabilities, and ownership rules. If the code passes, you can publish it to the Sui network. Always verify the bytecode to ensure it matches your source code, providing an extra layer of trust for users interacting with your contract.

This basic structure—module, resource struct, and transfer function—forms the skeleton of most Move applications. From here, you can expand with more complex logic, such as staking, governance, or NFT minting, all built on this secure foundation.

Handle resources and ownership

Move solves the double-spending problem by treating digital assets as linear types. In most blockchains, data is just bits in memory that can be copied or deleted at will. Move changes this: every resource is a unique object that must be explicitly moved, stored, or destroyed. You cannot duplicate it, and you cannot ignore it.

Think of a Move resource like a one-time use ticket. Once you hand it to someone, you no longer have it. The blockchain validates this transfer instantly. If your code tries to copy the ticket or leave it in a limbo state where no one owns it, the transaction fails immediately. This prevents the unauthorized duplication that plagues traditional smart contracts.

To implement this, you declare an asset using the resource keyword. This tells the compiler to enforce linear type rules. When you write a function to transfer this asset, the compiler ensures the sender’s balance is deducted and the receiver’s is credited in a single atomic step. There is no window for error.

Ownership in Move is strict. A resource can only exist in one place at a time: in a user’s account, in a module’s storage, or in a shared object. If you try to access a resource without holding the correct capability, the code won’t compile. This eliminates entire classes of bugs where contracts accidentally expose internal state or allow users to bypass payment checks.

Test and deploy your contract

Before sending real value to the blockchain, you must verify your Move module behaves exactly as intended. This section covers running local unit tests and deploying to the Sui testnet or mainnet. Treat the testnet as your proving ground; it mirrors mainnet conditions without risking actual capital.

move-based programming
1
Run local unit tests

Write unit tests inside your tests module. Use the #[test] and #[test_only] attributes to simulate transactions and check state changes. Run sui move test to execute them locally. This step catches logic errors before they touch the network. Aim for high coverage on critical functions like transfers and minting.

move-based programming
2
Compile for the target network

Compile your module specifically for the target network’s Move version. Use sui move build --package <package_name> to generate the bytecode. Verify the compilation output matches the Sui Move specification for your chosen chain (Sui Mainnet vs. Testnet). Mismatched versions cause deployment failures.

move-based programming
3
Deploy to testnet

Use sui client publish to deploy your contract to the Sui Testnet. This command uploads the bytecode and initializes the module. Confirm the transaction succeeded in the Sui Explorer. Testnet deployment validates that your code integrates correctly with the network’s consensus and state machine.

move-based programming
4
Run integration tests on testnet

Interact with your deployed testnet contract using sui client call. Test edge cases, failure paths, and gas consumption. Use the Sui CLI to simulate user interactions. This step ensures your contract handles real-world transaction patterns and network latency correctly.

move-based programming
5
Deploy to mainnet

Once testnet integration tests pass, deploy to Sui Mainnet. Double-check the package ID and object IDs. Mainnet deployment is irreversible; mistakes cost real money. Monitor the initial transactions closely for any unexpected behavior or gas spikes.

Common Move development mistakes

Even with Move’s safety guarantees, developers still face pitfalls that can lead to vulnerabilities or broken logic. The language’s resource model is strict, and misunderstanding how it works is the most common source of errors.

Misusing capabilities and resources

Move treats resources as first-class citizens that cannot be copied or dropped. If you try to use a resource without explicitly handling its lifecycle, the compiler will reject it. A frequent mistake is returning a capability without transferring ownership, leaving the object in an inaccessible state. Always ensure you are moving the resource to the intended destination or storing it securely.

Ignoring object lifecycles

Objects in Move have a specific lifecycle that must be respected. Failing to check if an object exists before accessing it, or attempting to destroy an object that is still in use, causes runtime errors. Treat object existence like a check before you open a door; if the door isn’t there, don’t try to walk through it. Always validate the state of an object before performing operations on it.

move-based programming

Move programming FAQs for 2026

As the landscape of blockchain development shifts, questions about AI's role and current industry standards come up frequently. Here are the most common queries regarding Move smart contracts in 2026.