Two Kinds of Account: EOAs and Contracts
The previous page settled why Ethereum keeps a mutable map from address to account instead of a set of coins: a world computer needs somewhere to remember things, and coins are designed to be forgotten. But that raises a sharper question. If the ledger is a map of accounts, and a “smart contract” is supposedly a program that lives on the chain, then where does the program live? The answer is disarmingly simple, and it is the whole subject of this page: a program lives inside an account. Ethereum does not keep contracts in a separate registry. It stores them in the same address-to-account map that holds ordinary wallets.
So the state map has exactly two kinds of entry. Some accounts are plain wallets, steered from the outside by whoever holds a private key. Others are programs, steered by their own code. This page defines both, states the single rule that separates them, shows how each comes into existence, and proves the payoff: that calling an account with code runs that code. Get this distinction crisp and the rest of the part — the account record, the global state map, and state transitions — falls into place.
Externally Owned Accounts: a wallet with a key
Section titled “Externally Owned Accounts: a wallet with a key”An Externally Owned Account (EOA) is what most people mean by “a wallet.” It is an account whose authority lives outside the chain, in a private key that some human (or their software) holds. Whoever can produce a valid signature for that key controls the account. There is no code inside an EOA; there is nothing to run. An EOA can do exactly one interesting thing: originate a transaction — sign a message that says “advance the world state this way” and broadcast it.
An EOA carries only two pieces of mutable state:
- a nonce — a counter of how many transactions this account has already sent. It increments by one every time the account originates a transaction, and it is what stops someone from replaying a signed transaction twice (more on replay protection when we dissect the account record).
- a balance — how much native currency (ether, denominated in wei) the account holds.
That is the entire footprint of a wallet: a counter and a number, guarded by a key that never touches the chain. Crucially, only an EOA can start a transaction. Every state change on Ethereum — every token transfer, every contract call, every deployment — traces back to a transaction signed by some EOA’s private key. Contracts, as we are about to see, can never kick things off by themselves.
Externally Owned Account (a wallet) ┌───────────────────────────────────┐ │ nonce: 7 │ ← txs sent so far │ balance: 4_000_000_000_000_000 │ ← wei │ code: (empty) │ ← nothing to run │ storage: (empty) │ └───────────────────────────────────┘ ▲ │ controlled by a private key held off-chain │ can SIGN and ORIGINATE transactionsContract Accounts: a program with a memory
Section titled “Contract Accounts: a program with a memory”A Contract Account is an account whose authority lives inside the chain, as code. Where an EOA is steered by an off-chain private key, a contract is steered by its own bytecode: it has no key, nobody can “sign as it,” and it does nothing on its own. A contract is a passive thing until something calls it. When a call arrives, the network runs the contract’s code, and that code can read and write the contract’s own persistent memory, move the contract’s balance around, and even call other contracts.
A contract carries everything an EOA has — a nonce and a balance — plus two things a wallet never has:
- code — the contract’s bytecode, installed once at creation and thereafter immutable. This is the program.
- storage — a persistent key/value map that survives between calls. This is the program’s memory. It does not live in any server’s RAM; it lives in the world state, so the value a contract wrote yesterday is exactly the value the next call reads today.
Contract Account (a program) ┌───────────────────────────────────┐ │ nonce: 1 │ │ balance: 250_000_000_000_000_000 │ ← wei the contract holds │ code: 60 00 54 60 01 01 … │ ← bytecode: THE PROGRAM │ storage: { 0 → 42, 1 → 99, … } │ ← persistent memory └───────────────────────────────────┘ ▲ │ controlled by its OWN code │ runs only when called; cannot originate a txThe most important limitation to internalise: a contract cannot originate a transaction. It can react — when a transaction (ultimately signed by some EOA) calls into it, its code may in turn call other contracts, and those are called internal or message calls. But there is no way for a contract to wake up on its own, on a timer, or “in the background.” Nothing happens on Ethereum until an EOA signs something. This is why patterns like “run this contract every hour” require an off-chain EOA (a bot, a keeper) to send the triggering transaction — the chain has no heartbeat of its own.
The single distinguishing rule
Section titled “The single distinguishing rule”Here is the rule the entire page turns on, and it is smaller than you might expect:
An account is a contract if and only if it has non-empty code.
That’s it. There is no is_contract flag stored on the chain, no type tag, no separate
table of “contracts” versus “wallets.” Both kinds are the same record shape, and the
network decides which behaviour to apply by asking one question: is the code field
empty? In real Ethereum this is phrased in terms of the account’s codeHash — the hash
of its code. An account whose codeHash equals the hash of the empty string has no code
and is an EOA; any other codeHash means there is code, and the account is a contract.
Our companion crate, ethmini, makes the rule literal. An account is four fields, and the
predicate that separates a wallet from a program is a single line:
pub struct Account { pub nonce: u64, // txs sent (EOA) / contracts created (contract) pub balance: u128, // native currency, in the smallest unit pub storage: BTreeMap<u64, u64>, // persistent memory — empty for a wallet pub code: Vec<u8>, // bytecode — empty for a wallet}
impl Account { /// Does this account hold contract code? If so, calling it *runs* that code; /// if not, a call is just a value transfer. pub fn is_contract(&self) -> bool { !self.code.is_empty() }}Read is_contract again: it does not consult a registry or a type field. It asks whether
code is empty. The presence of code is the only thing that turns an account from a
wallet into a program. Everything else — that a contract has storage, that calling it
runs logic, that it can’t be signed for — follows from that one bit of information.
How each kind comes into existence
Section titled “How each kind comes into existence”The two kinds are also born differently, and the difference is again about code.
An EOA exists implicitly
Section titled “An EOA exists implicitly”You do not “create” an EOA in any on-chain sense. An EOA is just an address, and an address is derived from a public key, which is derived from a private key. Generating a key pair is a purely local act — no transaction, no fee, no network involved. The address exists as a mathematical possibility the moment the key exists.
On-chain, an EOA “appears” the first time it is used: the first time someone sends ether to it, or the first time it originates a transaction. Before that, the state map simply has no entry for it — and an address with no entry is treated as an account with a zero balance, a zero nonce, and no code. There is no ceremony. An address is an EOA by default, precisely because the default account has empty code.
A contract is installed by a deploy transaction
Section titled “A contract is installed by a deploy transaction”A contract, by contrast, must be deployed. Because a contract is code sitting in an
account, creating one means running a special transaction whose effect is: “create a new
account and put this bytecode in its code field.” The new contract does not get to pick
its own address — the address is derived from the creator and the creator’s nonce, so
it is deterministic and collision-free:
TxKind::Deploy { code, value } => { let addr = contract_address(tx.from, tx.nonce); // derived, not chosen if *value > 0 { self.transfer_value(tx.from, addr, *value); } self.account_mut(addr).code = code.clone(); // the account now HAS code Ok(Receipt { status: Success, created: Some(addr), .. })}The load-bearing line is self.account_mut(addr).code = code.clone();. That single
assignment is what makes the new account a contract — it flips code from empty to
non-empty, and by the rule above, an account with non-empty code is a contract. Deployment
is not a magical act of creation; it is an ordinary state edit that happens to write the
code field. (Real Ethereum adds a wrinkle: the bytecode you send in a deploy is
init code that runs once and returns the runtime code to install. The idea is the
same — a transaction ends with bytecode written into a fresh account.)
The payoff: why calling a contract runs its code
Section titled “The payoff: why calling a contract runs its code”Now we can see why all of this matters. On Ethereum there is only one kind of action
that touches another account — a call carrying some value and some data. What that call
does depends entirely on whether the target has code. The network branches on exactly
the is_contract bit we defined above:
TxKind::Call { to, value, gas_limit } => { if *value > 0 { self.transfer_value(tx.from, *to, *value); }
let target = self.account_mut(*to); if !target.is_contract() { // ← no code? DONE. It was just a transfer. return Ok(Receipt { status: Success, .. }); }
// has code → RUN it against the account's own storage let (code, storage) = (target.code.clone(), target.storage.clone()); match vm::run(&code, &storage, *gas_limit) { Ok(exec) => { // clean halt → commit the new storage self.account_mut(*to).storage = exec.storage; Ok(Receipt { status: Success, gas_used: exec.gas_used, .. }) } Err(reason) => Err(Reverted { gas_used: *gas_limit, reason }), }}Two paths, one branch:
- Target has no code (an EOA). The value moves and the call is over. Sending ether to a friend’s wallet is literally a call to a codeless account: nothing runs, one balance goes down, another goes up.
- Target has code (a contract). The network loads that code and executes it against
the contract’s own storage. The code can read storage (
SLOAD), write it (SSTORE), and thereby change the world state persistently. Send a call to a token contract and itstransferlogic runs; send a call to a wallet and nothing runs at all.
This is the sentence to carry out of the page: calling an account with no code is a
value transfer; calling an account with code is a program invocation. Same operation,
same transaction shape — the only thing that decides which one you get is whether the
target account has code. The storage map the code runs against is the contract’s private
memory, and because it lives in the world state, its changes persist across calls; the
next page, Inside an Account, opens up
storageRoot and codeHash and shows how the state commits to both.
Under the hood — why a contract can’t just “run itself”
Section titled “Under the hood — why a contract can’t just “run itself””It is tempting to imagine a contract as a little daemon, always alive, waiting. It is not. A contract’s code is inert bytes in the state map, exactly like the text of a program on a disk that no one has launched. Execution only happens because a transaction pays for it: a signed message from an EOA supplies the gas, and the network — while processing that transaction — loads the target’s code and runs it. Remove the transaction and there is no execution, no thread, no timer. This is a deliberate design choice that keeps Ethereum deterministic: the state can only change as a function of the ordered list of transactions in a block, and never spontaneously. It is also why “autonomous” contracts in practice lean on off-chain EOAs to poke them — the world computer computes only when someone signs the request and pays the bill. We will see why the bill exists at all when this book reaches gas.
The architect’s lens
Section titled “The architect’s lens”- Why does it exist? Ethereum wanted programmable money — accounts that can hold logic, not just value. Rather than bolt on a separate “contract system” beside the wallet ledger, it made programs a kind of account, so the same state map, the same addressing, and the same call mechanism serve both wallets and code.
- What problem does it solve? It collapses “who can hold funds” and “what can run logic” into one uniform record. A contract can hold a balance and be sent money exactly like a wallet; a wallet can be called exactly like a contract (it just does nothing). No translation layer, no registry lookup, no second addressing scheme.
- What are the trade-offs? Uniformity costs safety rails. Because a call to any address might or might not run code, sending value is never purely a transfer — the target could be a contract that runs arbitrary logic on receipt. That single fact is the root of reentrancy and a whole family of “the recipient did something unexpected” bugs.
- When should I avoid it? When you need genuinely autonomous, always-on behaviour, a contract is the wrong mental model on its own — it can’t self-trigger. You must pair it with an off-chain EOA (a keeper/bot) to originate the transactions that wake it. And for pure value custody, an EOA (or a smart-contract wallet you understand) is simpler than a bespoke contract.
- What breaks if I remove it? Remove the “code makes it a contract” rule and you remove smart contracts entirely — Ethereum collapses back into an account-based payment system with no way to store or run on-chain logic. The world computer loses its programs and keeps only its money.
Check your understanding
Section titled “Check your understanding”- What are the two mutable fields an EOA carries, and what is the one action an EOA can perform that a contract cannot?
- State the single rule that distinguishes a contract account from an EOA. In real Ethereum, which stored field encodes that rule, and against what is it compared?
- How does an EOA come into existence compared with a contract account? Why does one require a transaction while the other does not?
- You send a transaction that transfers 1 ETH to some address
X. Describe both possible outcomes and explain what single property ofXdecides which one happens. - A friend says “my contract runs automatically every day.” What is technically wrong with that claim, and what must actually be happening for the contract to do work daily?
Show answers
- An EOA carries a nonce (a counter of transactions it has sent) and a balance (its ether holdings). The action it alone can perform is originating a transaction — signing a message with its private key to start a state change. A contract can only react when called; it can never kick off a transaction itself.
- An account is a contract if and only if it has non-empty code (otherwise it is an
EOA). Real Ethereum encodes this in the account’s
codeHash: ifcodeHashequals the hash of the empty string, the account has no code and is an EOA; any other value means it has code and is a contract. - An EOA exists implicitly — it is just an address derived from a key pair (a local,
off-chain act), and it “appears” in state the first time it is used; the default account
has empty code, so an unused address is already an EOA. A contract must be deployed
by a transaction that installs bytecode into a fresh account’s
codefield. The deploy transaction is needed precisely because writing thatcodefield is the on-chain edit that turns the new account into a program. - If
Xhas no code (an EOA), the call is just a value transfer: one balance goes down,X’s goes up, and nothing runs. IfXhas code (a contract), the network executes that code againstX’s own storage — the code may do anything, including reject the transfer or call other contracts. The single deciding property is whetherXhas non-empty code. - Nothing on Ethereum runs on its own — a contract is inert until called, and only an EOA can originate a transaction. So the contract cannot self-trigger “every day.” What must be happening is that some off-chain EOA (a bot or keeper) signs and sends a transaction each day that calls the contract, paying the gas to run its code.