Построение простого dApp для белых списков с использованием Solidity, Next.js, ethers.js на Ethereum


Whitelist-Dapp

Вы запускаете свою коллекцию NFT под названием Crypto Devs. Вы хотите предоставить своим ранним сторонникам доступ к белому списку для вашей коллекции, поэтому здесь вы создаете whitelist dapp для Crypto Devs.

Требования

  • Доступ к белому списку должен быть предоставлен первым 10 пользователям бесплатно, которые хотят попасть в него.
  • Должен быть сайт, где люди могут зайти и войти в белый список.

Приступим к созданию 🚀


Необходимые условия

  • Вы умеете писать код на JavaScript (уровень новичка — 0)
  • У вас есть настроенный кошелек Metamask (Уровень-4)
  • На вашем компьютере установлен Node.js. Если нет, скачайте отсюда

Предпочитаете видео?

Если вы предпочитаете учиться на видео, у нас есть запись этого урока на нашем YouTube. Посмотрите видео, нажав на скриншот ниже, или переходите к чтению учебника!

Создайте

Умный контракт

Для создания смарт-контракта мы будем использовать Hardhat.
Hardhat — это среда разработки Ethereum и фреймворк, предназначенный для разработки полного стека в Solidity. Проще говоря, вы можете написать свой смарт-контракт, развернуть его, запустить тесты и отладить код.

  • Для начала вам нужно создать папку Whitelist-Daap, куда впоследствии попадет проект Hardhat и ваше приложение Next.js.
  • Откройте терминал и выполните следующие команды
  mkdir Whitelist-Dapp
  cd Whitelist-Dapp
Войти в полноэкранный режим Выйти из полноэкранного режима
  • Затем, в папке Whitelist-Daap, вы установите проект Hardhat
  mkdir hardhat-tutorial
  cd hardhat-tutorial
  npm init --yes
  npm install --save-dev hardhat
Войти в полноэкранный режим Выйти из полноэкранного режима
  • В той же директории, где вы установили Hardhat, запустите его:
  npx hardhat
Enter fullscreen mode Выйти из полноэкранного режима
  • Выберите Create a basic sample project.
  • Нажмите ввод для уже указанного Hardhat Project root
  • Нажмите Enter для вопроса о том, хотите ли вы добавить .gitignore
  • Нажмите ввод для Хотите ли вы установить зависимости этого образца проекта с помощью npm (@nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers)?

Теперь у вас есть проект hardhat, готовый к работе!

Если вы не на mac, пожалуйста, сделайте этот дополнительный шаг и установите эти библиотеки тоже 🙂

npm install --save-dev @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers
Вход в полноэкранный режим Выход из полноэкранного режима
  • Начните с создания нового файла в директории contracts под названием Whitelist.sol.
  //SPDX-License-Identifier: Unlicense
  pragma solidity ^0.8.0;


  contract Whitelist {

      // Max number of whitelisted addresses allowed
      uint8 public maxWhitelistedAddresses;

      // Create a mapping of whitelistedAddresses
      // if an address is whitelisted, we would set it to true, it is false by default for all other addresses.
      mapping(address => bool) public whitelistedAddresses;

      // numAddressesWhitelisted would be used to keep track of how many addresses have been whitelisted
      // NOTE: Don't change this variable name, as it will be part of verification
      uint8 public numAddressesWhitelisted;

      // Setting the Max number of whitelisted addresses
      // User will put the value at the time of deployment
      constructor(uint8 _maxWhitelistedAddresses) {
          maxWhitelistedAddresses =  _maxWhitelistedAddresses;
      }

      /**
          addAddressToWhitelist - This function adds the address of the sender to the
          whitelist
       */
      function addAddressToWhitelist() public {
          // check if the user has already been whitelisted
          require(!whitelistedAddresses[msg.sender], "Sender has already been whitelisted");
          // check if the numAddressesWhitelisted < maxWhitelistedAddresses, if not then throw an error.
          require(numAddressesWhitelisted < maxWhitelistedAddresses, "More addresses cant be added, limit reached");
          // Add the address which called the function to the whitelistedAddress array
          whitelistedAddresses[msg.sender] = true;
          // Increase the number of whitelisted addresses
          numAddressesWhitelisted += 1;
      }

  }
Вход в полноэкранный режим Выход из полноэкранного режима
  • Создайте новый файл с именем deploy.js в папке scripts.

  • Теперь мы напишем код для развертывания контракта в файле deploy.js.

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

  async function main() {
    /*
    A ContractFactory in ethers.js is an abstraction used to deploy new smart contracts,
    so whitelistContract here is a factory for instances of our Whitelist contract.
    */
    const whitelistContract = await ethers.getContractFactory("Whitelist");

    // here we deploy the contract
    const deployedWhitelistContract = await whitelistContract.deploy(10);
    // 10 is the Maximum number of whitelisted addresses allowed

    // Wait for it to finish deploying
    await deployedWhitelistContract.deployed();

    // print the address of the deployed contract
    console.log(
      "Whitelist Contract Address:",
      deployedWhitelistContract.address
    );
  }

  // Call the main function and catch if there is any error
  main()
    .then(() => process.exit(0))
    .catch((error) => {
      console.error(error);
      process.exit(1);
    });
Вход в полноэкранный режим Выход из полноэкранного режима
  • Теперь создайте файл .env в папке hardhat-tutorial и добавьте следующие строки, используйте инструкции в комментариях для получения URL ключа API Alchemy и закрытого ключа RINKEBY. Убедитесь, что счет, с которого вы получаете приватный ключ rinkeby, пополнен эфиром Rinkeby.

  // Go to https://www.alchemyapi.io, sign up, create
  // a new App in its dashboard and select the network as Rinkeby, and replace "add-the-alchemy-key-url-here" with its key url
  ALCHEMY_API_KEY_URL="add-the-alchemy-key-url-here"

  // Replace this private key with your RINKEBY account private key
  // To export your private key from Metamask, open Metamask and
  // go to Account Details > Export Private Key
  // Be aware of NEVER putting real Ether into testing accounts
  RINKEBY_PRIVATE_KEY="add-the-rinkeby-private-key-here"

Войдите в полноэкранный режим Выйдите из полноэкранного режима
  • Теперь мы установим пакет dotenv, чтобы иметь возможность импортировать env-файл и использовать его в нашем конфиге. Откройте терминал в директории hardhat-tutorial и выполните следующую команду
  npm install dotenv
Войти в полноэкранный режим Выйти из полноэкранного режима
  • Теперь откройте файл hardhat.config.js, здесь мы добавим сеть rinkeby, чтобы мы могли развернуть наш контракт на rinkeby. Замените все строки в файле hardhar.config.js на приведенные ниже строки
  require("@nomiclabs/hardhat-waffle");
  require("dotenv").config({ path: ".env" });

  const ALCHEMY_API_KEY_URL = process.env.ALCHEMY_API_KEY_URL;

  const RINKEBY_PRIVATE_KEY = process.env.RINKEBY_PRIVATE_KEY;

  module.exports = {
    solidity: "0.8.4",
    networks: {
      rinkeby: {
        url: ALCHEMY_API_KEY_URL,
        accounts: [RINKEBY_PRIVATE_KEY],
      },
    },
  };
Войти в полноэкранный режим Выйти из полноэкранного режима
  • Скомпилируйте контракт, откройте терминал, указывающий на каталог hardhat-tutorial и выполните следующую команду
     npx hardhat compile
Войти в полноэкранный режим Выйти из полноэкранного режима
  • Для развертывания откройте терминал в директорииhardhat-tutorial и выполните эту команду
  npx hardhat run scripts/deploy.js --network rinkeby
Войти в полноэкранный режим Выйти из полноэкранного режима
  • Сохраните в блокноте адрес контракта Whitelist, который был напечатан на вашем терминале, он понадобится вам далее в учебнике.

Сайт

  • Для разработки веб-сайта мы будем использовать React и Next Js. React — это javascript-фреймворк, используемый для создания веб-сайтов, а Next.js — это фреймворк React, который также позволяет писать код бэкенд API вместе с фронтендом, поэтому вам не нужно два отдельных сервиса для фронтенда и бэкенда.
  • Во-первых, вам нужно будет создать новое приложение next. Структура папок должна выглядеть следующим образом
  - Whitelist-Dapp
      - hardhat-tutorial
      - my-app
Вход в полноэкранный режим Выход из полноэкранного режима
  • Чтобы создать это next-приложение, в терминале перейдите в папку Whitelist-Dapp и введите
  npx create-next-app@latest
Войти в полноэкранный режим Выйти из полноэкранного режима

и нажмите enter на все вопросы.

  • Теперь, чтобы запустить приложение, выполните в терминале следующие команды
  cd my-app
  npm run dev
Войти в полноэкранный режим Выйти из полноэкранного режима
  • Теперь перейдите по адресу http://localhost:3000, ваше приложение должно быть запущено 🤘.

  • Теперь установим библиотеку Web3Modal. Web3Modal — это простая в использовании библиотека, которая поможет разработчикам легко позволить своим пользователям подключаться к вашим dApps с помощью всевозможных кошельков. По умолчанию библиотека Web3Modal поддерживает инжектируемые провайдеры, такие как (Metamask, Dapper, Gnosis Safe, Frame, Web3 Browsers, etc) и WalletConnect, вы также можете легко настроить библиотеку на поддержку Portis, Fortmatic, Squarelink, Torus, Authereum, D’CENT Wallet и Arkane.
    Откройте терминал, указывающий на каталогmy-app и выполните эту команду

  npm install web3modal
Войти в полноэкранный режим Выйти из полноэкранного режима
  • В том же терминале установите ethers.js.
  npm install ethers
Войти в полноэкранный режим Выйти из полноэкранного режима
  • В папку my-app/public загрузите это изображение и переименуйте его в crypto-devs.svg.
  • Теперь перейдите в папку styles и замените все содержимое файла Home.modules.css следующим кодом, это добавит стилистику вашему dapp:
  .main {
    min-height: 90vh;
    display: flex;
    flex-direction: row;
    justify-content: center;
    align-items: center;
    font-family: "Courier New", Courier, monospace;
  }

  .footer {
    display: flex;
    padding: 2rem 0;
    border-top: 1px solid #eaeaea;
    justify-content: center;
    align-items: center;
  }

  .image {
    width: 70%;
    height: 50%;
    margin-left: 20%;
  }

  .title {
    font-size: 2rem;
    margin: 2rem 0;
  }

  .description {
    line-height: 1;
    margin: 2rem 0;
    font-size: 1.2rem;
  }

  .button {
    border-radius: 4px;
    background-color: blue;
    border: none;
    color: #ffffff;
    font-size: 15px;
    padding: 20px;
    width: 200px;
    cursor: pointer;
    margin-bottom: 2%;
  }
  @media (max-width: 1000px) {
    .main {
      width: 100%;
      flex-direction: column;
      justify-content: center;
      align-items: center;
    }
  }
Вход в полноэкранный режим Выйти из полноэкранного режима
  • Откройте файл index.js в папке pages и вставьте следующий код, объяснение кода можно найти в комментариях. Обязательно прочитайте о React и React Hooks, React Hooks Tutorial, если вы не знакомы с ними.
  import Head from "next/head";
  import styles from "../styles/Home.module.css";
  import Web3Modal from "web3modal";
  import { providers, Contract } from "ethers";
  import { useEffect, useRef, useState } from "react";
  import { WHITELIST_CONTRACT_ADDRESS, abi } from "../constants";

  export default function Home() {
    // walletConnected keep track of whether the user's wallet is connected or not
    const [walletConnected, setWalletConnected] = useState(false);
    // joinedWhitelist keeps track of whether the current metamask address has joined the Whitelist or not
    const [joinedWhitelist, setJoinedWhitelist] = useState(false);
    // loading is set to true when we are waiting for a transaction to get mined
    const [loading, setLoading] = useState(false);
    // numberOfWhitelisted tracks the number of addresses's whitelisted
    const [numberOfWhitelisted, setNumberOfWhitelisted] = useState(0);
    // Create a reference to the Web3 Modal (used for connecting to Metamask) which persists as long as the page is open
    const web3ModalRef = useRef();

    /**
     * Returns a Provider or Signer object representing the Ethereum RPC with or without the
     * signing capabilities of metamask attached
     *
     * A `Provider` is needed to interact with the blockchain - reading transactions, reading balances, reading state, etc.
     *
     * A `Signer` is a special type of Provider used in case a `write` transaction needs to be made to the blockchain, which involves the connected account
     * needing to make a digital signature to authorize the transaction being sent. Metamask exposes a Signer API to allow your website to
     * request signatures from the user using Signer functions.
     *
     * @param {*} needSigner - True if you need the signer, default false otherwise
     */
    const getProviderOrSigner = async (needSigner = false) => {
      // Connect to Metamask
      // Since we store `web3Modal` as a reference, we need to access the `current` value to get access to the underlying object
      const provider = await web3ModalRef.current.connect();
      const web3Provider = new providers.Web3Provider(provider);

      // If user is not connected to the Rinkeby network, let them know and throw an error
      const { chainId } = await web3Provider.getNetwork();
      if (chainId !== 4) {
        window.alert("Change the network to Rinkeby");
        throw new Error("Change network to Rinkeby");
      }

      if (needSigner) {
        const signer = web3Provider.getSigner();
        return signer;
      }
      return web3Provider;
    };

    /**
     * addAddressToWhitelist: Adds the current connected address to the whitelist
     */
    const addAddressToWhitelist = async () => {
      try {
        // We need a Signer here since this is a 'write' transaction.
        const signer = await getProviderOrSigner(true);
        // Create a new instance of the Contract with a Signer, which allows
        // update methods
        const whitelistContract = new Contract(
          WHITELIST_CONTRACT_ADDRESS,
          abi,
          signer
        );
        // call the addAddressToWhitelist from the contract
        const tx = await whitelistContract.addAddressToWhitelist();
        setLoading(true);
        // wait for the transaction to get mined
        await tx.wait();
        setLoading(false);
        // get the updated number of addresses in the whitelist
        await getNumberOfWhitelisted();
        setJoinedWhitelist(true);
      } catch (err) {
        console.error(err);
      }
    };

    /**
     * getNumberOfWhitelisted:  gets the number of whitelisted addresses
     */
    const getNumberOfWhitelisted = async () => {
      try {
        // Get the provider from web3Modal, which in our case is MetaMask
        // No need for the Signer here, as we are only reading state from the blockchain
        const provider = await getProviderOrSigner();
        // We connect to the Contract using a Provider, so we will only
        // have read-only access to the Contract
        const whitelistContract = new Contract(
          WHITELIST_CONTRACT_ADDRESS,
          abi,
          provider
        );
        // call the numAddressesWhitelisted from the contract
        const _numberOfWhitelisted = await whitelistContract.numAddressesWhitelisted();
        setNumberOfWhitelisted(_numberOfWhitelisted);
      } catch (err) {
        console.error(err);
      }
    };

    /**
     * checkIfAddressInWhitelist: Checks if the address is in whitelist
     */
    const checkIfAddressInWhitelist = async () => {
      try {
        // We will need the signer later to get the user's address
        // Even though it is a read transaction, since Signers are just special kinds of Providers,
        // We can use it in it's place
        const signer = await getProviderOrSigner(true);
        const whitelistContract = new Contract(
          WHITELIST_CONTRACT_ADDRESS,
          abi,
          signer
        );
        // Get the address associated to the signer which is connected to  MetaMask
        const address = await signer.getAddress();
        // call the whitelistedAddresses from the contract
        const _joinedWhitelist = await whitelistContract.whitelistedAddresses(
          address
        );
        setJoinedWhitelist(_joinedWhitelist);
      } catch (err) {
        console.error(err);
      }
    };

    /*
      connectWallet: Connects the MetaMask wallet
    */
    const connectWallet = async () => {
      try {
        // Get the provider from web3Modal, which in our case is MetaMask
        // When used for the first time, it prompts the user to connect their wallet
        await getProviderOrSigner();
        setWalletConnected(true);

        checkIfAddressInWhitelist();
        getNumberOfWhitelisted();
      } catch (err) {
        console.error(err);
      }
    };

    /*
      renderButton: Returns a button based on the state of the dapp
    */
    const renderButton = () => {
      if (walletConnected) {
        if (joinedWhitelist) {
          return (
            <div className={styles.description}>
              Thanks for joining the Whitelist!
            </div>
          );
        } else if (loading) {
          return <button className={styles.button}>Loading...</button>;
        } else {
          return (
            <button onClick={addAddressToWhitelist} className={styles.button}>
              Join the Whitelist
            </button>
          );
        }
      } else {
        return (
          <button onClick={connectWallet} className={styles.button}>
            Connect your wallet
          </button>
        );
      }
    };

    // useEffects are used to react to changes in state of the website
    // The array at the end of function call represents what state changes will trigger this effect
    // In this case, whenever the value of `walletConnected` changes - this effect will be called
    useEffect(() => {
      // if wallet is not connected, create a new instance of Web3Modal and connect the MetaMask wallet
      if (!walletConnected) {
        // Assign the Web3Modal class to the reference object by setting it's `current` value
        // The `current` value is persisted throughout as long as this page is open
        web3ModalRef.current = new Web3Modal({
          network: "rinkeby",
          providerOptions: {},
          disableInjectedProvider: false,
        });
        connectWallet();
      }
    }, [walletConnected]);

    return (
      <div>
        <Head>
          <title>Whitelist Dapp</title>
          <meta name="description" content="Whitelist-Dapp" />
          <link rel="icon" href="/favicon.ico" />
        </Head>
        <div className={styles.main}>
          <div>
            <h1 className={styles.title}>Welcome to Crypto Devs!</h1>
            <div className={styles.description}>
              Its an NFT collection for developers in Crypto.
            </div>
            <div className={styles.description}>
              {numberOfWhitelisted} have already joined the Whitelist
            </div>
            {renderButton()}
          </div>
          <div>
            <img className={styles.image} src="./crypto-devs.svg" />
          </div>
        </div>

        <footer className={styles.footer}>
          Made with &#10084; by Crypto Devs
        </footer>
      </div>
    );
  }
Войдите в полноэкранный режим Выйдите из полноэкранного режима
  • Теперь создайте новую папку в папке my-app и назовите ее constants.
  • В папке constants создайте файл index.js и вставьте в него следующий код.
  • Замените "YOUR_WHITELIST_CONTRACT_ADDRESS" на адрес контракта белого списка, который вы развернули.
  • Замените "YOUR_ABI" на ABI вашего контракта белого списка. Чтобы получить ABI для вашего контракта, перейдите в папку hardhat-tutorial/artifacts/contracts/Whitelist.sol и из файла Whitelist.json получите массив, отмеченный под ключом "abi" (это будет огромный массив, около 100 строк, если не больше).
  export const abi = YOUR_ABI;
  export const WHITELIST_CONTRACT_ADDRESS = "YOUR_WHITELIST_CONTRACT_ADDRESS";
Войдите в полноэкранный режим Выйдите из полноэкранного режима
  • Теперь в терминале, который указывает на папку my-app, выполните команду
  npm run dev
Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь ваше приложение из белого списка должно работать без ошибок 🚀.

Отправка на github

Перед тем, как продолжить, убедитесь, что вы выложили весь ваш код на github 🙂


Развертывание вашего dApp

Теперь мы развернем ваш dApp, чтобы все могли видеть ваш сайт, а вы могли поделиться им со всеми своими друзьями LearnWeb3 DAO.

  • Зайдите в Vercel и авторизуйтесь на GitHub.
  • Затем нажмите на кнопку New Project и выберите ваше репо dApp из белого списка.
  • При настройке нового проекта Vercel позволит вам настроить Root Directory.
  • Нажмите Edit рядом с Root Directory и установите значение my-app.
  • Выберите фреймворк как Next.js.
  • Нажмите Deploy.
  • Теперь вы можете увидеть ваш развернутый сайт, зайдя в панель управления, выбрав ваш проект и скопировав оттуда URL!

Поделитесь своим сайтом в Discord 😀


Эта статья предоставлена LearnWeb3 DAO. Бесплатная, всеобъемлющая программа обучения блокчейну от А до Я для разработчиков по всему миру.

Все от «Что такое блокчейн» до «Взлома смарт-контрактов» — и все, что между ними, а также многое другое!
Присоединяйтесь к нам сейчас, чтобы начать строить вместе с 25 000+ разработчиками.

Сайт
Discord
Twitter

Оцените статью
Procodings.ru
Добавить комментарий