启动您自己的初始代币发行
现在是时候为 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);
  });