本文详细介绍了如何使用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 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
 
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!