Aptos uses the Jellyfish Merkle Tree — an optimized sparse Merkle tree with efficient proofs. Combined with Block-STM (parallel execution via software transactional memory), Aptos achieves high throughput without pre-declaring account access.
Jellyfish Merkle Tree
Binary (not 16-way)
Ethereum MPT: 16-way trie (hex nibbles). JMT: binary tree (bit-by-bit). More space-efficient for sparse state.
Sparse Optimization
Empty subtrees collapse to a single null hash. When most of the 2^256 key space is empty, this saves enormous space.
Versioned
JMT supports multiple versions (like Cosmos IAVL). Historical proofs work without archive nodes.
1// Jellyfish Merkle Tree (JMT) — Aptos's State Tree2// An optimized Sparse Merkle Tree for account storage34// Key difference from regular SMT and Ethereum MPT:5// 1. Binary (not 16-way like MPT)6// 2. Sparse (not dense like MPT)7// 3. Leaf nodes store full key+value (efficient for sparse data)8// 4. Internal nodes only exist when needed (sparse optimization)910// Node types in JMT:11enum Node {12 // Leaf: stores the full key-value pair13 Leaf { key: HashValue, value_hash: HashValue },1415 // Internal: binary branch (left/right, not 16-way)16 Internal { left: Option<Child>, right: Option<Child> },1718 // Null: empty subtree (huge optimization!)19 Null,20}2122// Key derivation:23// key = keccak256(address) (same as Ethereum!)24// But traversal is BINARY (bit-by-bit), not 16-way (nibble-by-nibble)2526// Storage:27// DB[hash(node)] = serialize(node) — same concept as Ethereum
Move Resources — Aptos's State Model
Unlike Sui (objects), Aptos stores state as resources under account addresses. Think: account address → {resource_type → data}.
1// Aptos Move Resources — Account-Centric State2// Different from Sui's object model34// In Aptos, state lives under accounts as "resources"5module aptos_framework::coin {6 struct CoinStore<phantom CoinType> has key {7 coin: Coin<CoinType>,8 frozen: bool,9 deposit_events: EventHandle<DepositEvent>,10 withdraw_events: EventHandle<WithdrawEvent>,11 }1213 struct Coin<phantom CoinType> has store {14 value: u64,15 }16}1718// State access pattern:19// borrow_global<CoinStore<AptosCoin>>(alice_address)20// → looks up: alice_address / 0x1::coin::CoinStore<AptosCoin>2122// Storage path in JMT:23// key = hash(account_address ++ struct_tag)24// → Alice's APT balance path:25// key = hash(alice ++ "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>")
Aptos vs Sui Object Model
alice_address.CoinStore = { value: 100 }
alice_address.NFT = { id: 1, ... }object_0x1234 = { owner: alice, value: 100 }
object_0x5678 = { owner: alice, id: 1, ... }Storage Path Format
address / module::StructName
↓
JMT key = hash(this path)
↓
binary tree traversalStorage Path → JMT Key
1// Aptos Storage Paths — Account + Resource + Key23// State stored under account addresses as "resources":4// path = account_address / module_address::module_name::StructName / (optional key)56// Examples:7alice_apt_balance =8 alice_address / 0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>910alice_nft =11 alice_address / my_module::nft::NFT / nft_id1213// JMT key = hash(path) → binary trie traversal14// Value = BCS-encoded (Binary Canonical Serialization) resource1516// Compare to Ethereum:17// ETH: all state in one global trie, contract storage sub-trie18// Aptos: state scattered across account subtrees in JMT1920// Multiple version support (like Cosmos IAVL):21// JMT is versioned → can query historical state per version22statedb.get_with_proof(key, version: 1000000)
Block-STM — Dynamic Parallel Execution
Unlike Solana (must pre-declare accounts), Aptos detects conflicts during execution and re-runs only conflicting transactions.
1// Block-STM — Aptos's Parallel Execution2// Software Transactional Memory for blockchain34// Unlike Solana (pre-declared accounts),5// Aptos figures out conflicts DURING execution:67// Step 1: Optimistically execute all txs in parallel8// (assume no conflicts)910// Step 2: Track read/write sets during execution11tx1.reads = {alice.balance}12tx1.writes = {alice.balance: 100, bob.balance: 200}1314tx2.reads = {alice.balance} // reads same as tx1 writes!15tx2.writes = {alice.balance: 50}1617// Step 3: Detect conflicts — tx2 read alice.balance18// but tx1 wrote it. tx2 must re-execute after tx1!1920// Step 4: Re-execute conflicting txs in correct order21// Non-conflicting txs: committed immediately ✅22// Conflicting txs: re-executed with updated state2324// WHY this is better than Solana for complex apps:25// - No need to pre-declare all accessed accounts26// - Works with Ethereum-like smart contract patterns27// - Dynamic conflict detection = more flexible
Pre-declare all accounts. Scheduler knows conflicts upfront. Simple but rigid.
Execute optimistically. Detect conflicts. Re-execute conflicting txs. More flexible.
Sequential. No parallelism. Every tx runs after the previous. Bottleneck.
How JMT + Block-STM → Capabilities
JMT sparse optimizationVery efficient for sparse state (most accounts don't exist). Proof size much smaller than dense MPT for realistic datasets.
Block-STM dynamic conflict detectionDevelopers don't need to pre-declare account access (unlike Solana). Easier to port Ethereum-like patterns to Aptos.
Resources under accounts (not global objects)State is naturally co-located with accounts. Easy to query all resources for a user. But shared DeFi pools are less natural.
Re-execution on conflictHigh-contention scenarios (popular DEX, airdrop) can cause many re-executions. Throughput degrades under contention.
JMT versioningLike Cosmos, historical queries possible. But Aptos prunes by default — need to configure to keep history.