Full Web3 Tutorial (Smart Contracts, Solidity, Javascript)


Welcome to this comprehensive course designed to set you on the path to becoming a blockchain developer. The fascinating world of blockchain technology has dramatically grown over the past decade, and it's not slowing down. The demand for professionals skilled in blockchain development is soaring, with opportunities in a wide array of industries beyond just finance.

This course is designed to take you from the fundamentals of blockchain technology to creating your own decentralized applications (dApps) using Solidity and JavaScript. Whether you are completely new to programming, or an experienced developer looking to expand your skill set, this course has something to offer.

We will start by exploring the core concepts of blockchain technology and its transformative potential. This will set a solid foundation for understanding how digital currencies like Ethereum work, what smart contracts are, and why they're revolutionary.

We will then dive into Solidity, the primary programming language used for writing smart contracts on the Ethereum blockchain. You'll learn its syntax, data types, and control structures, along with best practices for security and efficiency.

Next, we'll explore full-stack Web3 development. You will learn how to create user-friendly interfaces for your dApps using JavaScript, one of the most popular programming languages in the web development world.

By the end of this course, you will have hands-on experience developing dApps and will understand the tools, techniques, and software that blockchain developers use daily, including Remix, Ethers.js, Hardhat, and more.

Welcome To Blockchain

Welcome to the world of Blockchain! Blockchain technology allows for the creation of a decentralized digital ledger of transactions on a distributed network. This ledger is accessible to all network participants, promoting transparency and accountability. Blockchain is the foundational technology behind cryptocurrencies, smart contracts, and much more.

Blockchain Basics

Blockchain consists of a series of blocks, each holding a list of transactions. Once a block is added to the chain, the information within it becomes viewable to everyone on the network and is immutable, i.e., it can't be altered or deleted. The blockchain is maintained by nodes or computers, each having a copy of the entire blockchain.

Welcome to Remix! Simple Storage

Remix is a powerful, open-source tool that allows you to write Solidity contracts straight from your browser. Solidity is a programming language for implementing smart contracts on various blockchain platforms, most notably, Ethereum.

As a starting point, let's create a simple storage contract. This contract will allow you to store a number and retrieve it. Here's how it looks in Solidity:

pragma solidity ^0.8.0;

contract SimpleStorage {
    uint public data;

    function set(uint x) public {
        data = x;

    function get() public view returns (uint) {
        return data;

Remix Storage Factory

Let's now introduce a bit more complexity by creating a contract that deploys our SimpleStorage contract. This new contract will be our StorageFactory.

pragma solidity ^0.8.0;

import "./SimpleStorage.sol";

contract StorageFactory {
    SimpleStorage[] public simpleStorageArray;

    function createSimpleStorageContract() public {
        SimpleStorage simpleStorage = new SimpleStorage();

    //... more code here ...

Remix Fund Me

The 'Fund Me' contract is a simple crowdfunding contract where users can donate money to the contract owner. The owner can then withdraw funds when needed. Below is a basic example:

pragma solidity ^0.8.0;

contract FundMe {
    mapping(address => uint) public contributors;
    address public admin;

    constructor() {
        admin = msg.sender;

    function contribute() public payable {
        contributors[msg.sender] += msg.value;

    function getBalance() public view returns(uint) {
        return address(this).balance;

    function withdraw() public {
        require(msg.sender == admin, "Only admin can withdraw");

The next sections will show how to interact with these contracts using Ethers.js and Hardhat, as well as how to create a frontend for these contracts using HTML/JavaScript and Next.js. It will further delve into more complex topics like ERC20s, DeFi, Aave, NFTs, DAOs, and more.

Remember, the key to mastering these concepts is practice. Try creating your own contracts, modifying existing ones, and always strive to understand what every line of code is doing. Don't just copy-paste!

Ethers.js Simple Storage

Ethers.js is a JavaScript library that makes it easier to interact with Ethereum and its smart contracts. Let's use Ethers.js to interact with our SimpleStorage contract:

First, you'll need to install ethers.js using npm:

npm install --save ethers

Then, you can interact with your SimpleStorage contract like this:

const ethers = require('ethers');

// Connect to the network
let provider = ethers.getDefaultProvider('ropsten');

// The address that the Contract WILL have once mined
let contractAddress = "your_contract_address_here";

// The ABI (interface)
let abi = [ "function get() view returns (uint)", "function set(uint)" ];

// Init contract
let contract = new ethers.Contract(contractAddress, abi, provider);

// Call the contract's 'get' function
let value = await contract.get();
console.log('Stored value in Remix contract:', value.toString());

// Set a new value (only works if you're the contract owner)
let newValue = 7;
let tx = await contract.set(newValue);
await tx.wait();

Hardhat Simple Storage

Hardhat is a development environment to compile, deploy, test, and debug your Ethereum software. It makes developing Ethereum applications efficient, more robust, and enjoyable.

To use Hardhat, install it using npm:

npm install --save-dev hardhat

Then, create a Hardhat config file (hardhat.config.js) and set up a local Ethereum network:

module.exports = {
  networks: {
    hardhat: {
      chainId: 1337
  solidity: "0.8.0",

You can then deploy and interact with your SimpleStorage contract in a script:

const ethers = require('ethers');
const { ethers } = hre;

async function main() {
  const SimpleStorage = await hre.ethers.getContractFactory("SimpleStorage");
  const simpleStorage = await SimpleStorage.deploy();
  await simpleStorage.deployed();

  console.log("SimpleStorage deployed to:", simpleStorage.address);

  let tx = await simpleStorage.set(23);
  await tx.wait();

  let value = await simpleStorage.get();
  console.log('Stored value in Hardhat contract:', value.toString());


Hardhat Fund Me

Similarly, you can deploy your FundMe contract using Hardhat:

const ethers = require('ethers');
const { ethers } = hre;

async function main() {
  const FundMe = await hre.ethers.getContractFactory("FundMe");
  const fundMe = await FundMe.deploy();
  await fundMe.deployed();

  console.log("FundMe deployed to:", fundMe.address);


You can then contribute to the contract and check its balance like this:

let tx = await fundMe.contribute({ value: ethers.utils.parseEther("1.0") });
await tx.wait();

let balance = await fundMe.getBalance();
console.log('Contract balance:', ethers.utils.formatEther(balance));

HTML / Javascript Fund Me (Full Stack / Front End)

To interact with your smart contracts from a webpage, we can use JavaScript along with the ethers.js library. Let's create a simple HTML page to interact with our FundMe contract:

<!DOCTYPE html>
    <button id="contribute">Contribute</button>
    <button id="getBalance">Get Balance</button>
    <div id="balance"></div>
  <script src="https://cdn.ethers.io/lib/ethers-5.0.umd.min.js" type="application/javascript"></script>
  <script src="app.js"></script>

In app.js, you can use ethers.js to interact with the contract:

const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const contractAddress = "your_contract_address_here";
const contractABI = [ "function contribute() public payable", "function getBalance() public view returns (uint)", "function withdraw() public" ];
const fundMeContract = new ethers.Contract(contractAddress, contractABI, signer);

document.getElementById('contribute').addEventListener('click', async () => {
  const tx = await fundMeContract.contribute({ value: ethers.utils.parseEther("0.1") });
  await tx.wait();

document.getElementById('getBalance').addEventListener('click', async () => {
  const balance = await fundMeContract.getBalance();
  document.getElementById('balance').innerText = `Balance: ${ethers.utils.formatEther(balance)} ETH`;

Hardhat Smart Contract Lottery

Next, let's create a simple lottery smart contract with Hardhat. Players can enter the lottery by sending some ether. Once there are enough players, anyone can end the lottery, and the contract will randomly pick a winner:

pragma solidity ^0.8.0;

contract Lottery {
    address payable[] public players;
    address public manager;

    constructor() {
        manager = msg.sender;

    function enter() public payable {
        require(msg.value >= 0.01 ether, "Not enough ether");

    function random() private view returns(uint) {
        return uint(keccak256(abi.encodePacked(block.difficulty, block.timestamp, players.length)));

    function pickWinner() public {
        require(msg.sender == manager, "Only manager can pick a winner");
        require(players.length >= 3, "Not enough players");

        address payable winner;
        uint r = random();

        uint index = r % players.length;
        winner = players[index];

        // Transfer the entire contract balance to the winner

        // Reset the players array for the next round of the lottery
        players = new address payable[](0);

NextJS Smart Contract Lottery (Full Stack / Front End)

Next.js is a popular framework for building server-side rendered React applications. To interact with our Lottery contract, you can create a simple Next.js app:

First, set up a new Next.js project with npx create-next-app.

Create a new file in the pages directory called lottery.js. This will be your lottery page:

import { ethers } from 'ethers';
import { useState } from 'react';

export default function Lottery() {
  const [players, setPlayers] = useState([]);

  async function loadPlayers() {
    const provider = new ethers.providers.Web3Provider(window.ethereum);
    const signer = provider.getSigner();
    const contractAddress = "your_lottery_contract_address_here";
    const contractABI = [ "function enter() public payable", "function pickWinner() public" ];
    const lotteryContract = new ethers.Contract(contractAddress, contractABI, signer);

    setPlayers(await lotteryContract.getPlayers());

  return (
      <button onClick={loadPlayers}>Load players</button>
        {players.map(player => <li key={player}>{player}</li>)}

Hardhat Starter Kit

Hardhat Starter Kit is an ideal template for developing Ethereum dApps. It provides pre-configured tools and scripts to kick-start your dApp development process. To use it, first, clone the Hardhat Starter Kit from its GitHub repository:

git clone https://github.com/nomiclabs/hardhat-starter-kit.git

Then, install the dependencies:

cd hardhat-starter-kit
npm install

This starter kit includes sample smart contracts, scripts, and test files to help you understand the structure of a dApp project. Explore these files and try modifying them to understand their function.

Hardhat ERC20s

ERC20 is a standard interface for tokens, which makes them predictable and enables them to interact seamlessly with other platforms and dApps. Let's create an ERC20 token using Hardhat.

First, install the OpenZeppelin library, which provides reusable, secure smart contracts for Ethereum and other EVM-compatible blockchains:

npm install @openzeppelin/contracts

Now, create a new contract MyToken.sol and use the ERC20 implementation from OpenZeppelin:

// contracts/MyToken.sol
pragma solidity ^0.8.0;

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

contract MyToken is ERC20 {
    constructor(uint256 initialSupply) ERC20("MyToken", "MTK") {
        _mint(msg.sender, initialSupply);

You can then write a script to deploy your token:

// scripts/deploy.js
async function main() {
    const [deployer] = await ethers.getSigners();
    console.log("Deploying contracts with the account:", deployer.address);
    const Token = await ethers.getContractFactory("MyToken");
    const token = await Token.deploy(1000000);
    console.log("Token address:", token.address);

This creates a token with an initial supply of 1,000,000 units, all of which are assigned to the account deploying the contract.

Hardhat DeFi & Aave

Aave is a DeFi lending protocol that enables users to lend and borrow a diverse range of cryptocurrencies using both variable and stable interest rates. You can integrate Aave into your Hardhat project to interact with the Aave protocol. We'll dive more into DeFi and Aave in later sections.

Hardhat NFTs

Non-Fungible Tokens (NFTs) represent ownership of unique items or assets on the blockchain. They can be used to represent real-world or virtual assets. To create an NFT, we use the ERC721 standard. Using OpenZeppelin, the creation of an NFT is straightforward:

// contracts/MyNFT.sol
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

contract MyNFT is ERC721 {
    constructor() ERC721("MyNFT", "MNFT") {}

    function mint(address to, uint256 tokenId) public {
        _mint(to, tokenId);

NextJS NFT Marketplace (Full Stack / Front End)

After setting up our NFT contract, we'll now build a front end to interact with it. We'll use Next.js and ethers.js to build an NFT marketplace where users can mint and view NFTs.

First, create a new file in the pages directory called nft.js:

import { ethers } from 'ethers';
import { useState } from 'react';

export default function NFTMarket() {
  const [tokenID, setTokenID] = useState(0);
  async function mintNFT() {
    const provider = new ethers.providers.Web3Provider(window.ethereum);
    const signer = provider.getSigner();
    const contractAddress = "your_NFT_contract_address_here";
    const contractABI = [ "function mint(address to, uint256 tokenId) public" ];
    const nftContract = new ethers.Contract(contractAddress, contractABI, signer);

    const tx = await nftContract.mint(signer.getAddress(), tokenID);
    await tx.wait();
    setTokenID(tokenID + 1);

  return (
      <button onClick={mintNFT}>Mint NFT</button>
      <p>Minted NFTs: {tokenID}</p>

Hardhat Upgrades

When developing smart contracts, it's essential to keep in mind that once deployed, their code cannot be changed. If you discover a bug in a contract that's already deployed, or you want to add more functionality in the future, you'd traditionally need to deploy a new one. Upgradable contracts solve this issue.

To create upgradable contracts in Hardhat, you'll need the Hardhat Upgrades plugin. Install it with the following command:

npm install @openzeppelin/hardhat-upgrades

Next, require it in your hardhat.config.js:


module.exports = {
  solidity: "0.8.4",

Now you can deploy your contracts as upgradable. An upgradable contract can be deployed using the deployProxy function, and upgraded using the upgradeProxy function. Here's an example:

const { ethers, upgrades } = require("hardhat");

async function main() {
  const MyContract = await ethers.getContractFactory("MyContract");
  const instance = await upgrades.deployProxy(MyContract);
  console.log("Deployed at:", instance.address);


Hardhat DAOs

A Decentralized Autonomous Organization (DAO) is a system of hard-coded rules that define which actions an organization can take. They replace the need for traditional documents and people in governing roles with smart contracts and token holders.

Implementing a DAO using Solidity involves creating a smart contract that enables token holders to propose and vote on proposals. Here's a simple implementation:

// contracts/SimpleDAO.sol
pragma solidity ^0.8.0;

contract SimpleDAO {
  struct Proposal {
    string description;
    uint voteCount;

  mapping(address => bool) public voters;
  Proposal[] public proposals;

  function propose(string memory description) public {
    voters[msg.sender] = true;
      description: description,
      voteCount: 0

  function vote(uint proposalIndex) public {
    require(voters[msg.sender], "Only voters can vote");

Security and Auditing

Security is paramount when developing smart contracts. Unlike regular web applications, once a smart contract is deployed, it cannot be altered, and any flaw in the contract could lead to irreversible damage or loss of funds.

Conducting regular audits of your contracts is a crucial aspect of maintaining security. Audits involve meticulously reviewing your contract code to identify potential vulnerabilities. When conducting an audit:

  1. Check for common vulnerabilities, like re-entrancy, integer overflow, and underflow, and ensure the contract doesn't use outdated versions of Solidity.
  2. Ensure the contract has appropriate access controls and that function visibility is set correctly.
  3. Check that the contract handles exceptions properly and has a safe fallback function.
  4. Make sure that any external contract calls are handled correctly.
  5. Test your contracts thoroughly. Write unit tests for all functions and run them with coverage. Consider using property-based and fuzz testing.
  6. Use static analysis tools like Slither or Securify.

Contract auditing is a complex task that requires a good understanding of Ethereum and Solidity. It is generally recommended to have your contracts audited by professionals if they are managing large amounts of value.

Wrapping up, blockchain development is a vast and exciting field, with Ethereum leading the way in the decentralized world. Keep practicing, learning, and never stop building. As the blockchain space continues to grow, so will the opportunities it offers.

This tutorial provided you with the necessary tools and knowledge to start your journey as a blockchain developer

What is Remix?

Remix is an open-source web and desktop application used for developing, testing, and debugging smart contracts written in Solidity programming language for Ethereum. It provides an easy-to-use interface and a powerful Solidity editor with features like syntax highlighting and auto-completion.

What is Ethers.js?

Ethers.js is a complete and compact library for interacting with the Ethereum blockchain. It's used to create wallets, sign transactions, interact with smart contracts, and much more. It is a lightweight alternative to web3.js and is often preferred for its cleaner API and modular design.

What is the difference between Web3.js and Ethers.js?

Both web3.js and ethers.js are libraries that provide tools to interact with the Ethereum blockchain. They can be used to send transactions, interact with smart contracts, among other things. The key difference between them lies in their design philosophy and API structure. Web3.js was the first library of its kind, and while it's comprehensive, some developers find it complex and inconsistent. On the other hand, ethers.js came later and offers a more compact, consistent, and modular API.

What is Hardhat?

Hardhat is a development environment for Ethereum software. It helps developers manage and automate the recurring tasks involved in building smart contracts and blockchain applications, and also introduces new features to help you get the most out of JavaScript and Solidity.

Is Hardhat a Truffle?

No, Hardhat and Truffle are two different development frameworks for Ethereum. They both provide tools for smart contract development, testing, and deployment, but they differ in terms of design and functionality. Some developers prefer Hardhat for its advanced debugging capabilities, flexible configuration, and TypeScript support, while others prefer Truffle for its comprehensive feature set and large community.

What is a Smart Contract?

A smart contract is a self-executing contract with the terms of the agreement directly written into code. They run on the blockchain, so they are stored on a public database and cannot be changed. Transactions that happen in a smart contract are processed by the blockchain, which means they can be sent automatically without a third party.

What is ERC20?

ERC20 is a technical standard for implementing tokens on the Ethereum blockchain. ERC stands for Ethereum Request for Comment, and 20 is the number assigned to this specification. ERC20 defines a set of rules that a token must implement, including how to transfer these tokens, how to access data about the token, and how to signal approval for third-parties to transfer tokens.

What is DeFi?

DeFi, or Decentralized Finance, is a term used to describe financial services that operate on the blockchain. These are software-based financial services that don't rely on intermediaries such as banks or brokers, but on smart contracts. Examples of DeFi services include lending and borrowing platforms, decentralized exchanges, prediction markets, and more.

What is Aave?

Aave is a decentralized non-custodial money market protocol where users can participate as depositors or borrowers. Depositors provide liquidity to the market to earn a passive income, while borrowers are able to borrow in an overcollateralized or undercollateralized manner.

What are NFTs?

NFTs, or Non-Fungible Tokens, are a type of digital asset created using blockchain technology. Unlike cryptocurrencies such as Bitcoin or Ethereum, which are fungible and can be exchanged on a one-for-one basis, NFTs are unique and can't be exchanged like for like.

What is a DAO?

DAO stands for Decentralized Autonomous Organization. These are essentially organizations governed by code and built on the blockchain. A DAO operates with smart contracts, which

Is Solidity harder than Python?

Python is often recommended for beginners due to its simplicity and readability, which makes it easier to grasp, especially for those new to programming. Solidity, on the other hand, is a statically typed language with a syntax closer to JavaScript and C++, and it is used specifically for creating smart contracts on the Ethereum blockchain.

Learning Solidity might be more challenging than learning Python, primarily because Solidity involves understanding blockchain concepts, and the environment in which your code runs is distributed and has peculiarities such as gas fees and immutability.

Is Solidity difficult to learn?

The difficulty of learning Solidity can depend on your prior programming and blockchain experience. If you are familiar with JavaScript or another C-style language and understand the basics of blockchain technology, you may find the syntax of Solidity familiar, making it easier to learn.

However, writing smart contracts in Solidity also involves understanding specific principles related to the blockchain, like how transactions work, what gas fees are, the concept of immutability, and more. Therefore, even experienced developers will need to learn and adapt to these new concepts.

Furthermore, Solidity's environment is less forgiving than most programming environments. Mistakes can be costly because once a contract is deployed on the blockchain, it can't be altered.

Despite these challenges, with the right resources and dedication, Solidity is certainly learnable. There's a growing ecosystem of educational content and development tools to assist you along the way. It is advisable to understand the basics of blockchain technology before diving into Solidity.

What is EVM?

The EVM, or Ethereum Virtual Machine, is the runtime environment for smart contracts in Ethereum. It is completely isolated from the main network, which makes it a perfect sandbox for executing smart contracts.

Each Ethereum node runs its own EVM implementation and can execute the same instructions. When a smart contract is executed, every node on the network independently runs the same contract using their EVM to arrive at the same result. This redundancy is what makes Ethereum and other blockchain networks exceptionally secure.

The EVM is Turing complete, meaning it can execute any algorithm given enough resources (time and gas). It interprets a low-level, stack-based bytecode language that is designed to be compatible with the Ethereum blockchain. This bytecode is what Solidity smart contracts get compiled to.

The EVM also manages Ethereum's state, including account balances, smart contract code and data, and more. Transactions in Ethereum change this state, and the EVM ensures that these state transitions are processed correctly.

Importantly, the EVM also handles the internal cryptocurrency of Ethereum, known as Ether. Ether is used to pay for transaction fees, which are calculated based on the computational resources used by a transaction. This system prevents spam transactions and abuse of the network.

What is OpenZeppelin?

OpenZeppelin is a library for secure smart contract development. It provides implementations of standards like ERC20 and ERC721 which you can deploy as-is or extend to suit your needs, and it also provides Solidity components to build custom contracts and more complex decentralized systems.

OpenZeppelin's main focus is security. Smart contracts can manage valuable assets and are highly visible to attackers, so they must be built carefully. The OpenZeppelin library is maintained by a team of experts and is extensively tested and audited for security vulnerabilities. It also provides detailed documentation and development guidelines to help ensure that your smart contracts are safe and reliable.

The OpenZeppelin suite also includes other tools for Ethereum development:

  • OpenZeppelin Contracts: A library of reusable, secure, and audited smart contracts for Ethereum and other EVM and eWASM blockchains.
  • OpenZeppelin Defender: A platform for automating Ethereum operations, managing governance, performing transactions, and more.
  • OpenZeppelin Test Environment: A testing library that makes it quick and easy to write tests for your Ethereum applications.

By leveraging OpenZeppelin's suite of tools and libraries, developers can build secure, reliable, and sophisticated blockchain applications.

Want to find a web3 job?

Receive emails of Full Web3 Tutorial (Smart Contracts, Solidity, Javascript)

More by Patrick Collins
Ask me anything