InsightsBlockchain

Your First Smart Contract: Solidity and Foundry Tutorial (2026)

A Solidity tutorial for beginners using Foundry: install, write a contract, test it, deploy and verify on the Sepolia testnet. Copy-paste code included.

Digital Silk Route7 min read

Your First Smart Contract: Solidity and Foundry Tutorial (2026)

To write your first smart contract in 2026, install Foundry, create a project with forge init, write a Solidity contract in src/, test it with forge test, and deploy it to the Sepolia testnet with forge script --broadcast. This tutorial walks through each step with a small "TipJar" contract, including tests, a fuzz test, a safe keystore setup and contract verification. Everything runs on a testnet, so you never risk real money.

Key takeaways

  • Foundry lets you write contracts and tests in the same language (Solidity) and runs tests very quickly.
  • Install with curl -L https://getfoundry.sh/install | bash, then run foundryup.
  • Test success and failure paths. Use vm.prank, vm.deal and vm.expectRevert from forge-std.
  • Store your deployer key in Foundry's encrypted keystore with cast wallet import, not in plain text.
  • Deploy and verify with one command: forge script ... --broadcast --verify.

What you will build

A TipJar contract that:

  • accepts tips in ETH with a short message,
  • records the total tipped,
  • emits an event for every tip,
  • lets only the owner withdraw.

It is small, but it covers the core ideas of Solidity: state variables, payable functions, events, custom errors, access control and sending ETH safely.

Step 1: Install Foundry

Foundry runs on macOS and Linux. On Windows, use WSL or Git Bash (PowerShell and Command Prompt are not supported).

curl -L https://getfoundry.sh/install | bash
# restart the terminal, or run: source ~/.bashrc
foundryup

foundryup installs four tools:

  • forge to build, test and deploy,
  • cast to talk to the chain from the command line,
  • anvil for a local test chain,
  • chisel for a Solidity REPL.

Check the install:

forge --version

Step 2: Create a project

forge init tipjar
cd tipjar

forge init creates src/, test/ and script/ folders with an example Counter contract, and installs forge-std, the standard testing library. Remove the example files so you start clean:

rm src/Counter.sol test/Counter.t.sol script/Counter.s.sol

Step 3: Write the Solidity contract

Create src/TipJar.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

/// @title TipJar: a minimal tipping contract for learning (testnet only)
contract TipJar {
    address public immutable owner;
    uint256 public totalTips;

    event Tipped(address indexed from, uint256 amount, string message);
    event Withdrawn(address indexed to, uint256 amount);

    error NotOwner();
    error ZeroTip();
    error TransferFailed();

    constructor() {
        owner = msg.sender;
    }

    function tip(string calldata message) external payable {
        if (msg.value == 0) revert ZeroTip();
        totalTips += msg.value;
        emit Tipped(msg.sender, msg.value, message);
    }

    function withdraw() external {
        if (msg.sender != owner) revert NotOwner();
        uint256 amount = address(this).balance;
        (bool ok, ) = payable(owner).call{value: amount}("");
        if (!ok) revert TransferFailed();
        emit Withdrawn(owner, amount);
    }
}

What each part does

  • pragma solidity ^0.8.24; sets the compiler version. Since 0.8, arithmetic overflow reverts automatically.
  • owner is immutable: set once in the constructor and cheap to read.
  • payable lets tip receive ETH; msg.value is the amount sent, in wei.
  • Custom errors (error NotOwner();) are cheaper than revert strings and easy to test.
  • Events are written to the transaction log. Frontends and indexers read them (we use them in BC-04).
  • withdraw uses a low-level call and checks the result. Older tutorials use transfer, which forwards a fixed 2,300 gas and can break with smart-contract wallets.

Build it:

forge build

Step 4: Write tests in Solidity

Create test/TipJar.t.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {Test} from "forge-std/Test.sol";
import {TipJar} from "../src/TipJar.sol";

contract TipJarTest is Test {
    TipJar jar;
    address alice = makeAddr("alice");

    // The test contract deploys TipJar, so it is the owner.
    // It needs receive() to accept ETH from withdraw().
    receive() external payable {}

    function setUp() public {
        jar = new TipJar();
        vm.deal(alice, 1 ether);
    }

    function test_TipIncreasesTotal() public {
        vm.prank(alice);
        jar.tip{value: 0.1 ether}("Shukriya!");
        assertEq(jar.totalTips(), 0.1 ether);
        assertEq(address(jar).balance, 0.1 ether);
    }

    function test_RevertWhen_TipIsZero() public {
        vm.expectRevert(TipJar.ZeroTip.selector);
        jar.tip("nothing");
    }

    function test_RevertWhen_NonOwnerWithdraws() public {
        vm.prank(alice);
        vm.expectRevert(TipJar.NotOwner.selector);
        jar.withdraw();
    }

    function test_OwnerCanWithdraw() public {
        vm.prank(alice);
        jar.tip{value: 0.2 ether}("hi");

        uint256 before = address(this).balance;
        jar.withdraw();

        assertEq(address(this).balance, before + 0.2 ether);
        assertEq(address(jar).balance, 0);
    }

    function testFuzz_TipAnyAmount(uint96 amount) public {
        vm.assume(amount > 0);
        vm.deal(alice, amount);
        vm.prank(alice);
        jar.tip{value: amount}("fuzz");
        assertEq(jar.totalTips(), amount);
    }
}

Run the tests:

forge test -vvv

The cheatcodes used here

CheatcodeWhat it does
makeAddr("alice")Creates a labelled test address
vm.deal(addr, amount)Sets an address's ETH balance
vm.prank(addr)Makes the next call come from addr
vm.expectRevert(selector)Expects the next call to revert with that error
vm.assume(cond)Skips fuzz inputs that do not meet the condition

The fuzz test runs testFuzz_TipAnyAmount with many random amounts. This is one of Foundry's biggest strengths: it finds edge cases you would not think to write by hand.

Check coverage too:

forge coverage

Step 5: Try it on a local chain with Anvil

Before touching a public testnet, deploy locally. Create script/DeployTipJar.s.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {Script, console} from "forge-std/Script.sol";
import {TipJar} from "../src/TipJar.sol";

contract DeployTipJar is Script {
    function run() external returns (TipJar jar) {
        vm.startBroadcast();
        jar = new TipJar();
        vm.stopBroadcast();
        console.log("TipJar deployed at", address(jar));
    }
}

In one terminal, start Anvil:

anvil

Anvil prints ten funded test accounts and their private keys. These are public, well-known keys for local use only; never send real funds to them. Import the first one into the keystore under a local-only name:

cast wallet import anvil-0 --interactive
# paste the first private key that Anvil printed, then choose a password

In a second terminal:

forge script script/DeployTipJar.s.sol \
  --rpc-url http://127.0.0.1:8545 \
  --account anvil-0 \
  --broadcast

Without --broadcast, forge script only simulates. That is a useful dry run before any real deployment.

Step 6: Deploy to the Sepolia testnet

Get the pieces you need

  1. A fresh testnet wallet. Create a new account in MetaMask (or with cast wallet new) that you only use for testnets.
  2. Sepolia test ETH from a public faucet. Test ETH has no monetary value.
  3. An RPC URL for Sepolia from a node provider such as Alchemy, Infura or a public endpoint.
  4. An Etherscan API key (free) for verification.

Store the key safely

cast wallet import sepolia-deployer --interactive

Foundry encrypts the key in ~/.foundry/keystores/. You type the password when you deploy. Do not put private keys in .env files that could be committed.

Put non-secret settings in your shell:

export SEPOLIA_RPC_URL="https://your-sepolia-rpc-url"
export ETHERSCAN_API_KEY="your-etherscan-key"

Deploy and verify in one command

forge script script/DeployTipJar.s.sol \
  --rpc-url $SEPOLIA_RPC_URL \
  --account sepolia-deployer \
  --broadcast \
  --verify \
  --etherscan-api-key $ETHERSCAN_API_KEY

Foundry sends the transaction, waits for it to be mined, and submits the source code to Etherscan. When it finishes, open the contract address on Sepolia Etherscan and you will see a green check on the "Contract" tab.

If verification fails because the explorer was slow, you can retry later with forge verify-contract <address> src/TipJar.sol:TipJar --chain sepolia --etherscan-api-key $ETHERSCAN_API_KEY.

Step 7: Interact with the contract using cast

Replace <ADDRESS> with your deployed address.

# Read state (no transaction, no gas)
cast call <ADDRESS> "totalTips()(uint256)" --rpc-url $SEPOLIA_RPC_URL

# Send a tip of 0.001 test ETH
cast send <ADDRESS> "tip(string)" "First tip from Pakistan" \
  --value 0.001ether \
  --rpc-url $SEPOLIA_RPC_URL \
  --account sepolia-deployer

Check the "Events" tab on Etherscan to see your Tipped event.

Common beginner errors and fixes

  • "Compiler version mismatch": check the pragma line and that foundryup is current.
  • Tests fail on withdraw with "TransferFailed": the receiving contract has no receive() function.
  • "insufficient funds for gas": get more Sepolia ETH from a faucet.
  • Script runs but nothing deploys: you forgot --broadcast.
  • Verification pending for a long time: wait a minute and use forge verify-contract.

Where to go next

  1. Add a minimumTip set in the constructor and test it.
  2. Replace the owner check with OpenZeppelin's Ownable and compare.
  3. Read BC-03 on smart contract vulnerabilities and look for weaknesses in your own code.
  4. Build a frontend for this exact contract in BC-04.

Next step with Digital Silk Route

This tutorial is the kind of first step the Blockchain track of the Digital Silk Route (DSR) AI & Crypto Academy builds on. The track runs from Beginner to Intermediate to Advanced, and each level ends with a capstone you can ship: a tested contract, then a full dApp, then a security-reviewed product. Once it is ready, you can list templates, tools or finished products on the DSR Marketplace. Learn. Build. Earn.

Sources

This article is for education only and is not financial, legal or investment advice.

Frequently asked questions

  • solidity tutorial for beginners
  • foundry tutorial
  • first smart contract
  • deploy smart contract sepolia
  • forge test
  • solidity 2026