// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
contract Airdropper {
using SafeERC20 for IERC20;
address private owner;
uint256 public airdropPerAmount; // 每人每次可以领取的空投数量
uint256 public airdropTotalAmount; // 每人可以领取的空投总数
uint256 public airdropTerm; // 多久可以领取一次(单位s),未设置则每人只能领一次
uint256 public airdropStartTime; // 空投开始时间(时间戳)
uint256 public airdropDeadline; // 空投截止时间(时间戳)
address public tokenAddress; // 空投token地址
mapping(address => uint256) public airdropRecord; // 每个人空投领取总额
mapping(address => uint256) public airdropTimeRecord; // 最后一次领取空投时间
// 发布合约时需传入5个参数,注意精度问题
constructor(uint256 _airdropPerAmount, uint256 _airdropTotalAmount, uint256 _airdropTerm, uint256 _airdropStartTime, uint256 _airdropDeadline) {
owner = msg.sender;
airdropPerAmount = _airdropPerAmount;
airdropTotalAmount = _airdropTotalAmount;
airdropTerm = _airdropTerm;
airdropStartTime = _airdropStartTime;
airdropDeadline = _airdropDeadline;
}
// 批量发放空投,dests和values两个数组长度若相等,则给不同地址发放对应数量token,如果values只有一个元素,则每个地址发放等量token
function doAirdrop(address[] memory dests, uint256[] memory values) external virtual returns (uint256) {
// 批量发放一般为官方操作,不受时间、领取额度等以上各种条件限制
require(msg.sender == owner, 'Airdropper: forbidden');
require(tokenAddress != address(0), 'Airdropper: address not zero');
uint256 i = 0;
while (i < dests.length) {
uint sendAmount = values.length == 1 ? values[0] : values[i];
// 判断当前合约中剩余token是否够发放数量,如果不够则结束发放并返回已发放的最后一个索引
if(ERC20(tokenAddress).balanceOf(address(this)) < sendAmount){
break;
}
// 接收地址不为0,发放数量不为0,则执行发放
if(dests[i] != address(0) && sendAmount > 0){
IERC20(tokenAddress).safeTransfer(dests[i], sendAmount);
}
i++;
}
return i;
}
// 个人领取空投
function getAirdrop() external virtual returns(bool){
// token地址不能为0地址
require(tokenAddress != address(0), 'Airdropper: address not zero');
// 每人每次可以领取的空投数量要大于0
require(airdropPerAmount > 0, 'Airdropper: no parameter set');
// 当前时间要大于空投开始时间
require(block.timestamp >= airdropStartTime, 'Airdropper: not started');
if(airdropTotalAmount > 0){
// 如果设置了 每人可以领取的空投总数 这个参数,则验证已领取数量要小于这个总数
require(airdropRecord[msg.sender] < airdropTotalAmount, 'Airdropper: total amount limit');
}
if (airdropTerm > 0) {
// 如果设置了领取周期参数,则验证当前时间减去上次领取时间大于这个周期
require(block.timestamp - airdropTimeRecord[msg.sender] > airdropTerm , 'Airdropper: term limit');
} else {
// 如果没有设置周期参数,则验证没有领取过可以领取,只能领1次
require(airdropRecord[msg.sender] == 0, 'Airdropper: you have already received');
}
if (airdropDeadline > 0) {
// 如果设置了空投截止时间,则验证当前时间小于截止时间
require(airdropDeadline > block.timestamp, 'Airdropper: deadline');
}
// 验证当前合约token数量够发放数量
require(ERC20(tokenAddress).balanceOf(address(this)) >= airdropPerAmount, 'Airdropper: insufficient assets');
// 执行发放
IERC20(tokenAddress).safeTransfer(msg.sender, airdropPerAmount);
// 累计领取总数
airdropRecord[msg.sender] += airdropPerAmount;
// 记录最后领取时间
airdropTimeRecord[msg.sender] = block.timestamp;
return true;
}
// 充入token
function recharge(address _tokenAddress, uint256 _amount) external virtual returns(bool) {
require(msg.sender == owner, 'Airdropper: forbidden');
require(_tokenAddress != address(0), 'Airdropper: forbidden');
if(tokenAddress == address(0)){
// 第一次充入,配置token地址
tokenAddress = _tokenAddress;
} else {
// 否则验证充入的token和配置的地址一致
require(_tokenAddress == tokenAddress, 'Airdropper: Error token address');
}
// 执行充入token
IERC20(tokenAddress).safeTransferFrom(msg.sender, address(this), _amount);
return true;
}
// 提出剩余token
function withdraw() external virtual returns(bool) {
require(msg.sender == owner, 'Airdropper: forbidden');
require(tokenAddress != address(0), 'Airdropper: address not zero');
// 将剩余token全部转给合约发布者
IERC20(tokenAddress).safeTransfer(owner, ERC20(tokenAddress).balanceOf(address(this)));
tokenAddress = address(0); // 重置token地址
return true;
}
/**
* 以下是配置各个参数的接口,只有合约发布者可以调用
*/
function setPerAmount(uint256 _airdropPerAmount) external virtual returns(bool) {
require(msg.sender == owner, 'Airdropper: forbidden');
airdropPerAmount = _airdropPerAmount;
return true;
}
function setTotalAmount(uint256 _airdropTotalAmount) external virtual returns(bool) {
require(msg.sender == owner, 'Airdropper: forbidden');
airdropTotalAmount = _airdropTotalAmount;
return true;
}
function setTerm(uint256 _airdropTerm) external virtual returns(bool) {
require(msg.sender == owner, 'Airdropper: forbidden');
airdropTerm = _airdropTerm;
return true;
}
function setStartTime(uint256 _airdropStartTime) external virtual returns(bool) {
require(msg.sender == owner, 'Airdropper: forbidden');
airdropStartTime = _airdropStartTime;
return true;
}
function setDeadline(uint256 _airdropDeadline) external virtual returns(bool) {
require(msg.sender == owner, 'Airdropper: forbidden');
airdropDeadline = _airdropDeadline;
return true;
}
}
这是一个经过实践的空投合约,在bsc和matic可以直接使用,在trc上问题应该也不大。很高兴分享给大家。在一些审计比较严格的情况下会报warning,每个函数都要有Event,自己加上就行。提示词就略过吧,实在懒得翻译,用的时候自己换一个自己喜欢的就好。
后续还会不断发布各类经过审计且完整可用的合约模板。现在正在整理过往项目,准备开发一个通用的Dapp快速开发框架。
加关注,不迷路,很开心和大家一起交流学习心得。