Why Move changes asset logic

Move-Based Programming works best as a clear sequence: define the constraint, compare the realistic options, test the tradeoff, and choose the path with the fewest hidden costs. That order keeps the advice usable instead of decorative. After each step, pause long enough to check whether the recommendation still fits the reader's actual situation. If it depends on perfect timing, unusual access, or a best-case budget, include a simpler fallback.

The simplest way to use this section is to write down the real constraint first, compare each option against it, and choose the path that still works outside ideal conditions.

Set up your Move development environment

Move is a resource-oriented language designed for blockchain applications where correctness and asset safety are paramount. Before writing smart contracts for Sui or Aptos, you must install the Move compiler and structure your project correctly. The Move language treats resources as first-class citizens, meaning they cannot be copied or discarded implicitly, which requires a specific development workflow.

move-based programming
1
Install the Move CLI

Use Cargo, the Rust package manager, to install the Move binary. This command fetches the latest stable compiler and CLI tools from the official repository.

Shell
Shell
cargo install --git https://github.com/move-language/move move-cli

Verify the installation by checking the version. This confirms the compiler is available in your system path and ready for project initialization.

Shell
Shell
move --version
move-based programming
2
Initialize a new Move package

Create a new directory for your smart contract and initialize it as a Move package. This command generates the standard project structure, including the Move.toml configuration file and a sources directory for your .move modules.

Shell
Shell
move init --name my-first-contract

The Move.toml file defines the package dependencies and compiler settings. For Sui or Aptos projects, you will later add the respective framework dependencies here to access standard library modules.

move-based programming
3
Verify the project structure

Ensure your project directory contains the essential files. The sources folder holds your contract code, while tests (optional) holds integration tests. A correctly initialized package allows you to run the compiler and type checker immediately.

Text
Text
my-first-contract/
├── Move.toml
└── sources/
    └── my_first_module.move

Run the build command to compile the module and check for type errors. This step ensures your environment is correctly configured before you start writing complex logic.

Shell
Shell
move build

Define resources and object models

Move treats digital assets as first-class resources rather than simple data structures. This object-centric model prevents accidental copying or deletion, ensuring that on-chain assets maintain their integrity and ownership rules at the language level.

To define a custom resource, you use the struct keyword combined with specific annotations. The key annotation marks the struct as a unique object identifier, while store allows it to be held inside other containers. Without these annotations, the struct remains a temporary value that cannot be stored on-chain or transferred as an asset.

The id: UID field is mandatory for any struct with the key capability. It serves as the unique identifier for the object in the global storage, allowing the Move VM to track ownership and prevent duplicate instances. The store capability determines whether this resource can be nested inside other structs or kept in account-specific storage, enabling complex asset compositions.

When you declare these capabilities, you are explicitly telling the compiler how the asset behaves. Resources with key can be moved into global storage, while those with store can be passed around freely within the constraints of the object model. This strict typing eliminates entire classes of bugs related to asset duplication or unauthorized transfers.

Write a programmable transaction block

Move-Based Programming works best as a clear sequence: define the constraint, compare the realistic options, test the tradeoff, and choose the path with the fewest hidden costs. That order keeps the advice usable instead of decorative. After each step, pause long enough to check whether the recommendation still fits the reader's actual situation. If it depends on perfect timing, unusual access, or a best-case budget, include a simpler fallback.

move-based programming
1
Define the constraint
Name the space, budget, timing, or skill limit that shapes the Move-Based Programming decision.
move-based programming
2
Compare realistic options
Use the same criteria for each option so the tradeoff is visible.
move-based programming
3
Choose the practical path
Pick the option that still works after cost, maintenance, and fallback needs are included.

Common Move Security Pitfalls

Even with Move’s type system, developers can still introduce vulnerabilities by mishandling resources. The most frequent errors involve improper resource dropping and incorrect dynamic field access. These mistakes often lead to lost assets or unintended state mutations.

Dropping Resources Incorrectly

In Move, a resource must be explicitly dropped using drop() or transferred to another owner. If you ignore a resource value, the compiler throws an error. However, some developers mistakenly try to "drop" resources inside functions that don’t return them, leading to compilation failures or logic gaps.

MOVE
// ❌ Wrong: Ignoring a resource value
fun bad_example() {
    let coin = Coin::new(100);
    // coin is ignored here, causing a compile error
}

// ✅ Correct: Explicitly dropping
fun good_example() {
    let coin = Coin::new(100);
    drop(coin);
}

Mishandling Dynamic Fields

Dynamic fields allow contracts to store arbitrary data on-chain. A common pitfall is failing to check if a field exists before accessing it, which can cause runtime panics. Always verify the existence of a dynamic field using exists() before calling borrow() or take().

MOVE
// ❌ Wrong: No existence check
fun unsafe_access(account: &signer) {
    let field = dynamic_field::borrow<address, String>(account, "my_key");
    // Panics if field doesn't exist
}

// ✅ Correct: Check existence first
fun safe_access(account: &signer) {
    if (dynamic_field::exists<address, String>(account, "my_key")) {
        let field = dynamic_field::borrow<address, String>(account, "my_key");
        // Safe to use field
    }
}

Pre-Deployment Checklist

  • Verify all resources are either transferred or explicitly dropped.
  • Check dynamic field access for existence before borrowing.
  • Audit permission modifiers to ensure only intended accounts can modify state.

These steps help prevent the most common resource-related vulnerabilities in Move smart contracts.

Frequently asked questions about Move

Is Move based on Rust?

Move shares syntax similarities with Rust, such as curly braces and semicolons, but it is not a subset of Rust. The core difference lies in the ownership model: Move enforces a linear type system where resources cannot be copied or discarded implicitly. This prevents common vulnerabilities like reentrancy attacks found in EVM chains. While Rust offers flexibility through lifetimes, Move prioritizes safety through strict resource accounting, making it easier to reason about asset integrity on-chain.

How does Move compare to Solidity for smart contracts?

Solidity is designed for the Ethereum Virtual Machine (EVM) and treats accounts as the primary unit of interaction. Move treats objects as the first-class citizen, allowing for parallel execution of transactions that touch different assets. This object-centric model enables sub-second finality and higher throughput. For developers, this means Move contracts can handle complex asset logic without the gas optimization headaches often required in Solidity.

Is Move difficult to learn for Rust developers?

For Rust developers, the learning curve is moderate. You already understand borrowing and ownership, which are the foundation of Move’s resource safety. The main adjustment is letting go of Rust’s flexibility in favor of Move’s stricter linear types. You cannot use standard libraries like std::vec; instead, you must use Move-specific modules like vector and address. Familiarity with Rust’s tooling (Cargo) helps, but you will need to adapt to Move’s specific compilation and testing workflows.