How Ethereum Works: EVM, Consensus, and Modern dApps

Ethereum is a public, distributed state machine that executes smart contracts and lets independent participants verify the same shared financial and application state.

That definition is less catchy than “the world computer,” but I find it much more useful. The world-computer slogan does not tell a developer where a program runs, who maintains its data, how the network resolves conflicting histories, why a failed transaction still costs money, or why anyone would choose a blockchain over PostgreSQL and a conventional API.

After 25 years in traditional finance, those are the questions I care about. Before I look at an application’s promised returns or polished interface, I want to understand its authority model: who can change the records, who can move the money, and what happens when something goes wrong.

On Ethereum, thousands of independent computers verify transactions under the same protocol rules. Programs that operate on the shared state are called smart contracts. Their execution environment is the Ethereum Virtual Machine, or EVM, and users pay for execution with Ethereum’s native asset, ETH.

Ethereum is not a general replacement for cloud infrastructure, databases, or backend frameworks. It is a specialized coordination and settlement layer for situations in which multiple parties need to share state without giving any one participant unilateral control over it.

What Is Ethereum, Without the Marketing?

Ethereum is a network for maintaining shared, publicly verifiable state under rules that independent nodes enforce.

A conventional application usually has an infrastructure owner. A company controls the servers, database, administrator accounts, and operating rules. Users must trust that operator not to rewrite records, block accounts, execute an undocumented transaction, or simply shut down the service.

Ethereum changes that authority model.

An application’s rules can be published as code on a public network. When a transaction calls a contract, one company’s backend does not determine whether the resulting state transition is valid. Ethereum nodes independently apply the protocol’s execution rules.

The user does not have to trust one particular server. Instead, the user relies on inspectable code, protocol rules, cryptography, and the network’s economic incentives. This is the foundation described in Ethereum’s technical introduction for developers.

That is an important capability, but it is a limited one.

Ethereum does not guarantee that an application is useful, that a contract has no bugs, that an oracle supplied an accurate price, or that a website is safe. It also does not guarantee immediate inclusion of every valid transaction.

Its narrower promise is that transactions included in valid blocks are processed according to the protocol, and the accepted state can be independently verified.

I separate Ethereum into four core functions:

  • The Ethereum protocol defines valid blocks and valid state transitions.
  • The EVM deterministically executes smart contract bytecode.
  • Proof-of-stake consensus selects the canonical chain and provides economic finality.
  • ETH pays for execution and serves as validators’ economic collateral.

Keeping those functions separate makes the rest of the system much easier to understand.

When Does Ethereum Actually Make Sense?

Ethereum makes sense when shared verification, independent ownership, or neutral settlement justifies the cost of distributed execution.

I look for at least one of the following requirements:

  • Multiple organizations need to update shared state without a central administrator.
  • Users need to own and transfer digital assets independently.
  • Settlement rules need to be public and verifiable.
  • An application needs to interact with existing tokens and protocols.
  • The history of changes needs to resist retrospective rewriting.
  • Funds need to be controlled by explicit code rather than one company’s ordinary account.
  • Users need to be able to leave one frontend and continue through another.
  • Access needs to be open without a prior agreement with the application operator.

Typical examples include decentralized exchanges, lending markets, stablecoin payments, escrow arrangements, shared treasuries, tokenized rights, digital ownership records, transparent auctions, and protocols that other applications can access.

Ethereum does not make sense merely because an application has users, payments, or an audit log.

If one company legitimately controls all the rules, records change frequently, privacy is the default requirement, or the system needs enormous numbers of inexpensive operations, a traditional backend is usually the better choice.

In financial systems, I find the strongest designs are often hybrid. Critical ownership and settlement live on-chain. Search, analytics, documents, notifications, and business workflows stay off-chain.

The question is not whether blockchain technology can perform an operation. The question is whether independent verification of that operation is valuable enough to justify the additional complexity.

Ethereum as a Deterministic State Machine

Ethereum’s most useful technical model is a deterministic state machine, not simply a distributed database.

At a simplified level, the process looks like this:

new_state = state_transition(previous_state, transactions)

A full execution node starts with a valid previous state and processes the same ordered transactions under the same rules. It must calculate the same result as other correctly functioning nodes.

If a proposed block claims a state that does not match the result required by those rules, the node rejects it.

The global execution state includes accounts, ETH balances, nonce values, contract bytecode, and each contract’s persistent storage. A cryptographic commitment to the resulting state, the state root, appears in the execution block header.

The simplified model above leaves out details such as block context and protocol-defined system operations, but it captures the central idea: identical valid inputs and identical rules must produce identical results.

Determinism creates restrictions that do not exist in an ordinary backend.

A smart contract cannot arbitrarily call an HTTP API, read a server’s local clock, or ask a bank for its current account balance. If different nodes could receive different answers, they could not agree on execution.

EVM code therefore operates on transaction inputs, existing state, and a defined set of block and execution-context values. External information must enter through transactions and systems such as oracles.

Ethereum Accounts: EOAs and Smart Contracts

Ethereum accounts hold ETH and participate in execution, but different account models have different authorization and execution behavior.

The traditional distinction in the Ethereum accounts documentation is between externally owned accounts and contract accounts.

Modern delegation mechanisms make that distinction less absolute than older explanations suggest.

Account typeControlCan originate a protocol transaction?Has bytecode?
Externally owned account, or EOAA private key, potentially combined with delegated execution behaviorYes, using a valid transaction signatureTraditionally no; EIP-7702 allows a code-delegation indicator
Contract accountProgrammed authorization and execution rulesNot independently; execution is triggered through a transaction or another callYes, for an ordinary deployed contract

An EOA is traditionally an account controlled by a private key. Its address is derived from the public key, and the private key authorizes signatures.

A contract account is created through contract deployment. It has an address, can hold ETH and tokens, maintains persistent storage, and executes code when called.

The important practical consequence is that a contract does not run continuously in the background. It has no cron job, thread, or event loop.

For ordinary dApps, execution happens because a transaction triggers a call. One contract can call other contracts during that execution, but the call chain remains part of the initiating transaction. Protocol-defined system calls are a separate mechanism, not a scheduling service available to arbitrary applications.

What Does an Ethereum Account Contain?

Conceptually, an account contains four fields:

  • nonce
  • ETH balance
  • storageRoot
  • codeHash

For a traditional EOA, the nonce tracks outgoing transactions. Under EIP-7702, processing account authorizations can also increment an EOA’s nonce, so it is no longer universally correct to describe it as nothing more than a transaction counter.

For contract accounts, the nonce is associated with contract creation activity.

The storageRoot commits to the account’s storage, while codeHash identifies its code.

An Ethereum address is 20 bytes, or 160 bits. It is not a username and does not inherently reveal the owner’s identity. However, activity associated with the same address can be linked.

Pseudonymity is not the same thing as privacy.

A Private Key Is Not a Password

A private key is an authorization secret, not a password that an administrator can simply reset.

Losing a conventional password usually starts a recovery process. Losing the only private key controlling an ordinary EOA generally does not.

Likewise, someone who obtains that key can usually sign transactions as the account.

Production systems should not keep private keys in source code, an ordinary .env file on a shared server, or a frontend bundle. Depending on the risk model, appropriate controls may include:

  • Hardware wallets
  • Multisignature contracts
  • Hardware security modules, or HSMs
  • Key management services, or KMSs
  • Separate signing services
  • Smart account recovery rules
  • Restricted session keys
  • Role-based contract permissions with minimal privileges

Ethereum’s account model is also evolving.

ERC-4337’s account abstraction specification introduces UserOperation objects, bundlers, paymasters, and a standard EntryPoint contract without requiring a consensus-layer change. A UserOperation is not itself an ordinary Ethereum transaction. A bundler submits a transaction that carries operations into the execution system.

EIP-7702’s code-delegation mechanism for EOAs allows an EOA to authorize execution through code at another address. This supports capabilities such as batched actions, sponsored transaction flows, and more flexible wallet behavior.

A delegation can remain in effect until it is changed or cleared. It should not be mistaken for a harmless, one-time login approval.

Account abstraction is not one product or one API. It is a collection of protocol-level and application-level mechanisms designed to move beyond the model in which one private key directly controls every action.

What Is an Ethereum Transaction?

An Ethereum transaction is a signed instruction requesting a state transition.

That is the essential definition in the Ethereum transaction documentation.

A typical transaction contains:

  • chainId, identifying the network
  • nonce, identifying the sender’s position in its transaction sequence
  • to, identifying the recipient or target contract
  • value, specifying the amount of ETH transferred
  • data, containing input bytes for execution
  • gasLimit, setting the maximum execution gas allocation
  • Fee parameters
  • The sender’s signature

For transaction types that support contract creation, an absent to field indicates deployment.

If to identifies a traditional EOA with no delegated code, the transaction commonly transfers ETH without executing recipient contract logic.

If the recipient is a contract account, the EVM executes its code using the supplied data and value. A delegated EOA can also execute code, which is another reason not to rely blindly on older EOA-versus-contract assumptions.

Nonces and Transaction Ordering

A nonce prevents repeated execution of the same sender transaction sequence and establishes ordering for transactions from that account.

Suppose a node reports an account’s current on-chain nonce as 12. Its next ordinary outgoing transaction must use nonce 12.

A transaction using nonce 13 may reach a node’s transaction pool, but it cannot execute before the account advances past nonce 12.

If transaction 12 is stuck because its fee is too low, later transactions from the same account may wait behind it.

There is a useful wording distinction here: if the last included transaction used nonce 12, the next nonce would normally be 13. The current account nonce and the nonce of the last transaction are not the same concept.

This becomes an operational problem when backend services submit transactions concurrently. A shared nonce manager, a queue per signing account, or another coordination mechanism is needed to stop multiple processes from reserving the same nonce.

Querying a node’s pending nonce can help, but it does not replace coordination across multiple workers or providers.

Signing and Broadcasting a Transaction

A wallet or signing service serializes the transaction, calculates the appropriate signing hash, and signs it with the private key.

The signature lets the network recover and verify the sender’s address without receiving the private key.

The signed transaction is usually submitted to a node through eth_sendRawTransaction. The node checks protocol and transaction-pool requirements, then may propagate the transaction to its peers.

The transaction subsequently waits for inclusion in a block.

An RPC provider does not gain custody of the funds merely because it receives a signed transaction. The private key is not part of that request.

However, the provider may observe the connecting IP address, timing, queries, and submitted transactions. It can also affect the application’s availability and the information it receives.

That is a meaningful privacy and infrastructure trade-off.

The Ethereum Transaction Lifecycle, Step by Step

A transaction moves through several distinct stages between a wallet confirmation and finalized settlement.

When a user clicks “Confirm” in a dApp, considerably more happens than a single API call:

  1. The frontend constructs an intended action, such as calling deposit(orderId).
  2. An ABI encoder converts the function signature and arguments into calldata.
  3. The wallet displays the network, destination contract, transferred value, and estimated fee.
  4. The user signs the transaction.
  5. The wallet submits the raw signed transaction through an RPC endpoint.
  6. The receiving node validates it for its transaction pool and propagates it through the peer-to-peer network.
  7. A block builder or proposing validator selects transactions and determines their order.
  8. An execution client executes the proposed transaction sequence in the EVM.
  9. Protocol-valid transactions and their results become part of a proposed execution payload. A transaction can be valid for inclusion even if its contract execution reverts.
  10. Consensus clients validate the proposed block, coordinate with execution clients, and follow validator votes.
  11. The application retrieves a transaction receipt after inclusion.
  12. Subsequent consensus activity strengthens the block’s settlement status, eventually bringing it under a finalized checkpoint.

I distinguish at least four conditions:

  • The transaction has been signed but not submitted.
  • The transaction is pending in one or more transaction pools.
  • The transaction has been included in a block.
  • The containing block is finalized.

A transaction hash is not proof that payment has settled.

A hash can exist for a pending transaction that never reaches a block. Even an included transaction can temporarily disappear from the canonical chain during a reorganization.

For a low-risk user interface, showing “confirmed” after inclusion may be acceptable if the label is clearly defined. Irreversible delivery of valuable goods, bridge settlement, and accounting close processes require a more deliberate policy.

This is where my banking background matters: authorization, processing, and final settlement are different events. A good blockchain application does not collapse them into one green check mark.

Ethereum Blocks and the Canonical Chain

A block establishes an ordered set of transactions and commits to their execution results.

Ethereum’s block documentation explains how blocks connect transaction processing to the chain’s cryptographic history.

At the execution layer, relevant block data includes:

  • The parent block hash
  • The state root after execution
  • The transaction root
  • The receipts root
  • The ordered transaction list
  • The block gas limit and gas used
  • The base fee
  • The timestamp
  • Other protocol-required fields

The parent hash links a block to its predecessor. Altering an old transaction changes the block’s cryptographic commitments and breaks the downstream hash links.

An attacker cannot quietly edit a historical database row. Producing an alternative accepted history requires satisfying the protocol’s validity rules and overcoming the relevant consensus protections.

Importantly, control over substantial stake does not make arbitrary invalid execution acceptable to honest full nodes. A block that creates ETH outside protocol rules or spends funds without valid authorization remains invalid.

A block determines:

  • Which transactions executed
  • Their execution order
  • The resulting state
  • The event logs produced
  • The gas consumed

Ordering matters enormously.

Two transactions can each be valid yet produce different outcomes depending on which executes first. That is the foundation of frontrunning, sandwich attacks, and the broader category called maximal extractable value, or MEV.

Execution Clients and Consensus Clients

A full Ethereum node combines execution-layer software with consensus-layer software.

As the Ethereum nodes and clients guide explains, a modern full node is not just one monolithic process.

Its main components are:

  • An execution client
  • A consensus client

A validating setup also uses validator functionality, often provided through a validator client or a component packaged with the consensus software.

The execution client:

  • Maintains EVM state
  • Receives and propagates transactions
  • Executes smart contracts
  • Exposes execution-layer JSON-RPC methods
  • Validates execution payloads
  • Stores data appropriate to its synchronization and retention configuration

The consensus client:

  • Follows proof-of-stake consensus
  • Processes Beacon Chain blocks
  • Checks attestations
  • Applies the fork-choice rule
  • Tracks justified and finalized checkpoints
  • Communicates with the execution client through the Engine API

This separation matters if you operate your own infrastructure.

Running an execution client alone is not enough for an independently functioning Ethereum node to follow the live proof-of-stake chain. The clients must be synchronized and correctly connected.

Implementation diversity is intentional. If nearly everyone uses the same software, one implementation bug can become a network-wide problem.

Different execution and consensus clients implement the same specifications in separate codebases and programming languages, reducing dependence on one implementation.

How Ethereum Proof of Stake Reaches Agreement

Ethereum proof of stake uses economically accountable validator votes to select the chain head and finalize checkpoints.

Under Ethereum’s proof-of-stake mechanism, validators stake ETH, operate the required software, and participate in proposing and attesting to blocks.

The minimum activation balance remains 32 ETH. However, 32 ETH is not a universal maximum per validator. EIP-7251’s effective-balance changes allow qualifying compounding validators to have effective balances up to 2,048 ETH.

That distinction matters because consensus weight is based on effective stake, not simply a head count of validators.

Validator duties include:

  • Proposing blocks
  • Attesting to proposed blocks
  • Voting for the chain head
  • Participating in checkpoint finalization
  • Performing sync committee duties when selected

Time is divided into slots and epochs.

A slot lasts 12 seconds. An epoch contains 32 slots. A slot can be missed, so a scheduled slot does not guarantee that a block will appear.

Under normal conditions, finality develops through checkpoint votes representing a supermajority of active effective stake.

Three concepts need to stay separate:

  • Head: The best current block according to the fork-choice rule.
  • Justified checkpoint: A checkpoint that has received the required supermajority support.
  • Finalized checkpoint: A checkpoint protected by the protocol’s accountable finality rules.

Ethereum combines a fork-choice mechanism, LMD-GHOST, with Casper FFG checkpoint finality.

A validator that goes offline misses rewards and may receive ordinary penalties. A validator that signs certain conflicting messages can be slashed.

Slashing destroys some of the validator’s stake and forces its exit. It is not the same thing as the routine consequence of a brief internet outage.

Proof of stake does not prevent every interruption. If enough stake stops participating, block production may continue while finalization stalls. Extended non-finality can trigger additional inactivity penalties.

The system distinguishes:

  • Safety: Conflicting histories should not both become finalized.
  • Liveness: The network should continue making progress.

Those are related goals, but they are not interchangeable.

What Does the EVM Actually Execute?

The EVM executes bytecode, not Solidity source code.

The Ethereum Virtual Machine documentation describes the deterministic execution environment that nodes use to evaluate smart contracts.

A Solidity compiler produces artifacts including bytecode, an ABI, and metadata.

During ordinary deployment, initialization code runs and returns the runtime bytecode that will be stored for the contract.

The EVM is a stack machine:

  • Its stack has a maximum depth of 1,024 items per execution frame.
  • Its basic word size is 256 bits.
  • Instructions are called opcodes.
  • Opcodes have defined gas-charging rules.
  • Execution is deterministic under the applicable protocol rules.

Common opcode families include:

  • Arithmetic: ADD, SUB, and MUL
  • Comparisons: LT, GT, and EQ
  • Stack operations: PUSH, DUP, and SWAP
  • Control flow: JUMP and JUMPI
  • Persistent state access: SLOAD and SSTORE
  • External calls: CALL, STATICCALL, and DELEGATECALL
  • Execution context: CALLER, CALLVALUE, and BLOCKHASH
  • Logs: LOG0 through LOG4

One Solidity statement can compile into multiple opcodes.

Consequently, execution cost cannot be estimated by counting lines of source code. It depends on the actual execution path, state access, memory expansion, and external calls.

Stack, Memory, Calldata, Storage, and Transient Storage

The EVM uses several data areas with different lifetimes and costs.

Data areaLifetimeMutable?Typical purpose
StackOne execution frameYesOperands and temporary values
MemoryOne execution frameYesTemporary arrays, structures, and working data
CalldataThe current call’s inputNoABI-encoded function arguments
StoragePersists between transactionsYesA contract’s durable state
Transient storageOne transaction, scoped to the owning contractYesTemporary state shared across that contract’s call frames
CodePersistent for an ordinary deployed contractNot replaceable in place for an ordinary contractRuntime bytecode

Persistent storage is expensive because it affects the shared state that execution nodes must process and maintain.

Memory is temporary and generally cheaper, although expanding it adds gas costs.

Calldata is read-only input. Keeping suitable arguments in calldata can avoid unnecessary copying into memory.

A new external call creates a new execution frame with its own memory. A Solidity internal function call does not necessarily create a separate EVM frame.

Transient storage, accessed with TSTORE and TLOAD, survives across relevant call frames during one transaction and is discarded at the transaction’s end. It is useful for temporary locks and other coordination that should not become permanent state.

The EIP-1153 transient storage specification is explicit that this storage belongs to the relevant contract context. It is not a universal temporary database that every contract can freely access.

Ordinary deployed contract code remains fixed at its address. Upgradeable proxies change behavior by pointing execution toward another implementation, while EIP-7702 introduces a separate delegation mechanism for EOAs.

A Smart Contract Is Not a Microservice

A smart contract is stored executable logic, not a continuously running server.

The anatomy of an Ethereum smart contract is built around functions, data, and execution context, not operating-system processes.

A contract has no listening port, filesystem, environment variables, or direct internet connection.

It can:

  • Read and modify its own storage
  • Send ETH
  • Call other contracts
  • Create contracts
  • Emit logs
  • Verify signatures
  • Stop execution and return an error

It cannot independently:

  • Call a REST API
  • Fetch a price from an exchange website
  • Schedule its own future execution
  • Hide plaintext from all nodes
  • Obtain trustworthy randomness from a local source
  • Guarantee that someone will call it at a particular time

Off-chain automation therefore remains necessary.

A keeper or automation service watches conditions and submits a transaction when a function needs to run.

An oracle brings external information into the system under a defined trust and verification model.

A frontend and indexer turn raw blockchain data into a usable product.

I consider that division of labor healthy. Trying to force every application responsibility into a smart contract usually makes the product more expensive without making it more trustworthy.

ABI, Calldata, and Function Selectors

The Application Binary Interface, or ABI, defines how contract functions and values are encoded into bytes.

Consider a function with this signature:

transfer(address recipient, uint256 amount)

The first four bytes of its ordinary ABI-encoded calldata are the function selector.

That selector comes from the first four bytes of the Keccak-256 hash of the canonical function signature:

transfer(address,uint256)

Notice what is missing: parameter names and extra spaces.

The arguments follow the selector. ABI encoding generally organizes values into 32-byte words and uses offsets for dynamic types.

An ABI tells an application library:

  • Which functions are available
  • Their input and output types
  • Their declared state mutability
  • Which events exist
  • How to decode custom errors

An ABI is not executable code. It also does not establish that the implementation is safe or that the supplied interface accurately describes a particular address.

eth_call, Transactions, and Simulations Are Different

An eth_call simulates execution without committing its changes to the blockchain.

It is commonly used for reads, but it can also simulate a state-changing function. The node executes against a selected state and returns a result or error.

There is no transaction inclusion, no required transaction signature, and no protocol gas fee paid for that call. The node still performs work, and a provider may charge for access or impose resource limits.

A write operation requires a transaction or an account-abstraction workflow that ultimately executes inside one. It can enter a transaction pool, reach a block, and consume paid gas.

A successful simulation does not guarantee that the later transaction will succeed.

Between simulation and inclusion:

  • Prices can move.
  • An allowance can be consumed.
  • An account’s nonce can advance.
  • A deadline can expire.
  • Another transaction can change contract state.

I treat simulation as an important safety check, not a settlement guarantee.

Transaction Atomicity and Reverts

A top-level execution failure rolls back that execution’s contract state changes, value transfers, and logs.

Suppose contract A updates storage, calls contract B, and B reverts. If A does not catch or otherwise handle that failure, the error propagates and A’s earlier execution changes are also rolled back.

The same principle applies through deeper call chains.

However, a caller can deliberately handle a failed subcall. In that case, the failed subcall’s effects revert while the caller may continue and eventually complete successfully.

A revert does not erase the cost of work already performed.

The sender pays for gas consumed up to the failure, subject to the applicable gas-accounting rules. Gas remaining after an ordinary REVERT is not automatically consumed, whereas an out-of-gas failure has different consequences.

The transaction nonce is still consumed when an included transaction’s execution fails, and the applicable transaction fees remain payable.

EIP-7702 adds another important qualification: successfully processed authorization changes occur before the execution call and can persist even if that call later reverts.

This is why I avoid the oversimplified claim that a failed transaction changes “absolutely nothing.”

It is also why inexpensive checks should happen early. If an operation is unauthorized or its arguments are invalid, reject it before expensive storage writes and external calls.

Ethereum Gas Fees: Paying for Execution

Gas measures Ethereum execution resources; ETH is the asset used to pay for them.

The Ethereum gas and fees documentation describes gas as the accounting unit for computational work.

A transaction’s gas usage reflects instruction execution, memory expansion, state access, calldata-related charges, and other applicable protocol costs.

Gas is not ETH.

For ordinary execution fees, the calculation is:

Transaction fee equals gas used multiplied by the effective gas price.

Prices must use consistent units, typically wei per gas. Wallets often display gas prices in gwei, with one gwei equal to one billion wei.

For an EIP-1559 transaction:

Effective gas price equals the base fee plus the actual priority fee, subject to maxFeePerGas.

More precisely, the effective price is the lower of:

  • maxFeePerGas
  • The base fee plus maxPriorityFeePerGas

The fee cap must cover the block’s base fee for the transaction to be eligible for inclusion.

Blob transactions have an additional blob-data fee component. The ordinary execution-fee calculation alone does not describe their full cost.

What Is the Base Fee?

The base fee is the protocol-determined execution fee per unit of gas for a block.

It adjusts according to the previous block’s gas usage relative to the target:

  • Usage above the target pushes the base fee upward.
  • Usage below the target pushes it downward.

The base fee is burned. It is not paid to the validator.

What Is the Priority Fee?

The priority fee is the additional payment per unit of gas intended to encourage transaction inclusion.

maxPriorityFeePerGas sets the maximum tip the sender is willing to pay.

A higher tip can improve competitiveness when transactions are competing for block space, although ordering and inclusion also depend on block-building arrangements and other economic factors.

The actual priority fee is limited by both the priority-fee cap and the amount left between maxFeePerGas and the block’s base fee.

The execution-layer priority fee is credited to the block’s designated fee recipient. The broader economics between builders and proposers can involve additional payments.

What Does maxFeePerGas Mean?

maxFeePerGas is the sender’s maximum permitted price per unit of execution gas.

A wallet can set a fee cap above the current base fee so the transaction remains eligible if the base fee rises before inclusion.

The unused difference between the fee cap and the effective price is not charged.

This is an important distinction for users accustomed to a bank quoting one fixed transfer fee. A maximum authorization is not necessarily the amount ultimately paid.

What Does the Gas Limit Mean?

gasLimit is the maximum execution gas the transaction is permitted to use.

A simple ETH transfer between ordinary, non-delegated EOAs traditionally uses 21,000 gas. That figure should not be assumed for contract calls or recipients with delegated code.

Setting a high limit does not automatically mean paying for the entire limit. The transaction pays for its actual charged gas usage.

If the limit is too low and top-level execution runs out of gas, execution changes revert and the consumed gas is charged.

The EIP-1559 fee-market specification defines the base-fee mechanism and the associated dynamic-fee transaction format.

Why Can the Same Contract Call Use Different Amounts of Gas?

A function’s gas usage depends on its execution path and the state it encounters.

The same Solidity function can cost different amounts because:

  • Writing a storage slot from zero to nonzero differs from updating or clearing an existing value.
  • A first access to an account or storage slot can cost more than a later access within the same transaction.
  • A longer array may require more iterations.
  • One branch of an if statement may call another contract while another branch does not.
  • Different calldata bytes can produce different data-related costs.
  • Deployment cost depends on initialization code and runtime code.
  • An early revert usually consumes less gas than a failure after several external calls.

For an actual transaction, estimate using the intended arguments and current state through eth_estimateGas.

That estimate is still a simulation. State can change before inclusion, and a provider’s estimation behavior may differ at the edges.

I do not budget a production system from one successful test call.

How Much Does It Cost to Use Ethereum?

There is no fixed price for “an Ethereum transaction.”

The total cost depends on:

  • Gas consumed
  • The block’s base fee
  • The effective priority fee
  • ETH’s price in the relevant fiat currency
  • Whether the transaction runs on L1 or L2
  • The L2 execution fee
  • The L1 data-related component of an L2 fee
  • Any bridge or service charges

For an ordinary L1 execution estimate:

Estimated cost in ETH equals estimated gas multiplied by the estimated price per gas, converted from wei to ETH where necessary.

For illustration, 21,000 gas at 20 gwei per gas costs 0.00042 ETH. That is an arithmetic example, not a current network quote.

If an application displays a fiat estimate, it should explain that the amount changes with ETH’s exchange rate and network conditions.

Protocol fees are only one part of a production budget. Additional costs can include:

  • RPC infrastructure
  • Historical and archive-state queries
  • Event indexing
  • Monitoring and alerting
  • Smart contract audits
  • Oracle fees
  • Bridge fees
  • Custody or key-management infrastructure
  • Legal and operational work

A free RPC plan and a testnet balance are not a realistic production cost model.

Testnet ETH is intended for testing, not as a production asset or a reliable store of value. Testnet demand and infrastructure also do not reproduce mainnet economics.

Ethereum Transaction Types and Why They Exist

Ethereum supports several transaction formats so new capabilities can be introduced without redefining every transaction.

Legacy transactions remain supported alongside the typed transaction envelope system.

The formats most relevant to application developers include:

  • Legacy transactions
  • EIP-2930 access-list transactions, type 1
  • EIP-1559 dynamic-fee transactions, type 2
  • Blob-carrying transactions, type 3
  • EIP-7702 set-code transactions, type 4

Wallets and libraries usually select the appropriate format. A backend that signs transactions still needs to understand what it is creating, whether the target network supports it, and how fees are calculated.

The EIP-4844 blob transaction specification introduced a dedicated data-publication mechanism particularly useful to rollups.

A blob transaction is not a cheap way for an ordinary dApp to store arbitrary files permanently.

Blob data has a distinct fee market and a limited protocol availability period. It is intended for data that rollups need to publish so their state can be reconstructed and verified.

Contracts can work with blob commitments and related verification mechanisms, but they do not directly read the full blob contents as ordinary calldata.

Ethereum Events, Logs, and Indexing

Events provide an application-friendly record of execution, but they are not a substitute for contract storage.

A contract emits an event, and the EVM records it as a log associated with the transaction receipt.

The core log contains:

  • The emitting contract’s address
  • Topics
  • Data

RPC responses add contextual fields such as the block hash, transaction hash, and log index.

For a non-anonymous event, the first topic is the hash of the event signature. Parameters marked indexed occupy additional topics, subject to ABI rules. Dynamic indexed values are represented by hashes rather than their full original contents.

Other parameters are ABI-encoded in the log’s data.

Events are useful for:

  • Deposit histories
  • Ownership changes
  • Backend integrations
  • Notifications
  • Analytics
  • Rebuilding application-specific read models

A contract cannot normally query its own historical logs as though they were storage. Logs are designed primarily for external consumers.

A backend indexer typically:

  1. Records the last processed block.
  2. Retrieves logs over a bounded block range.
  3. Decodes them using the ABI.
  4. Updates a relational database or another application read model.
  5. Applies the required confirmation or finality policy.
  6. Handles reorganizations.
  7. Resumes from a stored checkpoint.

A useful deduplication identifier combines:

chainId + transactionHash + logIndex

Here, the components are combined as an identifier, not numerically added.

A transaction hash alone is insufficient because one transaction can emit multiple logs of the same event type.

That identifier also does not eliminate the need to track block hashes and reorganizations. A transaction can be re-included in a different block context.

A frontend should not scan the entire blockchain every time someone opens a page. The blockchain is the authority for on-chain state; the indexed database is a read model optimized for the product.

Ethereum JSON-RPC: The Interface to a Node

JSON-RPC provides a standard interface for reading Ethereum data, simulating execution, and submitting signed transactions.

Applications normally communicate with execution clients through the Ethereum JSON-RPC API.

JSON-RPC’s core request-response model is stateless and transport-agnostic. Ethereum applications commonly use HTTP or WebSockets. WebSocket subscriptions add connection-bound behavior on top of that basic model.

A minimal request for the latest block number looks like this:

curl -X POST \
  -H "Content-Type: application/json" \
  --data '{
    "jsonrpc": "2.0",
    "method": "eth_blockNumber",
    "params": [],
    "id": 1
  }' \
  http://127.0.0.1:8545

The result is a hexadecimal-encoded quantity:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0x15f90a0"
}

Ethereum JSON-RPC distinguishes quantities from raw byte data.

A quantity has no unnecessary leading zeroes, with zero represented as 0x0. Byte data uses pairs of hexadecimal digits, and an empty byte sequence can be represented as 0x.

Common standard methods include:

MethodPurpose
eth_chainIdIdentify the network
eth_blockNumberRead the current head block number
eth_getBalanceRead an address’s ETH balance
eth_getCodeRead code at an address
eth_getTransactionCountRead an account nonce
eth_callSimulate a call locally
eth_estimateGasEstimate required gas
eth_feeHistoryRetrieve fee-market information
eth_sendRawTransactionSubmit a signed transaction
eth_getTransactionByHashRetrieve transaction details
eth_getTransactionReceiptRetrieve the execution receipt
eth_getLogsFilter event logs
eth_getBlockByNumberRetrieve a block

Where supported by the method and provider, state queries can specify:

  • latest
  • safe
  • finalized
  • pending
  • earliest
  • A particular block number

This is not a cosmetic parameter.

A dashboard requesting latest displays the newest state its node recognizes. A settlement process may need finalized.

The pending view is particularly implementation-dependent because different nodes can know about different pending transactions.

Hosted RPC Provider or Your Own Node?

A hosted RPC provider is usually the fastest route to a prototype.

You receive an endpoint, managed scaling, and often additional APIs.

The trade-off is dependence on:

  • Provider availability
  • Rate limits
  • Historical-data retention
  • Geographic infrastructure
  • Query privacy
  • Support for advanced RPC methods
  • Provider-specific behavior and extensions

Running your own node gives you more control and an independent verification path, but requires storage, bandwidth, monitoring, upgrades, and familiarity with the client software.

A reasonable production arrangement may use multiple RPC endpoints, health checks, and explicit failover.

I also separate standard JSON-RPC methods from proprietary provider APIs. That makes it easier to understand what is portable and what creates a vendor dependency.

Never expose administrative node interfaces or signing capabilities to the public internet merely because a local development configuration made them convenient.

Connecting a TypeScript Application with viem

A TypeScript library handles ABI encoding, typed values, and transport details that would otherwise require manual JSON-RPC work.

The viem getting-started guide introduces the public and wallet clients used for those tasks.

A minimal ETH balance read looks like this:

import { createPublicClient, formatEther, http } from "viem";
import { mainnet } from "viem/chains";

const client = createPublicClient({
  chain: mainnet,
  transport: http(process.env.RPC_URL),
});

const balance = await client.getBalance({
  address: "0x0000000000000000000000000000000000000000",
});

console.log(formatEther(balance));

This example assumes a Node.js or compatible server-side environment with appropriate module support.

The zero address is a valid address to query. It is not a recommendation to send funds there.

For production, validate that RPC_URL is configured rather than silently relying on an unintended default endpoint.

A PublicClient reads data and performs simulations. Write operations use a wallet client, an external wallet, or an appropriate smart account integration.

A production frontend must not contain a hardcoded private key.

Here is a contract-read example:

import {
  createPublicClient,
  http,
  parseAbi,
} from "viem";
import { mainnet } from "viem/chains";

const abi = parseAbi([
  "function balanceOf(address owner) view returns (uint256)",
]);

const client = createPublicClient({
  chain: mainnet,
  transport: http(process.env.RPC_URL),
});

const balance = await client.readContract({
  address: "0xContractAddress",
  abi,
  functionName: "balanceOf",
  args: ["0xOwnerAddress"],
});

The contract and owner strings in this second example are descriptive placeholders, not valid Ethereum addresses. Replace them with real 20-byte addresses on the selected network before running the call.

For a write workflow, I recommend:

  1. Check chainId.
  2. Validate user inputs.
  3. Simulate the intended call with the intended sender and value.
  4. Request the appropriate signature.
  5. Submit the transaction.
  6. Display the transaction hash.
  7. Wait for a receipt.
  8. Check the execution status.
  9. Update the interface and backend read model according to the confirmation policy.

Never assume success merely because a wallet returned a hash.

For ERC-4337, also track the UserOperation result. A bundler transaction can succeed at the transaction level while an individual user operation fails.

A Practical Ethereum Use Case: Escrow for Digital Work

An on-chain escrow can hold funds under explicit rules without giving a platform unrestricted custody of the payment.

Consider a platform connecting a client with a contractor.

In a conventional arrangement, the company may hold the money in its own account. That introduces custody obligations, operational discretion over payouts, and dependence on the operator.

An on-chain escrow can instead implement defined outcomes:

  • The client authorizes payment to the contractor.
  • An arbiter resolves a disagreement.
  • The client becomes eligible for a refund after a deadline if the escrow has not otherwise been resolved.

The simplified example below uses a pull-payment pattern. Resolution credits the beneficiary, and the beneficiary withdraws separately.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract MilestoneEscrow {
    enum State {
        AwaitingFunding,
        Funded,
        Released,
        Refunded
    }

    address public immutable client;
    address public immutable contractor;
    address public immutable arbiter;
    uint256 public immutable amount;
    uint256 public immutable deadline;

    State public state;
    mapping(address => uint256) public credits;

    error Unauthorized();
    error InvalidState();
    error InvalidAmount();
    error DeadlineNotReached();
    error TransferFailed();

    event Funded(uint256 amount);
    event Resolved(State state, address indexed beneficiary);
    event Withdrawn(address indexed account, uint256 amount);

    constructor(
        address contractor_,
        address arbiter_,
        uint256 amount_,
        uint256 deadline_
    ) {
        if (
            contractor_ == address(0) ||
            arbiter_ == address(0) ||
            amount_ == 0 ||
            deadline_ <= block.timestamp
        ) {
            revert InvalidAmount();
        }

        client = msg.sender;
        contractor = contractor_;
        arbiter = arbiter_;
        amount = amount_;
        deadline = deadline_;
    }

    function fund() external payable {
        if (msg.sender != client) revert Unauthorized();
        if (state != State.AwaitingFunding) revert InvalidState();
        if (msg.value != amount) revert InvalidAmount();

        state = State.Funded;
        emit Funded(msg.value);
    }

    function release() external {
        if (msg.sender != client) revert Unauthorized();
        _resolve(contractor, State.Released);
    }

    function refundAfterDeadline() external {
        if (msg.sender != client) revert Unauthorized();
        if (block.timestamp < deadline) revert DeadlineNotReached();

        _resolve(client, State.Refunded);
    }

    function resolveDispute(bool payContractor) external {
        if (msg.sender != arbiter) revert Unauthorized();

        if (payContractor) {
            _resolve(contractor, State.Released);
        } else {
            _resolve(client, State.Refunded);
        }
    }

    function withdraw() external {
        uint256 value = credits[msg.sender];
        if (value == 0) revert InvalidAmount();

        credits[msg.sender] = 0;

        (bool ok, ) = payable(msg.sender).call{value: value}("");
        if (!ok) revert TransferFailed();

        emit Withdrawn(msg.sender, value);
    }

    function _resolve(address beneficiary, State nextState) private {
        if (state != State.Funded) revert InvalidState();

        state = nextState;
        credits[beneficiary] += amount;

        emit Resolved(nextState, beneficiary);
    }
}

This is an educational example, not an audited production escrow.

It demonstrates several useful patterns:

  • An explicit state machine implemented with an enum
  • Critical addresses and terms declared immutable
  • Authorization checks before state changes
  • State updates before external value transfer
  • Pull-based withdrawals
  • Custom errors
  • Events describing business transitions
  • No administrator function that can arbitrarily seize the deposited funds

The business limitations matter just as much as those patterns.

The deadline does not prove whether work was delivered. It only enables the client’s refund path. The contractor has no automatic right to payment based on an off-chain delivery event.

The example also allows funding after the deadline because fund() does not reject it. Once the deadline has passed, a refund and an arbiter’s resolution can compete for ordering, with the first successful resolution determining the outcome.

There is no separate on-chain “dispute opened” state, and the arbiter can resolve a funded escrow without a formal dispute flag.

Those are not details I would leave buried in documentation. They are the economic rules of the product.

A production version must answer further questions:

  • Is payment made in ETH or an ERC-20 token?
  • What happens with fee-on-transfer tokens?
  • Does the contract support multiple milestones?
  • How is the arbiter selected or replaced?
  • Can the client delay approval indefinitely?
  • Are partial payments possible?
  • Is an emergency pause necessary?
  • Can a proxy administrator change the rules?
  • How is accidentally sent property handled?
  • Must the client, contractor, and arbiter be different parties?
  • Does the legal agreement match the on-chain behavior?

This example also shows why the blockchain component should remain small.

Documents, messages, delivered files, search, and notifications do not belong in EVM storage merely because payment uses Ethereum.

The on-chain core needs only the escrow state, amount, participants, deadline, and resolution outcome.

Solidity Storage Layout and Upgrade Compatibility

Storage layout determines where contract state lives and whether future implementations can interpret it correctly.

Solidity organizes persistent variables into 32-byte storage slots. Compatible smaller value types can share a slot, so declaration order can affect storage usage and execution cost.

Mappings and dynamic arrays use calculated storage locations. Dynamic bytes and strings have special layouts that differ between short and longer values.

A mapping does not automatically maintain an iterable list of its keys. Its value locations are derived from the relevant key and base storage slot.

These rules have practical design consequences:

  • Do not try to reproduce a relational database inside a contract.
  • Avoid unbounded loops over arrays that can keep growing.
  • Do not store data merely because it might be useful someday.
  • Emit events for historical information the contract does not need to read again.
  • Store aggregate state when that is sufficient.
  • Plan storage layout before deploying an upgradeable system.

Changing the order or interpretation of variables in a proxy implementation can corrupt the meaning of existing storage.

The Ethereum guide to upgrading smart contracts explains the separation between fixed deployed code and architectures that redirect behavior to new implementations.

Upgradeability is not just permission to replace logic. It is a continuing responsibility involving storage compatibility, initialization, administrator keys, and migration assumptions.

Ethereum Tokens Are Not a Separate Account Type

An Ethereum token is usually contract-defined accounting, not a special object stored inside a wallet application.

A typical ERC-20 contract maintains balances and exposes the interface defined by the ERC-20 token standard.

The familiar functions include:

  • totalSupply()
  • balanceOf(address)
  • transfer(address,uint256)
  • approve(address,uint256)
  • allowance(address,address)
  • transferFrom(address,address,uint256)

When someone says, “My wallet holds this token,” the wallet application is usually reading a contract that records a balance associated with an address.

The wallet is the interface. The contract supplies the token’s accounting rules.

The ERC-721 non-fungible token standard defines ownership and transfer interfaces for individually identified tokens, commonly distinguished by tokenId.

The ERC-1155 multi-token standard supports multiple fungible or non-fungible token types within one contract.

These standards provide interoperability, not economic value.

An ERC-20 token is not automatically trustworthy, liquid, stable, or adequately backed. Likewise, an NFT does not automatically convey copyright, enforceable legal ownership of an external asset, or permanent access to its metadata.

Token Approvals and Allowance Risk

An ERC-20 approval authorizes another address to spend a defined amount of the owner’s tokens.

Many applications ask the user to call approve before the protocol later uses transferFrom.

An unlimited approval reduces repeated signing and transaction costs, but increases the consequences of a compromised or malicious spender.

Depending on the remaining authorization and the token implementation, the spender may be able to transfer both the user’s current balance and tokens received later.

The frontend should clearly show:

  • Which spender receives permission
  • Which token the permission covers
  • The approved amount
  • Whether the allowance is limited or effectively unlimited

A signature is not harmless merely because signing does not itself consume gas.

The EIP-712 typed-data signing standard makes structured signing possible, but a typed-data signature can still authorize a valuable action when another party submits it to a contract.

Users should be able to inspect the domain, chain ID, verifying contract, nonce, expiration where applicable, and the action being authorized.

EIP-712 does not automatically supply replay protection for an application. The contract must enforce the intended domain and authorization rules.

Ethereum Layer 2 and the Rollup-Centric Architecture

Layer 2 rollups scale Ethereum by moving transaction execution away from L1 while using Ethereum for settlement and data publication.

Ethereum L1 prioritizes neutral verification and security rather than making every application action maximally cheap.

The Ethereum scaling overview explains why a substantial share of application activity is handled through L2 systems.

A rollup generally:

  1. Executes transactions outside Ethereum’s L1 EVM.
  2. Groups activity into batches.
  3. Publishes the data needed to reconstruct its state, along with relevant commitments, to Ethereum.
  4. Maintains L1 contracts representing or verifying its settlement state.
  5. Uses a fault-proof or validity-proof mechanism to establish correctness.

A rollup is not the same thing as a sidechain.

A sidechain has its own consensus and security model. A rollup is designed to derive important security properties from Ethereum through settlement, proofs, and data availability.

The exact strength of that inheritance depends on the deployed system, including its upgrade controls and whether its proof and exit mechanisms are actually operational.

How Optimistic Rollups Work

An optimistic rollup treats a proposed state transition as acceptable unless it is successfully challenged under the system’s rules.

The Ethereum explanation of optimistic rollups describes how challenge periods and fault proofs support that model.

Typical advantages include:

  • Strong EVM compatibility
  • Relatively straightforward migration of existing contracts
  • Lower user fees than L1 under typical conditions

Trade-offs include:

  • Canonical withdrawals to L1 may depend on a challenge period.
  • A sequencer can temporarily influence availability and transaction ordering.
  • Bridge contracts and fault-proof implementation become part of the security model.
  • Security depends on the actual availability of the intended challenge process.

A fast withdrawal through a liquidity provider is not the same thing as completion of the canonical withdrawal process. It introduces another service and another set of risks.

How ZK Rollups Work

A ZK rollup uses a cryptographic validity proof to demonstrate that a claimed state transition follows the execution rules.

As described in the Ethereum guide to zero-knowledge rollups, an L1 verifier checks the proof associated with an updated state commitment.

Potential advantages include:

  • Cryptographic verification of execution correctness
  • No equivalent optimistic fraud-challenge waiting model
  • Potentially faster withdrawal settlement once the relevant proof and L1 conditions are satisfied

Trade-offs include:

  • Complex proof generation
  • Critical reliance on the correctness of the proving and verification systems
  • Differences in EVM compatibility and development tooling across networks
  • Continued dependence on sequencer operation and upgrade controls

The name “zero knowledge” does not mean that an L2 automatically provides private transactions. Many ZK rollups publish transaction information openly and use proofs primarily for validity.

Data Availability and Ethereum Blobs

Data availability means the information needed to verify or reconstruct a chain’s state is available when participants need it.

That is different from merely publishing a hash.

If an operator announces a new state but withholds the underlying data, users may be unable to reconstruct their balances, challenge a claim, or exercise an intended exit mechanism.

The Ethereum data availability documentation explains why availability is a distinct security requirement.

Blobs give rollups a dedicated way to publish data without treating every byte as ordinary execution-layer calldata.

They are not permanent contract storage, and their protocol-level retention is limited. Longer-term historical access requires an appropriate archival strategy.

I keep three questions separate: Was the transition valid? Was the necessary data available? Who will preserve that data for future retrieval? One answer does not automatically settle the other two.

Should You Build on Ethereum L1 or an L2?

Choosing between L1 and L2 means choosing a complete operating and trust model, not just a lower average fee.

CriterionEthereum L1Typical rollup
ExecutionDirectly on L1On L2
CostUsually higherUsually lower
ThroughputMore constrainedGenerally higher
Final settlementDirectly through Ethereum consensusDepends on L1 settlement and the rollup’s rules
Additional assumptionsEthereum’s base protocol and infrastructureSequencer, bridge, proof system, and upgrade model
Withdrawal processNot applicable for remaining on L1Depends on the rollup’s design
User experienceLess suitable for frequent low-value actionsOften better suited to application activity

For each L2, I check:

  • Who controls the sequencer?
  • What happens if it stops?
  • Is forced inclusion available?
  • Is there an effective escape mechanism?
  • Who can upgrade the bridge contracts?
  • Does a security council have special powers?
  • How long is the upgrade delay?
  • Where is transaction data published?
  • How do withdrawals work?
  • Is the proof system active?
  • Who is allowed to submit proofs or challenges?
  • Which chain ID, RPC endpoints, and explorer identify the network?

EVM compatibility does not imply identical operational properties.

A contract may compile and deploy successfully on two networks while its users face very different settlement delays, censorship risks, fee behavior, and emergency controls.

Why a Blockchain Bridge Is Not an Ordinary Transfer

A bridge moves value or messages between separate execution domains through a multi-step protocol.

When a user bridges a token from L1 to L2, the asset is often locked in a contract on one side while a corresponding representation is minted or credited on the other.

Other bridge designs use burning, releasing, liquidity provision, or issuer-controlled mechanisms.

To the user, it may look like a transfer. To the system, it is a cross-domain process involving messages, verification, and additional assumptions.

The Ethereum bridge documentation makes these distinctions important for both developers and users.

Bridge risks include:

  • Contract vulnerabilities
  • Incorrect message validation
  • Compromised administrator keys
  • Counterfeit destination tokens
  • The wrong network or destination address
  • Challenge-period delays
  • Confusion between a canonical bridge and a third-party liquidity bridge

A bridge can introduce more security exposure than the token transfer it appears to simplify.

Applications should explicitly identify whether they are using the network’s canonical bridge or a separate liquidity service.

The Oracle Problem: How Ethereum Uses External Data

An oracle brings external information into a smart contract, introducing a separate data trust model.

A contract does not independently know ETH’s price in EUR, a sports result, or whether a package arrived. Those facts are not inherently part of Ethereum consensus.

The Ethereum oracle guide explains how external data becomes available to contracts.

Before accepting an oracle, I ask:

  • Who collects the data?
  • How many underlying sources are used?
  • How often is it updated?
  • What happens if the price is stale?
  • What happens when the underlying market lacks liquidity?
  • Can an attacker manipulate the source temporarily?
  • Does the consuming contract verify freshness?

A DeFi contract that relies on the current price in one shallow liquidity pool may be vulnerable to manipulation using flash liquidity.

A sound integration checks the appropriate feed properties, including:

  • Freshness
  • Decimal scale
  • Expected value ranges
  • Failure and outage behavior
  • L2 sequencer-related conditions where relevant

Blockchain consensus does not turn incorrect external data into truth. It makes execution over the supplied data consistent.

From a financial-risk perspective, the oracle is part of the pricing infrastructure. It deserves the same seriousness as the contract that moves the funds.

MEV: Why Transaction Ordering Matters

Maximal extractable value is value captured through transaction inclusion, exclusion, or ordering.

That is the core concept in Ethereum’s explanation of MEV.

Publicly broadcast pending transactions can often be observed before inclusion. Other participants may respond by submitting competing or strategically ordered transactions.

Examples include:

  • Arbitrage between pools
  • Liquidating a position
  • Buying before a large purchase
  • Selling immediately after that purchase
  • Claiming a publicly exposed secret
  • Exploiting a poorly designed commit-reveal process

Not every transaction follows the public mempool path, and some L2 ordering systems operate differently. The broader issue remains: someone controls or influences the order in which valid actions execute.

Mitigation depends on the application:

  • Set a reasonable slippage limit.
  • Use deadlines where appropriate.
  • Use commit-reveal when the action requires it.
  • Do not expose an unrevealed secret in calldata.
  • Consider batch auctions when ordering should not determine the winner.
  • Evaluate private transaction submission.
  • Bind outcomes to the intended user so a copier cannot claim someone else’s result.

Private submission changes who sees the transaction. It does not remove all trust, ordering risk, or execution uncertainty.

Also, a Solidity variable marked private is not cryptographically private. The keyword restricts access through Solidity’s language rules. It does not make blockchain storage secret.

Smart Contract Security: The Risks That Matter Most

Smart contract security combines code correctness, authorization design, economic assumptions, and operational controls.

A contract may hold assets without a conventional chargeback mechanism or an administrator who can reverse every mistake.

The Ethereum smart contract security guide and Solidity’s security considerations are useful starting points, but no checklist can establish that a financial contract is safe.

These are the risk categories I consider essential.

Reentrancy

Reentrancy occurs when an external call allows another execution path to interact with a contract before its intended invariants have been restored.

The classic pattern is an external transfer before the sender’s balance is updated.

Defenses include:

  • Checks-effects-interactions
  • Pull-payment patterns
  • Reentrancy guards where appropriate
  • Fewer external calls
  • Careful handling of token callbacks and other hooks

Reentrancy is not limited to ETH transfers or re-entering the same function. It can cross functions and contracts.

A guard is a tool, not a substitute for understanding the state machine.

Access Control

Access control determines who may mint, upgrade, withdraw, pause, or change critical parameters.

One incorrect authorization check can expose the entire system.

Do not use tx.origin for authorization. Check msg.sender or verify a deliberately designed signature-based authorization.

For more complex systems, the OpenZeppelin access-control documentation explains ownership and role-based patterns.

Roles should have clear administrators and the minimum privileges needed for their responsibilities.

An address being a contract, EOA, or smart account does not by itself establish that it is trustworthy.

Frontrunning

Visible transaction inputs can be copied or acted on before the original transaction executes.

If a function rewards the first person to submit a publicly visible answer, another participant may copy the calldata and submit a competing transaction.

A safer design can bind the right to claim to a particular account or use a properly constructed commit-reveal process.

Unbounded Loops

A loop over an ever-growing collection can eventually become impossible to execute within transaction or block gas limits.

Use:

  • Pagination
  • Pull-based actions
  • Bounded batches
  • Multi-transaction processing

A function that works with five users is not necessarily viable with fifty thousand.

Oracle and Price Manipulation

A spot price from one pool is not automatically a secure oracle.

Flash loans do not create a pricing bug. They give an attacker temporary capital to exploit an existing weakness.

The defense is an appropriate pricing model and robust integration, not an assumption that attackers lack funds.

Signature Replay

A signature should authorize one clearly defined action in one intended context.

Depending on the application, bind it to:

  • Chain ID
  • Verifying contract
  • Nonce
  • Expiration
  • Specific action
  • Specific arguments

Without domain separation and replay controls, the same signature may be reusable or valid in an unintended environment.

The contract must actually consume or invalidate the relevant authorization. Merely displaying a nonce in a wallet prompt is not enough.

delegatecall

delegatecall executes another contract’s code using the caller’s storage context.

It also preserves important calling-context values, including msg.sender and msg.value.

This is the foundation of many proxy architectures, but it is a significant trust boundary. The delegated implementation can modify the caller’s state, move its assets, and violate its invariants.

Treating a delegated implementation as “just a library” can conceal how much authority it has.

Upgrade Keys

An upgradeable contract is not trustless simply because its current code is public.

If one administrator can immediately replace the implementation, users are relying heavily on that administrator’s key.

Production documentation should explain:

  • Who can upgrade the system?
  • Is multisignature approval required?
  • Is there a timelock?
  • Can users exit before a change takes effect?
  • Which properties, if any, cannot be changed?
  • How is storage compatibility checked?

I consider these financial-control questions, not merely development details.

Denial of Service

A push-payment loop can fail because one recipient rejects payment.

A pull-payment design isolates a failed withdrawal to the affected recipient, rather than blocking everyone else’s payout.

Other denial-of-service risks arise from unbounded work, hostile dependencies, and assumptions that an external call must always succeed.

Decimals and Rounding

The EVM does not provide ordinary floating-point arithmetic.

Financial contracts generally use integers with an explicitly defined scale. The order of multiplication and division affects rounding, and intermediate arithmetic can introduce overflow risks or unintended truncation.

Tokens do not all use 18 decimals.

After years in finance, I do not treat rounding as cosmetic. The contract needs a deliberate policy for precision, dust, and who benefits from each rounding direction.

How to Test Ethereum Smart Contracts Properly

A financial contract needs adversarial testing, not just a few successful example transactions.

A meaningful test plan includes:

  • Authorized and unauthorized calls
  • Every permitted state transition
  • Every forbidden transition
  • Boundary timestamps
  • Zero amounts and zero addresses
  • Repeated calls to the same function
  • Reentrancy attempts
  • Failed ETH or token transfers
  • Different operation orderings
  • Argument fuzzing
  • Invariant testing
  • Fork tests against real deployed contracts
  • Pause behavior
  • Upgrade behavior

An invariant describes a property that should always hold.

For the escrow example, useful properties include:

Released and refunded cannot both be valid outcomes
for the same funded escrow.
Total withdrawals cannot exceed total legitimate funding.
Only the client or arbiter can resolve the escrow
under the defined authorization rules.

These are properties to encode and test, not claims that the compiler automatically proves.

A local workflow using the tools introduced in the Foundry getting-started documentation can begin with:

forge init ethereum-demo
cd ethereum-demo
forge build
forge test

To run a local node:

anvil

A local Ethereum development network provides convenient test accounts, fast block production, time manipulation, and easier debugging.

It does not reproduce real network latency, mainnet fee competition, or the behavior of a public transaction pool.

Local development accounts and their published private keys must never receive real funds.

Hardhat’s getting-started guide offers another widely used development environment, particularly for TypeScript-based projects.

The tool choice matters less than reproducible builds, a pinned compiler version, locked dependencies, and tests that challenge the contract’s assumptions.

An Ethereum Development Workflow from Prototype to Production

A production workflow should establish business invariants before deployment and maintain operational controls afterward.

This is the sequence I would use for a serious application.

1. Define the Invariants

Before writing Solidity, specify who can do what, when assets can leave, and which states must be impossible.

For an escrow, that includes defining what counts as approval, who has dispute authority, and what a deadline actually changes.

If a business rule cannot be expressed clearly, putting it on a blockchain will not make it clearer.

2. Define the On-Chain Boundary

Put only the minimum jointly verifiable state on-chain.

Keep large documents, redundant records, and application content elsewhere. If integrity verification is necessary, consider storing a cryptographic commitment such as a hash.

Remember that a hash can prove correspondence to supplied content. It does not establish that the content is true or legally sufficient.

3. Choose the Network

For high-value settlement with fewer additional infrastructure assumptions, L1 may be appropriate.

For frequent application interactions, a suitable L2 is often more practical.

Choose after examining the security and operating model, not after seeing one inexpensive fee estimate.

4. Implement a Minimal Contract

Avoid unnecessary generic administrator powers and upgradeability.

Use established standard libraries when they address a known problem.

A smaller contract is not automatically safe, but fewer moving parts usually make its rules easier to inspect and test.

5. Test Locally

Cover execution branches, fuzz arguments, and test invariants.

Use separate accounts for each role so the tests do not accidentally conceal authorization mistakes.

Include malicious recipients and failed external calls, not just cooperative users.

6. Test on a Public Testnet

Sepolia is the recommended general-purpose application testnet in the Ethereum network documentation.

Check the current network lifecycle and tooling support before starting a longer project.

Testnet balances and histories are separate from mainnet, even when an address is derived from the same key.

For security, do not reuse an operational mainnet key for testnet work.

If the application targets an L2, also test its actual L2 environment rather than assuming an L1 testnet covers every network-specific behavior.

7. Verify the Deployment

Publish the source code and compiler settings through the appropriate explorer or verification service.

Verification lets others connect source code to deployed bytecode under the relevant compilation settings.

It is not an audit or a guarantee that the contract’s business logic is safe.

8. Test the Frontend on the Wrong Network

The frontend must check chainId, select the correct contract address for that network, and validate the expected deployment.

An address that contains the intended contract on one network may be empty or contain unrelated code on another.

For proxy systems, validation should also account for the expected proxy and implementation arrangement.

9. Secure the Keys

Deployment, ownership, pausing, and upgrading should not automatically belong to the same EOA.

Use multisignature control and timelocks for critical roles where appropriate.

Document key rotation, recovery, and emergency access before a crisis.

10. Monitor the System

Monitoring should cover:

  • Unusual transfers
  • Administrator actions
  • Upgrade events
  • Pause status
  • RPC failures
  • Failed transactions
  • Oracle changes and stale data
  • L2 sequencer status
  • Bridge conditions
  • Finality and reorganizations

A contract being on-chain does not eliminate operations work. It changes which failures must be detected and how quickly the team needs to respond.

11. Prepare an Incident Procedure

If the system can pause, define who can activate that control and under what circumstances.

If it cannot upgrade, establish a migration strategy.

If the frontend is compromised, users should have a documented alternative for interacting with the contracts.

An incident plan is not complete until the responsible people can actually execute it.

Ethereum Reorganizations and Eventual Consistency

An included block is not the same thing as an immediately irreversible database commit.

An application may process a block and later discover that it is no longer part of the canonical chain.

Short reorganizations are a normal possibility in distributed consensus before finality. A reversal of finalized history is a different and much more serious event.

An indexer should:

  • Store the block hash alongside the block number.
  • Check parent-hash continuity.
  • Avoid treating every new block as final.
  • Support rolling back its application read model.
  • Reprocess logs from the replacement canonical branch.
  • Distinguish pending, included, confirmed, and finalized states.

A useful user-facing progression is:

  1. Signing
  2. Submitted
  3. Included
  4. Confirmed under the application’s policy
  5. Finalized

These labels should have documented meanings.

Do not display “failed” merely because eth_getTransactionReceipt currently returns null.

The transaction may still be pending, absent from that particular node’s pool, replaced by another transaction with the same nonce, or temporarily affected by a reorganization.

Receipt absence is a condition to investigate, not a definitive execution result.

Replacing or Canceling an Ethereum Transaction

Ethereum transaction cancellation is usually an attempt to replace a pending transaction, not delete it.

A sender can submit another transaction with the same nonce and a more competitive fee.

If the replacement sends no meaningful value to another party, often by sending to the sender’s own address, a wallet may present it as a cancellation.

The original transaction is not erased. The two transactions compete, and only one can occupy that sender’s nonce in the canonical history.

Replacement requirements are transaction-pool policy details and can vary between clients and providers.

A backend should track:

  • The nonce
  • The original transaction hash
  • Replacement hashes
  • Fee parameters
  • The receipt of the transaction actually included

A transaction hash is not always a stable business identifier.

If the transaction can be replaced, use an application-level identifier in calldata or an event to connect the eventual execution to the underlying order, invoice, or request.

Ethereum Privacy: What a Public Chain Reveals

Ethereum is transparent by default, and address pseudonymity does not make activity confidential.

Publicly observable information includes:

  • Addresses
  • Balances
  • Calldata
  • Reconstructable storage
  • Event logs
  • Deployed contract bytecode
  • Transaction order and timing
  • Interactions between addresses

Hashing personal information does not automatically anonymize it.

If the possible inputs are easy to enumerate, someone can hash candidate values and identify the original. Even an opaque permanent identifier can link activities across time.

Do not publish:

  • Passwords
  • Private keys
  • API secrets
  • Identity documents
  • Medical records
  • Business secrets
  • Unencrypted agreements containing personal information

Encrypted content also creates a long-term exposure question. Copies may remain available indefinitely, even if no individual node promises permanent retention.

If the key later leaks or the encryption becomes vulnerable, historical ciphertext can become readable.

My default is straightforward: keep sensitive content off-chain, minimize permanent identifiers, and treat public publication as potentially irreversible.

Does Ethereum Actually Work?

Ethereum works as shared execution and settlement infrastructure, but that does not make every Ethereum application decentralized, inexpensive, or safe.

If “works” means maintaining shared state, executing public programs, and settling value without one central database operator, the answer is yes.

If “works” means every product is automatically fast, private, secure, and free of intermediaries, the answer is no.

The infrastructure provides meaningful capabilities:

  • Consensus operates without one central database.
  • Smart contracts execute deterministically.
  • Independent participants can verify blocks.
  • Standards enable interoperability.
  • L2 networks reduce the cost of many application transactions.

The limitations remain substantial:

  • Fees are not fixed.
  • Public-chain activity is not private by default.
  • Contract bugs can cause severe losses.
  • L2 systems introduce additional assumptions.
  • Bridges expand the attack surface.
  • Wallet usability and key management remain difficult.
  • Oracles introduce external-data dependencies.
  • A centralized frontend can censor access even when the underlying contract is permissionless.
  • Upgrade administrators may have much more power than the marketing suggests.

I evaluate Ethereum as infrastructure, not ideology.

A credible implementation documents which components are decentralized, which are not, and where users still rely on identifiable operators.

Composability: Ethereum’s Biggest Developer Advantage

Composability lets contracts combine existing on-chain protocols within one atomic execution flow.

The interesting feature is not merely that a developer can deploy a new contract. It is that the contract can interact synchronously with other deployed contracts in the same execution environment.

One transaction might:

  • Receive a token
  • Swap it through a liquidity pool
  • Deposit the result into a lending protocol
  • Issue a representation of the resulting position
  • Return unused assets to the user

If an unhandled failure causes the overall execution to revert, the combined operation’s execution effects roll back.

This feels much closer to connecting open software components than integrating conventional financial institutions.

A contract does not inherently need an API key from another contract. It needs the relevant address, interface, and permission to use the exposed functions.

That final qualification matters. Public deployment does not override access controls.

Atomic composability also generally applies within the same synchronous execution environment. Crossing between L1 and L2, or between separate L2 networks, usually involves asynchronous messages and different settlement stages.

Composability transfers risk as well as functionality.

If an application depends on three protocols, it may also depend on their:

  • Contract implementations
  • Oracles
  • Governance processes
  • Upgrade keys
  • Liquidity
  • Pause mechanisms
  • Economic assumptions

An integration is not safe merely because calling it is technically simple.

When Not to Use Ethereum

Ethereum is a poor fit when public, independently verified coordination is not an important requirement.

I would be cautious if the primary need is:

  • Private processing of sensitive information
  • Millisecond latency
  • Millions of inexpensive, trivial writes
  • Straightforward deletion of records
  • An administrator who must be able to correct every mistake
  • Continuous processing without external transactions
  • Large-file storage
  • Completely predictable per-operation costs
  • An application whose participants already accept one legitimate authority

Do not create a token when a database entry would solve the problem.

Do not put a PDF in contract storage when an off-chain document and an appropriate integrity commitment would suffice.

Do not introduce a DAO simply because an application has several administrators.

The best Ethereum systems often have a small on-chain core surrounded by a much larger conventional software system.

A Practical Ethereum dApp Reference Architecture

A production dApp separates settlement rules from interfaces, indexing, automation, and ordinary application data.

A useful architecture can be described as the following flow:

  1. The user opens a web or mobile frontend. The frontend presents application information and proposed actions.
  2. The frontend connects to a wallet or smart account. It also uses a backend API for off-chain application features.
  3. The wallet authorizes and submits execution requests. Ordinary signed transactions reach an RPC endpoint; smart account flows may add bundler infrastructure.
  4. The backend also connects to RPC infrastructure. It reads chain data and submits transactions only when it has an explicitly authorized signing role.
  5. The RPC node provides access to Ethereum or the selected L2. Smart contracts enforce the on-chain rules.
  6. An indexer processes blockchain activity. It reads blocks and events, then updates the application database.
  7. The backend uses that database as its read model. Search, reporting, and user-facing histories do not require repeated full-chain scans.
  8. The backend integrates external services. These may include oracle infrastructure, document systems, notifications, and other business services.

Responsibilities should remain explicit.

Smart Contracts

Smart contracts:

  • Maintain minimal critical state
  • Enforce authorization rules
  • Control the relevant assets
  • Emit events describing state transitions

Frontend

The frontend:

  • Connects to the user’s wallet
  • Encodes intended calls
  • Displays simulations and fee estimates
  • Tracks transaction status
  • Never stores a shared central signing key

Backend

The backend:

  • Coordinates or consumes event indexing
  • Provides search and analytics
  • Sends notifications
  • Prepares unsigned requests
  • Runs authorized automation
  • Connects external systems

Application Database

The database:

  • Serves as a read model
  • Stores off-chain content
  • Accelerates queries
  • Does not pretend to be authoritative for on-chain ownership

RPC Infrastructure

RPC infrastructure:

  • Provides access to a node
  • Is not the application’s business database
  • Requires timeout, retry, and failover policies
  • Must be monitored for chain freshness and correctness assumptions

Retries deserve special care. Retrying a read and blindly creating a second business transaction are not the same operation.

A Minimal Plan for Your First Serious Ethereum Experiment

A small state machine teaches more about Ethereum than launching an unnecessary token.

For a first meaningful project, I suggest:

  1. Install Foundry or Hardhat.
  2. Start a local development network.
  3. Write a contract with two roles and three states.
  4. Emit an event for every state transition.
  5. Test each allowed and forbidden transition.
  6. Write at least one invariant test.
  7. Interact with the contract using cast, viem, or the library introduced in the ethers v6 getting-started guide.
  8. Index an event into a local database.
  9. Simulate a reorganization or reset the local chain.
  10. Deploy to Sepolia.
  11. Verify the deployed source.
  12. Connect a wallet without putting its private key in the frontend.

After that exercise, gas, ABI encoding, receipts, confirmations, and event indexing stop being abstract vocabulary.

You will also have encountered the boundary that matters most: the difference between proposing an action, executing it, observing it, and treating it as settled.

The Bottom Line: Keep the On-Chain Core Small and Verifiable

Ethereum is a distributed, publicly verifiable state machine for shared execution and settlement.

Smart contracts are not remote servers. They are deterministic programs executed under common rules. Transactions request state changes, gas measures and prices resource use, and proof-of-stake consensus determines the canonical history and finalizes checkpoints.

For developers, the most important shift is to stop treating Ethereum as an unusual database.

It is a coordination layer for parties that do not want one participant to be the sole administrator of their shared financial state.

A strong Ethereum use case justifies its expense and complexity through public verification, independent ownership, or neutral execution.

A weak one moves an ordinary create-read-update-delete application onto slower, more expensive, publicly observable infrastructure without gaining anything important.

My preferred approach is deliberately practical: a small, carefully tested on-chain core; a conventional backend for everything that does not require consensus; documented trust assumptions; secure operational controls; and a settlement policy appropriate to the value at risk.

That is how I would evaluate any serious financial system, whether its ledger runs in a bank’s data center or across Ethereum nodes.


Sponsored Partner: Volet.com

Looking for a simple, everyday way to buy, hold, and sell Ethereum and Bitcoin alongside your traditional currencies and payment cards?