白名单-Dapp

原文地址: 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
  • 在安装 Hardhat 的同一目录中运行:
1
npx 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;
}

}
  • 让我们将合约部署到rinkeby网络。创建一个新文件,或者替换文件夹scripts下命名的默认文件deploy.js

  • 现在我们将编写一些代码来在deploy.js文件中部署合约。

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目录的终端并执行此命令
1
npm install dotenv
  • 现在打开 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 目录的终端并执行这个命令
1
npx hardhat compile
  • 要部署,请打开指向hardhat-tutorial目录的终端并执行此命令
1
npx hardhat run scripts/deploy.js --network rinkeby

将打印在终端上的白名单合约地址保存在记事本中,您将在教程中进一步使用它。

网站

  • 为了开发网站,我们将使用 React 和 Next Js。React 是一个用于制作网站的 javascript 框架,Next.js 是一个 React 框架,它还允许与前端一起编写后端 API 代码,因此您不需要两个单独的前端和后端服务。

  • 首先,您需要创建一个新next应用程序。

  • 要创建它next-app,在终端指向 Whitelist-Dapp 文件夹并输入

1
npx create-next-app@latest

并按下enter所有问题

  • 您的文件夹结构应该类似于
1
2
3
- Whitelist-Dapp
- hardhat-tutorial
- my-app
  • 现在运行应用程序,在终端中执行这些命令
1
2
cd my-app
npm run dev
  • 现在转到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
npm install web3modal
  • 在同一个终端也安装ethers.js
1
npm install ethers
  • 在您的 my-app/public 文件夹中,下载此图像并将其重命名为crypto-devs.svg

  • 现在转到样式文件夹并用Home.modules.css以下代码替换文件的所有内容,这将为您的 dapp 添加一些样式:

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;
}
}
  • 打开 pages 文件夹下的 index.js 文件并粘贴以下代码,代码解释可以在评论中找到。如果您不熟悉 React 和React HooksReact Hooks Tutorial,请务必阅读它们。
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 &#10084; by Crypto Devs
</footer>
</div>
);
}
  • 现在在 my-app 文件夹下创建一个新文件夹并将其命名为constants

  • 在常量文件夹中创建一个文件index.js,然后粘贴以下代码。

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文件夹的终端中,执行

1
npm run dev

您的白名单 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