Building Blockchain Applications with Solidity, React, Next.js and Rust

Building Blockchain Applications with Solidity, React, Next.js and Rust

Blockchain technology is revolutionizing various industries by providing decentralized and transparent solutions. Developing applications on the blockchain can seem daunting, but with the right tools and guidance, it becomes a manageable and rewarding endeavor. In this guide, we will walk you through building a blockchain application using Solidity for smart contracts, React for the user interface, and Next.js for server-side rendering and routing.

Prerequisites

To follow along with this tutorial, you should have a basic understanding of JavaScript and React. Familiarity with blockchain concepts and Solidity will be helpful but not required. By the end of this tutorial, you will have a working blockchain application and a good grasp of how to develop and deploy decentralized applications (dApps).

Overview

  1. Introduction to Blockchain Technology

    • Understanding the basics of blockchain and its applications.

    • Overview of Ethereum and smart contracts.

  2. Setting Up the Development Environment

    • Installing Node.js and npm.

    • Installing Truffle or Hardhat for smart contract development.

    • Setting up a local Ethereum network using Ganache.

    • Installing Metamask for managing Ethereum accounts and transactions.

  3. Writing Smart Contracts in Solidity

    • Basic syntax and structure of a Solidity contract.

    • Writing a simple smart contract (e.g., a token contract or a voting contract).

    • Compiling and deploying the contract to the local Ethereum network.

  4. Interacting with Smart Contracts using Web3.js or Ethers.js

    • Setting up a React application with Next.js.

    • Installing Web3.js or Ethers.js for interacting with the blockchain.

    • Connecting the React app to the Ethereum network using Metamask.

    • Reading from and writing to the smart contract.

  5. Building the Frontend with React and Next.js

    • Creating components for the user interface.

    • Displaying data from the blockchain in the UI.

    • Handling user inputs and transactions.

  6. Creating Your Own Coin

    • Writing a token contract in Solidity.

    • Deploying the token contract.

    • Integrating the token with the frontend.

  7. Testing and Deployment

    • Writing tests for smart contracts.

    • Deploying the smart contract to a testnet (e.g., Ropsten or Rinkeby).

    • Deploying the React application to a hosting service (e.g., Vercel).

  8. Advanced Topics

    • Integrating Solana and Rust for high-performance blockchain applications.
  9. Conclusion

    • Recap of what was covered.

    • Additional resources for further learning.

    • Encouragement to build and share blockchain applications.

Let's dive into each section in detail.

1. Introduction to Blockchain Technology

Understanding the Basics of Blockchain and Its Applications

Blockchain is a distributed ledger technology that enables secure and transparent record-keeping. Unlike traditional databases, a blockchain stores data in a decentralized manner, meaning that no single entity has control over the entire network. This decentralized nature makes blockchain inherently secure and resistant to tampering.

Key Concepts in Blockchain

  1. Blocks: A blockchain is composed of a series of blocks, each containing a list of transactions. Blocks are linked together in chronological order, forming a chain.

  2. Nodes: Nodes are computers that participate in the blockchain network. Each node maintains a copy of the entire blockchain and validates transactions.

  3. Consensus Mechanisms: Consensus mechanisms, such as Proof of Work (PoW) and Proof of Stake (PoS), are used to achieve agreement among nodes on the state of the blockchain.

  4. Smart Contracts: Smart contracts are self-executing contracts with the terms of the agreement directly written into code. They run on the blockchain and can automate complex processes.

Blockchain has a wide range of applications, from financial services and supply chain management to voting systems and healthcare. By providing a transparent and immutable record of transactions, blockchain technology can increase trust and efficiency in various industries.

Overview of Ethereum and Smart Contracts

Ethereum is a decentralized platform that enables developers to build and deploy smart contracts. A smart contract is a self-executing contract with the terms of the agreement directly written into code. Smart contracts run on the Ethereum Virtual Machine (EVM) and can be used to automate and enforce agreements without the need for intermediaries.

Why Ethereum?

  1. Decentralization: Ethereum is a decentralized platform, meaning no single entity controls it. This ensures transparency and security.

  2. Smart Contract Support: Ethereum was the first blockchain to introduce smart contracts, allowing developers to create decentralized applications (dApps).

  3. Active Development Community: Ethereum has a large and active development community, providing extensive resources and support for developers.

Solidity is the most widely used programming language for writing smart contracts on the Ethereum platform. It is a statically-typed language with syntax similar to JavaScript, making it relatively easy for developers to learn and use.

2. Setting Up the Development Environment

To develop blockchain applications, we need to set up a development environment that includes tools for writing, testing, and deploying smart contracts, as well as for building the frontend of our application.

Installing Node.js and npm

Node.js is a JavaScript runtime that allows us to run JavaScript code on the server side. npm (Node Package Manager) is a package manager for Node.js that helps us install and manage dependencies for our project.

To check if Node.js and npm are already installed on your machine, open a terminal and run the following commands:

node -v
npm -v

If Node.js and npm are not installed, download and install them from the official Node.js website.

Installing Truffle or Hardhat

Truffle and Hardhat are popular frameworks for developing, testing, and deploying smart contracts. Both tools provide a suite of features that make it easier to manage the lifecycle of a smart contract project.

Installing Truffle

Truffle is a development framework for Ethereum that provides a suite of tools for smart contract development. It includes a built-in testing framework, a deployment pipeline, and a console for interacting with contracts.

To install Truffle globally, run the following command:

npm install -g truffle

Installing Hardhat

Hardhat is a development environment for Ethereum that provides a flexible and extensible toolset for smart contract development. It includes a task runner, a testing framework, and a local blockchain network for testing.

To install Hardhat as a development dependency in your project, run the following command:

npm install --save-dev hardhat

Setting Up a Local Ethereum Network Using Ganache

Ganache is a personal blockchain for Ethereum development that you can use to deploy contracts, develop applications, and run tests. It provides a local blockchain network with pre-funded accounts, making it easy to test and debug smart contracts.

To install Ganache CLI, run the following command:

npm install -g ganache-cli

To start a local Ethereum network with Ganache, run the following command:

ganache-cli

This will start a local blockchain network on http://127.0.0.1:8545 with 10 pre-funded accounts.

Installing Metamask

Metamask is a browser extension that allows you to manage Ethereum accounts and interact with the blockchain. It acts as a bridge between your browser and the Ethereum network, enabling you to send transactions and interact with smart contracts directly from your browser.

To install Metamask, visit the Metamask website and follow the instructions to add the extension to your browser.

3. Writing Smart Contracts in Solidity

Smart contracts are the backbone of blockchain applications. In this section, we will learn the basics of Solidity and write a simple smart contract.

Basic Syntax and Structure of a Solidity Contract

A Solidity contract is a collection of code (functions) and data (state variables) that resides at a specific address on the Ethereum blockchain. Here is a simple example of a Solidity contract:

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

contract SimpleStorage {
    uint256 public storedData;

    function set(uint256 x) public {
        storedData = x;
    }

    function get() public view returns (uint256) {
        return storedData;
    }
}

In this contract, we define a state variable storedData to store a value, and two functions set and get to update and retrieve the value, respectively.

Writing a Simple Smart Contract

Let's write a simple smart contract for storing and retrieving a value.

  1. Create a new directory for your project and navigate into it:

     mkdir my-blockchain-app
     cd my-blockchain-app
    
  2. Initialize a new Truffle or Hardhat project:

     # Using Truffle
     truffle init
    
     # Using Hardhat
     npx hardhat
    
  3. Create a new file SimpleStorage.sol in the contracts directory and add the following code:

     // SPDX-License-Identifier: MIT
     pragma solidity ^0.8.0;
    
     contract SimpleStorage {
         uint256 public storedData;
    
         function set(uint256 x) public {
             storedData = x;
         }
    
         function get() public view returns (uint256) {
             return storedData;
         }
     }
    

Compiling and Deploying the Contract

Next, we need to compile and deploy the smart contract to the local Ethereum network.

Using Truffle

  1. Compile the contract:

     truffle compile
    
  2. Deploy the contract:

    Create a new file 2_deploy_contracts.js in the migrations directory and add the following code:

     const SimpleStorage = artifacts.require("SimpleStorage");
    
     module.exports = function (deployer) {
         deployer.deploy(SimpleStorage);
     };
    

    Run the deployment script:

     truffle migrate
    

Using Hardhat

  1. Compile the contract:

     npx hardhat compile
    
  2. Deploy the contract:

    Create a new file deploy.js in the scripts directory and add the following code:

     async function main() {
         const SimpleStorage = await ethers.getContractFactory("SimpleStorage");
         const simpleStorage = await SimpleStorage.deploy();
    
         console.log("SimpleStorage deployed to:", simpleStorage.address);
     }
    
     main()
         .then(() => process.exit(0))
         .catch((error) => {
             console.error(error);
             process.exit(1);
         });
    

    Run the deployment script:

     npx hardhat run scripts/deploy.js
    

4. Interacting with Smart Contracts using Web3.js or Ethers.js

Now that we have deployed our smart contract, we need to interact with it from our frontend application. We will use Web3.js or Ethers.js to connect our React application to the Ethereum network and interact with the smart contract.

Setting Up a React Application with Next.js

Next.js is a popular framework for building React applications with server-side rendering and routing capabilities. Let's set up a new Next.js application.

  1. Create a new Next.js application:

     npx create-next-app my-blockchain-app
     cd my-blockchain-app
    

Installing Web3.js or Ethers.js

Web3.js and Ethers.js are JavaScript libraries that allow you to interact with the Ethereum blockchain from your frontend application.

  1. Install Web3.js or Ethers.js:

     # Install Web3.js
     npm install web3
    
     # Or install Ethers.js
     npm install ethers
    

Connecting to the Ethereum Network using Metamask

To interact with the Ethereum network from our React application, we need to connect to Metamask. Here is an example of how to do this using Web3.js:

import Web3 from 'web3';

if (typeof window.ethereum !== 'undefined') {
  const web3 = new Web3(window.ethereum);
  try {
    // Request account access
    await window.ethereum.enable();
    // Accounts now exposed
  } catch (error) {
    console.error("User denied account access...");
  }
}

Reading from and Writing to the Smart Contract

Let's create a React component to interact with our smart contract.

  1. Create a new file SimpleStorage.js in the components directory and add the following code:

     import React, { useEffect, useState } from 'react';
     import Web3 from 'web3';
    
     const SimpleStorage = ({ contract }) => {
       const [storedValue, setStoredValue] = useState(0);
       const [inputValue, setInputValue] = useState('');
    
       useEffect(() => {
         const getStoredValue = async () => {
           const value = await contract.methods.get().call();
           setStoredValue(value);
         };
    
         getStoredValue();
       }, [contract]);
    
       const handleSet = async () => {
         const accounts = await web3.eth.getAccounts();
         await contract.methods.set(inputValue).send({ from: accounts[0] });
         setInputValue('');
       };
    
       return (
         <div>
           <h1>Stored Value: {storedValue}</h1>
           <input
             type="number"
             value={inputValue}
             onChange={(e) => setInputValue(e.target.value)}
           />
           <button onClick={handleSet}>Set Value</button>
         </div>
       );
     };
    
     export default SimpleStorage;
    
  2. Update the index.js file to use the SimpleStorage component:

     import React, { useEffect, useState } from 'react';
     import Web3 from 'web3';
     import SimpleStorage from '../components/SimpleStorage';
     import SimpleStorageContract from '../build/contracts/SimpleStorage.json';
    
     const Home = () => {
       const [web3, setWeb3] = useState(null);
       const [contract, setContract] = useState(null);
    
       useEffect(() => {
         const initWeb3 = async () => {
           if (typeof window.ethereum !== 'undefined') {
             const web3Instance = new Web3(window.ethereum);
             try {
               await window.ethereum.enable();
               setWeb3(web3Instance);
    
               const networkId = await web3Instance.eth.net.getId();
               const deployedNetwork = SimpleStorageContract.networks[networkId];
               const contractInstance = new web3Instance.eth.Contract(
                 SimpleStorageContract.abi,
                 deployedNetwork && deployedNetwork.address
               );
    
               setContract(contractInstance);
             } catch (error) {
               console.error("User denied account access...");
             }
           }
         };
    
         initWeb3();
       }, []);
    
       return (
         <div>
           <h1>Blockchain Application</h1>
           {web3 && contract ? <SimpleStorage contract={contract} /> : <p>Loading...</p>}
         </div>
       );
     };
    
     export default Home;
    

5. Building the Frontend with React and Next.js

Let's build a user-friendly interface to interact with our smart contract. We will create components to display data from the blockchain and handle user inputs and transactions.

Creating Components for the User Interface

  1. Create a new file SimpleStorage.js in the components directory and add the following code:

     import React, { useEffect, useState } from 'react';
    
     const SimpleStorage = ({ contract }) => {
       const [storedValue, setStoredValue] = useState(0);
       const [inputValue, setInputValue] = useState('');
    
       useEffect(() => {
         const getStoredValue = async () => {
           const value = await contract.methods.get().call();
           setStoredValue(value);
         };
    
         getStoredValue();
       }, [contract]);
    
       const handleSet = async () => {
         const accounts = await web3.eth.getAccounts();
         await contract.methods.set(inputValue).send({ from: accounts[0] });
         setInputValue('');
       };
    
       return (
         <div>
           <h1>Stored Value: {storedValue}</h1>
           <input
             type="number"
             value={inputValue}
             onChange={(e) => setInputValue(e.target.value)}
           />
           <button onClick={handleSet}>Set Value</button>
         </div>
       );
     };
    
     export default SimpleStorage;
    

Displaying Data from the Blockchain in the UI

The SimpleStorage component retrieves the stored value from the blockchain and displays it in the UI. It also provides an input field and a button to update the stored value.

Handling User Inputs and Transactions

The handleSet function retrieves the user's account from Metamask and sends a transaction to update the stored value in the smart contract.

6. Creating Your Own Coin

In this section, we will create our own coin (ERC-20 token) and integrate it with our platform. ERC-20 is a standard interface for tokens, providing basic functionalities to transfer tokens and allow them to be approved to be spent by another on-chain third party.

Writing a Token Contract in Solidity

We will write a simple ERC-20 token contract using OpenZeppelin, a library for secure smart contract development.

  1. Install OpenZeppelin:

     npm install @openzeppelin/contracts
    
  2. Create a new file MyToken.sol in the contracts directory and add the following code:

     // SPDX-License-Identifier: MIT
     pragma solidity ^0.8.0;
    
     import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
     import "@openzeppelin/contracts/access/Ownable.sol";
    
     contract MyToken is ERC20, Ownable {
         constructor(uint256 initialSupply) ERC20("MyToken", "MTK") {
             _mint(msg.sender, initialSupply);
         }
    
         function mint(address to, uint256 amount) public onlyOwner {
             _mint(to, amount);
         }
     }
    

Deploying the Token Contract

Next, we need to compile and deploy the token contract.

Using Truffle

  1. Compile the contract:

     truffle compile
    
  2. Deploy the contract:

    Create a new file 3_deploy_token.js in the migrations directory and add the following code:

     const MyToken = artifacts.require("MyToken");
    
     module.exports = function (deployer) {
         deployer.deploy(MyToken, 1000000);
     };
    

    Run the deployment script:

     truffle migrate
    

Using Hardhat

  1. Compile the contract:

     npx hardhat compile
    
  2. Deploy the contract:

    Create a new file deploy_token.js in the scripts directory and add the following code:

     async function main() {
         const MyToken = await ethers.getContractFactory("MyToken");
         const myToken = await MyToken.deploy(1000000);
    
         console.log("MyToken deployed to:", myToken.address);
     }
    
     main()
         .then(() => process.exit(0))
         .catch((error) => {
             console.error(error);
             process.exit(1);
         });
    

    Run the deployment script:

     npx hardhat run scripts/deploy_token.js
    

Integrating the Token with the Frontend

We will create a React component to interact with our token contract.

  1. Create a new file Token.js in the components directory and add the following code:

     import React, { useEffect, useState } from 'react';
     import Web3 from 'web3';
    
     const Token = ({ contract }) => {
       const [balance, setBalance] = useState(0);
       const [recipient, setRecipient] = useState('');
       const [amount, setAmount] = useState('');
    
       useEffect(() => {
         const getBalance = async () => {
           const accounts = await web3.eth.getAccounts();
           const balance = await contract.methods.balanceOf(accounts[0]).call();
           setBalance(balance);
         };
    
         getBalance();
       }, [contract]);
    
       const handleTransfer = async () => {
         const accounts = await web3.eth.getAccounts();
         await contract.methods.transfer(recipient, amount).send({ from: accounts[0] });
         setRecipient('');
         setAmount('');
       };
    
       return (
         <div>
           <h1>Token Balance: {balance}</h1>
           <input
             type="text"
             value={recipient}
             onChange={(e) => setRecipient(e.target.value)}
             placeholder="Recipient Address"
           />
           <input
             type="number"
             value={amount}
             onChange={(e) => setAmount(e.target.value)}
             placeholder="Amount"
           />
           <button onClick={handleTransfer}>Transfer Tokens</button>
         </div>
       );
     };
    
     export default Token;
    
  2. Update the index.js file to use the Token component:

     import React, { useEffect, useState } from 'react';
     import Web3 from 'web3';
     import SimpleStorage from '../components/SimpleStorage';
     import Token from '../components/Token';
     import SimpleStorageContract from '../build/contracts/SimpleStorage.json';
     import TokenContract from '../build/contracts/MyToken.json';
    
     const Home = () => {
       const [web3, setWeb3] = useState(null);
       const [storageContract, setStorageContract] = useState(null);
       const [tokenContract, setTokenContract] = useState(null);
    
       useEffect(() => {
         const initWeb3 = async () => {
           if (typeof window.ethereum !== 'undefined') {
             const web3Instance = new Web3(window.ethereum);
             try {
               await window.ethereum.enable();
               setWeb3(web3Instance);
    
               const networkId = await web3Instance.eth.net.getId();
    
               // Initialize SimpleStorage contract
               const storageDeployedNetwork = SimpleStorageContract.networks[networkId];
               const storageInstance = new web3Instance.eth.Contract(
                 SimpleStorageContract.abi,
                 storageDeployedNetwork && storageDeployedNetwork.address
               );
               setStorageContract(storageInstance);
    
               // Initialize Token contract
               const tokenDeployedNetwork = TokenContract.networks[networkId];
               const tokenInstance = new web3Instance.eth.Contract(
                 TokenContract.abi,
                 tokenDeployedNetwork && tokenDeployedNetwork.address
               );
               setTokenContract(tokenInstance);
             } catch (error) {
               console.error("User denied account access...");
             }
           }
         };
    
         initWeb3();
       }, []);
    
       return (
         <div>
           <h1>Blockchain Application</h1>
           {web3 && storageContract ? <SimpleStorage contract={storageContract} /> : <p>Loading...</p>}
           {web3 && tokenContract ? <Token contract={tokenContract} /> : <p>Loading...</p>}
         </div>
       );
     };
    
     export default Home;
    

7. Testing and Deployment

Testing is an essential part of the development process. We need to ensure that our smart contracts work as expected before deploying them to a live network.

Writing Tests for Smart Contracts

Let's write tests for our SimpleStorage and MyToken contracts.

  1. Create a new file test/SimpleStorage.test.js and add the following code:

     const SimpleStorage = artifacts.require("SimpleStorage");
    
     contract("SimpleStorage", (accounts) => {
       it("should store the value 89.", async () => {
         const simpleStorageInstance = await SimpleStorage.deployed();
    
         // Set value of 89
         await simpleStorageInstance.set(89, { from: accounts[0] });
    
         // Get stored value
         const storedData = await simpleStorageInstance.get.call();
    
         assert.equal(storedData, 89, "The value 89 was not stored.");
       });
     });
    
  2. Create a new file test/MyToken.test.js and add the following code:

     const MyToken = artifacts.require("MyToken");
    
     contract("MyToken", (accounts) => {
       it("should mint tokens", async () => {
         const myTokenInstance = await MyToken.deployed();
         const balance = await myTokenInstance.balanceOf(accounts[0]);
         assert.equal(balance.toNumber(), 1000000, "Initial balance is not correct.");
       });
    
       it("should transfer tokens", async () => {
         const myTokenInstance = await MyToken.deployed();
         await myTokenInstance.transfer(accounts[1], 1000, { from: accounts[0] });
         const balance1 = await myTokenInstance.balanceOf(accounts[1]);
         assert.equal(balance1.toNumber(), 1000, "Balance of account 1 is not correct.");
       });
     });
    
  3. Run the tests:

     # Using Truffle
     truffle test
    
     # Using Hardhat
     npx hardhat test
    

Deploying the Smart Contract to a Testnet

Before deploying the smart contract to the mainnet, it's a good practice to deploy it to a testnet for further testing.

  1. Update the truffle-config.js file to include the configuration for a testnet (e.g., Ropsten):

     networks: {
       ropsten: {
         provider: () =>
           new HDWalletProvider(
             process.env.MNEMONIC,
             `https://ropsten.infura.io/v3/${process.env.INFURA_PROJECT_ID}`
           ),
         network_id: 3,       // Ropsten's id
         gas: 5500000,        // Ropsten has a lower block limit than mainnet
         confirmations: 2,    // # of confs to wait between deployments. (default: 0)
         timeoutBlocks: 200,  // # of blocks before a deployment times out  (minimum/default: 50)
         skipDryRun: true     // Skip dry run before migrations? (default: false for public nets )
       },
     },
    
  2. Deploy the contract to the testnet:

     truffle migrate --network ropsten
    

Deploying the React Application to a Hosting Service

Once our smart contract is deployed, we can deploy our React application to a hosting service like Vercel.

  1. Install the Vercel CLI:

     npm install -g vercel
    
  2. Deploy the application:

     vercel
    

Follow the prompts to configure and deploy your application to Vercel.

8. Advanced Topics

In this section, we will explore advanced topics, including integrating Solana and Rust for high-performance blockchain applications.

Integrating Solana and Rust

Solana is a high-performance blockchain that supports fast and scalable applications. Rust is a systems programming language that is used for writing Solana programs. Here’s how you can integrate Solana and Rust into your blockchain application.

Setting Up the Solana Development Environment

  1. Install Rust:

     curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
     source $HOME/.cargo/env
    
  2. Install Solana CLI:

     sh -c "$(curl -sSfL https://release.solana.com/v1.8.2/install)"
     export PATH="/Users/your-user-name/.local/share/solana/install/active_release/bin:$PATH"
    
  3. Create a New Solana Project:

     cargo new --bin solana-hello-world
     cd solana-hello-world
    
  4. Write a Simple Solana Program:

    Create a new file src/main.rs and add the following code:

     use solana_program::{
         account_info::AccountInfo,
         entrypoint,
         entrypoint::ProgramResult,
         msg,
         pubkey::Pubkey,
     };
    
     entrypoint!(process_instruction);
    
     pub fn process_instruction(
         _program_id: &Pubkey,
         _accounts: &[AccountInfo],
         _instruction_data: &[u8],
     ) -> ProgramResult {
         msg!("Hello, world!");
         Ok(())
     }
    
  5. Build and Deploy the Solana Program:

     cargo build-bpf
     solana program deploy /path/to/solana-hello-world/target/deploy/solana_hello_world.so
    
  6. Interact with the Solana Program from Your React App:

     import { Connection, PublicKey, clusterApiUrl } from '@solana/web3.js';
    
     const connection = new Connection(clusterApiUrl('devnet'), 'confirmed');
     const programId = new PublicKey('your-program-id');
    
     const interactWithSolanaProgram = async () => {
       // Implement your interaction logic here
     };
    
     interactWithSolanaProgram();
    

9. Conclusion

In this comprehensive guide, we have walked through the process of setting up a development environment, writing and deploying smart contracts in Solidity, building a frontend with React and Next.js, creating our own coin, and exploring advanced topics like integrating Solana and Rust. Blockchain development is a vast and evolving field, and there is always more to learn.

Happy coding!

Did you find this article valuable?

Support Ahmad W Khan by becoming a sponsor. Any amount is appreciated!