猛戳订阅学习专栏 solidity系列合约源码+解析
空投就是一种营销策略,通过空投活动将某种数字货币或代币分发给用户,通常需要用户完成一项简单的任务,如分享新闻、介绍朋友或拥有某种数字货币,目前也被广泛应用于宣传新币种,在数字货币市场中反响不错。本文将和大家一起编写我们常见的糖果空投合约。
“发送 0 个 ETH 到某个地址,立马获得 5000 枚 Token,每个地址只能获取一次”,相信大家对于此类糖果空投的信息都已经遇见过很多次了,也有很多朋友趁此机会薅了很多羊毛。身为开发人员,我们不应只是简单的薅羊毛,更应该深入地去研究此类糖果空投的实现原理。
最简单的实现方式是通过人工手动来处理每笔空投,但耗时耗力不够智能还极易出错。若是通过智能合约来实现,不仅省时省力,还不易出错。
本篇文章空投合约实现主要功能:
我们先来看一下标准的 ERC20 标准的接口:
本篇文章使用到的智能合约开发库:
openzeppelin
有兴趣的同学可以去看一下他们的源码。
下面让我们来给出完整的空投合约代码:
合约代码:
// SPDX-License-Identifier: MIT;
pragma solidity ^0.8;
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
contract kongTou {
address owner;
modifier onlyOwner() {
require(msg.sender == owner, "only owner can call this");
_;
}
modifier notAddress(address _useAdd){
require(_useAdd != address(0), "address is error");
_;
}
event Received(address, uint);
constructor() payable{
owner = msg.sender;
}
receive() external payable {
emit Received(msg.sender, msg.value);
}
function pay() public payable{
}
function transferEthsAvg(address[] memory _tos)
payable
public
onlyOwner
returns (bool) {
require(_tos.length > 0);
uint oneValue = address(this).balance/_tos.length;
for(uint i=0; i<_tos.length; i++){
require(_tos[i] != address(0));
payable(_tos[i]).transfer(oneValue);
}
return true;
}
function transferEths(address[] memory _tos,uint256[] memory _values)
payable
public
onlyOwner
returns (bool) {
require(_tos.length > 0);
require(_tos.length == _values.length);
for(uint32 i=0;i<_tos.length;i++){
require(_tos[i] != address(0));
require(_values[i] > 0);
payable(_tos[i]).transfer(_values[i]);
}
return true;
}
function transferEth(address _to)
payable
public
onlyOwner
returns (bool){
require(_to != address(0));
require(msg.value > 0);
payable(_to).transfer(msg.value);
return true;
}
function checkBalance()
public
view
returns (uint) {
return address(this).balance;
}
function destroy()
public
onlyOwner
{
selfdestruct(payable(msg.sender));
}
function transferTokensAvg(address from,address _constractAdd,address[] memory _tos,uint _v)
public
onlyOwner
notAddress(from)
notAddress(_constractAdd)
returns (bool){
require(_tos.length > 0);
require(_v > 0);
IERC20 _token = IERC20(_constractAdd);
//要调用的方法id进行编码
// bytes4 methodId = bytes4(keccak256("transferFrom(address,address,uint256)"));
for(uint i=0;i<_tos.length;i++){
require(_tos[i] != address(0));
require(_token.transferFrom(from,_tos[i],_v));
// _constractAdd.call(abi.encodeWithSignature("transferFrom(address,address,uint256)",from,_tos[i],_v));
// _constractAdd.call(methodId,from,_tos[i],_v);
}
return true;
}
function transferTokens(address from,address _constractAdd, address[] memory _tos,uint[] memory _values)
public
onlyOwner
notAddress(from)
returns (bool){
require(_tos.length > 0);
require(_values.length > 0);
require(_values.length == _tos.length);
bool status;
bytes memory msgs;
//要调用的方法id进行编码
// bytes4 methodId = bytes4(keccak256("transferFrom(address,address,uint256)"));
for(uint i=0;i<_tos.length;i++){
require(_tos[i] != address(0));
require(_values[i] > 0);
(status,msgs) = _constractAdd.call(abi.encodeWithSignature("transferFrom(address,address,uint256)",from,_tos[i],_values[i]));
require(status == true);
// require(_constractAdd.call(methodId,from,_tos[i],_values[i]));
}
return true;
}
function transferTokenOne(address _from,address _constractAdd,address _to,uint _tokenId)
public
notAddress(_from)
notAddress(_constractAdd)
notAddress(_to)
onlyOwner
returns(bool){
IERC721 _token = IERC721(_constractAdd);
_token.safeTransferFrom(_from,_to,_tokenId);
return true;
}
function transferToken1155(address _from,address _contractAdd,address _to,uint _tokenId,uint _num)
public
notAddress(_from)
notAddress(_contractAdd)
notAddress(_to)
returns(bool){
IERC1155 _token = IERC1155(_contractAdd);
_token.safeTransferFrom(_from,_to,_tokenId,_num,"");
return true;
}
function transferTokenBatch1155(address _from,address _contractAdd,address _to,uint[] memory _tokenIds,uint[] memory _nums)
public
notAddress(_from)
notAddress(_contractAdd)
notAddress(_to)
returns(bool){
IERC1155 _token = IERC1155(_contractAdd);
_token.safeBatchTransferFrom(_from,_to,_tokenIds,_nums,"");
return true;
}
}
下面解释一下上面用到的合约和方法:
@openzeppelin/contracts/token/ERC721/IERC721.sol
该合约实现了标准的IERC721接口中的方法@openzeppelin/contracts/token/ERC20/IERC20.sol
该合约实现了标准的IERC20接口中的方法@openzeppelin/contracts/token/ERC1155/IERC1155.sol
该合约实现了标准的IERC1155接口中的方法modifier onlyOwner()
该修饰器为修饰某个方法权限为只能合约部署者调用modifier notAddress(address _useAdd)
改修饰器为修饰判断某个address类型的值不为0地址event Received()
该event事件为合约收到转账时候触发constructor()
//合约的构造方法,在合约创建时候存储合约创建者的地址,添加payable,支持在创建合约的时候,value往合约里面转ethreceive()
当合约收到转账时候该方法会被调用,方法内实现了去触发Received event事件pay()
该方法定义了payable,代表后面可以通过该方法往合约里面转移ETHtransferEthsAvg(address[] memory _tos)
//批量转移ETH,平分发给_tos数组里面的地址,transferEths(address[] memory _tos,uint256[] memory _values)
//给每个地址转不同数量的ETH,其中_tos和_values数组里面的地址和数量要一一对应transferEth(address _to)
//通过合约直接给_to地址转移ETHcheckBalance()
//检查合约账户剩余的可用ETH数量destroy()
销毁合约,合约被销毁后,合约中剩余的ETH将会被转移到合约铸造者地址中,且合约后面将不再可用transferTokensAvg(address from,address _constractAdd,address[] memory _tos,uint _v)
//批量给每一个地址转移ERC20 Token,每个地址转一样数量, 其中 from为要转出token的地址,_constractAdd为要转移的ERC20的合约地址,_tos为要批量转给的用户数组,_v为给每个地址转移的ERC20的数量transferTokens(address from,address _constractAdd, address[] memory _tos,uint[] memory _values)
//批量给每一个地址转移ERC20 Token,每个地址转不一样的数量,其中 from为要转出token的地址,_constractAdd为要转移的ERC20的合约地址,_tos为要批量转给的用户数组,_values为给每个地址转移的ERC20的数量,_tos和_values要一一对应transferTokenOne(address _from,address _constractAdd,address _to,uint _tokenId)
//转移单个ERC721的NFT,其中 from为要转出token的地址,_constractAdd为要转移的ERC721的合约地址,_to为要转到的地址,_tokenId为要转移的ERC721的token IDtransferToken1155(address _from,address _contractAdd,address _to,uint _tokenId,uint _num)
//转移单个ERC1155的代币,其中 from为要转出token的地址,_constractAdd为要转移的ERC1155的合约地址,_to为要转到的地址,_tokenId为要转移的ERC721的token ID,_num为要转移的该token ID的数量transferTokenBatch1155(address _from,address _contractAdd,address _to,uint[] memory _tokenIds,uint[] memory _nums)
//给某个address转移多个ERC1155的代币,其中 from为要转出token的地址,_constractAdd为要转移的ERC1155的合约地址,_to为要转到的地址,_tokenIds为要转移的ERC721的token ID组成的数组,_nums为要转移的token ID的数量组成的数组注意:在操作转移相应的token之前,都要先用相应的_from地址调用token合约的授权方法,允许该空投合约转移_from地址一定数量的token
我们选择要部署的合约为 kongtou
,点击 Deploy 进行部署,可以看到部署后的合约地址和相应的合约方法
为了测试我们的空投合约的功能,再部署一下前面学到的ERC20合约,部署时候要换一个地址,并给该地址铸造了1000个代币。
下面给空投合约授权转移的token数量为100个,为了下一步的批量转移token。并且查询验证一下。
接下来我们给2个地址转移了ERC20 token,每个地址转移的数量为25个,这一步调用合约时候的地址为 部署空投合约的地址
最后一步我们用之前接收ERC20代币的地址数组中的一个查询一下他的ERC20的余额,结果为25个,验证了我们的此次批量转移token是成功的。
好了,批量转移token的空投合约到这里就结束了,学会了这个合约就可以举一反三做出来很多其他类似的空投合约!