first smart contract

Writing your first smart contract โ€” build an ERC-20 token with Hardhat

๐Ÿ“˜ defi-academy · Part 6 of 15 · Module 2 · Development
A free, step-by-step course on buying, selling & trading domains. View the full series →
Writing your first smart contract โ€” build an ERC-20 token with Hardhat
Key takeaways
  • OpenZeppelin provides audited, battle-tested base contracts โ€” always use them for production ERC-20 tokens.
  • Test every path: transfers, permissions, edge cases, and supply caps before deploying to any network.
  • Deploy to testnet first, verify on Etherscan, then consider mainnet only after thorough testing.

Understanding first smart contract is the foundation of this guide. Below we break it down step by step so you can act with confidence.

The best way to learn smart contract development is to build something real. In this lesson, you will create a complete ERC-20 token โ€” the standard behind every fungible token in DeFi โ€” and deploy it to a testnet using Hardhat, the industry-standard development framework.

By the end, you will have a deployed, verified smart contract that you can interact with from any wallet or dApp.

First Smart Contract: Setting up Hardhat

Hardhat is the most popular Ethereum development framework. Set it up:

๐Ÿ”ฌ Demo: Initialize a Hardhat project
mkdir my-token && cd my-token
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox
npx hardhat init
# Select "Create a JavaScript project"

# Install OpenZeppelin contracts
npm install @openzeppelin/contracts

This gives you a complete development environment with compilation, testing, deployment, and verification tools.

Building the ERC-20 token

Create contracts/MyToken.sol:

๐Ÿ”ฌ Demo: Complete ERC-20 token contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract MyToken is ERC20, Ownable {
    uint256 public constant MAX_SUPPLY = 1_000_000 * 10 ** 18;

    constructor()
        ERC20("MyToken", "MTK")
        Ownable(msg.sender)
    {
        // Mint half the supply to the deployer
        _mint(msg.sender, MAX_SUPPLY / 2);
    }

    /// @notice Owner can mint new tokens up to MAX_SUPPLY
    function mint(address to, uint256 amount) public onlyOwner {
        require(totalSupply() + amount <= MAX_SUPPLY, "Exceeds max supply");
        _mint(to, amount);
    }

    /// @notice Anyone can burn their own tokens
    function burn(uint256 amount) public {
        _burn(msg.sender, amount);
    }
}

OpenZeppelin's ERC20 base contract implements all 6 standard functions (totalSupply, balanceOf, transfer, allowance, approve, transferFrom) plus events. You inherit battle-tested, audited code.

Writing tests

Create test/MyToken.test.js:

๐Ÿ”ฌ Demo: Comprehensive token tests
const { expect } = require('chai');
const { ethers } = require('hardhat');

describe('MyToken', function () {
  let token, owner, addr1, addr2;

  beforeEach(async function () {
    [owner, addr1, addr2] = await ethers.getSigners();
    const MyToken = await ethers.getContractFactory('MyToken');
    token = await MyToken.deploy();
  });

  it('should set the correct name and symbol', async function () {
    expect(await token.name()).to.equal('MyToken');
    expect(await token.symbol()).to.equal('MTK');
  });

  it('should mint half supply to deployer', async function () {
    const balance = await token.balanceOf(owner.address);
    const maxSupply = await token.MAX_SUPPLY();
    expect(balance).to.equal(maxSupply / 2n);
  });

  it('should transfer tokens between accounts', async function () {
    const amount = ethers.parseEther('100');
    await token.transfer(addr1.address, amount);
    expect(await token.balanceOf(addr1.address)).to.equal(amount);
  });

  it('should not allow minting beyond max supply', async function () {
    const maxSupply = await token.MAX_SUPPLY();
    await expect(
      token.mint(addr1.address, maxSupply)
    ).to.be.revertedWith('Exceeds max supply');
  });

  it('should only allow owner to mint', async function () {
    await expect(
      token.connect(addr1).mint(addr1.address, 100n)
    ).to.be.reverted;
  });

  it('should allow burning own tokens', async function () {
    const amount = ethers.parseEther('100');
    await token.burn(amount);
    const balance = await token.balanceOf(owner.address);
    const expected = (await token.MAX_SUPPLY()) / 2n - amount;
    expect(balance).to.equal(expected);
  });
});

Run with npx hardhat test. All tests should pass.

Explore Crypto Trading

Cryptocurrency trading is one of the fastest-growing financial markets โ€” open 24/7, globally accessible, and full of opportunity for those who learn the fundamentals first.

  • Trade spot, futures & copy-trading on one platform
  • Start with as little as $10 โ€” practice risk-free on the testnet
  • Built-in TradingView charts, grid bots & DCA tools

Free Crypto Trading Academy → ยท Domain Trading Academy →

Deploying to a testnet

Create scripts/deploy.js and deploy to Sepolia:

๐Ÿ”ฌ Demo: Deploy and verify
// scripts/deploy.js
const hre = require('hardhat');

async function main() {
  const MyToken = await hre.ethers.getContractFactory('MyToken');
  const token = await MyToken.deploy();
  await token.waitForDeployment();

  const address = await token.getAddress();
  console.log('MyToken deployed to:', address);

  // Wait for a few confirmations before verifying
  console.log('Waiting for confirmations...');
  await token.deploymentTransaction().wait(5);

  // Verify on Etherscan
  await hre.run('verify:verify', {
    address: address,
    constructorArguments: [],
  });
  console.log('Contract verified on Etherscan');
}

main().catch(console.error);
# Configure Sepolia in hardhat.config.js, then:
npx hardhat run scripts/deploy.js --network sepolia

After deployment, your token is live on Sepolia. Anyone can see the verified source code on Etherscan, interact with it, and add it to their MetaMask wallet.

When evaluating first smart contract, start with the fundamentals: what problem it solves, who it serves, and how it compares to the alternatives buyers consider first.

Start with the fundamentals. Define who you are serving, the single outcome you want, and the budget you are comfortable committing before you compare any options.

A practical rule is to favour clarity over cleverness. The choice that is easiest to understand, spell, remember and act on tends to win over the long run.

Context changes the answer. What is optimal for a fast-moving startup is rarely optimal for an established brand, so weigh your own stage, market and ambitions.

Avoid the common mistakes: rushing the decision, copying competitors blindly, and ignoring the total cost of ownership beyond the very first year of use.

Think in terms of trade-offs rather than a single perfect answer. Every option trades memorability, cost, reach and flexibility in a slightly different mix.

Document your reasoning. Writing down why you chose one path makes it far easier to revisit the decision later and to explain it to partners and investors.

Test with real people. A quick check with a handful of your actual customers will surface problems that no amount of internal debate ever reveals.

Plan for growth. The option that fits today should still make sense when your audience, catalogue or team is several times larger than it is right now.

Finally, revisit the decision periodically. Markets, tools and best practices move quickly, and a choice that was ideal last year may deserve a fresh look today.

When evaluating first smart contract, start with the fundamentals: what problem it solves, who it serves, and how it compares to the alternatives buyers consider first.

Start with the fundamentals. Define who you are serving, the single outcome you want, and the budget you are comfortable committing before you compare any options.

WSOT 2026: Squad Leader Slots Are Open โ€” Up to $128,000 per Leader

Join Now

Frequently asked questions

How much does deploying a smart contract cost?

On Ethereum mainnet, a simple ERC-20 costs $20-80 in gas depending on network congestion. On Layer 2s (Arbitrum, Base), it costs $0.50-5. Testnets are completely free โ€” always test there first.

What is the approve + transferFrom pattern?

When you approve a DEX to spend your tokens, you call approve(dexAddress, amount). The DEX then calls transferFrom(yourAddress, dexAddress, amount) to move tokens. This two-step pattern is how all DeFi protocols interact with ERC-20 tokens.

Should I use OpenZeppelin or write from scratch?

Always use OpenZeppelin for production contracts. Their code is audited, battle-tested, and used by the majority of DeFi protocols. Writing from scratch is educational but introduces unnecessary risk.

Free Courses โ€” Learn New Skills

No sign-up · No fees · Learn at your own pace

Start Trading

Trade crypto and premium domains โ€” digital assets with real opportunities.

Find your name on Atom

DominantBrand curates the best premium, brandable names from Atom.com — the marketplace with a free AI appraisal, a USPTO trademark check, and secure escrow. Every listing even ships with a designed logo.

Browse Domains