Smart contracts and Solidity · Beginner
Deploy to a local chain and talk to it with cast
Why it matters
Web3 tooling such as Foundry, Hardhat and ethers.js appeared in a third of the Blockchain Developer job descriptions we read. Before anything reaches a testnet, developers deploy to a local chain to try real transactions, check events and debug reverts. It is fast, free and repeatable.
The concept
Anvil runs a local EVM chain with pre-funded test accounts. Chain ID 31337 marks it as a local network.
RPC: tools talk to nodes over JSON-RPC (here
http://127.0.0.1:8545). Testnets and mainnet use the same interface through a node provider.Transactions change state and cost gas (
cast send); calls only read state and are free (cast call).Reverts return error data. A custom error is identified by its 4-byte selector, the first bytes of the Keccak-256 hash of its signature.
The same flow works from application code with ethers.js or viem in TypeScript.
Worked example
Start the chain in one terminal (anvil), then in another, using Anvil's public test keys (never use them with real funds):
$ export RPC=http://127.0.0.1:8545
$ export OWNER_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
$ export FAN_KEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
$ cast chain-id --rpc-url $RPC
31337
$ forge create src/TipJar.sol:TipJar --rpc-url $RPC --private-key $OWNER_KEY --broadcast
Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Transaction hash: 0x9efb89191ae19b3aefc14e85bbfffff6a51d2544a55fa147feeb9a75bb4be394
$ export JAR=0x5FbDB2315678afecb367f032d93F642f64180aa3
$ cast send $JAR "tip(string)" "great talk" --value 0.25ether --rpc-url $RPC --private-key $FAN_KEY
...
status 1 (success)
...
$ cast call $JAR "totalTips()(uint256)" --rpc-url $RPC
250000000000000000 [2.5e17]
$ cast balance $JAR --ether --rpc-url $RPC
0.250000000000000000
$ cast send $JAR "withdraw(address)" 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 --rpc-url $RPC --private-key $FAN_KEY
Error: Failed to estimate gas: server returned an error response: error code 3: execution reverted: custom error 0x30cd7471 ...
$ cast sig "NotOwner()"
0x30cd7471
$ cast send $JAR "withdraw(address)" 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --rpc-url $RPC --private-key $OWNER_KEY
...
status 1 (success)
...
$ cast balance $JAR --ether --rpc-url $RPC
0.000000000000000000Amounts on chain are integers in wei: 0.25 ether is 250,000,000,000,000,000 wei. When the fan tried to withdraw, the node refused while estimating gas, and the error data 0x30cd7471 matches the selector of NotOwner(), so the access control worked before any gas was spent. The owner's withdrawal then emptied the jar.
Common mistakes
Using mainnet or real keys for experiments. Use Anvil, then a public testnet with test ether.
Forgetting units. Contracts use wei and token base units; front ends must convert carefully.
Hard-coding addresses and chain IDs in apps instead of reading them from configuration per network.
Interview angle
"What is the difference between a call and a transaction?" "How do you debug a revert?" (decode the error selector, use cast run or forge test -vvvv traces). "How would a front end show a tip as it happens?" (listen for the Tipped event through ethers.js or viem).
Practice task
Write a Foundry script (
forge script) that deploys the jar and sends one tip, and run it against Anvil.Write a small TypeScript program with viem that reads
totalTipsand listens forTippedevents.