What move-based programming solves

Move-based programming addresses a specific failure mode in smart contracts: the accidental loss or duplication of digital assets. Unlike Solidity, which treats tokens as simple balances in a database, Move treats assets as first-class resources. This means a token isn't just a number; it is an object that must be explicitly moved from one account to another.

This resource-oriented approach prevents two of the most common vulnerabilities in blockchain development. First, it eliminates reentrancy attacks because resources cannot be copied. Second, it prevents inflation bugs because resources cannot be dropped or destroyed without explicit code. In Solidity, a developer might accidentally leave a balance in a contract; in Move, the compiler forces you to account for every single unit of value.

The language draws heavily from Rust, adopting its ownership model to ensure memory safety, but applies it to on-chain state. While Rust focuses on preventing memory leaks in systems programming, Move focuses on preventing value leaks in decentralized finance. This distinction makes Move-based programming particularly effective for high-value transactions where asset integrity is non-negotiable.

Set up the move development environment

Move-based programming requires a dedicated toolchain to compile smart contracts and simulate transactions before deployment. You can choose between the Sui or Aptos ecosystems, as both provide robust environments for building secure, resource-oriented applications. This guide focuses on the foundational setup steps required to begin writing and testing Move code.

move-based programming
1
Install the Move CLI

Start by installing the Move Command Line Interface (CLI). This tool is essential for compiling Move modules and managing project dependencies. You can install it using Cargo, Rust’s package manager, by running cargo install move-cli. Verify the installation by checking the version with move --version to ensure the tool is correctly configured in your system path.

2
Initialize a new project

Once the CLI is installed, create a new project directory using the move new command. This generates a standard directory structure including sources for your Move code, tests for integration tests, and a Move.toml configuration file. This file defines your package dependencies and specifies the Move version you intend to use, ensuring consistency across your development environment.

3
Configure your ecosystem

Move is not tied to a single blockchain; it is a language that can be hosted on various platforms. If you are building for Sui or Aptos, you must update your Move.toml to include the specific standard library dependencies for that ecosystem. For example, Sui projects typically require the sui-framework dependency, while Aptos projects use aptos-move frameworks. This step ensures your code compiles against the correct runtime libraries.

4
Verify with a test build

Before writing complex logic, run a test build to confirm your environment is functional. Use the move build command to compile your empty or sample modules. If the build succeeds without errors, your development environment is ready. This step catches configuration issues early, such as missing dependencies or incorrect Move version specifications, saving time during later development stages.

Write your first resource-based contract

Move-based programming treats digital assets as resources rather than simple data. This distinction prevents common vulnerabilities like double-spending by ensuring that a resource can only exist in one place at a time. You will define a struct with the resource annotation to create a unique, non-copyable asset, then write a function to mint it.

move-based programming
1
Define the resource struct

In Move, a struct defines the shape of your data. To make it a secure resource, add the resource annotation. This tells the Move VM that this struct cannot be copied or destroyed implicitly. It must be explicitly moved or destroyed by code.

MOVE
MOVE
struct Coin has key {
    value: u64
}

The has key capability allows this struct to be stored in global storage. The value field holds the amount. Without has key, the struct could only exist in local variables.

2
Create the minting function

Next, write a function that creates a new instance of your resource. This function typically takes a signer argument, which represents the account creating the asset. The signer’s capabilities are required to publish the resource to the blockchain.

MOVE
MOVE
fun mint_coin(signer: signer, amount: u64) {
    let coin = Coin { value: amount };
    move_to(&signer, coin);
}

The move_to function transfers the coin struct into the signer’s account storage. This makes the resource accessible to that specific address.

3
Verify security constraints

Move’s compiler enforces strict rules. If you try to copy the Coin struct without the copy capability, the code will fail to compile. This ensures that every unit of your asset has a single, verifiable owner. You can test this by attempting to duplicate the coin in a test script.

This basic structure forms the foundation of Move-based programming. By anchoring your logic to resource semantics, you build contracts that are inherently safer against common blockchain exploits. As you expand, you will add transfer functions to allow users to send these resources to others.

Avoid common move syntax mistakes

Developers transitioning from Rust or Solidity often hit specific syntax and semantic walls in Move. These errors usually stem from applying familiar patterns where Move enforces stricter resource safety. Understanding these distinctions prevents compilation failures and logical bugs in production contracts.

Misunderstanding ownership transfer

Move’s core innovation is its explicit ownership model. In Rust, moving a value transfers ownership silently. In Move, this is not just a compiler hint; it is a fundamental constraint for resource types. If you attempt to use a resource after it has been "moved," the compiler rejects the code immediately.

A common pitfall is assuming you can copy or clone a resource type as you might a standard struct. By default, resources cannot be copied. You must explicitly implement the copy and drop abilities in your struct definition if you want those behaviors. Without these abilities, the resource is destroyed when it goes out of scope or is explicitly consumed. Treat every resource like a unique physical asset: it has one owner, and that owner decides its fate.

Misusing the destroy keyword

The destroy keyword in Move is often confused with Rust’s drop or Solidity’s deletion. In Move, destroy explicitly consumes a resource, invoking its drop ability and freeing the associated storage. This is a permanent action. Unlike Solidity, where deleting a mapping entry merely zeroes out the data but may leave storage slots occupied depending on the implementation, Move’s destroy fully reclaims the resource.

Developers frequently misuse destroy on non-resource structs or forget to implement drop when they intend to destroy a resource. If a struct lacks the drop ability, the compiler will refuse to let you destroy it, forcing you to keep it alive or redesign the struct. Always ensure your resource types explicitly declare drop if they are meant to be transient or consumable. This explicitness is what makes Move secure; it prevents accidental resource leaks or double-spending.

Forgetting use statements for standard libraries

Another frequent error is omitting use statements for core Move libraries like std::option or std::vector. While Rust and Solidity often provide these implicitly or through different import mechanisms, Move requires explicit imports for most standard types. Forgetting to import std::option::Option when trying to handle nullable values will result in confusing compiler errors. Always check the Move standard library documentation for the exact module path required for the types you are using.

Ignoring ability constraints in generics

When writing generic functions, you must explicitly specify the abilities required for the generic type parameter. If you write a function that takes a generic T and tries to destroy it, you must constrain T with drop. Similarly, if you need to copy a value, you must constrain T with copy. Failing to specify these constraints leads to type errors that can be difficult to debug, especially in complex contract architectures. Be precise with your generic constraints to ensure your code compiles and behaves as expected.

Test and deploy your move contract

Before sending your Move-based programming to mainnet, you must verify that the bytecode behaves exactly as intended. This section walks you through running local unit tests and deploying to the Sui testnet.

move-based programming
1
Run local unit tests

Open your terminal in the project directory and run sui move test. This command compiles your module and executes all functions tagged with #[test]. Review the output for any failures; if a test fails, the error message will point to the specific line in your Move code. Fix any logic errors and re-run until all tests pass green.

2
Check test coverage and edge cases

Passing tests is not enough. Ensure your test suite covers edge cases like zero-value transfers, invalid owner inputs, and gas limit scenarios. If your contract handles unique assets, verify that the ownership transfer logic holds under concurrent or repeated calls. This step prevents common vulnerabilities that unit tests might miss if they only check the "happy path."

3
Deploy to the Sui testnet

Once tests pass, deploy your contract to the Sui testnet using sui client publish --gas-budget 10000000. The testnet mimics mainnet behavior but uses free, faucet-provided SUI tokens. This allows you to verify that the deployment process works and that the contract initializes correctly in a live environment without risking real assets.

4
Verify deployment on the explorer

After deployment, the CLI returns a package ID and transaction digest. Copy the package ID and paste it into the Sui Explorer. Confirm that the module is published and that you can interact with the contract functions via the explorer’s UI. This final check ensures your bytecode is live and accessible before you consider the contract ready for production.

A pre-deployment checklist helps catch errors before they become expensive mistakes on mainnet.

Frequently asked questions about move