| name | smart-contract-reading-guide |
| description | How to read and understand smart contracts — navigating Etherscan, reading Solidity code, understanding ABIs, decoding transactions, and spotting common patterns. Use when helping users verify contracts, understand DeFi protocol mechanics, or decode on-chain activity. |
| metadata | {"openclaw":{"emoji":"📜"}} |
Smart Contract Reading Guide
You don't need to be a Solidity developer to read smart contracts. This guide teaches you to understand what contracts do by reading their code on block explorers.
Finding Contract Code
Block Explorers
| Chain | Explorer | URL |
|---|
| Ethereum | Etherscan | etherscan.io |
| Arbitrum | Arbiscan | arbiscan.io |
| Base | BaseScan | basescan.org |
| Optimism | Optimistic Etherscan | optimistic.etherscan.io |
| Polygon | PolygonScan | polygonscan.com |
Steps to Read a Contract
- Go to the explorer → Enter contract address
- Click "Contract" tab
- Look for the green checkmark ("Contract Source Code Verified")
- If not verified → RED FLAG — don't interact with unverified contracts
Understanding Contract Structure
Solidity 101 for Readers
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// Interface — defines what functions exist
interface IERC20 {
function transfer(address to, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
}
// Contract — the actual code
contract MyToken is IERC20 {
// State variables (stored on blockchain)
string public name = "My Token";
mapping(address => uint256) private _balances;
address public owner;
// Events (logs, used for tracking)
event Transfer(address indexed from, address indexed to, uint256 value);
// Modifier (access control)
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
// View function (read-only, free to call)
function balanceOf(address account) external view returns (uint256) {
return _balances[account];
}
// State-changing function (costs gas)
function transfer(address to, uint256 amount) external returns (bool) {
_balances[msg.sender] -= amount;
_balances[to] += amount;
emit Transfer(msg.sender, to, amount);
return true;
}
// Owner-only function (⚠️ check these carefully)
function mint(address to, uint256 amount) external onlyOwner {
_balances[to] += amount;
}
}
Key Solidity Concepts
| Concept | Meaning | Why It Matters |
|---|
public | Anyone can call/read | Normal, expected |
external | Only callable from outside | Normal for functions |
view / pure | Read-only (free to call) | Safe — no state changes |
onlyOwner | Only the owner can call | Check what owner can do |
payable | Can receive ETH | May collect fees |
mapping | Key-value storage | Stores balances, approvals |
require | Validation check | If false, transaction reverts |
emit | Logs an event | Used for tracking |
Reading on Etherscan
"Read Contract" Tab
Free queries — anyone can call these:
| Function | What It Returns |
|---|
name() | Token name |
symbol() | Token symbol (e.g., "USDC") |
decimals() | Decimal places (6 for USDC, 18 for most tokens) |
totalSupply() | Total tokens in existence |
balanceOf(address) | How many tokens an address holds |
owner() | Who controls the contract |
paused() | Whether the contract is paused |
"Write Contract" Tab
Requires wallet connection and gas:
| Function | What It Does | Risk Level |
|---|
transfer() | Send tokens | Normal |
approve() | Grant spending permission | Medium (check amount) |
stake() | Lock tokens for rewards | Normal |
mint() | Create new tokens | Check who can call |
Decoding Transactions
Transaction Overview
On any transaction page:
| Field | What It Shows |
|---|
| Status | Success or Failed |
| From | Sender address |
| To | Contract called |
| Value | ETH sent |
| Input Data | Function call + parameters |
| Gas Used | Actual gas consumed |
Reading Input Data
Raw input data looks like:
0xa9059cbb000000000000000000000000abcdef...00000000000000000000000000000000000000000000000000000002540be400
Decoded (Etherscan does this automatically for verified contracts):
Function: transfer(address, uint256)
to: 0xabcdef...
amount: 10000000000 (10,000 USDC with 6 decimals)
Event Logs
Every transaction emits events (in the "Logs" tab):
Transfer(
from: 0x1234...,
to: 0x5678...,
value: 1000000000000000000 (1 ETH in wei)
)
Common DeFi Contract Patterns
ERC-20 Token
| Function | What to Check |
|---|
mint() | Who can call? If unrestricted → inflation risk |
burn() | Deflationary mechanism |
pause() | Can transfers be frozen? |
blacklist() | Can addresses be blocked? |
setFee() | Can transfer tax be changed? |
Lending Protocol (Aave-style)
| Function | What It Does |
|---|
supply() | Deposit collateral |
borrow() | Take a loan |
repay() | Pay back loan |
liquidationCall() | Liquidate unhealthy position |
getReserveData() | Read pool stats (APY, utilization) |
DEX (Uniswap-style)
| Function | What It Does |
|---|
swap() | Execute a token swap |
mint() / addLiquidity() | Provide liquidity |
burn() / removeLiquidity() | Remove liquidity |
getReserves() | Current pool balances (determines price) |
Stablecoin (like USDs)
| Function | What to Check |
|---|
mint() | How is new supply created? What collateral is accepted? |
redeem() | Can you always redeem for underlying? |
rebase() | How yield is distributed (USDs auto-rebases) |
collateralRatio() | Is it fully backed? |
ABI (Application Binary Interface)
The ABI defines how to interact with a contract programmatically:
[
{
"name": "balanceOf",
"type": "function",
"inputs": [{ "name": "account", "type": "address" }],
"outputs": [{ "name": "", "type": "uint256" }],
"stateMutability": "view"
}
]
Where to get ABIs:
- Etherscan → Contract tab → "Contract ABI" section
- Protocol documentation
- GitHub repositories
Proxy Contracts
Many DeFi protocols use proxies (upgradeable contracts):
User → Proxy Contract → Implementation Contract
(fixed address) (logic, can be upgraded)
On Etherscan: Look for "Read as Proxy" / "Write as Proxy" tabs. If you see a proxy, click through to read the implementation contract.
Security Checklist for Contract Review
| Check | How | Risk If Failed |
|---|
| ✅ Contract verified | Green checkmark on explorer | Can't see what code does |
| ✅ Check owner functions | Search for onlyOwner, onlyAdmin | Owner could rug |
| ✅ Check mint capability | Search for mint function | Infinite inflation |
| ✅ Check pause/blacklist | Search for pause, blacklist | Funds could be frozen |
| ✅ Check fee functions | Search for fee, tax | Fees could be raised to 100% |
| ✅ Audit report | Check project website | Unaudited = higher risk |
| ✅ Timelock on upgrades | Check if proxy has a timelock | Instant upgrade = rug risk |
Agent Tips
- Verified contract is non-negotiable — never recommend interacting with unverified contracts
- "Read as Proxy" — always check for proxy implementation for the real logic
- Owner functions are key — what the owner can do defines the trust assumptions
- View functions are free — encourage users to read contract state before transacting
- Etherscan does the heavy lifting — auto-decodes transactions, ABI, and events
- Sperax contracts are verified — USDs, SPA, and Farms contracts on Arbiscan are fully verified and audited
Links