# APE 空投合约代码详解 **Published by:** [xyyme.eth](https://paragraph.com/@xyyme/) **Published on:** 2022-03-22 **URL:** https://paragraph.com/@xyyme/ape ## Content 上周 BAYC 发币,引起了一阵热度。这篇文章,就来看看 APE 空投合约的代码。规则先来看看官网上空投的规则:APE 空投规则注意到,必须拥有 BAYC 或者 MAYC 才能够领取空投。而仅仅拥有 Kennel Club ,是不能够领取的,必须搭配前两者才能领取。代码接下来看看代码。 数据结构:// APE 币 IERC20 public immutable grapesToken; // BAYC nft ERC721Enumerable public immutable alpha; // MAYC nft ERC721Enumerable public immutable beta; // Kennel Club nft ERC721Enumerable public immutable gamma; // BAYC可以领取的数量:10094 uint256 public immutable ALPHA_DISTRIBUTION_AMOUNT; // MAYC可以领取的数量:2042 uint256 public immutable BETA_DISTRIBUTION_AMOUNT; // Kennel Club可以领取的数量:856 uint256 public immutable GAMMA_DISTRIBUTION_AMOUNT; // 总共领取了多少token uint256 public totalClaimed; // 领取时间:7776000秒 -> 90 天 uint256 public claimDuration; // 开始领取时间:2022-03-17 20:08:07(北京时间) uint256 public claimStartTime; // 记录这些 nft id 是否被领取过 // BAYC mapping (uint256 => bool) public alphaClaimed; // MAYC mapping (uint256 => bool) public betaClaimed; // Kennel Club mapping (uint256 => bool) public gammaClaimed; 这些数据都是在构造方法中直接赋值。 下面的方法计算用户可以领取的数量:// 计算可以领取的数量 function getClaimableTokenAmount(address _account) public view returns (uint256) { uint256 tokensAmount; (tokensAmount,) = getClaimableTokenAmountAndGammaToClaim(_account); return tokensAmount; } function getClaimableTokenAmountAndGammaToClaim(address _account) private view returns (uint256, uint256) { // 计算可以领取token的BAYC有效数量 uint256 unclaimedAlphaBalance; for(uint256 i; i < alpha.balanceOf(_account); ++i) { uint256 tokenId = alpha.tokenOfOwnerByIndex(_account, i); // 如果该id已经领取过,则跳过 if(!alphaClaimed[tokenId]) { ++unclaimedAlphaBalance; } } // 计算可以领取token的MAYC有效数量 uint256 unclaimedBetaBalance; for(uint256 i; i < beta.balanceOf(_account); ++i) { uint256 tokenId = beta.tokenOfOwnerByIndex(_account, i); // 如果该id已经领取过,则跳过 if(!betaClaimed[tokenId]) { ++unclaimedBetaBalance; } } // 计算可以领取token的Kennel Club有效数量 uint256 unclaimedGamaBalance; for(uint256 i; i < gamma.balanceOf(_account); ++i) { uint256 tokenId = gamma.tokenOfOwnerByIndex(_account, i); if(!gammaClaimed[tokenId]) { ++unclaimedGamaBalance; } } // 我们前面说过,Kennel Club必须搭配BAYC或者MAYC才能领取 // 仅仅拥有Kennel Club不可以领取 // 这里就是对这个条件进行计算 uint256 gammaToBeClaim = min(unclaimedAlphaBalance + unclaimedBetaBalance, unclaimedGamaBalance); // 计算出用户可以领取的token总量 uint256 tokensAmount = (unclaimedAlphaBalance * ALPHA_DISTRIBUTION_AMOUNT) + (unclaimedBetaBalance * BETA_DISTRIBUTION_AMOUNT) + (gammaToBeClaim * GAMMA_DISTRIBUTION_AMOUNT); // 返回的两个参数分别为: // 1.可以领取的token数量 // 2.与前两种nft搭配的Kennel Club配对的数量 return (tokensAmount, gammaToBeClaim); } 用户领取token的方法:function claimTokens() external whenNotPaused { // 校验当前时间在有效时间区间内 require(block.timestamp >= claimStartTime && block.timestamp < claimStartTime + claimDuration, "Claimable period is finished"); // 校验用户拥有有效nft require((beta.balanceOf(msg.sender) > 0 || alpha.balanceOf(msg.sender) > 0), "Nothing to claim"); uint256 tokensToClaim; uint256 gammaToBeClaim; // 根据上面的方法,得到用户可以领取的数量 (tokensToClaim, gammaToBeClaim) = getClaimableTokenAmountAndGammaToClaim(msg.sender); // 更新BAYC的领取数据并发送事件 for(uint256 i; i < alpha.balanceOf(msg.sender); ++i) { uint256 tokenId = alpha.tokenOfOwnerByIndex(msg.sender, i); if(!alphaClaimed[tokenId]) { alphaClaimed[tokenId] = true; emit AlphaClaimed(tokenId, msg.sender, block.timestamp); } } // 更新MAYC的领取数据并发送事件 for(uint256 i; i < beta.balanceOf(msg.sender); ++i) { uint256 tokenId = beta.tokenOfOwnerByIndex(msg.sender, i); if(!betaClaimed[tokenId]) { betaClaimed[tokenId] = true; emit BetaClaimed(tokenId, msg.sender, block.timestamp); } } // 更新Kennel Club的领取数据并发送事件 uint256 currentGammaClaimed; for(uint256 i; i < gamma.balanceOf(msg.sender); ++i) { uint256 tokenId = gamma.tokenOfOwnerByIndex(msg.sender, i); // 注意这里是根据Kennel Club nft配对数量进行计算 if(!gammaClaimed[tokenId] && currentGammaClaimed < gammaToBeClaim) { gammaClaimed[tokenId] = true; emit GammaClaimed(tokenId, msg.sender, block.timestamp); currentGammaClaimed++; } } grapesToken.safeTransfer(msg.sender, tokensToClaim); totalClaimed += tokensToClaim; emit AirDrop(msg.sender, tokensToClaim, block.timestamp); } 其他的方法例如,管理员转回未领取的token等,都比较简单,这里不再赘述。总结APE 的空投合约相对来说比较简单,它有一个特点是根据用户的 NFT 实时持仓来进行空投,而不是在某一个区块或时间点进行快照。而这一点恰恰造成了套利的机会,有科学家根据这个特点进行闪电贷套利,获取了大量的 ETH。 ## Publication Information - [xyyme.eth](https://paragraph.com/@xyyme/): Publication homepage - [All Posts](https://paragraph.com/@xyyme/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@xyyme): Subscribe to updates - [Twitter](https://twitter.com/xyymeeth): Follow on Twitter