A smart contract is a program that lives on a blockchain and holds rules, and often money, that nobody can quietly change. That property is the whole point, and it is also what makes the work unforgiving: once a contract is deployed, its code cannot be patched like a web application, and a bug can be exploited by anyone in the world within minutes. This guide walks through the workflow we teach for building an Ethereum contract with care, from the first line of Solidity to a verified deployment.
Decide Whether You Need a Smart Contract
A contract makes sense when several parties who do not fully trust each other need shared rules and a shared record: token issuance, escrow, on-chain voting, NFTs that must be transferable between platforms, or settlement between companies. If one organisation controls all the data and every user already trusts it, a normal database is cheaper, faster, and easier to fix. Being honest about this early saves a project from paying gas fees and audit costs for no real benefit.
Set Up the Development Environment
- Hardhat: a Node.js toolkit for compiling, testing, and deploying contracts, with a local network for fast tests and good error messages.
- Foundry: an alternative where tests are written in Solidity, with very fast execution and built-in fuzzing. Many teams use it alongside Hardhat.
- OpenZeppelin Contracts: audited implementations of ERC-20, ERC-721, access control, and security helpers, so you do not write these from scratch.
- ethers.js or viem: libraries for calling contracts from scripts and from the web application.
- MetaMask with a separate development wallet that never holds real funds.
Build on Audited Code
Most token contracts need very little custom code. An ERC-20 token can inherit from the OpenZeppelin implementation and add only what is specific to the project, such as a capped supply or a minting role. Every line you write yourself is a line that needs testing and review, so keep custom logic small and reuse standard building blocks wherever possible. Pin the library version in the project and read its changelog before upgrading.
Test More Than You Would for a Web App
- Unit tests for every public function, covering the normal path and every condition that should revert.
- Tests for permissions: confirm that accounts without the right role cannot mint, pause, withdraw, or change settings.
- Edge cases: zero amounts, the maximum supply, transfers to the contract itself, and repeated calls.
- Fuzz tests that throw random inputs at functions and check that invariants, such as total supply, always hold.
- A gas report, so you notice when a change makes a common function much more expensive for users.
Common Security Mistakes
| Mistake | What can happen | How to prevent it |
|---|---|---|
| Reentrancy | An external contract calls back into your function before balances are updated and drains funds | Update state before external calls (checks-effects-interactions) and use ReentrancyGuard |
| Missing access control | Anyone can call an admin function such as mint or withdraw | Use Ownable or AccessControl and test every restricted function |
| Using tx.origin for authorisation | A malicious contract tricks a user into calling it and acts on their behalf | Check msg.sender instead |
| Trusting a single price source | An attacker manipulates a thin market to move the price your contract reads | Use a reliable oracle and sanity limits on price changes |
| Unbounded loops | A function that loops over a growing list eventually exceeds the block gas limit and stops working | Avoid iterating over user-controlled arrays and let users claim individually |
Solidity 0.8 and later reverts automatically on integer overflow, which removed a whole class of older bugs, but it does not protect against logic errors. Run a static analyser such as Slither on every change. It catches many of the patterns above in seconds.
Deploy to the Sepolia Testnet
- 1Create a dedicated deployer wallet and fund it with test ETH from a Sepolia faucet.
- 2Store the private key and RPC URL in environment variables or Hardhat's encrypted configuration, and keep them out of Git.
- 3Write a deployment script that sets constructor arguments explicitly and prints the deployed address.
- 4Deploy, then verify the source code on Etherscan so anyone can read the contract and interact with it.
- 5Run through the real user flow from the web application against the testnet contract, including failed transactions.
Plan for Upgrades Before Mainnet
Because deployed code cannot change, decide early how mistakes will be handled. Some projects keep contracts immutable and deploy a new version with a migration path. Others use an upgradeable proxy pattern such as UUPS, which allows fixes but adds complexity and puts significant trust in whoever controls the upgrade key. Either choice is valid if it is deliberate and explained to users. A pause function for emergencies is useful in many contracts, as long as the pause role is protected.
Before Going to Mainnet
- Freeze the code and have it reviewed by someone who did not write it. For contracts that hold significant value, commission an external audit.
- Move admin roles to a multisig wallet such as Safe, so no single lost or stolen key can take over the contract.
- Deploy from a hardware wallet, not from a key stored on a developer laptop.
- Document every admin function, who holds each role, and what the emergency procedure is.
- Set up monitoring for contract events and large transfers, so unusual activity is noticed quickly.
Run the full deployment script against a local fork of mainnet first. It catches wrong addresses, missing approvals, and gas estimates that only show up with real network state.
Key takeaways
- Use a smart contract only when multiple parties need shared rules that no single party can change.
- Build on audited OpenZeppelin contracts and keep custom logic as small as possible.
- Test permissions, edge cases, and invariants, and run Slither on every change.
- Deploy and verify on Sepolia, and test the full user flow before touching mainnet.
- Decide your upgrade strategy early, and protect admin roles with a multisig and hardware wallet.


