Events and Error Handling
Smart contracts run in an isolated environment with no built-in "print to console" for the outside world, events are how a contract communicates what happened to applications and users watching the blockchain.
contract Token {
mapping(address => uint256) public balances;
event Transfer(address indexed from, address indexed to, uint256 amount);
function transfer(address to, uint256 amount) public {
require(amount > 0, "Amount must be positive");
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
balances[to] += amount;
emit Transfer(msg.sender, to, amount);
}
}
emit Transfer(...) writes a permanent, cheap-to-store log entry to the blockchain, off-chain applications (like a wallet's UI, or a block explorer) listen for these events instead of constantly polling contract state, it's how "your transaction went through" notifications actually work under the hood. indexed parameters can be efficiently searched/filtered by event listeners.
Custom errors
Modern Solidity favors custom errors over plain string messages, they're significantly cheaper in gas:
error InsufficientBalance(uint256 available, uint256 requested);
function withdraw(uint256 amount) public {
if (balances[msg.sender] < amount) {
revert InsufficientBalance(balances[msg.sender], amount);
}
balances[msg.sender] -= amount;
}
revert with a custom error immediately stops execution and undoes all state changes in the current transaction, exactly like require, but lets you attach structured data (here, the actual vs. requested amounts) instead of just a plain string.