Skip to main content

Account model

If you've built on Ethereum, Polygon, BSC, or any EVM-compatible chain, you're already intimately familiar with the Account model. But have you ever stopped to think about why things work the way they do? Why do you need to worry about nonces? Why does MEV exist? Why is achieving privacy so challenging?

These aren't random quirks—they're direct consequences of the Account model's fundamental design. Understanding these connections will help you appreciate why Midnight's UTXO approach enables entirely different capabilities, particularly around privacy and parallelism. More importantly, it will help you understand when to use Midnight's account-style contract tokens versus UTXO-based ledger tokens.

How accounts work

In the Account model, the blockchain maintains a global state database where every address has an entry. Think of it as a massive, distributed spreadsheet that every node must keep in perfect sync:

Account {
balance: uint256, // How much ETH/native token
nonce: uint256, // Transaction counter (prevents replay attacks)
codeHash: bytes32, // Contract code reference (empty for EOAs)
storageRoot: bytes32 // Merkle root of contract storage tree
}

When you check your wallet balance, you're querying this global state. When you send a transaction, you're requesting an atomic update to this massive shared database. Every node must process these updates identically to maintain consensus.

For smart contracts, each storageRoot points to another tree structure containing all the contract's variables:

// What you write in Solidity:
mapping(address => uint256) balances;
uint256 totalSupply;

// What's actually stored:
storageSlot[0x0] = totalSupply
storageSlot[keccak256(address, 0x1)] = balances[address]
// ... potentially millions more slots

Every token contract, every DEX, every NFT collection adds to this ever-growing state tree that nodes must maintain forever. This is why Ethereum node requirements keep increasing—the state never shrinks.

Transaction Lifecycle: The Atomic Dance

When you submit a transaction in Ethereum, here's the precise sequence that unfolds:

StepActionWhat Really HappensFailure Mode
1Load StateNode loads sender's account from global stateAccount doesn't exist
2Validate NonceCheck tx nonce matches account nonce exactlyWrong nonce = rejected
3Check BalanceVerify balance ≥ value + (gasPrice × gasLimit)Insufficient funds
4Deduct GasReduce balance by maximum possible gas costAlways happens
5ExecuteRun EVM code, updating multiple accountsRevert (lose gas)
6Apply ChangesWrite all state changes to global treeState conflict
7Refund GasReturn unused gas × gasPrice to senderN/A
8Update NonceIncrement sender's nonce by exactly 1N/A

The critical insight: all affected accounts must be updated atomically. If your transaction touches 10 different token contracts, all 10 state updates must succeed together or all must fail together.

Nonces: Order from Chaos

Nonces prevent replay attacks and enforce transaction ordering, but they create their own challenges:

// Alice's account state
{
address: "0xAlice...",
balance: 100 ETH,
nonce: 5 // Next transaction MUST use nonce 5
}

// What happens with transaction ordering:
tx1: { nonce: 5, ... } // ✅ Processes immediately
tx2: { nonce: 7, ... } // ❌ Must wait for nonce 6
tx3: { nonce: 6, ... } // ⏳ Unblocks tx2 when it arrives
tx4: { nonce: 5, ... } // ❌ Rejected - nonce already used

This sequential requirement is why sophisticated users run "nonce managers" and why dApps sometimes fail with "nonce too low" errors. In Midnight's UTXO model, each coin is independent—no sequence requirements, no stuck transactions.

Smart Contracts: The Global Computer

When you deploy a smart contract, you're adding executable code to the global state:

contract SimpleToken {
mapping(address => uint256) public balances;

function transfer(address to, uint256 amount) external {
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
balances[to] += amount;
}
}

During execution:

  1. Every node loads the contract's code and storage
  2. Every node executes the exact same computation
  3. Every node applies the exact same state changes
  4. Every node must reach consensus on the result

This redundancy ensures security but limits efficiency. If 10,000 nodes run the network, that transfer calculation happens 10,000 times. Midnight's approach with off-chain execution and zero-knowledge proofs eliminates this redundancy while maintaining security.

MEV: The Dark Side of Transparency

Maximum Extractable Value (MEV) isn't a bug—it's an inevitable consequence of transparent, sequential execution:

// 1. User submits DEX trade (visible in mempool)
userTrade = {
to: "DEX",
data: "swap(USDC, ETH, 1000000)", // Large trade will move price
gasPrice: 100 gwei
}

// 2. MEV bot sees opportunity and front-runs
botTrade = {
to: "DEX",
data: "swap(USDC, ETH, 50000)", // Buy before price moves
gasPrice: 500 gwei // Higher gas to go first
}

// 3. Block includes: [botTrade, userTrade]
// Bot profits from price movement they didn't create

MEV exists because:

  • All pending transactions are visible
  • Execution order matters for profit
  • Higher gas prices buy priority
  • Global state makes outcomes predictable

Midnight's private state and parallel execution naturally resist many MEV strategies—attackers can't exploit what they can't see.

Privacy: The Impossible Dream?

The Account model makes privacy exceptionally difficult:

// Everything about your address is permanently public:
YourAddress: 0x742d35Cc...
├── Current Balance: 50.23 ETH
├── Every Transaction Ever:
│ ├── Received from Coinbase (links real identity)
│ ├── Sent to DEX (reveals trading)
│ ├── Interacted with lending protocol
│ └── ... complete financial history
└── All Token Balances:
├── 50,000 USDC
├── 10,000 DAI
└── ... every token ever held

Even privacy mixers only break links at specific points—everything before and after remains transparent. Midnight's UTXO model with selective shielding enables true transaction privacy without sacrificing the ability to prove compliance when needed.

Why It Works (When It Works)

Despite these challenges, the Account model excels in certain areas.

Developer Experience

// Intuitive mental model - just like programming class
contract Token {
mapping(address => uint) balances;

function transfer(address to, uint amount) public {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount; // Simple!
balances[to] += amount; // Obvious!
}
}

Composability Power

The shared global state enables complex interactions:

  • Flash loans (borrow → use → repay atomically)
  • Multi-protocol strategies in one transaction
  • Rich contract-to-contract communication
  • Complex DeFi "money legos"

This is why Midnight supports account-style tokens through Compact contracts—sometimes this model is exactly what you need.

Key Takeaways for Midnight Development

Understanding the Account model deeply helps you make better architectural decisions:

Account Model CharacteristicResulting LimitationMidnight's Solution
Global shared stateNo privacy possibleShielded Tokens
Sequential noncesTransaction bottlenecksIndependent UTXO processing
Everything on-chainHigh gas costsOff-chain execution with proofs
Transparent mempoolMEV exploitationPrivate transaction submission

When to Use Account-Style Tokens on Midnight

Even with UTXO advantages, account-style patterns (through Compact contracts) make sense for:

  • Complex DeFi protocols requiring intricate state machines
  • Gaming systems with complex rules and interactions
  • Governance tokens with delegation mechanisms
  • Social tokens tracking relationships

The key insight: Midnight lets you choose the right model for each use case rather than forcing everything into one paradigm.