本文详细介绍了如何使用OpenZeppelin和Foundry进行NFT的空投,包括设置项目、创建智能合约和进行测试与部署。文中也讨论了空投的概念、优点、挑战和相关的法律问题,为Web3项目提供了实用指导。
空投代币和NFT已成为web3项目吸引流动性和增加社区参与的首选方法。本指南将向你展示如何向大量用户空投NFT。我们将使用OpenZeppelin的代币实现来构建智能合约,并使用Foundry作为我们的智能合约开发工具包。
让我们开始吧!
依赖项 | 版本 |
---|---|
node.js | 20.14.0 |
foundryup | 0.2.0 |
forge-std | 1.8.2 |
openzeppelin-solidity | 5.0.2 |
空投涉及将数字资产(如代币或NFT)分发到钱包地址,通常是那些参与特定活动的人,例如与DeFi平台互动或参与活动。空投在web3项目中变得越来越流行,作为提高可见性和吸引流动性的一种方式。空投不仅作为社区成员的奖励,也作为吸引新用户和保持参与的战略工具。
例如,2020年9月1日进行的Uniswap空投奖励了在设定日期之前与Uniswap协议互动的用户,赠送UNI代币。接收者必须主动领取他们的代币,这是一种在web3项目中常见的方法,将Gas费用的负担从项目转移到用户。这种“领取”方法有助于管理项目的财务影响,同时大规模分发奖励。
要与区块链进行通信,你需要访问一个节点。你可以使用公共节点或部署和管理自己的基础设施;但是,如果你希望获得8倍更快的响应时间,可以将繁重的工作交给我们。请在这里注册一个免费账户。
登录QuickNode后,点击创建端点按钮,然后选择Ethereum链和Sepolia网络。
创建端点后,复制HTTP Provider URL链接并将其保存,因为在开发空投代码时你将需要它。
为了将NFT合约部署到Sepolia测试网,你需要Sepolia测试网的ETH来支付Gas费用。Multi-Chain QuickNode Faucet使你轻松获得测试ETH!
导航到多链QuickNode水龙头,连接你的钱包(例如,MetaMask,Coinbase Wallet)或粘贴你的钱包地址以检索测试ETH。请注意,在以太坊主网的EVM水龙头上,要使用其必须满足0.001 ETH的主网余额要求。你还可以通过推特或使用QuickNode账户登录来获得额外奖励!
在我们开始编码之前,请确保你已安装Node.js(v20+)和Foundry。接下来,让我们运行以下命令设置项目目录:
forge init airdrop
接下来,进入项目目录,创建所需文件并安装依赖项:
cd airdrop
touch src/NFT.sol
touch test/NFT.t.sol
touch script/Claim.s.sol
touch remappings.txt
forge install OpenZeppelin/openzeppelin-contracts --no-commit
我们需要设置重映射,以便我们的智能合约(.sol
文件)能够正确访问OpenZeppelin库。将以下配置添加到remappings.txt
文件中。
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
在该项目中配置RPC端点的一种方法是将其包含在foundry.toml
文件中,但如果你计划将此代码开源,我们建议将其排除,并仅将其包含在命令行指令中。让我们创建环境变量以包括我们的RPC端点和私钥(我们将使用来部署智能合约):
export RPC_URL=<你的RPC端点>
export PRIVATE_KEY=<你的钱包私钥>
设置完项目目录并安装依赖项后,让我们继续创建智能合约。
NFT合约将包含用户可以与之互动以领取其NFT的空投(例如,领取逻辑)。NFT智能合约具有以下功能:
owner
地址、地址数组initialWhitelist
和用于元数据的baseTokenURI
。claimNFT
函数来铸造他们的NFT。不在白名单上的用户尝试调用此函数将收到错误。addToWhitelist
和removeFromWhitelist
函数添加/移除更多地址到白名单。pause
函数来暂停领取。一种unpause
函数也包含在内,以恢复领取。whitelist
和hasClaimed
存储已列入白名单和已领取地址的列表。ERC721URIStorage
用于元数据使用和ERC721Burnable
用于可销毁性。打开src/NFT.sol
文件并添加以下代码:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract NFT is ERC721, ERC721URIStorage, ERC721Burnable, Pausable, Ownable {
uint256 private _nextTokenId;
mapping(address => bool) public whitelist;
mapping(address => bool) public hasClaimed;
string private _baseTokenURI;
constructor(address initialOwner, address[] memory initialWhitelist, string memory baseTokenURI)
ERC721("NFT", "NFT")
Ownable(initialOwner)
{
transferOwnership(initialOwner);
addToWhitelist(initialWhitelist);
_baseTokenURI = baseTokenURI;
}
function claimNFT() public whenNotPaused {
address to = msg.sender;
require(whitelist[to], "Caller is not whitelisted");
require(!hasClaimed[to], "Caller has already claimed an NFT");
uint256 tokenId = _nextTokenId++;
_safeMint(to, tokenId);
hasClaimed[to] = true;
}
function addToWhitelist(address[] memory addresses) public onlyOwner {
for (uint i = 0; i < addresses.length; i++) {
if (!whitelist[addresses[i]]) {
whitelist[addresses[i]] = true;
}
}
}
function removeFromWhitelist(address[] memory addresses) public onlyOwner {
for (uint i = 0; i < addresses.length; i++) {
if (whitelist[addresses[i]]) {
whitelist[addresses[i]] = false;
}
}
}
function pause() public onlyOwner {
_pause();
}
function unpause() public onlyOwner {
_unpause();
}
// 以下函数是Solidity的重写要求。
function tokenURI(uint256 tokenId)
public
view
override(ERC721, ERC721URIStorage)
whenNotPaused
returns (string memory)
{
return super.tokenURI(tokenId);
}
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721, ERC721URIStorage)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}
在将合约部署到测试网之前,让我们进行测试,以确保智能合约按预期工作。
将以下测试代码添加到test/NFT.t.sol
文件中:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/NFT.sol";
contract NFTTest is Test {
NFT nft;
address owner;
address[] initialWhitelist;
function setUp() public {
owner = address(this);
initialWhitelist = [vm.addr(1), vm.addr(2), vm.addr(3), vm.addr(4)];
nft = new NFT(owner, initialWhitelist, "https://api.example.com/");
nft.transferOwnership(owner); // 确保拥有者设置正确
}
function testClaimNFT() public {
vm.startPrank(vm.addr(1)); // 模拟白名单用户
nft.claimNFT();
assertTrue(nft.balanceOf(vm.addr(1)) == 1, "应铸造1个NFT");
vm.stopPrank();
}
function testFailClaimNFTNotWhitelisted() public {
vm.startPrank(vm.addr(5)); // 非白名单用户
nft.claimNFT(); // 这应该失败
vm.stopPrank();
}
function testFailClaimNFTTwice() public {
vm.startPrank(vm.addr(1));
nft.claimNFT();
nft.claimNFT(); // 尝试第二次领取应该失败
vm.stopPrank();
}
function testPauseUnpause() public {
nft.pause();
assertTrue(nft.paused(), "合约应已暂停");
nft.unpause();
assertFalse(nft.paused(), "合约应已恢复");
}
}
然后使用forge test命令运行测试:
forge test
输出将显示如下内容:
Ran 4 tests for test/NFT.t.sol:NFTTest
[PASS] testClaimNFT() (gas: 107481)
[PASS] testFailClaimNFTNotWhitelisted() (gas: 13262)
[PASS] testFailClaimNFTTwice() (gas: 106417)
[PASS] testPauseUnpause() (gas: 17536)
Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 1.96ms (1.02ms CPU time)
Ran 1 test suite in 176.35ms (1.96ms CPU time): 4 tests passed, 0 failed, 0 skipped (4 total tests)
现在是将智能合约部署到测试网的时候了。运行下面的forge create命令来部署智能合约。
环境变量RPC_URL
和PRIVATE_KEY
在项目设置部分之前已经设置。然而,在执行命令之前,你必须包含下面的构造函数参数。
intialOwner
: 可执行添加/移除白名单地址、暂停/恢复合约等管理功能的NFT合约拥有者array of whitelist addresses
: 一个EVM兼容地址的数组。你在合约中使用的数组长度没有特定限制。然而,根据Gas成本和你所部署区块链的块Gas限制,实际上是存在限制的。metadataURI
: 指向你的NFT项目元数据的URL。你可以使用像IPFS这样的提供商在QuickNode上以去中心化的方式托管元数据。重要:确保你在白名单数组中包含你自己的地址,以便在下一部分测试领取功能。
forge create --rpc-url=$RPC_URL --private-key=$PRIVATE_KEY src/NFT.sol:NFT --constructor-args "intialOwnerAddress" "[0xAddr1, 0xAddr2, 0xAddr3]" "http://examplemetadatauri.com"
你应该看到以下输出:
No files changed, compilation skipped
Deployer: 0xe0f2b924422909626D539b0FBd96239B31767400
Deployed to: 0xf4Bc97338Ddc886c5DA20f0fF91cF1c7Ea4e2760
Transaction hash: 0x664741640fb13b3ffdc659251fc8ba43a5d141d7b5c4d789820bf41485218c8c
现在,你的NFT合约已部署在测试网上,我们可以与之交互。
提示
如果你想在像Etherscan这样的区块浏览器上验证(开源)你的智能合约,请查看本指南:验证你的智能合约代码的不同方法
现在,为了测试在Sepolia测试网上领取空投,请在script/Claim.s.sol
文件中输入以下代码:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Script.sol";
import "forge-std/console.sol";
import "../src/NFT.sol";
contract ClaimScript is Script {
NFT public nftContract;
function setUp() public {
// 使用已部署合约地址初始化NFT合约接口
nftContract = NFT("INSERT_DEPLOYED_CONTRACT_ADDRESS"); // 用实际合约地址替换
}
function run() public {
vm.startBroadcast(); // 开始事务广播会话
nftContract.claimNFT();
vm.stopBroadcast();
}
}
然后要执行脚本,运行以下命令:
forge script script/Claim.s.sol --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast
重要:确保你在上面包含的私钥对应于你在部署时包含的白名单地址。
你将看到如下输出:
## Setting up 1 EVM.
==========================
Chain 11155111
Estimated gas price: 14.618536578 gwei
Estimated total gas used for script: 162145
Estimated amount required: 0.00237032261343981 ETH
==========================
Sending transactions [0 - 0].
⠁ [00:00:00] [#####################################################################################################################################################################] 1/1 txes (0.0s)##
Waiting for receipts.
⠉ [00:00:12] [#################################################################################################################################################################] 1/1 receipts (0.0s)
##### sepolia
✅ [Success]Hash: 0x7832ddd563e757e57aadb2fb55d16f282b9f4419a38feace946cbd3c56d4777b
Block: 5997706
Paid: 0.001167971638330575 ETH (117391 gas * 9.949413825 gwei)
==========================
ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.
Total Paid: 0.001167971638330575 ETH (117391 gas * avg 9.949413825 gwei)
就这样!你现在知道如何部署自己的NFT合约,使其兼容空投。请记住,区块链开发是一个不断发展的领域,保持更新是🔑。订阅我们的时事通讯,获取更多关于Web3和区块链的文章和指南。如果你有任何问题或需要进一步的帮助,请随时加入我们的Discord服务器或使用下面的表单提供反馈。通过关注我们的Twitter (@QuickNode)和我们的Telegram公告频道保持消息灵通并建立联系。
告诉我们你是否有任何反馈或新主题请求。我们期待你的来信。
- 原文链接: quicknode.com/guides/eth...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!