ICO (Initial Coin Offering)
Теперь пришло время запустить токен для Crypto Devs
. Назовем токен Crypto Dev Token.
Предпочитаете видео?
Если вы предпочитаете учиться на видео, у нас есть запись этого урока на нашем YouTube. Посмотрите видео, нажав на скриншот ниже, или переходите к чтению руководства!
Сборка
Требования
- Должно быть максимум
10,000 токенов CD
. - Каждый держатель
Crypto Dev
NFT должен получить 10 токенов бесплатно, но ему придется заплатить за газ. - Цена одного CD на момент проведения ICO должна составлять
0,001 эфира
. - Должен быть веб-сайт, который пользователи смогут посетить для проведения ICO.
Давайте приступим к созданию 🚀.
Предварительные условия
- Вы должны пройти учебник по NFT-Collection
Теория
- Что такое ERC20?
- ERC-20 – это технический стандарт; он используется во всех смарт-контрактах на блокчейне Ethereum для реализации токенов и предоставляет список правил, которым должны следовать все токены на базе Ethereum.
- Пожалуйста, ознакомьтесь со всеми функциями ERC20, прежде чем двигаться дальше
Построить
Смарт-контракт
Для создания смарт-контракта мы будем использовать Hardhat. Hardhat – это среда разработки Ethereum и фреймворк, предназначенный для разработки полного стека в Solidity. Проще говоря, вы можете написать свой смарт-контракт, развернуть его, запустить тесты и отладить код.
- Чтобы создать проект Hardhat, откройте терминал и выполните следующие команды
mkdir hardhat-tutorial
cd hardhat-tutorial
npm init --yes
npm install --save-dev hardhat
- В том же каталоге, где вы установили Hardhat, запустите программу:
npx hardhat
- Выберите
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
- В том же терминале установите
@openzeppelin/contracts
, поскольку мы будем импортировать контракт ERC20 Openzeppelin и контракт Ownable Openzeppelin в наш контрактCryptoDevToken
.
npm install @openzeppelin/contracts
-
Нам нужно будет вызвать
CryptoDevs Contract
, который вы развернули для предыдущего уровня, чтобы проверить владельцев CryptoDev NFT. Поскольку нам нужно вызвать только методыtokenOfOwnerByIndex
иbalanceOf
, мы можем создать интерфейс дляCryptoDevs contract
только с этими двумя функциями. Таким образом, мы сэкономимгаз
, поскольку нам не нужно будет наследовать и развертывать весьCryptoDevs Contract
, а только его часть. -
Создайте новый файл в каталоге
contracts
и назовите егоICryptoDevs.sol
и добавьте следующие строки
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
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.10;
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);
}
// 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
- Теперь создайте файл
.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"
-
Давайте развернем контракт в сети
rinkeby
. Создайте новый файл с именемdeploy.js
в папкеscripts
. -
Теперь мы напишем код для развертывания контракта в файле
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
);
// 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
требует константу. Давайте создадим папкуconstants
в папкеhardhat-tutorial
. -
Внутри папки
constants
создайте новый файл с именемindex.js
и добавьте в него следующие строки.- Замените “address-of-the-nft-contract” на адрес
CryptoDevs.sol
, который вы развернули в предыдущем модуле (NFT-Collection
)
// 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 };
- Замените “address-of-the-nft-contract” на адрес
-
Теперь откройте файл
hardhat.config.js
, здесь мы добавим сетьrinkeby
, чтобы мы могли развернуть наш контракт на rinkeby. Замените все строки в файлеhardhat.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.10",
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
- Сохраните в блокноте адрес контракта CryptoDevToken, который был напечатан в терминале, он понадобится вам далее в учебнике.
Сайт
- Для разработки веб-сайта мы будем использовать React и Next Js. React – это javascript-фреймворк, который используется для создания веб-сайтов, а Next Js построен поверх React.
- Во-первых, вам нужно создать новое приложение
next
. Структура папок должна выглядеть следующим образом
- ICO
- hardhat-tutorial
- my-app
- Чтобы создать это
my-app
, в терминале укажите на папку ICO и введите
npx create-next-app@latest
и нажмите enter
на все вопросы.
- Теперь, чтобы запустить приложение, выполните в терминале следующие команды
cd my-app
npm run dev
-
Теперь перейдите по адресу
http://localhost:3000
, ваше приложение должно быть запущено 🤘 -
Теперь установим библиотеку Web3Modal(https://github.com/Web3Modal/web3modal). Web3Modal – это простая в использовании библиотека, которая поможет разработчикам добавить поддержку нескольких провайдеров в свои приложения с помощью простой настраиваемой конфигурации. По умолчанию библиотека Web3Modal поддерживает инжектируемые провайдеры, такие как (Metamask, Dapper, Gnosis Safe, Frame, Web3 Browsers и т.д.), вы также можете легко настроить библиотеку на поддержку Portis, Fortmatic, Squarelink, Torus, Authereum, D’CENT Wallet и Arkane.
Откройте терминал, указывающий на каталогmy-app
и выполните эту команду
npm install web3modal
- В том же терминале установите
ethers.js
.
npm i ethers
-
В общую папку загрузите следующее изображение (https://github.com/LearnWeb3DAO/NFT-Collection/tree/main/my-app/public/cryptodevs/0.svg). Убедитесь, что имя загруженного изображения
0.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%;
}
.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;
}
}
- Откройте файл index.js в папке pages и вставьте следующий код, объяснение кода можно найти в комментариях.
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);
// 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);
}
};
/**
* 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;
};
/*
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: "rinkeby",
providerOptions: {},
disableInjectedProvider: false,
});
connectWallet();
getTotalTokensMinted();
getBalanceOfCryptoDevTokens();
getTokensToBeClaimed();
}
}, [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 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 и назовите ее
constants
. -
В папке constants создайте файл
index.js
и вставьте в него следующий код.- Замените
"abi-of-your-nft-contract"
на abi контракта NFT, который вы развернули в последнем уроке. - Замените
"address-of-your-nft-contract"
адресом контракта NFT, который вы развернули в предыдущем уроке. - Замените
"abi-of-your-token-contract"
на abi контракта токена. Чтобы получить abi контракта токена, перейдите вhardhat-tutorial/artifacts/contracts/CryptoDevToken.sol
и затем из файлаCryptoDevToken.json
получите массив, отмеченный под ключом"abi"
. - Замените
"address-of-your-token-contract"
на адрес контракта токена, который вы сохранили в блокноте в самом начале руководства.
- Замените
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";
- Теперь в терминале, который находится в папке
my-app
, выполните команду
npm run dev
Теперь ваш ICO dapp должен работать без ошибок 🚀.
Внесите в Github
Перед тем, как перейти к следующему шагу, обязательно разместите весь код на Github.
Развертывание вашего dApp
Теперь мы развернем ваш dApp, чтобы все могли видеть ваш сайт, а вы могли поделиться им со всеми своими друзьями по LearnWeb3 DAO.
- Перейдите на https://vercel.com/ и войдите в систему с помощью GitHub.
- Затем нажмите на кнопку
New Project
и выберите репозиторий вашего ICO dApp. - При настройке нового проекта Vercel позволит вам настроить
Root Directory
. - Нажмите
Edit
рядом сRoot Directory
и установите значениеmy-app
. -
Выберите
Framework Preset
какNext.js
-
Нажмите
Deploy
. -
Теперь вы можете увидеть ваш развернутый сайт, зайдя в панель управления, выбрав ваш проект и скопировав оттуда URL!
ПОЗДРАВЛЯЕМ! Все готово!
Надеюсь, вам понравилось это руководство. Не забудьте поделиться своим сайтом ICO в канале #showcase
на Discord 😀
Эта статья предоставлена LearnWeb3 DAO. Бесплатная, всеобъемлющая программа обучения по блокчейну от А до Я для разработчиков по всему миру.
Все от “Что такое блокчейн” до “Взлома смарт-контрактов” – и все, что между ними, а также многое другое!
Присоединяйтесь к нам сейчас, чтобы начать строить вместе с 25 000+ разработчиками.
Сайт
Discord
Twitter