上周 BAYC 发币,引起了一阵热度。这篇文章,就来看看 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。