Ethereum's account model is a foundational concept in blockchain development, serving as the backbone for how users interact with the network and how smart contracts operate. Unlike Bitcoin’s UTXO (Unspent Transaction Output) model, Ethereum employs a more intuitive account-based model, where each user and contract has a persistent state. This structure simplifies balance tracking and enables powerful programmability through smart contracts.
In this guide, we'll explore the core components of Ethereum’s account system, including external and contract accounts, address generation mechanisms, how to distinguish between account types, internal data structures, storage operations, and the strengths and limitations of this design.
Types of Accounts on Ethereum
Ethereum supports two primary types of accounts: External Accounts and Contract Accounts. Both hold balances and can send transactions, but they differ significantly in control mechanisms and functionality.
External Accounts (EOAs)
External accounts, also known as Externally Owned Accounts (EOAs), are controlled by private keys. These are typically user wallets like MetaMask or hardware wallets such as Ledger.
- Created using cryptographic key pairs (private and public keys)
- No associated code
- Can initiate transactions and deploy or interact with smart contracts
- Cannot natively support multi-signature logic unless wrapped in a smart contract
Despite being unable to run code, EOAs are essential for triggering state changes on the blockchain.
👉 Discover how wallet interactions shape blockchain activity
Contract Accounts
Contract accounts are smart contracts deployed on the Ethereum Virtual Machine (EVM). They are created when an EOA sends a transaction containing initialization code.
- Generated via SHA3 (Keccak256) hashing algorithms
- Possess executable code and internal storage
- Controlled entirely by their code logic
- Can only be triggered by EOAs or other contracts
- Support advanced features like multi-signature logic, token issuance, and automated execution
Once deployed, a contract account cannot be altered—its code is immutable.
Key Differences Between EOAs and Contract Accounts
| Feature | External Account (EOA) | Contract Account |
|---|---|---|
| Controlled by | Private key | Code execution |
| Has associated code? | No | Yes |
| Can hold balance? | Yes | Yes |
| Can create contracts? | Yes | Yes (if coded to do so) |
| Supports multi-sig? | Only via contract | Yes (programmatically) |
Understanding these distinctions helps developers design secure and efficient decentralized applications (dApps).
How Contract Addresses Are Generated
Ethereum uses deterministic methods to generate contract addresses, ensuring predictability and consistency across nodes.
Method 1: Sender Address + Nonce
The traditional method combines the creator's address and transaction nonce:
Keccak256(rlp([sender, nonce]))[12:]This means:
- Every time an EOA deploys a new contract, the nonce increments.
- The resulting hash is truncated to 20 bytes (the standard address length).
Example implementation in Go:
func CreateAddress(b common.Address, nonce uint64) common.Address {
data, _ := rlp.EncodeToBytes([]interface{}{b, nonce})
return common.BytesToAddress(Keccak256(data)[12:])
}Method 2: CREATE2 Opcode (EIP-1014)
To enable predictable deployment at a fixed address—even before deployment—Ethereum introduced CREATE2:
keccak256(0xff ++ address ++ salt ++ keccak256(init_code))[12:]This allows:
- Pre-computation of contract addresses
- Deployment of identical contracts at the same address across different networks
- Use cases like wallet recovery and universal profile systems
Notable standards leveraging this include EIP-1820 (interface detection) and EIP-2470 (singleton contracts).
Detecting Contract vs. External Accounts
Determining whether an address belongs to a contract is crucial for security and logic enforcement.
Using EXTCODESIZE in Solidity
The EVM provides EXTCODESIZE, an opcode that returns the size of code at a given address:
function isContract(address addr) internal view returns (bool) {
uint256 size;
assembly { size := extcodesize(addr) }
return size > 0;
}If the size is greater than zero, it's a contract.
Via Web3.js or JSON-RPC
You can query directly using:
web3.eth.getCode("0x8415A51d68e80aebb916A6f5Dafb8af18bFE2F9d")Returns:
"0x"→ likely an EOA- Bytecode string → confirmed contract
⚠️ Note: An empty response doesn’t guarantee it’s an EOA—code might not yet be deployed.
Common Use Case: Restricting Access to EOAs Only
To prevent contracts from calling your smart contract (e.g., to block flash loan attacks):
require(tx.origin == msg.sender, "No contracts allowed");While effective, this pattern has trade-offs in composability and future-proofing.
Internal Account Data Structure
All Ethereum accounts share the same underlying structure within the state trie:
type Account struct {
Nonce uint64
Balance *big.Int
Root common.Hash
CodeHash []byte
}Breakdown of Fields
Nonce:
- For EOAs: number of transactions sent
- For contracts: number of contracts created
- Balance: Balance in Wei (1 ETH = 10¹⁸ Wei)
- Root: Hash of the root node of the account’s storage trie (Merkle Patricia Tree)
CodeHash:
- For contracts: hash of compiled bytecode
- For EOAs: hash of empty string (
c5d246...)
👉 Learn how real-time balance tracking powers modern dApps
Simulating Account State Management
Below is a simplified simulation showing how account states are managed in Ethereum’s state database:
func main() {
statadb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()))
acct1 := toAddr("0x0bB141C2F7d4d12B1D27E62F86254e6ccEd5FF9a")
acct2 := toAddr("0x77de172A492C40217e48Ebb7EEFf9b2d7dF8151B")
statadb.AddBalance(acct1, big.NewInt(100))
statadb.AddBalance(acct2, big.NewInt(888))
contract := crypto.CreateAddress(acct1, statadb.GetNonce(acct1))
statadb.CreateAccount(contract)
statadb.SetCode(contract, []byte("contract code bytes"))
statadb.SetNonce(contract, 1)
statadb.SetState(contract, toHash([]byte("owner")), toHash(acct1.Bytes()))
statadb.SetState(contract, toHash([]byte("name")), toHash([]byte("ysqi")))
statadb.SetState(contract, toHash([]byte("online")), toHash([]byte{})) // Deletes key
statadb.Commit(true)
fmt.Println(string(statadb.Dump()))
}After committing, Dump() outputs full state data in JSON format—showing balances, codes, and storage entries. Only contract accounts contain non-empty code and storage.
Advantages and Limitations of the Account Model
✅ Advantages
- High Programmability: Contracts store both code and state, enabling complex logic like DeFi protocols and NFT marketplaces.
- Simplified UX: Users can easily track balances without parsing transaction history.
- Efficient Batch Operations: Payment systems (e.g., mining pools) can use single contract calls to distribute funds.
❌ Limitations
- Replay Attacks: Transactions aren’t inherently tied to chains; safeguards like chain ID are required.
- Scalability Challenges: Off-chain scaling solutions like Plasma or Lightning Network require complex proof systems due to shared account states.
- State Bloat: Persistent account storage increases node storage requirements over time.
Frequently Asked Questions (FAQ)
Q: Can a contract have a private key?
A: No. Contracts are controlled by code logic, not private keys. Only EOAs possess private keys.
Q: Is it safe to use tx.origin for access control?
A: It works but is discouraged due to potential proxy bypass risks. Prefer msg.sender with explicit checks.
Q: How does nonce prevent double-spending?
A: Each transaction from an EOA must have a unique, sequential nonce. Nodes reject out-of-order or duplicate nonces.
Q: Why do EOAs have a CodeHash field?
A: For structural uniformity. EOAs have a fixed CodeHash value representing an empty string.
Q: Can I change a contract’s code after deployment?
A: No. Code is immutable. Use upgradeable patterns (e.g., proxy contracts) for flexibility.
Q: What happens if I send ETH to a non-existent address?
A: As long as the address format is valid, funds are sent—but recoverable only if you own the private key.
By understanding Ethereum’s account model deeply, developers can build more secure, efficient, and scalable applications. Whether you're deploying tokens or designing complex financial instruments, this knowledge forms the bedrock of blockchain innovation.
👉 Explore tools that simplify Ethereum development workflows