# Decentraland Wearables空投合约开发 **Published by:** [Yooma](https://paragraph.com/@yooma/) **Published on:** 2022-12-26 **URL:** https://paragraph.com/@yooma/decentraland-wearables ## Content 操作系统: macos 13.0.1 python版本:3.9.7 eth-brownie版本:1.19.2一:介绍本文使用开发语言python,用基于python的开发和测试框架Brownie来开发和部署合约,该空投合约可以对Decentranland Wearables进行批量空投。二:合约代码对于infura 项目的创建在本篇文章中一讲到 1: 在Infura中创建一个Polygon的project(Decentranland Wearables在polygon链上) 2:安装eth-browniepip install eth-brownie 安装成功后终端输入brownie查看操作命令3: 初始化项目 创建个空文件夹,执行初始化命令,之后会生成一些文件夹 mkdir air_drop cd air_drop brownie init 4: 在contracts 文件夹中来创建.sol文件来写入合约代码cd contracts/ touch air_drop_batch.sol 首先是两个interface基于ERC165和ERC721,这里是固定的直接写入即可,也可以把这两个interface放入文件夹interfaces/中,然后在contracts/air_drop_batch.sol调用,这里我们直接放到一个文件中。// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/IERC721.sol) pragma solidity ^0.8.0; interface IERC165 { /// @notice Query if a contract implements an interface /// @param interfaceID The interface identifier, as specified in ERC-165 /// @dev Interface identification is specified in ERC-165. This function /// uses less than 30,000 gas. /// @return `true` if the contract implements `interfaceID` and /// `interfaceID` is not 0xffffffff, `false` otherwise function supportsInterface(bytes4 interfaceID) external view returns (bool); } /** * @dev Required interface of an ERC721 compliant contract. */ interface IERC721 is IERC165 { /** * @dev Emitted when `tokenId` token is transferred from `from` to `to`. */ event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); /** * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token. */ event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); /** * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. */ event ApprovalForAll(address indexed owner, address indexed operator, bool approved); /** * @dev Returns the number of tokens in ``owner``'s account. */ function balanceOf(address owner) external view returns (uint256 balance); /** * @dev Returns the owner of the `tokenId` token. * * Requirements: * * - `tokenId` must exist. */ function ownerOf(uint256 tokenId) external view returns (address owner); /** * @dev Safely transfers `tokenId` token from `from` to `to`. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must exist and be owned by `from`. * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. * * Emits a {Transfer} event. */ function safeTransferFrom( address from, address to, uint256 tokenId, bytes calldata data ) external; /** * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients * are aware of the ERC721 protocol to prevent tokens from being forever locked. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must exist and be owned by `from`. * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}. * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. * * Emits a {Transfer} event. */ function safeTransferFrom( address from, address to, uint256 tokenId ) external; /** * @dev Transfers `tokenId` token from `from` to `to`. * * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721 * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must * understand this adds an external call which potentially creates a reentrancy vulnerability. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must be owned by `from`. * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}. * * Emits a {Transfer} event. */ function transferFrom( address from, address to, uint256 tokenId ) external; /** * @dev Gives permission to `to` to transfer `tokenId` token to another account. * The approval is cleared when the token is transferred. * * Only a single account can be approved at a time, so approving the zero address clears previous approvals. * * Requirements: * * - The caller must own the token or be an approved operator. * - `tokenId` must exist. * * Emits an {Approval} event. */ function approve(address to, uint256 tokenId) external; /** * @dev Approve or remove `operator` as an operator for the caller. * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller. * * Requirements: * * - The `operator` cannot be the caller. * * Emits an {ApprovalForAll} event. */ function setApprovalForAll(address operator, bool _approved) external; /** * @dev Returns the account approved for `tokenId` token. * * Requirements: * * - `tokenId` must exist. */ function getApproved(uint256 tokenId) external view returns (address operator); /** * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`. * * See {setApprovalForAll} */ function isApprovedForAll(address owner, address operator) external view returns (bool); } 为什么是ERC721? 来随便找个decentranland wearable来看下https://market.decentraland.org/contracts/0xaae279e59c80879d7e0158c224a5b4a050aacfcd/items/0https://polygonscan.com/address/0x006080c6061c4af79b39da0842a3a22a7b3f185e#code接下来就是要写对于空投逻辑处理的合约,接上面代码contract AirdropBatchNFT{ event Airdrop721( address from, address batchContractAddress ); function ERC721AirdropBatch( address from, // 发送wearable用户的地址 address batchContractAddress, // 要发送的wearable的合约地址 uint256[] memory tokenIdSet, // 发送wearable的tokenid 列表 address[] memory receivers, // 接收空投wearable的用户钱包地址列表 ) public { // 判断是否是发送者自己操作 require(msg.sender == from, "danger sender"); // 判断发送者是否对本空投合约授权,只有授权空投合约才可以对发送者的wearable进行空投 require(IERC721(batchContractAddress).isApprovedForAll(msg.sender, address(this)), "not approve"); // 接收者列表的长度要与空投的tokenid列表长度一样 require(tokenIdSet.length == receivers.length, "The length of tokenIdSet is different from the length of receivers"); // 判断要空投的wearable是否都属于空投者 for(uint256 j = 0; j < tokenIdSet.length; ++j) { require(IERC721(batchContractAddress).ownerOf(tokenIdSet[j]) == from, "tokens not enough"); } // 把两个列表中第n个token空投给弟n个接受者 for(uint256 i = 0; i < receivers.length; ++i) { IERC721(batchContractAddress).safeTransferFrom(from, receivers[i], tokenIdSet[i]); } emit Airdrop721( from, batchContractAddress ); } 三:部署项目根目录创建配置文件touch brownie-config.yaml dependencies: - OpenZeppelin/openzeppelin-contracts@4.6.0 # 在上文中提到的IERC721&IERC165 可以从OpenZeppelin中导入,这里也指定下版本,只不过上文中是直接填的源码,并不是导入 # 如果使用 openzeppelin 需要下载 # brownie pm install OpenZeppelin/openzeppelin-contracts@4.6.0 compiler: solc: version: null optimizer: enabled: true runs: 200 remappings: - '@openzeppelin=OpenZeppelin/openzeppelin-contracts@4.6.0' # .env 由于WEB3_INFURA_PROJECT_ID和PRIVATE_KEY 存放在文件中 dotenv: .env networks: default: polygon-main polygon-main: host: https://polygon-mainnet.infura.io/v3/${WEB3_INFURA_PROJECT_ID} # 上面创建的infura项目的连接 wallets: PRIVATE_KEY: ${PRIVATE_KEY} # 钱包地址的私钥 项目根创建 .env 存放私钥等信息touch .env PRIVATE_KEY=xxxxxxxxx WEB3_INFURA_PROJECT_ID=xxxxxxxxxx 终端执行 brownie compile编译代码,生成abi文件 接下来是部署,如果部署失败原因指定WEB3_INFURA_PROJECT_ID 或是PRIVATE_KEYsource .env检查两个值是否正确在scripts/中创建depoly.py文件cd scripts/ touch deploy.py from brownie import AirdropBatchNFT, config, accounts # AirdropBatchNFT 是合约中的构造函数,项目要以brownie来启动 def get_account(): return accounts.add(config['wallets']['PRIVATE_KEY']) def test(account): air_drop = AirdropBatchNFT.deploy({'from': account}) def main(): account = get_account() test(account) print('account=', account) 接下来执行部署命令brownie run deploy.py 之后会看到部署的合约地址以及在 build/deployments/map.json中也可以看到部署的纪录在polygonscan中找到部署的合约来验证添加合约代码填入合约代码bytecode 可以不填 optimization 以及下方settings中的内容配置可以在 build/depolyments/137/合约地址.json中找到 搜索compiler验证之后就可以看到这个页面这样,Decentranland 空投wearable的合约就完成了! ## Publication Information - [Yooma](https://paragraph.com/@yooma/): Publication homepage - [All Posts](https://paragraph.com/@yooma/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@yooma): Subscribe to updates - [Twitter](https://twitter.com/_yoonama): Follow on Twitter