Move-based programming 2026 overview

Move-based programming 2026 shifts smart contract development away from generic state management toward strict object ownership. In this paradigm, every digital asset is a distinct resource that can only be created, transferred, or destroyed by explicit code logic. This approach eliminates the accidental duplication or loss of assets that has plagued earlier smart contract models.

The language was originally designed for the Diem blockchain and has since become the standard for high-throughput networks like Sui and Aptos. Unlike traditional reactive models where state changes are often implicit and prone to race conditions, Move treats resources as first-class citizens. The compiler enforces safety rules at build time, ensuring that assets follow precise lifecycles.

This security-first foundation enables richer composability and scalable design. Developers can build complex financial instruments and gaming mechanics with confidence that the underlying assets cannot be forged or double-spent. The result is a more robust environment for decentralized applications that require high transaction throughput and strict financial integrity.

Set up your move development environment

To start building with move-based programming, you need a local environment that mirrors the mainnet conditions. This section walks you through installing the Move CLI and configuring a local testnet node. These tools allow you to compile, test, and deploy contracts without risking real assets.

move-based programming
1
Install the Move CLI

The Move CLI is the primary interface for managing Move projects. It handles project scaffolding, compilation, and testing.

Install the latest version using the official installer script:

Shell
Shell
curl -sSf https://raw.githubusercontent.com/move-language/move/main/cli/move-cli/scripts/install.sh | sh

Verify the installation by checking the version. This ensures your toolchain matches the language specifications required for 2026 development.

Shell
Shell
move --version
move-based programming
2
Create a new Move project

Initialize a new project directory. The CLI will generate the standard folder structure, including the sources directory for your .move files and the tests directory for unit tests.

Shell
Shell
move new my_first_move_project
cd my_first_move_project

This structure enforces consistency across all Move-based programming efforts, making it easier to share and audit code later.

move-based programming
3
Configure a local testnet node

Run a local node to simulate network conditions. This step is critical for testing state transitions and transaction execution before deploying to a public chain like Sui or Aptos.

Start the local testnet using the Move CLI:

Shell
Shell
move test --with-move-node

This command spins up a local instance of the Move VM. You can now send transactions and inspect the state changes in real-time. For more complex network simulations, consider using Docker containers provided by the Sui or Aptos SDKs.

move-based programming
4
Write and compile your first module

Create a simple Move module in the sources directory. A basic module defines a struct and a function to initialize it.

MOVE
MOVE
module my_first_move_project::hello {
    use std::string;

    struct HelloMessage has key {
        id: UID,
        message: string::String
    }

    public fun init(msg: string::String, ctx: &mut TxContext) {
        let hello = HelloMessage {
            id: object::new(ctx),
            message: msg
        };
        transfer::public_freeze_object(hello);
    }
}

Compile the module to check for type errors and ownership violations:

Shell
Shell
move build
move-based programming
5
Run unit tests

Test your logic in isolation. Move’s testing framework allows you to create mock accounts and transactions.

Create a test file in the tests directory:

MOVE
MOVE
#[test]
fun test_hello_message() {
    use super::*;
    let ctx = test_context();
    hello::init(string::utf8(b"Hello, Move!"), &mut ctx);
}

Run the tests to ensure your logic holds up under scrutiny:

Shell
Shell
move test

Once your environment is set up, you are ready to write secure, composable smart contracts. The local testnet provides a safe sandbox for iterating on your logic before facing the public chain.

Write your first resource module

In Move, the resource type is the primary mechanism for enforcing strict ownership. Unlike standard data types, a resource struct cannot be copied or dropped implicitly. This ensures that digital assets—like tokens, NFTs, or game items—remain unique and traceable throughout their lifecycle.

To define a resource, you use the struct keyword alongside the resource annotation. The key annotation marks the struct as unique within a specific account. This combination prevents the accidental duplication or loss of critical assets, which is a common vulnerability in traditional smart contract languages.

Define the struct with key and resource annotations

Start by declaring a struct that represents your asset. The key field must be unique for each instance. This field often serves as an ID or a public address.

MOVE
module my_package::my_asset {
    use std::string;
    
    /// A unique asset that cannot be copied or dropped
    public struct MyAsset has key {
        id: UID,
        name: String,
        value: u64,
    }
}

The has key capability tells the Move compiler that this struct represents a unique entity. The id: UID field is automatically managed by the Move runtime to ensure uniqueness. You do not need to manually generate IDs; the compiler handles this behind the scenes.

Compare standard structs vs. resource structs

Understanding the difference between a standard struct and a resource struct is critical. A standard struct can be copied and dropped freely. A resource struct cannot. This distinction is what makes Move secure for financial and asset-based applications.

In the standard struct, you can copy Coin values and drop them without consequence. In the resource struct, you must explicitly move the Asset to another account or destroy it using a drop function. This prevents accidental duplication.

Implement the mint function

To create a new resource, you need a function that initializes the UID and sets the initial values. This function is typically called mint.

MOVE
public fun mint(sender: &signer, name: String, value: u64) {
    let asset = MyAsset {
        id: object::new(sender),
        name,
        value,
    };
    // Move the asset to the sender's account
    transfer::public_transfer(asset, sender);
}

The object::new(sender) call generates a unique UID tied to the transaction sender. The transfer::public_transfer function moves the asset into the sender's account, where it is now stored as a resource. This process is atomic and secure, ensuring that the asset is never left in a limbo state.

Execute programmable transaction blocks

A programmable transaction block (PTB) lets you bundle multiple operations into a single atomic unit. Instead of sending separate transactions for each action, you chain them together so they either all succeed or all fail. This is the standard way to move resources between accounts, swap assets, or interact with smart contracts on Move-based networks like Sui.

Think of a PTB as a single envelope containing several letters. The postal service delivers the whole envelope at once. If one letter is damaged, the entire envelope is returned undelivered. In blockchain terms, this atomicity ensures that complex multi-step operations don't leave your account in an inconsistent state.

Step 1: Define the transaction builder

Start by initializing a transaction builder object. This object acts as the container for your sequence of operations. You will add commands to it one by one, defining the logic flow before signing.

JavaScript
import { Transaction } from '@mysten/sui/transactions';

const tx = new Transaction();

Step 2: Load or create the resource

Before moving a resource, you need a reference to it. If you are transferring an existing asset, you load it from the sender's account. If you are creating a new asset, you call the module's creation function. The resource must be marked as transferable to be moved between accounts.

JavaScript
// Example: Loading an existing SUI coin
const coin = tx.object("0x123..."); // Resource object ID

Step 3: Add the transfer command

Use the moveCall or specific helper functions to move the resource. The transfer command moves assets directly to another address. The transferObject command moves objects that carry struct data. Ensure the recipient address is correctly formatted.

JavaScript
// Move a coin to a recipient
const recipient = "0xRecipientAddress...";
tx.splitCoins(coin, [tx.pure.u64(1000)]); // Split for partial transfer
tx.transferObjects([coin], recipient);

Step 4: Sign and execute

Once all commands are added, sign the transaction with your private key. The network validates the atomicity and resource ownership before committing the block. If any step fails, the entire PTB is reverted, and no state changes occur.

JavaScript
const result = await client.signAndExecuteTransaction({
  transaction: tx,
  options: { showEffects: true }
});
console.log("Block Digest:", result.digest);

Step 5: Verify the result

Check the transaction effects to confirm the resource moved successfully. The effects field will show the new object ownership and any events emitted. If the status is success, the PTB executed as intended.

JavaScript
if (result.effects.status.status === "success") {
  console.log("PTB executed successfully");
} else {
  console.error("PTB failed:", result.effects.status.error);
}

Common PTB mistakes

  • Forgetting atomicity: Assuming intermediate steps persist if the final step fails. They do not.
  • Incorrect type tags: Move requires precise type tags for generic resources. Mismatched types cause immediate rejection.
  • Gas estimation errors: Complex PTBs consume more gas. Always estimate gas before signing to avoid transaction failures due to insufficient funds.

Why use PTBs?

PTBs reduce network latency and transaction fees by bundling operations. They also improve security by ensuring that dependent actions never execute in isolation. This is essential for applications like decentralized exchanges or NFT marketplaces where state consistency is critical.

What happens if one command in a PTB fails?

The entire transaction block is reverted. No changes are committed to the blockchain, and the sender retains their resources.

Can I call multiple functions in one PTB?

Yes. You can chain multiple moveCall commands, object transfers, and coin splits in a single PTB.

Do PTBs require more gas than single transactions?

PTBs often have lower total gas costs than executing the same operations as separate transactions, due to reduced overhead.

Debug common ownership errors

Move-based programming 2026 enforces strict ownership rules to prevent resource duplication. When you try to use a value after it has been moved, the compiler throws an error. This safety net prevents the double-spend vulnerabilities common in earlier blockchain languages.

Missing ownership transfers

The most frequent mistake is attempting to access a resource after it has been transferred to another variable or function. In Move, ownership is singular. Once a resource is moved, the original handle is invalid.

To fix this, ensure you are not using a variable after its value has been moved. If you need to share data, pass a reference instead. References borrow the value without taking ownership, allowing the original variable to remain valid.

Accidental duplication

Move resources cannot be copied unless explicitly marked with the copy capability. Attempting to duplicate a resource that lacks this capability results in a compilation error. This design ensures that unique assets, like NFTs or tokens, remain distinct and cannot be accidentally cloned.

If you need to duplicate data, verify that the resource struct includes the copy attribute. For standard resources, always use move semantics to transfer ownership cleanly.

move-based programming

Verify your move-based application

Before deploying to mainnet, you must validate that your smart contract enforces Move’s security guarantees. The language’s ownership model prevents duplicate spending and unauthorized access, but only if your logic correctly implements resource constraints and access controls.

Start by running unit tests that cover edge cases, particularly around resource destruction and transfer. Use the Move framework’s testing utilities to simulate on-chain behavior, ensuring that no state can be manipulated outside defined rules. Focus on verifying that resources are properly encapsulated and that only authorized functions can modify them.

Once local tests pass, proceed to a testnet deployment. This step exposes your contract to real network conditions, including gas limits and transaction ordering. Monitor the deployment logs for any unexpected failures or gas spikes, which often indicate inefficient logic or security loopholes.

For final verification, consider using formal verification tools if your contract handles high-value assets. These tools mathematically prove that your code satisfies specific safety properties, offering an extra layer of assurance beyond standard testing. Always review the Move specification documentation to ensure your implementation aligns with the language’s core security principles [src-serp-1].

move-based programming

Pre-deployment checklist

  • Resource safety: Ensure all resources are properly tracked and cannot be duplicated or lost.
  • Access control: Verify that only authorized functions can modify critical state.
  • Gas optimization: Check for inefficient loops or redundant calculations that could lead to transaction failures.
  • Testnet validation: Confirm the contract behaves as expected under real network conditions.
  • Formal verification: For high-value contracts, use mathematical proofs to validate security properties.

Move-based programming 2026: what to check next

Here are the most common technical questions about Move-based programming, performance, and compatibility.