Whitelist-Dapp
Вы запускаете свою коллекцию NFT под названием Crypto Devs
. Вы хотите предоставить своим ранним сторонникам доступ к белому списку для вашей коллекции, поэтому здесь вы создаете whitelist dapp для Crypto Devs
.
Требования
- Доступ к белому списку должен быть предоставлен первым
10
пользователям бесплатно, которые хотят попасть в него. - Должен быть сайт, где люди могут зайти и войти в белый список.
Приступим к созданию 🚀
Необходимые условия
- Вы умеете писать код на JavaScript (уровень новичка — 0)
- У вас есть настроенный кошелек Metamask (Уровень-4)
- На вашем компьютере установлен Node.js. Если нет, скачайте отсюда
Предпочитаете видео?
Если вы предпочитаете учиться на видео, у нас есть запись этого урока на нашем YouTube. Посмотрите видео, нажав на скриншот ниже, или переходите к чтению учебника!
Создайте
Умный контракт
Для создания смарт-контракта мы будем использовать Hardhat.
Hardhat — это среда разработки Ethereum и фреймворк, предназначенный для разработки полного стека в Solidity. Проще говоря, вы можете написать свой смарт-контракт, развернуть его, запустить тесты и отладить код.
- Для начала вам нужно создать папку Whitelist-Daap, куда впоследствии попадет проект Hardhat и ваше приложение Next.js.
- Откройте терминал и выполните следующие команды
mkdir Whitelist-Dapp
cd Whitelist-Dapp
- Затем, в папке Whitelist-Daap, вы установите проект 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
- Начните с создания нового файла в директории
contracts
под названиемWhitelist.sol
.
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
contract Whitelist {
// Max number of whitelisted addresses allowed
uint8 public maxWhitelistedAddresses;
// Create a mapping of whitelistedAddresses
// if an address is whitelisted, we would set it to true, it is false by default for all other addresses.
mapping(address => bool) public whitelistedAddresses;
// numAddressesWhitelisted would be used to keep track of how many addresses have been whitelisted
// NOTE: Don't change this variable name, as it will be part of verification
uint8 public numAddressesWhitelisted;
// Setting the Max number of whitelisted addresses
// User will put the value at the time of deployment
constructor(uint8 _maxWhitelistedAddresses) {
maxWhitelistedAddresses = _maxWhitelistedAddresses;
}
/**
addAddressToWhitelist - This function adds the address of the sender to the
whitelist
*/
function addAddressToWhitelist() public {
// check if the user has already been whitelisted
require(!whitelistedAddresses[msg.sender], "Sender has already been whitelisted");
// check if the numAddressesWhitelisted < maxWhitelistedAddresses, if not then throw an error.
require(numAddressesWhitelisted < maxWhitelistedAddresses, "More addresses cant be added, limit reached");
// Add the address which called the function to the whitelistedAddress array
whitelistedAddresses[msg.sender] = true;
// Increase the number of whitelisted addresses
numAddressesWhitelisted += 1;
}
}
-
Создайте новый файл с именем
deploy.js
в папкеscripts
. -
Теперь мы напишем код для развертывания контракта в файле
deploy.js
.
const { ethers } = require("hardhat");
async function main() {
/*
A ContractFactory in ethers.js is an abstraction used to deploy new smart contracts,
so whitelistContract here is a factory for instances of our Whitelist contract.
*/
const whitelistContract = await ethers.getContractFactory("Whitelist");
// here we deploy the contract
const deployedWhitelistContract = await whitelistContract.deploy(10);
// 10 is the Maximum number of whitelisted addresses allowed
// Wait for it to finish deploying
await deployedWhitelistContract.deployed();
// print the address of the deployed contract
console.log(
"Whitelist Contract Address:",
deployedWhitelistContract.address
);
}
// Call the main function and catch if there is any error
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
- Теперь создайте файл
.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"
- Теперь мы установим пакет
dotenv
, чтобы иметь возможность импортировать env-файл и использовать его в нашем конфиге. Откройте терминал в директорииhardhat-tutorial
и выполните следующую команду
npm install dotenv
- Теперь откройте файл hardhat.config.js, здесь мы добавим сеть
rinkeby
, чтобы мы могли развернуть наш контракт на rinkeby. Замените все строки в файлеhardhar.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.4",
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
- Сохраните в блокноте адрес контракта Whitelist, который был напечатан на вашем терминале, он понадобится вам далее в учебнике.
Сайт
- Для разработки веб-сайта мы будем использовать React и Next Js. React — это javascript-фреймворк, используемый для создания веб-сайтов, а Next.js — это фреймворк React, который также позволяет писать код бэкенд API вместе с фронтендом, поэтому вам не нужно два отдельных сервиса для фронтенда и бэкенда.
- Во-первых, вам нужно будет создать новое приложение
next
. Структура папок должна выглядеть следующим образом
- Whitelist-Dapp
- hardhat-tutorial
- my-app
- Чтобы создать это
next-приложение
, в терминале перейдите в папку Whitelist-Dapp и введите
npx create-next-app@latest
и нажмите enter
на все вопросы.
- Теперь, чтобы запустить приложение, выполните в терминале следующие команды
cd my-app
npm run dev
-
Теперь перейдите по адресу
http://localhost:3000
, ваше приложение должно быть запущено 🤘. -
Теперь установим библиотеку Web3Modal. Web3Modal — это простая в использовании библиотека, которая поможет разработчикам легко позволить своим пользователям подключаться к вашим dApps с помощью всевозможных кошельков. По умолчанию библиотека Web3Modal поддерживает инжектируемые провайдеры, такие как (Metamask, Dapper, Gnosis Safe, Frame, Web3 Browsers, etc) и WalletConnect, вы также можете легко настроить библиотеку на поддержку Portis, Fortmatic, Squarelink, Torus, Authereum, D’CENT Wallet и Arkane.
Откройте терминал, указывающий на каталогmy-app
и выполните эту команду
npm install web3modal
- В том же терминале установите
ethers.js
.
npm install ethers
- В папку my-app/public загрузите это изображение и переименуйте его в
crypto-devs.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%;
}
.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;
}
}
- Откройте файл index.js в папке pages и вставьте следующий код, объяснение кода можно найти в комментариях. Обязательно прочитайте о React и React Hooks, React Hooks Tutorial, если вы не знакомы с ними.
import Head from "next/head";
import styles from "../styles/Home.module.css";
import Web3Modal from "web3modal";
import { providers, Contract } from "ethers";
import { useEffect, useRef, useState } from "react";
import { WHITELIST_CONTRACT_ADDRESS, abi } from "../constants";
export default function Home() {
// walletConnected keep track of whether the user's wallet is connected or not
const [walletConnected, setWalletConnected] = useState(false);
// joinedWhitelist keeps track of whether the current metamask address has joined the Whitelist or not
const [joinedWhitelist, setJoinedWhitelist] = useState(false);
// loading is set to true when we are waiting for a transaction to get mined
const [loading, setLoading] = useState(false);
// numberOfWhitelisted tracks the number of addresses's whitelisted
const [numberOfWhitelisted, setNumberOfWhitelisted] = useState(0);
// Create a reference to the Web3 Modal (used for connecting to Metamask) which persists as long as the page is open
const web3ModalRef = useRef();
/**
* Returns a Provider or Signer object representing the Ethereum RPC with or without the
* signing capabilities of metamask attached
*
* A `Provider` is needed to interact with the blockchain - reading transactions, reading balances, reading state, etc.
*
* A `Signer` is a special type of Provider used in case a `write` transaction needs to be made to the blockchain, which involves the connected account
* needing to make a digital signature to authorize the transaction being sent. Metamask exposes a Signer API to allow your website to
* request signatures from the user using Signer functions.
*
* @param {*} needSigner - True if you need the signer, default false otherwise
*/
const getProviderOrSigner = async (needSigner = false) => {
// Connect to Metamask
// Since we store `web3Modal` as a reference, we need to access the `current` value to get access to the underlying object
const provider = await web3ModalRef.current.connect();
const web3Provider = new providers.Web3Provider(provider);
// If user is not connected to the Rinkeby network, let them know and throw an error
const { chainId } = await web3Provider.getNetwork();
if (chainId !== 4) {
window.alert("Change the network to Rinkeby");
throw new Error("Change network to Rinkeby");
}
if (needSigner) {
const signer = web3Provider.getSigner();
return signer;
}
return web3Provider;
};
/**
* addAddressToWhitelist: Adds the current connected address to the whitelist
*/
const addAddressToWhitelist = async () => {
try {
// We need a Signer here since this is a 'write' transaction.
const signer = await getProviderOrSigner(true);
// Create a new instance of the Contract with a Signer, which allows
// update methods
const whitelistContract = new Contract(
WHITELIST_CONTRACT_ADDRESS,
abi,
signer
);
// call the addAddressToWhitelist from the contract
const tx = await whitelistContract.addAddressToWhitelist();
setLoading(true);
// wait for the transaction to get mined
await tx.wait();
setLoading(false);
// get the updated number of addresses in the whitelist
await getNumberOfWhitelisted();
setJoinedWhitelist(true);
} catch (err) {
console.error(err);
}
};
/**
* getNumberOfWhitelisted: gets the number of whitelisted addresses
*/
const getNumberOfWhitelisted = async () => {
try {
// Get the provider from web3Modal, which in our case is MetaMask
// No need for the Signer here, as we are only reading state from the blockchain
const provider = await getProviderOrSigner();
// We connect to the Contract using a Provider, so we will only
// have read-only access to the Contract
const whitelistContract = new Contract(
WHITELIST_CONTRACT_ADDRESS,
abi,
provider
);
// call the numAddressesWhitelisted from the contract
const _numberOfWhitelisted = await whitelistContract.numAddressesWhitelisted();
setNumberOfWhitelisted(_numberOfWhitelisted);
} catch (err) {
console.error(err);
}
};
/**
* checkIfAddressInWhitelist: Checks if the address is in whitelist
*/
const checkIfAddressInWhitelist = async () => {
try {
// We will need the signer later to get the user's address
// Even though it is a read transaction, since Signers are just special kinds of Providers,
// We can use it in it's place
const signer = await getProviderOrSigner(true);
const whitelistContract = new Contract(
WHITELIST_CONTRACT_ADDRESS,
abi,
signer
);
// Get the address associated to the signer which is connected to MetaMask
const address = await signer.getAddress();
// call the whitelistedAddresses from the contract
const _joinedWhitelist = await whitelistContract.whitelistedAddresses(
address
);
setJoinedWhitelist(_joinedWhitelist);
} catch (err) {
console.error(err);
}
};
/*
connectWallet: Connects the MetaMask wallet
*/
const connectWallet = async () => {
try {
// Get the provider from web3Modal, which in our case is MetaMask
// When used for the first time, it prompts the user to connect their wallet
await getProviderOrSigner();
setWalletConnected(true);
checkIfAddressInWhitelist();
getNumberOfWhitelisted();
} catch (err) {
console.error(err);
}
};
/*
renderButton: Returns a button based on the state of the dapp
*/
const renderButton = () => {
if (walletConnected) {
if (joinedWhitelist) {
return (
<div className={styles.description}>
Thanks for joining the Whitelist!
</div>
);
} else if (loading) {
return <button className={styles.button}>Loading...</button>;
} else {
return (
<button onClick={addAddressToWhitelist} className={styles.button}>
Join the Whitelist
</button>
);
}
} else {
return (
<button onClick={connectWallet} className={styles.button}>
Connect your wallet
</button>
);
}
};
// useEffects are used to react to changes in state of the website
// The array at the end of function call represents what state changes will trigger this effect
// In this case, whenever the value of `walletConnected` changes - this effect will be called
useEffect(() => {
// if wallet is not connected, create a new instance of Web3Modal and connect the MetaMask wallet
if (!walletConnected) {
// Assign the Web3Modal class to the reference object by setting it's `current` value
// The `current` value is persisted throughout as long as this page is open
web3ModalRef.current = new Web3Modal({
network: "rinkeby",
providerOptions: {},
disableInjectedProvider: false,
});
connectWallet();
}
}, [walletConnected]);
return (
<div>
<Head>
<title>Whitelist Dapp</title>
<meta name="description" content="Whitelist-Dapp" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div className={styles.main}>
<div>
<h1 className={styles.title}>Welcome to Crypto Devs!</h1>
<div className={styles.description}>
Its an NFT collection for developers in Crypto.
</div>
<div className={styles.description}>
{numberOfWhitelisted} have already joined the Whitelist
</div>
{renderButton()}
</div>
<div>
<img className={styles.image} src="./crypto-devs.svg" />
</div>
</div>
<footer className={styles.footer}>
Made with ❤ by Crypto Devs
</footer>
</div>
);
}
- Теперь создайте новую папку в папке my-app и назовите ее
constants
. - В папке constants создайте файл
index.js
и вставьте в него следующий код. - Замените
"YOUR_WHITELIST_CONTRACT_ADDRESS"
на адрес контракта белого списка, который вы развернули. - Замените
"YOUR_ABI"
на ABI вашего контракта белого списка. Чтобы получить ABI для вашего контракта, перейдите в папкуhardhat-tutorial/artifacts/contracts/Whitelist.sol
и из файлаWhitelist.json
получите массив, отмеченный под ключом"abi"
(это будет огромный массив, около 100 строк, если не больше).
export const abi = YOUR_ABI;
export const WHITELIST_CONTRACT_ADDRESS = "YOUR_WHITELIST_CONTRACT_ADDRESS";
- Теперь в терминале, который указывает на папку
my-app
, выполните команду
npm run dev
Теперь ваше приложение из белого списка должно работать без ошибок 🚀.
Отправка на github
Перед тем, как продолжить, убедитесь, что вы выложили весь ваш код на github 🙂
Развертывание вашего dApp
Теперь мы развернем ваш dApp, чтобы все могли видеть ваш сайт, а вы могли поделиться им со всеми своими друзьями LearnWeb3 DAO.
- Зайдите в Vercel и авторизуйтесь на GitHub.
- Затем нажмите на кнопку
New Project
и выберите ваше репо dApp из белого списка. - При настройке нового проекта Vercel позволит вам настроить
Root Directory
. - Нажмите
Edit
рядом сRoot Directory
и установите значениеmy-app
. - Выберите фреймворк как
Next.js
. - Нажмите
Deploy
. - Теперь вы можете увидеть ваш развернутый сайт, зайдя в панель управления, выбрав ваш проект и скопировав оттуда URL!
Поделитесь своим сайтом в Discord 😀
Эта статья предоставлена LearnWeb3 DAO. Бесплатная, всеобъемлющая программа обучения блокчейну от А до Я для разработчиков по всему миру.
Все от «Что такое блокчейн» до «Взлома смарт-контрактов» — и все, что между ними, а также многое другое!
Присоединяйтесь к нам сейчас, чтобы начать строить вместе с 25 000+ разработчиками.
Сайт
Discord
Twitter