Skip to content

Move Resources

On Aptos, on-chain state is organized into resources and modules, stored under accounts (and object addresses). That differs from Ethereum, where each contract owns a private storage space. See Accounts for how addresses work.

Move modules define structs. Abilities such as key and store control where values of those structs may live:

  • A resource is a struct with key. It sits in global storage at an address.
  • An instance with store can live inside a resource (or another store value), but not as a top-level resource on its own.

The original Coin model is a clear example: CoinStore is the resource; Coin is the inner instance. After the Fungible Asset migration, APT balances live in a primary fungible store (paired with the legacy Coin type). New tokens should use Fungible Asset primary stores.

/// A holder of a specific coin type and associated event handles.
/// These are kept in a single resource to ensure locality of data.
struct CoinStore<phantom CoinType> has key {
coin: Coin<CoinType>,
}
/// Main structure representing a coin/token in an account's custody.
struct Coin<phantom CoinType> has store {
/// Amount of coin this address has.
value: u64,
}

With the owning account’s permission, the module can take the Coin out of CoinStore and move it into another CoinStore or into a custom resource:

struct CustomCoinBox<phantom CoinType> has key {
coin: Coin<CoinType>,
}

Every instance and resource is defined in a module at an address. For example 0x1234::coin::CoinStore<0x1234::coin::SomeCoin> looks like:

module 0x1234::coin {
struct CoinStore<phantom CoinType> has key {
coin: Coin<CoinType>,
}
struct SomeCoin {}
}

Here 0x1234 is the address, coin is the module, CoinStore can be stored as a resource, and SomeCoin is a phantom type tag. Phantom type parameters let many distinct CoinStore resources exist at the same address.

Objects extend this model: instead of stuffing many unrelated resources onto a user’s account, you create a dedicated address that owns a group of resources representing one NFT, vault, or app entity.

The module that defines a struct owns its internals. Another module may receive an instance and store it, but it cannot read or mutate private fields without functions from the defining module.

Ownership is either “this resource sits under this account” or logic in the defining module (for example an object’s owner field).

Find a resource by owner address and full type (address::module::Struct<TypeArgs>). View it in the Aptos Explorer or fetch it from a fullnode’s REST API (GET /accounts/{address}/resource/{resource_type}).

The defining module chooses where to put data. Deposit events for a token, for example, can live on the receiver or on the module’s own account.

Storing per-user data under each user’s account (or object) usually parallelizes better: transactions from different addresses do not contend on the same resource, so Block-STM can run them together. Hot global resources (a single shared registry) serialize those writers.