Sestavte si jednoduchý whitelist dApp pomocí Solidity, Next.js, ethers.js na Ethereu

Whitelist-Dapp

Spouštíte svou kolekci NFT s názvem Crypto Devs . Chcete dát svým raným příznivcům přístup k bílé listině pro vaši sbírku, takže zde vytváříte whitelist dapp pro Crypto Devs

Požadavky

  • Přístup na seznam povolených by měl být udělen prvnímu 10 uživatelům zdarma, kteří se chtějí dostat dovnitř.
  • Měla by existovat webová stránka, kam by lidé mohli přejít a vstoupit do seznamu povolených.

Začněme stavět 🚀

Předpoklady

  • Můžete psát kód v JavaScriptu (Track pro začátečníky – úroveň 0)
  • Nastavili jste peněženku Metamask (skladba pro začátečníky – úroveň 4)
  • Ve vašem počítači je nainstalován soubor Node.js. Pokud ne, stáhněte si zde

Preferujete video?

Pokud byste se raději učili z videa, máme záznam tohoto návodu k dispozici na našem YouTube. Podívejte se na video kliknutím na níže uvedený snímek obrazovky nebo pokračujte a přečtěte si tutoriál!

Sestavit

Chytrá smlouva

K vytvoření chytré smlouvy budeme používat Hardhat.
Hardhat je Ethereum vývojové prostředí a framework navržený pro full stack vývoj v Solidity. Jednoduše řečeno, můžete napsat svou inteligentní smlouvu, nasadit je, spustit testy a ladit svůj kód.

  • Nejprve musíte vytvořit složku Whitelist-Daap, kam se později přesune projekt Hardhat a vaše aplikace Next.js
  • Otevřete terminál a spusťte tyto příkazy
  mkdir Whitelist-Dapp
  cd Whitelist-Dapp
  • Poté ve složce Whitelist-Daap nastavíte projekt Hardhat
  mkdir hardhat-tutorial
  cd hardhat-tutorial
  npm init --yes
  npm install --save-dev hardhat
  • Ve stejném adresáři, do kterého jste nainstalovali Hardhat, spusťte:
  npx hardhat
  • Vyberte Create a basic sample project
  • Stiskněte Enter pro již zadaný Hardhat Project root
  • Chcete-li přidat .gitignore, stiskněte Enter u otázky
  • Stiskněte Enter pro Do you want to install this sample project's dependencies with npm (@nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers)?

Nyní máte připravený projekt bezpečnostní přilby!

Pokud nejste na Macu, udělejte prosím tento krok navíc a nainstalujte si také tyto knihovny :)

npm install --save-dev @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers
  • Začněte vytvořením nového souboru v contracts adresář s názvem 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;
      }

  }
  • Umožňuje nasazení smlouvy na rinkeby network.Vytvořte nový soubor s názvem deploy.js pod scripts složku

  • Nyní napíšeme nějaký kód pro nasazení smlouvy v deploy.js soubor.

  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);
    });
  • Nyní vytvořte .env soubor v hardhat-tutorial složku a přidejte následující řádky, použijte pokyny v komentářích k získání adresy URL klíče Alchemy API a soukromého klíče RINKEBY. Ujistěte se, že účet, ze kterého získáte soukromý klíč rinkeby, je financován pomocí Rinkeby Ether.

  // 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"

  • Nyní nainstalujeme dotenv balíček, abyste mohli importovat soubor env a použít jej v naší konfiguraci. Otevřete terminál směřující na hardhat-tutorial adresář a spusťte tento příkaz
  npm install dotenv
  • Nyní otevřete soubor hardhat.config.js, přidali bychom rinkeby síť zde, abychom mohli nasadit naši smlouvu na rinkeby. Nahraďte všechny řádky v hardhar.config.js soubor s níže uvedenými řádky
  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],
      },
    },
  };
  • Zkompilujte smlouvu a otevřete terminál směřující na hardhat-tutorial adresář a spusťte tento příkaz
     npx hardhat compile
  • Pro nasazení otevřete terminál směřující na hardhat-tutorial adresář a spusťte tento příkaz
  npx hardhat run scripts/deploy.js --network rinkeby
  • Adresu smlouvy na bílou listinu, která byla vytištěna na vašem terminálu, si uložte do poznámkového bloku, budete ji potřebovat dále ve výukovém programu.

Webové stránky

  • K vývoji webu použijeme React a Next Js. React je javascriptový framework používaný k vytváření webových stránek a Next.js je React framework, který také umožňuje psát kód backendového API spolu s frontendem, takže nepotřebujete dvě samostatné frontendové a backendové služby.
  • Nejprve budete muset vytvořit nový next aplikace. Struktura vaší složky by měla vypadat nějak takto
  - Whitelist-Dapp
      - hardhat-tutorial
      - my-app
  • K vytvoření tohoto next-app , v terminálu přejděte na složku Whitelist-Dapp a zadejte
  npx create-next-app@latest

a stiskněte enter na všechny otázky

  • Aplikaci nyní spustíte provedením těchto příkazů v terminálu
  cd my-app
  npm run dev
  • Nyní přejděte na http://localhost:3000 , vaše aplikace by měla být spuštěna 🤘

  • Nyní umožňuje nainstalovat knihovnu Web3Modal. Web3Modal je snadno použitelná knihovna, která pomáhá vývojářům snadno umožnit jejich uživatelům připojit se k vašim dApps pomocí nejrůznějších peněženek. Ve výchozím nastavení Web3Modal Library podporuje vložené poskytovatele jako (Metamask, Dapper, Gnosis Safe, Frame, Web3 Browsers atd.) a WalletConnect. Knihovnu můžete také snadno nakonfigurovat tak, aby podporovala Portis, Fortmatic, Squarelink, Torus, Authereum, D'CENT Wallet a Arkane.
    Otevřete terminál směřující na my-app adresář a spusťte tento příkaz

  npm install web3modal
  • Do stejného terminálu také nainstalujte ethers.js
  npm install ethers
  • Ve složce my-app/public stáhněte tento obrázek a přejmenujte jej na crypto-devs.svg
  • Nyní přejděte do složky stylů a nahraďte veškerý obsah Home.modules.css soubor s následujícím kódem, přidalo by to vašemu dapp určitý styl:
  .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;
    }
  }
  • Otevřete svůj soubor index.js ve složce pages a vložte následující kód, vysvětlení kódu naleznete v komentářích. Ujistěte se, že jste si přečetli o React a React Hooks, React Hooks Tutorial, pokud je neznáte.
  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>
    );
  }
  • Nyní vytvořte novou složku ve složce my-app a pojmenujte ji constants .
  • Ve složce konstant vytvořte soubor index.js a vložte následující kód.
  • Nahraďte "YOUR_WHITELIST_CONTRACT_ADDRESS" s adresou smlouvy na seznam povolených, kterou jste nasadili.
  • Nahraďte "YOUR_ABI" s ABI vaší smlouvy na Whitelist. Chcete-li získat ABI pro svou smlouvu, přejděte na hardhat-tutorial/artifacts/contracts/Whitelist.sol a z vašeho Whitelist.json soubor získat pole označené pod "abi" klíč (bude to obrovské pole, téměř 100 řádků, ne-li více).
  export const abi = YOUR_ABI;
  export const WHITELIST_CONTRACT_ADDRESS = "YOUR_WHITELIST_CONTRACT_ADDRESS";
  • Nyní ve vašem terminálu, který ukazuje na my-app složku, spustit
  npm run dev

Váš whitelist dapp by nyní měl fungovat bez chyb 🚀

Push to github

Než budete pokračovat, ujistěte se, že jste do githubu vložili veškerý svůj kód :)

Nasazení vaší dApp

Nyní nasadíme vaši dApp, aby každý viděl vaše webové stránky a vy je mohli sdílet se všemi svými přáteli LearnWeb3 DAO.

  • Přejděte na Vercel a přihlaste se pomocí GitHubu
  • Potom klikněte na New Project a poté vyberte své úložiště dApp na seznamu povolených
  • Při konfiguraci nového projektu vám Vercel umožní přizpůsobit váš Root Directory
  • Klikněte na Edit vedle Root Directory a nastavte jej na my-app
  • Vyberte rámec jako Next.js
  • Klikněte na Deploy
  • Nyní můžete vidět své nasazené webové stránky tak, že přejdete na svůj řídicí panel, vyberete svůj projekt a zkopírujete adresu URL odtud!

Sdílejte svůj web v Discordu :D

Tento článek vám přináší LearnWeb3 DAO. Bezplatný komplexní školicí program blockchainu od A do Z pro vývojáře z celého světa.

Vše od „Co je to blockchain“ po „Hackování chytrých kontraktů“ - a vše mezi tím, ale také mnohem více!
Připojte se k nám a začněte stavět s více než 25 000 staviteli.

webová stránka
Svár
Twitter