⚠️Blast API (blastapi.io) ends Oct 31. Migrate to Dwellir and skip Alchemy's expensive compute units.
Switch Today →
Skip to main content

Resource Management

Resource management is one of Move's most distinctive and powerful features, providing built-in safety guarantees that prevent common blockchain vulnerabilities. Move's resource model ensures that digital assets cannot be copied, accidentally lost, or double-spent through compiler-enforced linear type semantics and explicit resource handling patterns.

Overview#

Resources in Move are special types marked with the key ability that represent digital assets or critical state. Unlike regular data structures, resources have strict lifecycle rules: they cannot be copied or implicitly destroyed, must be explicitly moved between storage locations, and can only exist in one place at a time. This linear type system eliminates entire classes of vulnerabilities that plague other blockchain platforms.

Resource Lifecycle#

Resources follow a strict lifecycle from creation to destruction:

module 0x1::resource_example {
use std::signer;

// Resource definition - key ability required for global storage
struct Vault has key {
balance: u64,
owner: address
}

// Creating a resource
public entry fun create_vault(account: &signer, initial_balance: u64) {
let vault = Vault {
balance: initial_balance,
owner: signer::address_of(account)
};
// Move resource into global storage
move_to(account, vault);
}

// Accessing a resource - requires acquires annotation
public fun get_balance(addr: address): u64 acquires Vault {
let vault_ref = borrow_global<Vault>(addr);
vault_ref.balance
}

// Modifying a resource
public entry fun deposit(account: &signer, amount: u64) acquires Vault {
let addr = signer::address_of(account);
let vault_ref = borrow_global_mut<Vault>(addr);
vault_ref.balance = vault_ref.balance + amount;
}

// Moving resource out of storage
public entry fun destroy_vault(account: &signer) acquires Vault {
let addr = signer::address_of(account);
let Vault { balance: _, owner: _ } = move_from<Vault>(addr);
// Resource is destructured and destroyed
}
}

Acquires Annotation#

The acquires keyword is mandatory for functions that access global resources. It serves both as documentation and as a compiler check to prevent reentrancy vulnerabilities:

// Function that reads from one resource and writes to another
public fun transfer_between_vaults(
from: address,
to: address,
amount: u64
) acquires Vault {
let from_vault = borrow_global_mut<Vault>(from);
from_vault.balance = from_vault.balance - amount;

let to_vault = borrow_global_mut<Vault>(to);
to_vault.balance = to_vault.balance + amount;
}

// Multiple resource types require listing all
public fun complex_operation(addr: address) acquires Vault, UserProfile, Settings {
// Can access all three resource types
}

Resource Patterns#

Capability Pattern#

Use capability resources to control access to privileged operations:

struct MintCapability has key, store {
total_minted: u64
}

public fun mint_with_cap(
cap: &mut MintCapability,
recipient: address,
amount: u64
) {
cap.total_minted = cap.total_minted + amount;
// Mint logic here
}

Witness Pattern#

Use one-time witnesses for initialization guarantees:

struct COIN has drop {}

public fun initialize(witness: COIN, account: &signer) {
// Can only be called once because witness is consumed
}

Hot Potato Pattern#

Create types without drop ability to force handling:

struct Receipt {
amount: u64,
must_be_consumed: bool
}

public fun create_receipt(): Receipt {
Receipt { amount: 100, must_be_consumed: true }
}

public fun consume_receipt(receipt: Receipt) {
let Receipt { amount: _, must_be_consumed: _ } = receipt;
}

Real-World Use Cases#

  1. Token Implementations: Manage token balances as resources to guarantee conservation of supply and prevent unauthorized minting or burning through compiler-enforced linearity.

  2. NFT Ownership: Represent unique digital assets as resources that cannot be duplicated, ensuring true scarcity and provable ownership on-chain.

  3. Vault Systems: Build secure storage mechanisms where assets can only be accessed by authorized parties, leveraging resource semantics for safety.

  4. Permission Systems: Create capability resources that grant specific permissions, ensuring that access rights cannot be forged or duplicated.

  5. Escrow Services: Hold resources in escrow with guaranteed atomic transfers, where assets must either complete the full transfer or remain in the original location.

  6. Gaming Assets: Represent in-game items as resources with unique properties that cannot be duplicated or destroyed outside intended game mechanics.

Best Practices#

Always Document Acquires: Explicitly list all resource types a function accesses in the acquires clause, even when it seems obvious. This helps prevent subtle bugs.

Check Resource Existence: Use exists<T>(address) before borrowing resources to handle cases where resources might not be initialized.

Avoid Cross-Module Resource Access: Design modules to manage their own resources rather than accessing resources defined in other modules when possible.

Use Immutable Borrows: Prefer borrow_global over borrow_global_mut when only reading data to reduce potential for concurrent access issues.

Structured Destruction: When moving resources out of storage, always destructure them completely to ensure all fields are properly handled.

Resource Initialization: Provide clear initialization functions and document preconditions for resource creation to prevent misuse.

Capability Distribution: Carefully control how and when capability resources are created and distributed to maintain system security.

Common Patterns#

// Check-then-access pattern
if (exists<Vault>(addr)) {
let vault = borrow_global<Vault>(addr);
// Use vault
};

// Modify with validation
public entry fun safe_withdraw(account: &signer, amount: u64) acquires Vault {
let addr = signer::address_of(account);
assert!(exists<Vault>(addr), EVAULT_NOT_FOUND);

let vault = borrow_global_mut<Vault>(addr);
assert!(vault.balance >= amount, EINSUFFICIENT_BALANCE);
vault.balance = vault.balance - amount;
}