Difficulty: Beginner
A transaction is the fundamental unit of state change on the Ethereum blockchain. Every modification to the blockchain state - transferring ETH, calling a smart contract function, deploying a new contract - is triggered by a transaction. Understanding the anatomy of a transaction, how it is signed, encoded, and processed is essential for any Ethereum developer.
An Ethereum transaction contains several fields. The 'to' field specifies the recipient address (or is empty for contract creation). The 'value' field indicates the amount of ETH to transfer (in wei, where 1 ETH = 10^18 wei). The 'data' field contains the input data - for contract calls, this is the ABI-encoded function selector and arguments; for simple transfers, it is typically empty. The 'nonce' is the sender's transaction count, ensuring each transaction is unique and processed in order. The 'gasLimit' specifies the maximum gas the transaction can consume, and under EIP-1559, 'maxFeePerGas' and 'maxPriorityFeePerGas' define the fee structure.
EIP-1559, activated in August 2021, fundamentally changed how transaction fees work on Ethereum. Before EIP-1559, users bid a single gas price in a first-price auction, leading to overpayment and fee unpredictability. EIP-1559 introduced a base fee that adjusts algorithmically based on network congestion - when blocks are more than 50% full, the base fee increases; when they are less than 50% full, it decreases. The base fee is burned (destroyed), removing ETH from circulation. Users also specify a priority fee (tip) to incentivize validators to include their transaction. This system makes fees more predictable and, during periods of high activity, can make ETH deflationary.
Transaction signing is the process of cryptographically authorizing a transaction using the sender's private key. The transaction fields are serialized using Recursive Length Prefix (RLP) encoding - a space-efficient binary serialization format specific to Ethereum. The RLP-encoded transaction is then hashed with Keccak-256, and the hash is signed using the ECDSA (Elliptic Curve Digital Signature Algorithm) on the secp256k1 curve. The signature consists of three values: v, r, and s. The r and s values are the actual signature components, while v is a recovery identifier that allows the sender's public key (and thus address) to be recovered from the signature without needing the public key in advance.
Once signed, the transaction is broadcast to the Ethereum network through the JSON-RPC API (typically via eth_sendRawTransaction). It propagates through the peer-to-peer network and enters each node's transaction pool (mempool). Validators select transactions from the mempool to include in the next block, generally prioritizing those with higher tips. Once included in a block and the block is finalized, the transaction's state changes become permanent. The entire lifecycle - creation, signing, broadcast, mempool, inclusion, confirmation, finalization - typically takes 12-15 seconds for inclusion and about 12-13 minutes for full finality on Ethereum PoS.
import { ethers } from 'ethers';
// Connect to a test network
const provider = new ethers.JsonRpcProvider('https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY');
// Create wallet from private key (NEVER use real keys in code)
const wallet = new ethers.Wallet('0xYOUR_PRIVATE_KEY', provider);
async function sendTransaction() {
// 1. Check current balance and nonce
const balance = await provider.getBalance(wallet.address);
console.log('Balance:', ethers.formatEther(balance), 'ETH');
const nonce = await provider.getTransactionCount(wallet.address);
console.log('Current nonce:', nonce);
// 2. Get fee data for EIP-1559 transaction
const feeData = await provider.getFeeData();
// 3. Build an EIP-1559 transaction
const tx = {
type: 2, // EIP-1559 transaction type
to: '0xRecipientAddress',
value: ethers.parseEther('0.01'), // Send 0.01 ETH
nonce: nonce,
gasLimit: 21000n, // Standard ETH transfer gas
maxFeePerGas: feeData.maxFeePerGas,
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas,
chainId: 11155111, // Sepolia testnet
};
console.log('\nTransaction to send:', {
...tx,
value: ethers.formatEther(tx.value) + ' ETH',
maxFeePerGas: ethers.formatUnits(tx.maxFeePerGas, 'gwei') + ' gwei',
maxPriorityFeePerGas: ethers.formatUnits(tx.maxPriorityFeePerGas, 'gwei') + ' gwei',
});
// 4. Sign and send the transaction
const signedTx = await wallet.sendTransaction(tx);
console.log('\nTransaction hash:', signedTx.hash);
console.log('Waiting for confirmation...');
// 5. Wait for the transaction to be mined
const receipt = await signedTx.wait();
console.log('\nTransaction confirmed!');
console.log('Block number:', receipt.blockNumber);
console.log('Gas used:', receipt.gasUsed.toString());
console.log('Effective gas price:', ethers.formatUnits(receipt.gasPrice, 'gwei'), 'gwei');
console.log('Total cost:', ethers.formatEther(receipt.gasUsed * receipt.gasPrice), 'ETH');
console.log('Status:', receipt.status === 1 ? 'Success' : 'Failed');
}
// Example: Calling a smart contract function
async function callContract() {
const contractAddress = '0xContractAddress';
const abi = [
'function transfer(address to, uint256 amount) returns (bool)',
'function balanceOf(address owner) view returns (uint256)',
];
const contract = new ethers.Contract(contractAddress, abi, wallet);
// Read-only call (no transaction needed, no gas cost)
const balance = await contract.balanceOf(wallet.address);
console.log('Token balance:', balance.toString());
// State-changing call (sends a transaction, costs gas)
const tx = await contract.transfer(
'0xRecipientAddress',
ethers.parseUnits('100', 18) // 100 tokens with 18 decimals
);
console.log('Transfer tx hash:', tx.hash);
const receipt = await tx.wait();
console.log('Transfer confirmed in block:', receipt.blockNumber);
}
sendTransaction();
This example demonstrates the full lifecycle of an Ethereum transaction. It shows how to construct an EIP-1559 transaction with proper fee parameters, sign and send it using ethers.js, wait for confirmation, and examine the receipt. The second function shows the difference between read-only calls (free, instant) and state-changing transactions (cost gas, require confirmation).
Transaction Fields, Signing, RLP Encoding, EIP-1559