Set up the Move toolchain
Before writing smart contracts, you need a working development environment. The Move language is primarily used by two major ecosystems: Sui and Aptos. Both provide their own CLI tools that bundle the Move compiler and blockchain interaction utilities.
This guide covers the setup for both ecosystems. Choose the one that matches your target blockchain.
Create a new Move project
Start by initializing a Move package using the Move package manager. This command scaffolds the directory structure and creates the configuration files needed to build smart contracts for Sui or Aptos.
Run the following command in your terminal. Replace my_move_project with your desired package name.
move init my_move_project
Project Structure
The move init command generates a specific directory layout. Understanding this structure is essential before writing your first module.
| Directory/File | Purpose |
|---|---|
Move.toml | Package configuration, dependencies, and compiler settings. |
sources/ | Contains your .move source files. |
tests/ | Holds integration tests for your modules. |
scripts/ | Optional folder for Move script files. |
The Move.toml File
The Move.toml file is the core configuration for your project. It defines the package name, version, and dependencies. For Sui, you typically use the sui framework; for Aptos, you use the aptos framework.
Here is an example of a basic Move.toml for a Sui project:
[package]
name = "my_move_project"
version = "0.0.1"
[dependencies]
Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "main" }
[addresses]
mymove = "0x0"
Next Steps
Once the project is initialized, you can start writing your first module in the sources/ directory. The Move compiler will automatically detect your files when you run move build.
For more details on project configuration, refer to the Move Package Manager documentation.
Write a basic Move module
Move treats digital assets as resources, which are unique objects that cannot be copied or dropped accidentally. This design prevents common smart contract bugs like double-spending or accidental value loss. In this section, you will build a simple module that defines a resource, stores it in an account, and transfers it to another address.
We will use a generic structure compatible with both Sui and Aptos, focusing on the core Move concepts: struct, key, and transfer. The code below demonstrates how to create a custom asset and move it between accounts.
Step 1: Define the Resource Structure
In Move, any type that holds value must be marked with the key or store ability. The key ability allows the struct to be stored in an account's object table. We define a simple Token struct that holds an amount.
module my_package::token {
use std::signer;
use std::string;
// Define a resource struct
struct Token has key, store {
id: UID,
amount: u64
}
// Helper to create a new token
fun mint(signer: &signer, amount: u64) {
let token = Token {
id: object::new(&signer),
amount
};
// In a real scenario, you might store this or transfer it immediately
transfer::public_transfer(token, signer::address_of(&signer));
}
}
Step 2: Implement Transfer Logic
Move enforces strict ownership rules. To move a resource, you must explicitly transfer it. The transfer::public_transfer function moves a resource to another account's address. This is safer than sending coins because the receiver must have a way to handle the specific resource type.
// Transfer token to another address
fun transfer_token(
signer: &signer,
recipient: address,
token: Token
) {
// Ensure the signer owns the token before transferring
assert!(signer::address_of(&signer) == owner_of(&token), EUnauthorized);
transfer::public_transfer(token, recipient);
}
// Helper to check ownership (internal use)
fun owner_of(token: &Token): address {
object::address_of(&token.id)
}
Step 3: Test the Module
After writing the module, you must compile it using the Move CLI. This checks for type safety and resource handling errors. If the compilation succeeds, you can deploy it to a testnet.
- Compile: Run
move buildin your project directory. This generates bytecode that the Move VM can execute. - Deploy: Use
move publishto deploy the module to a local node or testnet. - Interact: Use a client script or Move console to call
mintandtransfer_token.
This simple example illustrates how Move’s resource model prevents accidental duplication. By marking Token with key, you ensure it exists in only one place at a time. This is the foundation of secure asset management in Move-based blockchains like Sui and Aptos.
Note: Always verify the
keyandstoreabilities in your structs. Missingstoreprevents the type from being sent across modules, while missingkeyprevents it from being stored in accounts.
For more detailed documentation on Move’s type system, refer to the official Move documentation. This resource covers advanced topics like generics and abilities in depth.
Compile and test the contract
Before deploying a Move-based smart contract to any network, you must verify that the code compiles without errors and passes all unit tests. This process acts as a safety net, catching syntax mistakes and logical flaws before they reach the mainnet. In ecosystems like Sui and Aptos, the compiler is strict about resource ownership and access control, so catching these issues locally saves significant time and gas fees later.
Verify compilation
Start by compiling the contract using the official Move compiler (move). This step checks for syntax errors, type mismatches, and structural issues specific to the Move language. For Sui projects, you can use the sui move build command, which compiles the module and generates the bytecode. For Aptos, use aptos move compile. If the compilation fails, the compiler will provide detailed error messages pointing to the exact line and nature of the issue. Address these errors iteratively until the build succeeds.
Run unit tests
Once the contract compiles, run the unit tests to verify the logic. Move allows you to write test functions directly within the source code, marked with the #[test] attribute. Use sui move test for Sui or aptos move test for Aptos to execute these tests. Focus on testing critical paths, such as token transfers, ownership checks, and state changes. If a test fails, the output will show which assertion was violated, helping you pinpoint the bug. Ensure all tests pass before proceeding to deployment.
Pre-deployment checklist
-
Contract compiles without errors
-
All unit tests pass locally
-
Resource ownership rules are verified
-
Access control modifiers are correct
-
Gas estimation is reviewed
Deploy your Move contract to a testnet
Once your Move module compiles without errors, the next step is getting it onto a live network. Deploying to a testnet allows you to interact with your smart contracts using real blockchain mechanics without risking mainnet capital. Both Sui and Aptos provide robust testnet environments that mirror mainnet behavior, making them ideal for final validation before production.
1. Configure your wallet and fund it
Before deploying, ensure your wallet is set to the correct testnet network and has sufficient native tokens to cover gas fees. For Sui, switch your wallet (such as Sui Wallet or Ethos) to the Sui Testnet. For Aptos, select the Aptos Testnet. You can claim free testnet tokens from the official Sui Faucet or the Aptos Faucet to cover transaction costs.
2. Upload the compiled bytecode
Use the respective CLI tools to publish your compiled Move module. This process uploads the bytecode to the network and initializes the module with an optional initialization function.
For Sui, use the Sui CLI:
sui client publish --gas-budget 100000000 ./path/to/your_module
For Aptos, use the Aptos CLI:
aptos move publish --named-addresses your_module=0x123...
This command sends a transaction to the network. The CLI will return a transaction digest or hash. You can use this hash to verify the transaction status on the block explorer.
3. Verify the deployment
After the transaction is confirmed, verify that your module is live. On Sui, you can check the Published Objects section in the Sui Explorer. On Aptos, look for the module under the account address in the Aptos Explorer. You can then interact with your contract using the sui client or aptos move run commands to test functions.
Common deployment pitfalls
- Gas estimation errors: If the transaction fails due to insufficient gas, increase the
--gas-budgetflag. Complex Move logic requires more computational resources. - Address mismatch: Ensure the address specified in your
Move.tomlmatches the wallet address you are using to deploy. Mismatches will cause the transaction to revert. - Network confusion: Always double-check that your CLI is pointed to the testnet endpoint, not mainnet or localnet.
Interactive deployment checklist
-
Wallet switched to testnet network
-
Testnet tokens received from faucet
-
Move.toml configured with correct network
-
Contract compiled with sui move build or aptos move compile
-
Transaction digest recorded for verification
Common Move mistakes to avoid
Even with Move’s type system, developers frequently trip over resource management and ability modifiers. These errors often compile locally but fail at runtime or leave contracts vulnerable. Understanding these pitfalls is essential for writing secure code on Sui and Aptos.
Misusing the key and store abilities
The most common error is misapplying the key or store abilities. In Move, key marks a struct as a global resource that can be stored in the blockchain’s state store, while store allows a struct to be nested inside other stored resources.
If you mark a struct as key but it contains fields without store, the compiler will reject it. Conversely, if you need a struct to be part of a larger stored object but forget store, you’ll hit a type mismatch. Always ensure your nested types respect the store constraint before declaring a parent as key.
Violating resource invariants
Move’s resource model ensures that every resource is either used exactly once or destroyed. A frequent mistake is attempting to duplicate a resource or ignoring it entirely. For example, creating a Coin<T> but never transferring or destroying it results in a "resource leak" warning or a compilation error if the resource is moved.
// Error: 'coin' is never used
fun mint_bad(account: &signer) {
let coin = mint(account, 100);
// Forgot to transfer or destroy coin
}
Always ensure resources are consumed. If you create a resource, you must either transfer it to another account, destroy it explicitly, or store it in a global resource. This strictness prevents accidental duplication and ensures predictable state transitions.
Ignoring ability constraints in function signatures
Another subtle error is ignoring ability constraints in function parameters. If a function expects a &T where T must have drop, but you pass a struct without drop, the code will fail. Similarly, passing a &mut T to a function that expects &T is allowed, but the reverse is not.
Always match the ability constraints in your function signatures to the types you intend to pass. This prevents runtime errors and ensures your contract behaves as expected under different network conditions.


No comments yet. Be the first to share your thoughts!