Skip to content

Latest commit

 

History

History
426 lines (319 loc) · 13.5 KB

File metadata and controls

426 lines (319 loc) · 13.5 KB

Guestbook dApp Tutorial / Boilerplate

Overview

This guide will walk you through creating a decentralized guestbook application using Hardhat for development, Solidity for smart contracts, and React for the frontend. This setup is tailored for the Core network.

By following this tutorial, you'll learn how to:

  • Develop and deploy smart contracts on the Core Testnet.
  • Build a React frontend to interact with your smart contracts.
  • Integrate MetaMask for secure user interactions and transactions.

Why Use This Boilerplate?

  • Decentralization: Store guestbook entries on the blockchain, ensuring data integrity and transparency.
  • Security: Immutable and tamper-proof records stored on the blockchain.
  • Transparency: All entries and transactions are publicly visible and verifiable.
  • Real-World Applications: Ideal for creating transparent feedback systems, community engagement tools, or personal guestbooks.

Prerequisites

Before getting started, ensure you have the following:

  • Node.js: Install Node.js from nodejs.org.
  • npm: Node.js includes npm (Node Package Manager).
  • MetaMask: Install the MetaMask browser extension from metamask.io.
  • Core Testnet Configuration: Configure MetaMask to connect to the Core Testnet.
    • Network Name: Core Testnet
    • New RPC URL: https://rpc.test.btcs.network
    • Chain ID: 1115
    • Currency Symbol: CORE
  • Text Editor: Use a text editor like Visual Studio Code.
  • Core Faucet: To get test CORE tokens for transactions, visit the Core Faucet and refer to the instructions for use.

Project Setup

1. Initialize the Project

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

mkdir guestbook-dapp
cd guestbook-dapp
npm init --yes
gdscreenshot1

2. Install Dependencies

Install Hardhat and other necessary dependencies:

npm install --save-dev hardhat
npm install --save-dev chai @nomiclabs/hardhat-waffle
npm install react react-dom
gdscreenshot2
  1. Set Up Hardhat Initialize a new Hardhat project:
npx hardhat
gdscreenshot3

Select "Create a Javascript Project" and "no" for installing the project's sample dependencies.

We will now open up our newly created hardhat project in a code editor, for this guide, we will be using Visual Studio Code. navigate to the root of your directory in the terminal and type code . and hit enter

4. Create secret.json File

Create a secret.json file in the root directory of your project to store your private key securely. Replace YOUR_PRIVATE_KEY with your actual private key.

{
  "PrivateKey": "YOUR_PRIVATE_KEY"
}
gdscreenshot5

Refer to this guide for instructions on how to export your account's private key via Metamask.

5. Update .gitignore File

Update your .gitignore file to ensure that your secret.json file and other sensitive files are not committed to version control.

Create or update the .gitignore file in your root directory with the following content:

# Node.js dependencies
/node_modules

# Hardhat artifacts
/artifacts
/cache

# Environment and secret files
.env
.secret.json

# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Ignore build output
/build

# Miscellaneous
.DS_Store

6. Update hardhat.config.js

Replace the contents of hardhat.config.js with the following configuration:

Ensure that the network settings are configured correctly for Core Testnet

/**
 * @type import('hardhat/config').HardhatUserConfig
 */

 require('@nomiclabs/hardhat-ethers');
 require("@nomiclabs/hardhat-waffle");

 const { PrivateKey } = require('./secret.json');

 module.exports = {
    defaultNetwork: 'testnet',
 
    networks: {
       hardhat: {
       },
       testnet: {
          url: 'https://rpc.test.btcs.network',
          accounts: [PrivateKey],
          chainId: 1115,
       }
    },
    solidity: {
       compilers: [
         {
            version: '0.8.9',
            settings: {
               optimizer: {
                  enabled: true,
                  runs: 200,
               },
            },
         },
       ],
    },
    paths: {
       sources: './contracts',
       cache: './cache',
       artifacts: './artifacts',
    },
    mocha: {
       timeout: 20000,
    },
 };
gdscreenshot5 1

Writing the Smart Contract

  1. Create the Guestbook Smart Contract

Delete the lock.sol file in Contracts Create a file contracts/Guestbook.sol with the following content:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

/// @title Guestbook
contract Guestbook {
    struct Entry {
        string name;
        string message;
    }

    Entry[] public entries;

    function signGuestbook(string memory _name, string memory _message) public {
        entries.push(Entry(_name, _message));
    }

    function getEntries() public view returns (Entry[] memory) {
        return entries;
    }
}
gdscreenshot6

Deploying the Smart Contract

  1. Create Deployment Script Create a file scripts/deploy.js with the following content:
const hre = require("hardhat");

async function main() {
  await hre.run('compile'); // Ensure the contracts are compiled

  const Guestbook = await hre.ethers.getContractFactory("Guestbook");
  const guestbook = await Guestbook.deploy();

  await guestbook.deployed();
  console.log("Guestbook contract deployed to:", guestbook.address);
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
gdscreenshot7
  1. Deploy the Contract

Deploy the contract to the Core network:

npx hardhat run scripts/deploy.js --network testnet
gdscreenshot8

Setting Up the React Frontend

  1. Create the React App Create the basic structure for a React frontend:
npx create-react-app frontend
cd frontend
gdscreenshot9
  1. Install Ethers.js

Install the Ethers.js library:

npm install ethers
gdscreenshot10
  1. Add GuestbookAbi.json

Copy the Guestbook.json file from artifacts/contracts/Guestbook.sol/ to the frontend/src directory and rename it to GuestbookAbi.json.

Ensure you replace placeholders like YOUR_CONTRACT_ADDRESS with actual values from your deployment.

gdscreenshot11 gdscreenshot12
  1. Update frontend/src/App.js Create or update the src/App.js file with the following content:
import React, { useState, useEffect } from 'react';
import { ethers } from 'ethers';
import GuestbookArtifact from './GuestbookAbi.json'; // Import the entire JSON artifact

const contractAddress = 'YOUR_CONTRACT_ADDRESS'; // Replace with your deployed contract address
const GuestbookAbi = GuestbookArtifact.abi; // Extract the ABI

function App() {
  const [contract, setContract] = useState(null);
  const [entries, setEntries] = useState([]);
  const [name, setName] = useState("");
  const [message, setMessage] = useState("");
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState("");

  useEffect(() => {
    const init = async () => {
      if (window.ethereum) {
        try {
          console.log("Connecting to MetaMask...");
          const provider = new ethers.providers.Web3Provider(window.ethereum);
          
          await provider.send("eth_requestAccounts", []); // Request account access if needed

          const network = await provider.getNetwork();
          if (network.chainId !== 1115) {
            throw new Error("Please switch to the Core Testnet in MetaMask.");
          }

          const signer = provider.getSigner();
          console.log("Signer obtained:", signer);
          const tempContract = new ethers.Contract(contractAddress, GuestbookAbi, signer);
          setContract(tempContract);
          console.log("Contract initialized:", tempContract);

          const entries = await tempContract.getEntries();
          setEntries(entries);
          console.log("Entries loaded:", entries);
        } catch (error) {
          console.error("Error connecting to contract:", error);
          setError(error.message);
        } finally {
          setLoading(false);
        }
      } else {
        console.error("Please install MetaMask!");
        setError("Please install MetaMask!");
        setLoading(false);
      }
    };
    init();
  }, []);

  const signGuestbook = async () => {
    if (!contract) {
      console.error("Contract is not initialized!");
      setError("Contract is not initialized!");
      return;
    }

    try {
      console.log("Signing guestbook with:", name, message);
      const tx = await contract.signGuestbook(name, message, {
        gasLimit: 300000, // Adjust gas limit as necessary
      });
      console.log("Transaction sent:", tx);
      await tx.wait(); // Wait for the transaction to be mined
      console.log("Transaction mined:", tx);

      const updatedEntries = await contract.getEntries();
      setEntries(updatedEntries);
      console.log("Updated entries:", updatedEntries);
    } catch (error) {
      console.error("Error signing guestbook:", error);
      setError("Error signing guestbook. Please try again.");
    }
  };

  return (
    <div>
      <h1>Guestbook DApp</h1>
      {loading ? (
        <p>Loading contract...</p>
      ) : (
        <>
          {error && <p style={{ color: 'red' }}>{error}</p>}
          <input 
            type="text" 
            placeholder="Your Name" 
            value={name} 
            onChange={(e) => setName(e.target.value)} 
          />
          <input 
            type="text" 
            placeholder="Your Message" 
            value={message} 
            onChange={(e) => setMessage(e.target.value)} 
          />
          <button onClick={signGuestbook}>Sign Guestbook</button>
          <ul>
            {entries.map((entry, index) => (
              <li key={index}>
                <strong>{entry.name}:</strong> {entry.message}
              </li>
            ))}
          </ul>
        </>
      )}
    </div>
  );
}

export default App;
gdscreenshot13

Start the React app:

Ensure you are in /frontend within your repository (cd frontend)

npm start

You will be redirected to the development server

gdscreenshot14

In this example, we've already submitted our response, so we will navigate to the Core Explorer.

Here you can see, the transaction has been submitted successfully.

gdscreenshot16

The dapp webpage will update, and you will notice the feed on the UI has been updated with your your name and message.

gdscreenshot15

Deploying the Frontend

If you want your DApp to be accessible even when your local server is not running, consider deploying your frontend to a hosting service. Here are some common options:

GitHub Pages: Free hosting for static sites directly from your GitHub repository. Netlify: A popular choice for deploying static sites with easy CI/CD integration. Vercel: Another popular choice, especially for React applications. AWS S3 + CloudFront: For more control over hosting and scaling.

By deploying your frontend to one of these services, users can access your DApp from anywhere without needing your local development server to be running.

Conclusion

By following these steps, you should have a basic decentralized guestbook application deployed on the Core Test network with a React frontend to interact with it.

You can clone this repository to use as a boilerplate, or an educational resource.