Open this lesson in your favourite AI. It'll walk you through the why, explain the demo, and quiz you on the try-it list.
Two ledger philosophies split the industry. Bitcoin records transactions as unspent transaction outputs (UTXOs) — a wallet's balance is implicit, derived by summing all UTXOs you can spend. Ethereum records state directly — your balance lives in a key-value store at your address. The choice ripples into everything from concurrency to privacy to scripting.
A Bitcoin wallet showing 0.5 BTC is actually a sum over maybe 12 UTXOs — each a separate 'coin' you received at different times with separate spending scripts. When you send 0.2 BTC, your wallet picks UTXOs totaling ≥0.2, consumes them entirely, and creates two new UTXOs: one for the recipient, one for your change. In contrast, your Ethereum wallet at 0x742d…f44e has one balance slot that literally reads 500000000000000000 (0.5 ETH in wei). Sending 0.2 ETH is a -0.2 subtraction on your slot and a +0.2 on the recipient's, nothing more.
Use these three in order. Each builds on the one before.
Define UTXO and account model. Give one chain that uses each.
Walk through, step by step, what happens when Alice sends 0.2 BTC to Bob vs. 0.2 ETH to Bob. Include what gets deleted, what gets created, and what signatures are required in each case.
If I'm designing a new L1 and expect most usage to be high-frequency DEX trades (lots of contention on a few pools), which model makes parallelism easier, and what hybrid approaches (Solana's account model with declared access lists, Cardano's eUTXO) try to get the best of both?
// main.go
// Same transfer, two worldviews.
// Bitcoin (UTXO): consume whole coins, produce new ones
// inputs: [utxo_A (0.3 BTC), utxo_B (0.25 BTC)] // total 0.55
// outputs: [to_bob (0.2 BTC), change_to_me (0.349)] // 0.001 fee
// After: utxo_A and utxo_B cease to exist. Two new UTXOs appear.
// Ethereum (account): mutate balance slots directly
// state[0xMe] = state[0xMe] - 0.2 ETH - gas
// state[0xBob] = state[0xBob] + 0.2 ETH
// Two key-value updates, no coins.
package main
import "fmt"
type UTXO struct {
ID string
Owner string
BTC float64
}
func main() {
// --- UTXO model ---
inputs := []UTXO{
{"utxo_A", "me", 0.3},
{"utxo_B", "me", 0.25},
}
sendAmt := 0.2
fee := 0.001
total := 0.0
for _, u := range inputs {
total += u.BTC
}
change := total - sendAmt - fee
outputs := []UTXO{
{"utxo_new1", "bob", sendAmt},
{"utxo_new2", "me", change},
}
fmt.Println("=== Bitcoin (UTXO) ===")
fmt.Print("Consumed: ")
for i, u := range inputs {
if i > 0 {
fmt.Print(", ")
}
fmt.Printf("%s(%.3f BTC)", u.ID, u.BTC)
}
fmt.Println()
fmt.Print("Created: ")
for i, u := range outputs {
if i > 0 {
fmt.Print(", ")
}
fmt.Printf("%s->%s(%.3f BTC)", u.ID, u.Owner, u.BTC)
}
fmt.Println()
fmt.Printf("Fee: %.3f BTC\n", fee)
// --- Account model ---
state := map[string]float64{"0xMe": 1.0, "0xBob": 0.5}
gas := 0.0003
fmt.Println("\n=== Ethereum (Account) ===")
fmt.Printf("Before: 0xMe=%.4f 0xBob=%.4f\n", state["0xMe"], state["0xBob"])
state["0xMe"] -= sendAmt + gas
state["0xBob"] += sendAmt
fmt.Printf("After: 0xMe=%.4f 0xBob=%.4f\n", state["0xMe"], state["0xBob"])
}
go run main.go