启动您自己的初始代币发行
现在是时候为 Crypto Devs 推出代币了。 我们将令牌称为 Crypto Dev Token。 我们将把这个代币免费分发给我们所有的 NFT 持有者,并让其他人用 ETH 购买它们。
喜欢视频?
如果您想从视频中学习,我们的 YouTube 上有本教程的录音。单击下面的屏幕截图观看视频,或继续阅读教程!
建造
要求
- 最多应该有 10,000 个 CD 令牌。
 - 每个 Crypto Dev NFT 持有者都应该免费获得 10 个代币,但他们必须支付汽油费。
 - ICO 时一张 CD 的价格应该是 0.001 以太币。
 - 应该有一个用户可以访问 ICO 的网站。
 
让我 们开始建造吧 🚀
先决条件
你必须已经完成了之前的 NFT Collection 教程。
理论
- 什么是 ERC20?
- ERC-20 是一个技术标准; 它用于以太坊区块链上的所有智能合约以实现代币,并提供所有基于以太坊的代币必须遵循的规则列表。
 - 请在继续之前查看所有 ERC20 功能。
 
 
建造
智能合约
为了构建智能合约,我们将使用 Hardhat。 Hardhat 是一个以太坊开发环境和框架,专为 Solidity 中的全栈开发而设计。 简单来说,您可以编写智能合约、部署它们、运行测试和调试代码。
要设置安全帽项目,请打开终端并执行以下命令:
mkdir ICO
cd ICO
mkdir hardhat-tutorial
cd hardhat-tutorial
npm init --yes
npm install --save-dev hardhat
在安装 Hardhat 的同一目录中运行:
npx hardhat
确保选择创建 Javascript 项目,然后按照终端中的步骤完成安全帽设置。
现在在同一个终端中安装 @openzeppelin/contracts,因为我们将在我们的 CryptoDevs 合约中导入 Openzeppelin 的 ERC721Enumerable 合约。
npm install @openzeppelin/contracts
我们需要调用上一个教程中部署的 CryptoDevs 合约,以检查 CryptoDev NFT 的所有者。 由于我们只需要调用 tokenOfOwnerByIndex 和 balanceOf 方法,我们可以只用这两个函数为 CryptoDevs 合约创建一个接口。 这样我们节省了 gas,因为我们不需要继承和部署整个 CryptoDevs 合约,而只需要其中的一部分。
在 contracts 目录中创建一个新文件并将其命名为 ICryptoDevs.sol。 添加以下行:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface ICryptoDevs {
    /**
     * @dev Returns a token ID owned by `owner` at a given `index` of its token list.
     * Use along with {balanceOf} to enumerate all of ``owner``'s tokens.
     */
    function tokenOfOwnerByIndex(address owner, uint256 index)
        external
        view
        returns (uint256 tokenId);
    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);
}
在 contracts 目录中创建另一个文件并将其命名为 CryptoDevToken.sol。 添加以下行:
// SPDX-License-Identifier: MIT
  pragma solidity ^0.8.0;
  import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
  import "@openzeppelin/contracts/access/Ownable.sol";
  import "./ICryptoDevs.sol";
  contract CryptoDevToken is ERC20, Ownable {
      // Price of one Crypto Dev token
      uint256 public constant tokenPrice = 0.001 ether;
      // Each NFT would give the user 10 tokens
      // It needs to be represented as 10 * (10 ** 18) as ERC20 tokens are represented by the smallest denomination possible for the token
      // By default, ERC20 tokens have the smallest denomination of 10^(-18). This means, having a balance of (1)
      // is actually equal to (10 ^ -18) tokens.
      // Owning 1 full token is equivalent to owning (10^18) tokens when you account for the decimal places.
      // More information on this can be found in the Freshman Track Cryptocurrency tutorial.
      uint256 public constant tokensPerNFT = 10 * 10**18;
      // the max total supply is 10000 for Crypto Dev Tokens
      uint256 public constant maxTotalSupply = 10000 * 10**18;
      // CryptoDevsNFT contract instance
      ICryptoDevs CryptoDevsNFT;
      // Mapping to keep track of which tokenIds have been claimed
      mapping(uint256 => bool) public tokenIdsClaimed;
      constructor(address _cryptoDevsContract) ERC20("Crypto Dev Token", "CD") {
          CryptoDevsNFT = ICryptoDevs(_cryptoDevsContract);
      }
      /**
       * @dev Mints `amount` number of CryptoDevTokens
       * Requirements:
       * - `msg.value` should be equal or greater than the tokenPrice * amount
       */
      function mint(uint256 amount) public payable {
          // the value of ether that should be equal or greater than tokenPrice * amount;
          uint256 _requiredAmount = tokenPrice * amount;
          require(msg.value >= _requiredAmount, "Ether sent is incorrect");
          // total tokens + amount <= 10000, otherwise revert the transaction
          uint256 amountWithDecimals = amount * 10**18;
          require(
              (totalSupply() + amountWithDecimals) <= maxTotalSupply,
              "Exceeds the max total supply available."
          );
          // call the internal function from Openzeppelin's ERC20 contract
          _mint(msg.sender, amountWithDecimals);
      }
      /**
       * @dev Mints tokens based on the number of NFT's held by the sender
       * Requirements:
       * balance of Crypto Dev NFT's owned by the sender should be greater than 0
       * Tokens should have not been claimed for all the NFTs owned by the sender
       */
      function claim() public {
          address sender = msg.sender;
          // Get the number of CryptoDev NFT's held by a given sender address
          uint256 balance = CryptoDevsNFT.balanceOf(sender);
          // If the balance is zero, revert the transaction
          require(balance > 0, "You dont own any Crypto Dev NFT's");
          // amount keeps track of number of unclaimed tokenIds
          uint256 amount = 0;
          // loop over the balance and get the token ID owned by `sender` at a given `index` of its token list.
          for (uint256 i = 0; i < balance; i++) {
              uint256 tokenId = CryptoDevsNFT.tokenOfOwnerByIndex(sender, i);
              // if the tokenId has not been claimed, increase the amount
              if (!tokenIdsClaimed[tokenId]) {
                  amount += 1;
                  tokenIdsClaimed[tokenId] = true;
              }
          }
          // If all the token Ids have been claimed, revert the transaction;
          require(amount > 0, "You have already claimed all the tokens");
          // call the internal function from Openzeppelin's ERC20 contract
          // Mint (amount * 10) tokens for each NFT
          _mint(msg.sender, amount * tokensPerNFT);
      }
      /**
        * @dev withdraws all ETH and tokens sent to the contract
        * Requirements:
        * wallet connected must be owner's address
        */
      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 {}
  }
现在让我们安装 dotenv 包,以便能够导入 env 文件并在我们的配置中使用它。打开指向 hardhat-tutorial 目录的终端并执行此命令
npm install dotenv
现在在 hardhat-tutorial 文件夹中创建一个 .env 文件并添加以下行。请按照以下说明进行操作。
转到 Quicknode 并注册一个帐户。如果您已经有一个帐户,请登录。Quicknode 是一个节点提供商,可让您连接到各种不同的区块链。我们将使用它通过 Hardhat 部署我们的合约。创建账户后,在 Quicknode 上创建一个 endpoint,选择 Ethereum,然后选择 Goerli 网络。单击右下角的继续,然后单击创建端点。复制 HTTP Provider 中提供给您的链接,并将其添加到 QUICKNODE_HTTP_URL 下面的 .env 文件中。
注意:如果您之前在新生跟踪期间在 Quicknode 上设置了 Goerli Endpoint,则可以使用与之前相同的 URL。无需删除它并设置一个新的。
要获取您的私钥,您需要从 Metamask 中导出它。打开 Metamask,点击三个点,点击 Account Details,然后点击 Export Private Key。确保您使用的是没有主网资金的测试帐户。在您的 .env 文件中为 PRIVATE_KEY 变量添加此私钥。
QUICKNODE_HTTP_URL="添加-quicknode-http-provider-url-这里"
PRIVATE_KEY="在此处添加私钥"
让我们将合约部署到 goerli 网络。在脚本文件夹下创建一个新文件,或替换默认文件,名为 deploy.js
让我们编写一些代码来在 deploy.js 文件中部署合约。
const { ethers } = require("hardhat");
require("dotenv").config({ path: ".env" });
const { CRYPTO_DEVS_NFT_CONTRACT_ADDRESS } = require("../constants");
async function main() {
  // Address of the Crypto Devs NFT contract that you deployed in the previous module
  const cryptoDevsNFTContract = CRYPTO_DEVS_NFT_CONTRACT_ADDRESS;
  /*
    A ContractFactory in ethers.js is an abstraction used to deploy new smart contracts,
    so cryptoDevsTokenContract here is a factory for instances of our CryptoDevToken contract.
    */
  const cryptoDevsTokenContract = await ethers.getContractFactory(
    "CryptoDevToken"
  );
  // deploy the contract
  const deployedCryptoDevsTokenContract = await cryptoDevsTokenContract.deploy(
    cryptoDevsNFTContract
  );
  await deployedCryptoDevsTokenContract.deployed();
  // print the address of the deployed contract
  console.log(
    "Crypto Devs Token Contract Address:",
    deployedCryptoDevsTokenContract.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);
  });
如您所见,deploy.js 需要一些常量。 让我们在 hardhat-tutorial 文件夹下创建一个名为 constants 的文件夹。 在常量文件夹中创建一个 index.js 文件,并将以下行添加到文件中。 将“address-of-the-whitelist-contract”替换为您在上一教程中部署的白名单合约的地址。 对于 Metadata_URL,只需复制已提供的示例。 我们将在教程中进一步替换它。
// Address of the NFT Contract that you deployed
const CRYPTO_DEVS_NFT_CONTRACT_ADDRESS = "address-of-the-nft-contract";
module.exports = { CRYPTO_DEVS_NFT_CONTRACT_ADDRESS };
现在打开 hardhat.config.js 文件,我们将在这里设置 goerli 网络,以便我们可以将合约部署到 Goerli 网络。 用下面给出的行替换 hardhat.config.js 文件中的所有行
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config({ path: ".env" });
const QUICKNODE_HTTP_URL = process.env.QUICKNODE_HTTP_URL;
const PRIVATE_KEY = process.env.PRIVATE_KEY;
module.exports = {
  solidity: "0.8.4",
  networks: {
    goerli: {
      url: QUICKNODE_HTTP_URL,
      accounts: [PRIVATE_KEY],
    },
  },
};
编译合约,打开指向 hardhat-tutorial 目录的终端并执行此命令
npx hardhat compile
要部署,请打开指向 hardhat-tutorial 目录的终端并执行此命令
npx hardhat run scripts/deploy.js --network goerli
将打印到终端的 CryptoDevToken 合约地址保存在记事本中。 您将在本教程的后面部分用到它。
网站
为了开发网站,我们将使用 React 和 Next Js。 React 是一个用于制作网站的 javascript 框架,Next Js 构建在 React 之上。 您首先需要创建一个新的下一个应用程序。 您的文件夹结构应如下所示:
- ICO
       - hardhat-tutorial
       - my-app
要创建这个我的应用程序,在终端中指向 ICO 文件夹并输入:
npx create-next-app@latest
并按回车键所有问题
现在运行应用程序,在终端中执行这些命令
cd my-app
npm run dev
现在转到 http://localhost:3000,您的应用程序应该正在运行 🤘
现在让我们安装 Web3Modal 库(https://github.com/Web3Modal/web3modal)。 Web3Modal 是一个易于使用的库,可帮助开发人员通过简单的可自定义配置在其应用程序中添加对多个提供程序的支持。 默认情况下,Web3Modal 库支持注入的提供程序,例如(Metamask、Dapper、Gnosis Safe、Frame、Web3 浏览器等),您还可以轻松配置库以支持 Portis、Fortmatic、Squarelink、Torus、Authereum、D'CENT 钱包和 Arkane。 打开指向 atmy-app 目录的终端并执行此命令
npm install web3modal
同样的安装 ethers.js
npm install ethers
在您的公共文件夹中,下载此文件夹及其中的所有图像(下载链接)。 确保下载的文件夹的名称是 cryptodevs
现在转到样式文件夹并将 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%;
}
.input {
  width: 200px;
  height: 100%;
  padding: 1%;
  margin-bottom: 2%;
  box-shadow: 0 0 15px 4px rgba(0, 0, 0, 0.06);
  border-radius: 10px;
}
.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: 5px;
  width: 100px;
  cursor: pointer;
  margin-bottom: 2%;
}
@media (max-width: 1000px) {
  .main {
    width: 100%;
    flex-direction: column;
    justify-content: center;
    align-items: center;
  }
}
打开 pages 文件夹下的 index.js 文件并粘贴以下代码,代码解释可以在评论中找到。
import { BigNumber, Contract, providers, utils } from "ethers";
import Head from "next/head";
import React, { useEffect, useRef, useState } from "react";
import Web3Modal from "web3modal";
import {
  NFT_CONTRACT_ABI,
  NFT_CONTRACT_ADDRESS,
  TOKEN_CONTRACT_ABI,
  TOKEN_CONTRACT_ADDRESS,
} from "../constants";
import styles from "../styles/Home.module.css";
export default function Home() {
  // Create a BigNumber `0`
  const zero = BigNumber.from(0);
  // walletConnected keeps track of whether the user's wallet is connected or not
  const [walletConnected, setWalletConnected] = useState(false);
  // loading is set to true when we are waiting for a transaction to get mined
  const [loading, setLoading] = useState(false);
  // tokensToBeClaimed keeps track of the number of tokens that can be claimed
  // based on the Crypto Dev NFT's held by the user for which they havent claimed the tokens
  const [tokensToBeClaimed, setTokensToBeClaimed] = useState(zero);
  // balanceOfCryptoDevTokens keeps track of number of Crypto Dev tokens owned by an address
  const [balanceOfCryptoDevTokens, setBalanceOfCryptoDevTokens] =
    useState(zero);
  // amount of the tokens that the user wants to mint
  const [tokenAmount, setTokenAmount] = useState(zero);
  // tokensMinted is the total number of tokens that have been minted till now out of 10000(max total supply)
  const [tokensMinted, setTokensMinted] = useState(zero);
  // isOwner gets the owner of the contract through the signed address
  const [isOwner, setIsOwner] = useState(false);
  // Create a reference to the Web3 Modal (used for connecting to Metamask) which persists as long as the page is open
  const web3ModalRef = useRef();
  /**
   * getTokensToBeClaimed: checks the balance of tokens that can be claimed by the user
   */
  const getTokensToBeClaimed = 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();
      // Create an instance of NFT Contract
      const nftContract = new Contract(
        NFT_CONTRACT_ADDRESS,
        NFT_CONTRACT_ABI,
        provider
      );
      // Create an instance of tokenContract
      const tokenContract = new Contract(
        TOKEN_CONTRACT_ADDRESS,
        TOKEN_CONTRACT_ABI,
        provider
      );
      // 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();
      // call the balanceOf from the NFT contract to get the number of NFT's held by the user
      const balance = await nftContract.balanceOf(address);
      // balance is a Big number and thus we would compare it with Big number `zero`
      if (balance === zero) {
        setTokensToBeClaimed(zero);
      } else {
        // amount keeps track of the number of unclaimed tokens
        var amount = 0;
        // For all the NFT's, check if the tokens have already been claimed
        // Only increase the amount if the tokens have not been claimed
        // for a an NFT(for a given tokenId)
        for (var i = 0; i < balance; i++) {
          const tokenId = await nftContract.tokenOfOwnerByIndex(address, i);
          const claimed = await tokenContract.tokenIdsClaimed(tokenId);
          if (!claimed) {
            amount++;
          }
        }
        //tokensToBeClaimed has been initialized to a Big Number, thus we would convert amount
        // to a big number and then set its value
        setTokensToBeClaimed(BigNumber.from(amount));
      }
    } catch (err) {
      console.error(err);
      setTokensToBeClaimed(zero);
    }
  };
  /**
   * getBalanceOfCryptoDevTokens: checks the balance of Crypto Dev Tokens's held by an address
   */
  const getBalanceOfCryptoDevTokens = 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();
      // Create an instace of token contract
      const tokenContract = new Contract(
        TOKEN_CONTRACT_ADDRESS,
        TOKEN_CONTRACT_ABI,
        provider
      );
      // 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();
      // call the balanceOf from the token contract to get the number of tokens held by the user
      const balance = await tokenContract.balanceOf(address);
      // balance is already a big number, so we dont need to convert it before setting it
      setBalanceOfCryptoDevTokens(balance);
    } catch (err) {
      console.error(err);
      setBalanceOfCryptoDevTokens(zero);
    }
  };
  /**
   * mintCryptoDevToken: mints `amount` number of tokens to a given address
   */
  const mintCryptoDevToken = async (amount) => {
    try {
      // We need a Signer here since this is a 'write' transaction.
      // Create an instance of tokenContract
      const signer = await getProviderOrSigner(true);
      // Create an instance of tokenContract
      const tokenContract = new Contract(
        TOKEN_CONTRACT_ADDRESS,
        TOKEN_CONTRACT_ABI,
        signer
      );
      // Each token is of `0.001 ether`. The value we need to send is `0.001 * amount`
      const value = 0.001 * amount;
      const tx = await tokenContract.mint(amount, {
        // value signifies the cost of one crypto dev token which is "0.001" eth.
        // We are parsing `0.001` string to ether using the utils library from ethers.js
        value: utils.parseEther(value.toString()),
      });
      setLoading(true);
      // wait for the transaction to get mined
      await tx.wait();
      setLoading(false);
      window.alert("Sucessfully minted Crypto Dev Tokens");
      await getBalanceOfCryptoDevTokens();
      await getTotalTokensMinted();
      await getTokensToBeClaimed();
    } catch (err) {
      console.error(err);
    }
  };
  /**
   * claimCryptoDevTokens: Helps the user claim Crypto Dev Tokens
   */
  const claimCryptoDevTokens = async () => {
    try {
      // We need a Signer here since this is a 'write' transaction.
      // Create an instance of tokenContract
      const signer = await getProviderOrSigner(true);
      // Create an instance of tokenContract
      const tokenContract = new Contract(
        TOKEN_CONTRACT_ADDRESS,
        TOKEN_CONTRACT_ABI,
        signer
      );
      const tx = await tokenContract.claim();
      setLoading(true);
      // wait for the transaction to get mined
      await tx.wait();
      setLoading(false);
      window.alert("Sucessfully claimed Crypto Dev Tokens");
      await getBalanceOfCryptoDevTokens();
      await getTotalTokensMinted();
      await getTokensToBeClaimed();
    } catch (err) {
      console.error(err);
    }
  };
  /**
   * getTotalTokensMinted: Retrieves how many tokens have been minted till now
   * out of the total supply
   */
  const getTotalTokensMinted = 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();
      // Create an instance of token contract
      const tokenContract = new Contract(
        TOKEN_CONTRACT_ADDRESS,
        TOKEN_CONTRACT_ABI,
        provider
      );
      // Get all the tokens that have been minted
      const _tokensMinted = await tokenContract.totalSupply();
      setTokensMinted(_tokensMinted);
    } catch (err) {
      console.error(err);
    }
  };
  /**
   * getOwner: gets the contract owner by connected address
   */
  const getOwner = async () => {
    try {
      const provider = await getProviderOrSigner();
      const tokenContract = new Contract(
        TOKEN_CONTRACT_ADDRESS,
        TOKEN_CONTRACT_ABI,
        provider
      );
      // call the owner function from the contract
      const _owner = await tokenContract.owner();
      // we get signer to extract address of currently connected Metamask account
      const signer = await getProviderOrSigner(true);
      // Get the address associated to signer which is connected to Metamask
      const address = await signer.getAddress();
      if (address.toLowerCase() === _owner.toLowerCase()) {
        setIsOwner(true);
      }
    } catch (err) {
      console.error(err.message);
    }
  };
  /**
   * withdrawCoins: withdraws ether and tokens by calling
   * the withdraw function in the contract
   */
  const withdrawCoins = async () => {
    try {
      const signer = await getProviderOrSigner(true);
      const tokenContract = new Contract(
        TOKEN_CONTRACT_ADDRESS,
        TOKEN_CONTRACT_ABI,
        signer
      );
      const tx = await tokenContract.withdraw();
      setLoading(true);
      await tx.wait();
      setLoading(false);
      await getOwner();
    } 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 Goerli network, let them know and throw an error
    const { chainId } = await web3Provider.getNetwork();
    if (chainId !== 5) {
      window.alert("Change the network to Goerli");
      throw new Error("Change network to Goerli");
    }
    if (needSigner) {
      const signer = web3Provider.getSigner();
      return signer;
    }
    return web3Provider;
  };
  /*
        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);
    }
  };
  // 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: "goerli",
        providerOptions: {},
        disableInjectedProvider: false,
      });
      connectWallet();
      getTotalTokensMinted();
      getBalanceOfCryptoDevTokens();
      getTokensToBeClaimed();
      withdrawCoins();
    }
  }, [walletConnected]);
  /*
        renderButton: Returns a button based on the state of the dapp
      */
  const renderButton = () => {
    // If we are currently waiting for something, return a loading button
    if (loading) {
      return (
        <div>
          <button className={styles.button}>Loading...</button>
        </div>
      );
    }
    // if owner is connected, withdrawCoins() is called
    if (walletConnected && isOwner) {
      return (
        <div>
          <button className={styles.button1} onClick={withdrawCoins}>
            Withdraw Coins
          </button>
        </div>
      );
    }
    // If tokens to be claimed are greater than 0, Return a claim button
    if (tokensToBeClaimed > 0) {
      return (
        <div>
          <div className={styles.description}>
            {tokensToBeClaimed * 10} Tokens can be claimed!
          </div>
          <button className={styles.button} onClick={claimCryptoDevTokens}>
            Claim Tokens
          </button>
        </div>
      );
    }
    // If user doesn't have any tokens to claim, show the mint button
    return (
      <div style={{ display: "flex-col" }}>
        <div>
          <input
            type="number"
            placeholder="Amount of Tokens"
            // BigNumber.from converts the `e.target.value` to a BigNumber
            onChange={(e) => setTokenAmount(BigNumber.from(e.target.value))}
            className={styles.input}
          />
        </div>
        <button
          className={styles.button}
          disabled={!(tokenAmount > 0)}
          onClick={() => mintCryptoDevToken(tokenAmount)}
        >
          Mint Tokens
        </button>
      </div>
    );
  };
  return (
    <div>
      <Head>
        <title>Crypto Devs</title>
        <meta name="description" content="ICO-Dapp" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <div className={styles.main}>
        <div>
          <h1 className={styles.title}>Welcome to Crypto Devs ICO!</h1>
          <div className={styles.description}>
            You can claim or mint Crypto Dev tokens here
          </div>
          {walletConnected ? (
            <div>
              <div className={styles.description}>
                {/* Format Ether helps us in converting a BigNumber to string */}
                You have minted {utils.formatEther(balanceOfCryptoDevTokens)} Crypto
                Dev Tokens
              </div>
              <div className={styles.description}>
                {/* Format Ether helps us in converting a BigNumber to string */}
                Overall {utils.formatEther(tokensMinted)}/10000 have been minted!!!
              </div>
              {renderButton()}
            </div>
          ) : (
            <button onClick={connectWallet} className={styles.button}>
              Connect your wallet
            </button>
          )}
        </div>
        <div>
          <img className={styles.image} src="./0.svg" />
        </div>
      </div>
      <footer className={styles.footer}>
        Made with ❤ by Crypto Devs
      </footer>
    </div>
  );
}
现在在 my-app 文件夹下创建一个新文件夹并将其命名为常量。 在常量文件夹中创建一个名为 index.js 的文件并粘贴以下代码:
export const NFT_CONTRACT_ABI = "abi-of-your-nft-contract";
export const NFT_CONTRACT_ADDRESS = "address-of-your-nft-contract";
export const TOKEN_CONTRACT_ABI = "abi-of-your-token-contract";
export const TOKEN_CONTRACT_ADDRESS = "address-of-your-token-contract";
- 将“abi-of-your-nft-contract”替换为您在上一个教程中部署的 NFT 合约的 abi。
 - 将“address-of-your-nft-contract”替换为您在之前教程中部署的 NFT 合约的地址。
 - 将“abi-of-your-token-contract”替换为代币合约的 abi。 要获取 Token 合约的 abi,请转到 hardhat-tutorial/artifacts/contracts/CryptoDevToken.sol ,然后从 CryptoDevToken.json 文件获取标记在“abi”键下的数组。
 - 将“address-of-your-token-contract”替换为您在本教程前面保存到记事本的代币合约的地址。
 
现在在指向 my-app 文件夹的终端中,执行以下命令:
npm run dev
您的 ICO dapp 现在应该可以正常运行了 🚀
推送到 Github
在继续下一步之前,请确保将所有代码推送到 Github。
部署你的 dApp
我们现在将部署您的 dApp,以便每个人都可以看到您的网站,并且您可以与所有 LearnWeb3 DAO 朋友分享它。
转到 https://vercel.com/ 并使用您的 GitHub 登录。
然后单击新建项目按钮,然后选择您的 ICO dApp 存储库。
配置新项目时,Vercel 将允许您自定义根目录。
单击根目录旁边的编辑并将其设置为 my-app。
选择框架预设为 Next.js.Image
单击部署
现在,您可以通过转到仪表板、选择您的项目并从那里复制 URL 来查看您部署的网站!
恭喜! 你们都完成了!
希望您喜欢本教程。 不要忘记在 Discord 的#showcase 频道中分享您的 ICO 网站:D