Vybudujte si celou sbírku NFT na Ethereu pomocí Solidity, Next.js, Web3Modal, Ethers.js

Kolekce NFT

Nyní je čas, abyste spustili svou vlastní kolekci NFT - Crypto Devs .

Požadavky

  • Mělo by existovat pouze 20 Crypto Dev NFT a každé z nich by mělo být jedinečné.
  • Uživatelé by měli být schopni vytěžit pouze 1 NFT s jednou transakcí.
  • Uživatelé na bílé listině by měli mít před samotným prodejem 5minutovou předprodejní lhůtu, během níž mají zaručeno 1 NFT na transakci.
  • Měly by existovat webové stránky pro vaši sbírku NFT.

Začněme stavět 🚀

Předpoklady

  • Měli byste absolvovat kurz Whitelist dApp

Teorie

  • Co je to nezaměnitelný token?
    Zastupitelný znamená být stejný nebo zaměnitelný, např. Eth je zastupitelný. S ohledem na to jsou NFT jedinečné; každá je jiná. Každý jednotlivý token má jedinečné vlastnosti a hodnoty. Všechny jsou od sebe odlišitelné a nejsou zaměnitelné, např. Unique Art

  • Co je ERC-721?
    ERC-721 je otevřený standard, který popisuje, jak vytvořit nezaměnitelné tokeny na blockchainech kompatibilních s EVM (Virtuální stroj Ethereum); je to standardní rozhraní pro nefunkční tokeny; má soubor pravidel, která usnadňují práci s NFT. Než budete pokračovat, podívejte se na všechny funkce podporované ERC721

Sestavit

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!

Chytrá smlouva

  • Také bychom použili Ownable.sol od Openzeppelin, který vám pomůže spravovat Ownership smlouvy

    • Ve výchozím nastavení je vlastníkem smlouvy Ownable účet, který ji nasadil, což je obvykle přesně to, co chcete.
    • Ownable vám také umožňuje:
    • převést vlastnictví z účtu vlastníka na nový a
    • zřekněte se vlastnictví, aby se vlastník vzdal tohoto administrátorského privilegia, což je běžný vzorec po skončení počáteční fáze centralizované správy.
  • Také bychom používali rozšíření ERC721 známé jako ERC721 Enumerable

    • ERC721 Enumerable vám pomáhá sledovat všechna tokenId ve smlouvě a také tokensIds držená adresou pro danou smlouvu.
    • Před pokračováním se prosím podívejte na funkce, které implementuje

K vytvoření chytré smlouvy bychom použili 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.

  • Chcete-li nastavit projekt Hardhat, otevřete terminál a spusťte tyto příkazy
  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

a stiskněte enter pro všechny otázky.

  • Do stejného terminálu nyní nainstalujte @openzeppelin/contracts protože bychom importovali Openzeppelinův ERC721Enumerable kontrakt v našem CryptoDevs smlouvy.
  npm install @openzeppelin/contracts
  • Budeme muset zavolat Whitelist Contract které jste nasadili pro předchozí úroveň, abyste zkontrolovali adresy, které byly na seznamu povolených, a poskytli jim předprodejní přístup. Protože potřebujeme volat pouze mapping(address => bool) public whitelistedAddresses; Můžeme vytvořit rozhraní pro Whitelist contract s funkcí pouze pro toto mapování bychom tímto způsobem ušetřili gas protože bychom nemuseli zdědit a nasadit celý Whitelist Contract ale jen jeho část.

  • Vytvořte nový soubor uvnitř contracts adresář a nazvěte jej IWhitelist.sol

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

      interface IWhitelist {
          function whitelistedAddresses(address) external view returns (bool);
      }
  • Nyní vám umožní vytvořit nový soubor uvnitř contracts adresář a nazvěte jej CryptoDevs.sol
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.4;

    import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
    import "@openzeppelin/contracts/access/Ownable.sol";
    import "./IWhitelist.sol";

    contract CryptoDevs is ERC721Enumerable, Ownable {
        /**
         * @dev _baseTokenURI for computing {tokenURI}. If set, the resulting URI for each
         * token will be the concatenation of the `baseURI` and the `tokenId`.
         */
        string _baseTokenURI;

        //  _price is the price of one Crypto Dev NFT
        uint256 public _price = 0.01 ether;

        // _paused is used to pause the contract in case of an emergency
        bool public _paused;

        // max number of CryptoDevs
        uint256 public maxTokenIds = 20;

        // total number of tokenIds minted
        uint256 public tokenIds;

        // Whitelist contract instance
        IWhitelist whitelist;

        // boolean to keep track of whether presale started or not
        bool public presaleStarted;

        // timestamp for when presale would end
        uint256 public presaleEnded;

        modifier onlyWhenNotPaused {
            require(!_paused, "Contract currently paused");
            _;
        }

        /**
         * @dev ERC721 constructor takes in a `name` and a `symbol` to the token collection.
         * name in our case is `Crypto Devs` and symbol is `CD`.
         * Constructor for Crypto Devs takes in the baseURI to set _baseTokenURI for the collection.
         * It also initializes an instance of whitelist interface.
         */
        constructor (string memory baseURI, address whitelistContract) ERC721("Crypto Devs", "CD") {
            _baseTokenURI = baseURI;
            whitelist = IWhitelist(whitelistContract);
        }

        /**
        * @dev startPresale starts a presale for the whitelisted addresses
         */
        function startPresale() public onlyOwner {
            presaleStarted = true;
            // Set presaleEnded time as current timestamp + 5 minutes
            // Solidity has cool syntax for timestamps (seconds, minutes, hours, days, years)
            presaleEnded = block.timestamp + 5 minutes;
        }

        /**
         * @dev presaleMint allows a user to mint one NFT per transaction during the presale.
         */
        function presaleMint() public payable onlyWhenNotPaused {
            require(presaleStarted && block.timestamp < presaleEnded, "Presale is not running");
            require(whitelist.whitelistedAddresses(msg.sender), "You are not whitelisted");
            require(tokenIds < maxTokenIds, "Exceeded maximum Crypto Devs supply");
            require(msg.value >= _price, "Ether sent is not correct");
            tokenIds += 1;
            //_safeMint is a safer version of the _mint function as it ensures that
            // if the address being minted to is a contract, then it knows how to deal with ERC721 tokens
            // If the address being minted to is not a contract, it works the same way as _mint
            _safeMint(msg.sender, tokenIds);
        }

        /**
        * @dev mint allows a user to mint 1 NFT per transaction after the presale has ended.
        */
        function mint() public payable onlyWhenNotPaused {
            require(presaleStarted && block.timestamp >=  presaleEnded, "Presale has not ended yet");
            require(tokenIds < maxTokenIds, "Exceed maximum Crypto Devs supply");
            require(msg.value >= _price, "Ether sent is not correct");
            tokenIds += 1;
            _safeMint(msg.sender, tokenIds);
        }

        /**
        * @dev _baseURI overides the Openzeppelin's ERC721 implementation which by default
        * returned an empty string for the baseURI
        */
        function _baseURI() internal view virtual override returns (string memory) {
            return _baseTokenURI;
        }

        /**
        * @dev setPaused makes the contract paused or unpaused
         */
        function setPaused(bool val) public onlyOwner {
            _paused = val;
        }

        /**
        * @dev withdraw sends all the ether in the contract
        * to the owner of the contract
         */
        function withdraw() public onlyOwner  {
            address _owner = owner();
            uint256 amount = address(this).balance;
            (bool sent, ) =  _owner.call{value: amount}("");
            require(sent, "Failed to send Ether");
        }

         // Function to receive Ether. msg.data must be empty
        receive() external payable {}

        // Fallback function is called when msg.data is not empty
        fallback() external payable {}
    }
  • Nyní bychom nainstalovali 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í 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"
  • Umožňuje nasazení smlouvy na rinkeby síť. Vytvořte nový soubor s názvem deploy.js pod scripts složku

  • Nyní bychom napsali nějaký kód pro nasazení smlouvy v deploy.js soubor.

  const { ethers } = require("hardhat");
  require("dotenv").config({ path: ".env" });
  const { WHITELIST_CONTRACT_ADDRESS, METADATA_URL } = require("../constants");

  async function main() {
    // Address of the whitelist contract that you deployed in the previous module
    const whitelistContract = WHITELIST_CONTRACT_ADDRESS;
    // URL from where we can extract the metadata for a Crypto Dev NFT
    const metadataURL = METADATA_URL;
    /*
    A ContractFactory in ethers.js is an abstraction used to deploy new smart contracts,
    so cryptoDevsContract here is a factory for instances of our CryptoDevs contract.
    */
    const cryptoDevsContract = await ethers.getContractFactory("CryptoDevs");

    // deploy the contract
    const deployedCryptoDevsContract = await cryptoDevsContract.deploy(
      metadataURL,
      whitelistContract
    );

    // print the address of the deployed contract
    console.log(
      "Crypto Devs Contract Address:",
      deployedCryptoDevsContract.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);
    });
  • Jak si můžete přečíst, deploy.js vyžaduje nějaké konstanty. Vytvořte složku s názvem constants pod hardhat-tutorial složka
  • Nyní přidejte index.js soubor uvnitř constants složku a přidejte do souboru následující řádky. Nahraďte „adresu-smlouvy na whitelistu“ adresou smlouvy na whitelist, kterou jste nasadili v předchozím tutoriálu. Pro Metadata_URL stačí zkopírovat vzorovou adresu, která byla poskytnuta. Toto bychom nahradili níže v tutoriálu.
  // Address of the Whitelist Contract that you deployed
  const WHITELIST_CONTRACT_ADDRESS = "address-of-the-whitelist-contract";
  // URL to extract Metadata for a Crypto Dev NFT
  const METADATA_URL = "https://nft-collection-sneh1999.vercel.app/api/";

  module.exports = { WHITELIST_CONTRACT_ADDRESS, METADATA_URL };
  • 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 hardhart.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
  • Chcete-li nasadit, 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 Crypto Devs, 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 bychom použili React a Next Js. React je javascriptový framework, který se používá k vytváření webových stránek a Next Js je postaven na Reactu.
  • Nejprve byste museli vytvořit nový next aplikace. Struktura vaší složky by měla vypadat nějak takto
     - NFT-Collection
         - hardhat-tutorial
         - my-app
  • K vytvoření tohoto my-app , v terminálu přejděte na složku NFT-Collection 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 (https://github.com/Web3Modal/web3modal). Web3Modal je snadno použitelná knihovna, která pomáhá vývojářům přidat podporu pro více poskytovatelů v jejich aplikacích pomocí jednoduché přizpůsobitelné konfigurace. Ve výchozím nastavení Web3Modal Library podporuje injektované poskytovatele jako (Metamask, Dapper, Gnosis Safe, Frame, Web3 Browsers atd.), Můžete také snadno nakonfigurovat knihovnu 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 své veřejné složce si stáhněte tuto složku a všechny obrázky v ní (https://github.com/LearnWeb3DAO/NFT-Collection/tree/main/my-app/public/cryptodevs). Ujistěte se, že název stažené složky je cryptodevs

  • 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 nějaký 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 soubor index.js ve složce pages a vložte následující kód, vysvětlení kódu naleznete v komentářích.
  import { Contract, providers, utils } from "ethers";
  import Head from "next/head";
  import React, { useEffect, useRef, useState } from "react";
  import Web3Modal from "web3modal";
  import { abi, NFT_CONTRACT_ADDRESS } from "../constants";
  import styles from "../styles/Home.module.css";

  export default function Home() {
    // walletConnected keep track of whether the user's wallet is connected or not
    const [walletConnected, setWalletConnected] = useState(false);
    // presaleStarted keeps track of whether the presale has started or not
    const [presaleStarted, setPresaleStarted] = useState(false);
    // presaleEnded keeps track of whether the presale ended
    const [presaleEnded, setPresaleEnded] = useState(false);
    // loading is set to true when we are waiting for a transaction to get mined
    const [loading, setLoading] = useState(false);
    // checks if the currently connected MetaMask wallet is the owner of the contract
    const [isOwner, setIsOwner] = useState(false);
    // tokenIdsMinted keeps track of the number of tokenIds that have been minted
    const [tokenIdsMinted, setTokenIdsMinted] = 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();

    /**
     * presaleMint: Mint an NFT during the presale
     */
    const presaleMint = 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(
          NFT_CONTRACT_ADDRESS,
          abi,
          signer
        );
        // call the presaleMint from the contract, only whitelisted addresses would be able to mint
        const tx = await whitelistContract.presaleMint({
          // value signifies the cost of one crypto dev which is "0.01" eth.
          // We are parsing `0.01` string to ether using the utils library from ethers.js
          value: utils.parseEther("0.01"),
        });
        setLoading(true);
        // wait for the transaction to get mined
        await tx.wait();
        setLoading(false);
        window.alert("You successfully minted a Crypto Dev!");
      } catch (err) {
        console.error(err);
      }
    };

    /**
     * publicMint: Mint an NFT after the presale
     */
    const publicMint = 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(
          NFT_CONTRACT_ADDRESS,
          abi,
          signer
        );
        // call the mint from the contract to mint the Crypto Dev
        const tx = await whitelistContract.mint({
          // value signifies the cost of one crypto dev which is "0.01" eth.
          // We are parsing `0.01` string to ether using the utils library from ethers.js
          value: utils.parseEther("0.01"),
        });
        setLoading(true);
        // wait for the transaction to get mined
        await tx.wait();
        setLoading(false);
        window.alert("You successfully minted a Crypto Dev!");
      } 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);
      } catch (err) {
        console.error(err);
      }
    };

    /**
     * startPresale: starts the presale for the NFT Collection
     */
    const startPresale = 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(
          NFT_CONTRACT_ADDRESS,
          abi,
          signer
        );
        // call the startPresale from the contract
        const tx = await whitelistContract.startPresale();
        setLoading(true);
        // wait for the transaction to get mined
        await tx.wait();
        setLoading(false);
        // set the presale started to true
        await checkIfPresaleStarted();
      } catch (err) {
        console.error(err);
      }
    };

    /**
     * checkIfPresaleStarted: checks if the presale has started by quering the `presaleStarted`
     * variable in the contract
     */
    const checkIfPresaleStarted = 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 nftContract = new Contract(NFT_CONTRACT_ADDRESS, abi, provider);
        // call the presaleStarted from the contract
        const _presaleStarted = await nftContract.presaleStarted();
        if (!_presaleStarted) {
          await getOwner();
        }
        setPresaleStarted(_presaleStarted);
        return _presaleStarted;
      } catch (err) {
        console.error(err);
        return false;
      }
    };

    /**
     * checkIfPresaleEnded: checks if the presale has ended by quering the `presaleEnded`
     * variable in the contract
     */
    const checkIfPresaleEnded = 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 nftContract = new Contract(NFT_CONTRACT_ADDRESS, abi, provider);
        // call the presaleEnded from the contract
        const _presaleEnded = await nftContract.presaleEnded();
        // _presaleEnded is a Big Number, so we are using the lt(less than function) instead of `<`
        // Date.now()/1000 returns the current time in seconds
        // We compare if the _presaleEnded timestamp is less than the current time
        // which means presale has ended
        const hasEnded = _presaleEnded.lt(Math.floor(Date.now() / 1000));
        if (hasEnded) {
          setPresaleEnded(true);
        } else {
          setPresaleEnded(false);
        }
        return hasEnded;
      } catch (err) {
        console.error(err);
        return false;
      }
    };

    /**
     * getOwner: calls the contract to retrieve the owner
     */
    const getOwner = 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 nftContract = new Contract(NFT_CONTRACT_ADDRESS, abi, provider);
        // call the owner function from the contract
        const _owner = await nftContract.owner();
        // We will get the signer now to extract the address of the currently connected MetaMask account
        const signer = await getProviderOrSigner(true);
        // Get the address associated to the signer which is connected to  MetaMask
        const address = await signer.getAddress();
        if (address.toLowerCase() === _owner.toLowerCase()) {
          setIsOwner(true);
        }
      } catch (err) {
        console.error(err.message);
      }
    };

    /**
     * getTokenIdsMinted: gets the number of tokenIds that have been minted
     */
    const getTokenIdsMinted = 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 nftContract = new Contract(NFT_CONTRACT_ADDRESS, abi, provider);
        // call the tokenIds from the contract
        const _tokenIds = await nftContract.tokenIds();
        //_tokenIds is a `Big Number`. We need to convert the Big Number to a string
        setTokenIdsMinted(_tokenIds.toString());
      } catch (err) {
        console.error(err);
      }
    };

    /**
     * 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;
    };

    // 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();

        // Check if presale has started and ended
        const _presaleStarted = checkIfPresaleStarted();
        if (_presaleStarted) {
          checkIfPresaleEnded();
        }

        getTokenIdsMinted();

        // Set an interval which gets called every 5 seconds to check presale has ended
        const presaleEndedInterval = setInterval(async function () {
          const _presaleStarted = await checkIfPresaleStarted();
          if (_presaleStarted) {
            const _presaleEnded = await checkIfPresaleEnded();
            if (_presaleEnded) {
              clearInterval(presaleEndedInterval);
            }
          }
        }, 5 * 1000);

        // set an interval to get the number of token Ids minted every 5 seconds
        setInterval(async function () {
          await getTokenIdsMinted();
        }, 5 * 1000);
      }
    }, [walletConnected]);

    /*
        renderButton: Returns a button based on the state of the dapp
      */
    const renderButton = () => {
      // If wallet is not connected, return a button which allows them to connect their wllet
      if (!walletConnected) {
        return (
          <button onClick={connectWallet} className={styles.button}>
            Connect your wallet
          </button>
        );
      }

      // If we are currently waiting for something, return a loading button
      if (loading) {
        return <button className={styles.button}>Loading...</button>;
      }

      // If connected user is the owner, and presale hasnt started yet, allow them to start the presale
      if (isOwner && !presaleStarted) {
        return (
          <button className={styles.button} onClick={startPresale}>
            Start Presale!
          </button>
        );
      }

      // If connected user is not the owner but presale hasn't started yet, tell them that
      if (!presaleStarted) {
        return (
          <div>
            <div className={styles.description}>Presale hasnt started!</div>
          </div>
        );
      }

      // If presale started, but hasn't ended yet, allow for minting during the presale period
      if (presaleStarted && !presaleEnded) {
        return (
          <div>
            <div className={styles.description}>
              Presale has started!!! If your address is whitelisted, Mint a
              Crypto Dev 🥳
            </div>
            <button className={styles.button} onClick={presaleMint}>
              Presale Mint 🚀
            </button>
          </div>
        );
      }

      // If presale started and has ended, its time for public minting
      if (presaleStarted && presaleEnded) {
        return (
          <button className={styles.button} onClick={publicMint}>
            Public Mint 🚀
          </button>
        );
      }
    };

    return (
      <div>
        <Head>
          <title>Crypto Devs</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}>
              {tokenIdsMinted}/20 have been minted
            </div>
            {renderButton()}
          </div>
          <div>
            <img className={styles.image} src="./cryptodevs/0.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 "addres of your NFT contract" s adresou smlouvy CryptoDevs, kterou jste nasadili a uložili do svého poznámkového bloku.
    • Nahraďte ---your abi--- s abi vaší smlouvy CryptoDevs. Chcete-li získat abi pro svou smlouvu, přejděte na hardhat-tutorial/artifacts/contracts/CryptoDevs.sol a z vašeho CryptoDevs.json soubor získat pole označené pod "abi" klíč.
  export const abi =---your abi---
  export const NFT_CONTRACT_ADDRESS = "address of your NFT contract"
  • Nyní ve vašem terminálu, který ukazuje na my-app složku, spustit
  npm run dev

Váš Crypto Devs NFT 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 https://vercel.com/ a přihlaste se pomocí GitHubu
  • Potom klikněte na New Project a poté vyberte své úložiště NFT-Collection
  • Při konfiguraci nového projektu vám Vercel umožní přizpůsobit 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 svůj nasazený web zobrazit tak, že přejdete na svůj řídicí panel, vyberete projekt a zkopírujete domain odtamtud! Uložte domain v poznámkovém bloku, budete jej potřebovat později.

Prohlédněte si svou sbírku na Opensea

Nyní umožňuje, aby byla vaše sbírka dostupná na Opensea

Aby byla kolekce dostupná na Opensea, museli bychom vytvořit koncový bod metadat. Tento koncový bod by vrátil metadata pro NFT vzhledem k jeho tokenId .

  • Otevřete my-app složku a podpages/api vytvořte nový soubor s názvem [tokenId].js (Ujistěte se, že název obsahuje také závorky). Přidání závorek pomůže vytvořit dynamické trasy v dalším js
  • Přidejte následující řádky do [tokenId].js soubor. Přečtěte si o přidávání tras API v next js zde
  export default function handler(req, res) {
    // get the tokenId from the query params
    const tokenId = req.query.tokenId;
    // As all the images are uploaded on github, we can extract the images from github directly.
    const image_url =
      "https://raw.githubusercontent.com/LearnWeb3DAO/NFT-Collection/main/my-app/public/cryptodevs/";
    // The api is sending back metadata for a Crypto Dev
    // To make our collection compatible with Opensea, we need to follow some Metadata standards
    // when sending back the response from the api
    // More info can be found here: https://docs.opensea.io/docs/metadata-standards
    res.status(200).json({
      name: "Crypto Dev #" + tokenId,
      description: "Crypto Dev is a collection of developers in crypto",
      image: image_url + tokenId + ".svg",
    });
  }
  • Nyní máte api trasu, která Opensea může zavolat a získat metadata pro NFT

  • Pojďme nasadit nový Crypto Devs smlouvu s touto novou cestou rozhraní API jako METADATA_URL

  • Otevřete hardhat-tutorial/constants složky a uvnitř vašeho index.js soubor, nahraďte "https://nft-collection-sneh1999.vercel.app/api/" doménou, kterou jste uložili do poznámkového bloku, a na jeho konec přidejte "/api/".

  • Uložte soubor a otevřete nový terminál ukazující na hardhat-tutorial složku a nasadit novou smlouvu

    npx hardhat run scripts/deploy.js --network rinkeby
  • Uložte si novou adresu smlouvy NFT do poznámkového bloku.

  • Otevřete složku "my-app/constants" a uvnitř index.js soubor nahradit starou adresu smlouvy NFT novou

  • Odešlete veškerý kód na github a počkejte, až vercel nasadí nový kód.

  • Poté, co vercel nasadí váš kód, otevřete svůj web a vyražte NFT

  • Po úspěšné transakci otevřete tento odkaz ve svém prohlížeči nahrazením your-nft-contract-address s adresou vaší smlouvy o NFT (https://testnets.opensea.io/assets/your-nft-contract-address/1)

  • Vaše NFT je nyní k dispozici na Opensea 🚀 🥳

  • Sdílejte svůj odkaz na Opensea se všemi na sváru :) a šiřte štěstí.

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