原文地址: Whitelist-Dapp
翻译: JulySong
您正在启动名为Crypto Devs
. 你想让你的早期支持者访问你收藏的白名单,所以在这里你要创建一个白名单 dappCrypto Devs
要求
名单访问权应该10
免费提供给想要进入的第一批用户。
应该有一个网站,人们可以进入白名单。
让我们开始建造 🚀
先决条件
您可以使用 JavaScript 编写代码 (Beginner Track - Level-0 )
已设置 Metamask 钱包 (Beginner Track - Level-4 )
您的计算机已安装 Node.js。如果不是从 here
喜欢视频? 如果您想从视频中学习,我们的 YouTube 上有本教程的录音。单击下面的屏幕截图观看视频,或继续阅读教程!
视频 1
视频 2
建造 智能合约 为了构建智能合约,我们将使用 Hardhat . Hardhat 是一个以太坊开发环境和框架,专为 Solidity 中的全栈开发而设计。简单来说,您可以编写智能合约、部署它们、运行测试和调试代码。
首先,您需要创建一个 Whitelist-Daap 文件夹,Hardhat 项目和您的 Next.js 应用程序稍后将进入该文件夹
打开终端并执行这些命令
1 2 mkdir Whitelist-Dapp cd Whitelist-Dapp
然后,在 Whitelist-Daap 文件夹中,您将设置 Hardhat 项目
1 2 3 4 mkdir hardhat-tutorial cd hardhat-tutorial npm init --yes npm install --save-dev hardhat
- Select `Create a Javascript project`
- Press enter for the already specified `Hardhat Project root`
- Press enter for the question on if you want to add a `.gitignore`
- Press enter for `Do you want to install this sample project's dependencies with npm (@nomicfoundation/hardhat-toolbox)?`
现在你有一个安全帽项目准备好了!
如果您不在 Mac 上,请执行此额外步骤并安装这些库:)
1 npm install --save-dev @nomicfoundation/hardhat-toolbox
首先在contracts
文件夹下创建一个名为Whitelist.sol
的文件.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 //SPDX-License-Identifier: Unlicense pragma solidity ^0.8.0; contract Whitelist { // 允许的最大白名单地址数 uint8 public maxWhitelistedAddresses; // 创建 whitelistedAddresses 的映射 // 如果地址被列入白名单,我们会将其设置为 true,对于所有其他地址默认为 false。 mapping(address => bool) public whitelistedAddresses; // numAddressesWhitelisted 将用于跟踪有多少地址被列入白名单 // 注意:不要更改此变量名,因为它将是验证的一部分 uint8 public numAddressesWhitelisted; // 设置白名单地址的最大数量 // 用户将在部署时放置该值 constructor(uint8 _maxWhitelistedAddresses) { maxWhitelistedAddresses = _maxWhitelistedAddresses; } /** addAddressToWhitelist - 此函数将发送者的地址添加到白名单 */ function addAddressToWhitelist() public { // 检查用户是否已被列入白名单 require(!whitelistedAddresses[msg.sender], "Sender has already been whitelisted"); // 检查是否 numAddressesWhitelisted < maxWhitelistedAddresses,如果不是则抛出错误。 require(numAddressesWhitelisted < maxWhitelistedAddresses, "More addresses cant be added, limit reached"); // 将调用函数的地址添加到 whitelistedAddress 数组 whitelistedAddresses[msg.sender] = true; // 增加白名单地址数量 numAddressesWhitelisted += 1; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 const { ethers } = require("hardhat"); async function main() { /* ethers.js 中的 ContractFactory 是用于部署新智能合约的抽象, 所以 whitelistContract 这里是我们白名单合约实例的工厂。 */ const whitelistContract = await ethers.getContractFactory("Whitelist"); // 这里我们部署合约 const deployedWhitelistContract = await whitelistContract.deploy(10); // 10 是允许的最大白名单地址数 // 等待它完成部署 await deployedWhitelistContract.deployed(); // 打印部署合约的地址 console.log( "Whitelist Contract Address:", deployedWhitelistContract.address ); } // 调用main函数,如果有错误就catch main() .then(() => process.exit(0)) .catch((error) => { console.error(error); process.exit(1); });
现在在hardhat-tutorial
文件夹中创建一个.env
文件并添加以下行,使用注释中的说明获取您的 Alchemy API key URL 和 RINKEBY Private Key。确保您获得 rinkeby 私钥的帐户由 Rinkeby 以太币提供资金。
1 2 3 4 5 6 7 8 9 // 转到 https://www.alchemyapi.io, 注册, //在其仪表板中创建一个新应用程序并选择网络为 Rinkeby,并将“add-the-alchemy-key-url-here”替换为其密钥 url its key url ALCHEMY_API_KEY_URL="add-the-alchemy-key-url-here" // 将此私钥替换为您的 RINKEBY 帐户私钥 // 要从 Metamask 导出您的私钥,请打开 Metamask 并 // 转到 Account Details > Export Private Key // 请注意永远不要将真实的 Ether 放入测试帐户 RINKEBY_PRIVATE_KEY="add-the-rinkeby-private-key-here"
现在我们将安装dotenv
包以便能够导入 env 文件并在我们的配置中使用它。打开指向hardhat-tutorial
目录的终端并执行此命令
现在打开 hardhat.config.js 文件,我们将在此处添加rinkeby
网络,以便我们可以将合约部署到 rinkeby。用下面给出的行替换hardhar.config.js
文件中的所有行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 require("@nomicfoundation/hardhat-toolbox"); 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.9", networks: { rinkeby: { url: ALCHEMY_API_KEY_URL, accounts: [RINKEBY_PRIVATE_KEY], }, }, };
编译合约,打开一个指向 hardhat-tutorial 目录的终端并执行这个命令
要部署,请打开指向hardhat-tutorial
目录的终端并执行此命令
1 npx hardhat run scripts/deploy.js --network rinkeby
将打印在终端上的白名单合约地址保存在记事本中,您将在教程中进一步使用它。
网站
1 npx create-next-app@latest
并按下enter
所有问题
1 2 3 - Whitelist-Dapp - hardhat-tutorial - my-app
现在转到http://localhost:3000
,您的应用程序应该正在运行 🤘
现在让我们安装Web3Modal library 。Web3Modal 是一个易于使用的库,可帮助开发人员轻松地让他们的用户使用各种不同的钱包连接到您的 dApp。默认情况下,Web3Modal 库支持注入的提供程序,如(Metamask、Dapper、Gnosis Safe、Frame、Web3 浏览器等)和 WalletConnect,您还可以轻松配置库以支持 Portis、Fortmatic、Squarelink、Torus、Authereum、D’CENT 钱包和阿卡内。(这是Codesandbox.io 上的一个活生生的例子)
打开指向my-app
目录的终端并执行此命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 .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; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 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 跟踪用户的钱包是否连接 const [walletConnected, setWalletConnected] = useState(false); // joinedWhitelist 跟踪当前元掩码地址是否加入白名单 const [joinedWhitelist, setJoinedWhitelist] = useState(false); // 当我们等待交易被挖掘时,loading 设置为 true const [loading, setLoading] = useState(false); // numberOfWhitelisted 跟踪地址的白名单数量 const [numberOfWhitelisted, setNumberOfWhitelisted] = useState(0); // 创建对 Web3 Modal 的引用(用于连接到 Metamask),只要页面打开,它就会一直存在 const web3ModalRef = useRef(); /** * 返回代表以太坊 RPC 的 Provider 或 Signer 对象,带有或不带有 * 附加元掩码的签名功能 * * 需要一个 `Provider` 来与区块链交互 - 读取交易、读取余额、读取状态等 * * `Signer` 是一种特殊类型的 Provider,用于在需要对区块链进行`write` 交易的情况下,这涉及到连接的帐户 * 需要进行数字签名以授权正在发送的交易。Metamask 公开了一个 Signer API 以允许您的网站 * 使用 Signer 函数向用户请求签名。 * * @param {*} needSigner - 如果需要签名者则为真,否则默认为假 */ const getProviderOrSigner = async (needSigner = false) => { // 连接到 Metamask // 因为我们存储 `web3Modal` 作为参考,我们需要访问 `current` 值来访问底层对象 const provider = await web3ModalRef.current.connect(); const web3Provider = new providers.Web3Provider(provider); // 如果用户没有连接到 Rinkeby 网络,让他们知道并抛出错误 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:将当前连接的地址添加到白名单 */ const addAddressToWhitelist = async () => { try { // 我们在这里需要一个签名者,因为这是一个“写入”事务。 const signer = await getProviderOrSigner(true); // 使用 Signer 创建一个新的 Contract 实例,它允许 // 更新方法 const whitelistContract = new Contract( WHITELIST_CONTRACT_ADDRESS, abi, signer ); // 从合约中调用 addAddressToWhitelist const tx = await whitelistContract.addAddressToWhitelist(); setLoading(true); // 等待交易被挖掘 await tx.wait(); setLoading(false); // 获取更新后的白名单地址数 await getNumberOfWhitelisted(); setJoinedWhitelist(true); } catch (err) { console.error(err); } }; /** * getNumberOfWhitelisted: 获取白名单地址的数量 */ const getNumberOfWhitelisted = async () => { try { // 从 web3Modal 获取提供者,在我们的例子中是 MetaMask // 这里不需要签名者,因为我们仅从区块链中读取状态 const provider = await getProviderOrSigner(); // 我们使用提供者连接到合约,因此我们将只有 // 对合约拥有只读权限 const whitelistContract = new Contract( WHITELIST_CONTRACT_ADDRESS, abi, provider ); // 从合约中调用 numAddressesWhitelisted const _numberOfWhitelisted = await whitelistContract.numAddressesWhitelisted(); setNumberOfWhitelisted(_numberOfWhitelisted); } catch (err) { console.error(err); } }; /** * checkIfAddressInWhitelist: 检查地址是否在白名单中 */ const checkIfAddressInWhitelist = async () => { try { // 我们稍后需要签名者来获取用户的地址 // 即使它是一个读交易,因为签名者只是特殊类型的提供者, // 我们可以在它的位置使用它 const signer = await getProviderOrSigner(true); const whitelistContract = new Contract( WHITELIST_CONTRACT_ADDRESS, abi, signer ); // 获取与 MetaMask 连接的签名者关联的地址 const address = await signer.getAddress(); // 从合约中调用 whitelistedAddresses const _joinedWhitelist = await whitelistContract.whitelistedAddresses( address ); setJoinedWhitelist(_joinedWhitelist); } catch (err) { console.error(err); } }; /* connectWallet: 连接 MetaMask 钱包 */ const connectWallet = async () => { try { // 从 web3Modal 获取提供者,在我们的例子中是 MetaMask // 第一次使用时提示用户连接他们的钱包 await getProviderOrSigner(); setWalletConnected(true); checkIfAddressInWhitelist(); getNumberOfWhitelisted(); } catch (err) { console.error(err); } }; /* renderButton: 根据 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 用于对网站状态的变化做出反应 // 函数调用末尾的数组表示什么状态变化会触发这个效果 // 在这种情况下,只要 `walletConnected` 的值发生变化 - 这个效果就会被称为 useEffect(() => { // 如果钱包没有连接,则创建一个新的 Web3Modal 实例并连接 MetaMask 钱包 if (!walletConnected) { // 通过将 Web3Modal 类设置为 `current` 将其分配给引用对象value // 只要此页面打开,`current` 值就会一直保持 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> ); }
1 2 export const WHITELIST_CONTRACT_ADDRESS = "YOUR_WHITELIST_CONTRACT_ADDRESS"; export const abi = YOUR_ABI;
替换"YOUR_WHITELIST_CONTRACT_ADDRESS"
为您部署的白名单合约的地址。
替换"YOUR_ABI"
为您的白名单合约的 ABI。要为您的合同获取 ABI,请转到您的hardhat-tutorial/artifacts/contracts/Whitelist.sol
文件夹并从您的Whitelist.json
文件中获取标记在"abi"
密钥下的数组(这将是一个巨大的数组,如果不是更多的话,接近 100 行)。
现在在指向my-app
文件夹的终端中,执行
您的白名单 dapp 现在应该可以正常运行了 🚀
推送到 github 确保在继续之前您已将 所有代码推送到 github :)
部署你的 dApp 我们现在将部署您的 dApp,以便每个人都可以看到您的网站,并且您可以与所有 LearnWeb3 DAO 朋友分享它。
转到Vercel 并使用您的 GitHub 登录
然后单击New Project
按钮,然后选择您的白名单 dApp 存储库
在配置您的新项目时,Vercel 将允许您自定义您的Root Directory
单击Edit
旁边Root Directory
并将其设置为my-app
选择框架为Next.js
点击Deploy
现在,您可以通过转到仪表板、选择您的项目并复制 URL 来查看您部署的网站 - 从那里!
在 Discord 中分享您的网站 :D