# Decentraland Wearables空投合约开发

By [Yooma](https://paragraph.com/@yooma) · 2022-12-26

---

操作系统： macos 13.0.1

python版本：3.9.7

eth-brownie版本：1.19.2

一：介绍
----

本文使用开发语言python，用基于python的开发和测试框架[Brownie](https://eth-brownie.readthedocs.io/en/stable/)来开发和部署合约，该空投合约可以对Decentranland Wearables进行批量空投。

二：合约代码
------

对于[infura](https://infura.io/dashboard/explorer) 项目的创建在[本篇文章中一](https://mirror.xyz/dashboard/edit/e3bgX7ufBgeiFex8Ht1A5ab7XU2-GlArWypqqGhT-_M)讲到

1: 在Infura中创建一个Polygon的project（Decentranland Wearables在polygon链上）

2：安装eth-brownie

    pip install eth-brownie
    

![安装成功后终端输入brownie查看操作命令](https://storage.googleapis.com/papyrus_images/5d51ea3215837d3ab7be999f7cd5f9f2274e977f2153ed34f41d8ad07203b793.png)

安装成功后终端输入brownie查看操作命令

3： 初始化项目

创建个空文件夹，执行初始化命令，之后会生成一些文件夹

     mkdir air_drop
     cd air_drop
     brownie init
    

![](https://storage.googleapis.com/papyrus_images/6eed9801e887752e356169fe55d5e13329ce8ff72db6bf7eaea42899b5f5f45a.png)

4: 在contracts 文件夹中来创建.sol文件来写入合约代码

    cd contracts/
    touch air_drop_batch.sol
    

首先是两个interface基于ERC165和[ERC721](https://erc721.org/)，这里是固定的直接写入即可，也可以把这两个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/0](https://storage.googleapis.com/papyrus_images/70cc524674bfe0472a81db6b4a8cc62fc8d8e07ad79a840a7d77c1681c4283c5.png)

https://market.decentraland.org/contracts/0xaae279e59c80879d7e0158c224a5b4a050aacfcd/items/0

![https://polygonscan.com/address/0x006080c6061c4af79b39da0842a3a22a7b3f185e#code](https://storage.googleapis.com/papyrus_images/db3630e7ac1aa5cca308b434d9653e28da07c7dc95aa1001075740a49cbe5c07.png)

https://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\_KEY

1.  source .env
    
2.  检查两个值是否正确
    

在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中也可以看到部署的纪录

![](https://storage.googleapis.com/papyrus_images/8ddf2874437dad7b5c92776543f88cc3d7ebbfda88ae84a2c086da0e10642a46.png)

![](https://storage.googleapis.com/papyrus_images/8eb10e67c5a55fe2d9da5b98cb4742eedc1177199e91a6f9f650839f3acde1fb.png)

![在polygonscan中找到部署的合约来验证添加合约代码](https://storage.googleapis.com/papyrus_images/b9a77b916dfec7b904d259d21ba0143d6c3bf6036b20b642b4e0ab20da5cf87c.png)

在polygonscan中找到部署的合约来验证添加合约代码

![](https://storage.googleapis.com/papyrus_images/dc707b65318b296993a3d877611528064a6afc5eb9cd55573d148470faf2ebae.png)

![填入合约代码](https://storage.googleapis.com/papyrus_images/617c281f7236b8daf3586cebaf27d8649b9e8bf0d1e4f2ba892a8233d091a5ce.png)

填入合约代码

bytecode 可以不填

optimization 以及下方settings中的内容配置可以在 build/depolyments/137/合约地址.json中找到 搜索compiler

![](https://storage.googleapis.com/papyrus_images/8c15cc63c3da9e2998be581de265c005cc857513d039b6339694734f871db882.png)

![验证之后就可以看到这个页面](https://storage.googleapis.com/papyrus_images/d8e989a6b70683c38cc1bfc5ef7eba79c2cbadbd743411a368e3edb495a196e7.png)

验证之后就可以看到这个页面

这样，Decentranland 空投wearable的合约就完成了！

---

*Originally published on [Yooma](https://paragraph.com/@yooma/decentraland-wearables)*
