All articles

How to Build and Deploy an Ethereum Smart Contract Safely

A practical path from Solidity code to a verified contract on the Sepolia testnet: setting up Hardhat, using OpenZeppelin, writing tests, avoiding reentrancy and access control mistakes, keeping keys safe, and preparing for mainnet.

Blockchain|Published |10 min read
Connected blocks illustrating a blockchain network

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

MistakeWhat can happenHow to prevent it
ReentrancyAn external contract calls back into your function before balances are updated and drains fundsUpdate state before external calls (checks-effects-interactions) and use ReentrancyGuard
Missing access controlAnyone can call an admin function such as mint or withdrawUse Ownable or AccessControl and test every restricted function
Using tx.origin for authorisationA malicious contract tricks a user into calling it and acts on their behalfCheck msg.sender instead
Trusting a single price sourceAn attacker manipulates a thin market to move the price your contract readsUse a reliable oracle and sanity limits on price changes
Unbounded loopsA function that loops over a growing list eventually exceeds the block gas limit and stops workingAvoid 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

  1. 1Create a dedicated deployer wallet and fund it with test ETH from a Sepolia faucet.
  2. 2Store the private key and RPC URL in environment variables or Hardhat's encrypted configuration, and keep them out of Git.
  3. 3Write a deployment script that sets constructor arguments explicitly and prints the deployed address.
  4. 4Deploy, then verify the source code on Etherscan so anyone can read the contract and interact with it.
  5. 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.

Related articles

More articles on software development, AI, cloud, and infrastructure.

A desktop screen showing landing page designs next to a tablet and a phone
Web Development|

How to Build a Company Profile Website That Brings in Leads

What a company profile website needs to bring in enquiries: clear service pages, proof such as case studies and client logos, easy contact options, fast loading on mobile, SEO basics, the right platform, and tracking that shows which pages produce leads.

A customer paying with a phone at a shop counter
Software Development|

Integrating a Payment Gateway Like Midtrans or Xendit Safely

How to integrate an Indonesian payment gateway such as Midtrans or Xendit: choosing payment methods, hosted checkout versus direct API, verifying webhooks, handling duplicate notifications, order status design, expiry, refunds, testing in sandbox, and daily reconciliation.

Looking for a software development partner?

Tell us about your project, what you need to build, and the challenges you are facing. We can discuss the technical approach, scope, timeline, and estimated cost.

Start a conversation