We understand how important it is for you to do your due diligence before you airdrop your token with us. So we would like to make the process as easy as possible by providing an explanation of how our airdrop smart contract works. The code of our smart contract has been verified on Etherscan and can be viewed here. In this post, we will break down the code into segments and explain how each segment works. However, we will assume you have some experience in writing smart contracts with the Solidity programming language. If you do not have any familiarity with Solidity, we recommend you seek the advice of a developer who does.
The ERCInterface Smart Contract
Moving on, now let's get to reviewing the code. To begin, we will start with the snippet below that defines an abstract contract that will be used as an interface between the CryptoMultisender smart contract and all ERC20 / BEP20 tokens. The reason why we use this interface is that our smart contract will interact directly with your token's smart contract if you're not airdropping a native currency such as Ethereum, BNB or Matic.
abstract contract ERCInterface {
function transferFrom(address _from, address _to, uint256 _value) public virtual;
function balanceOf(address who) public virtual returns (uint256);
function allowance(address owner, address spender) public view virtual returns (uint256);
function transfer(address to, uint256 value) public virtual returns(bool);
}
The MultiSender Contract
Now let's take a look at the code of the multisender contract. First, we will start with the state variables.
State Variables
mapping (address => uint256) public tokenTrialDrops;
mapping (address => bool) public isPremiumMember;
mapping (address => bool) public isAffiliate;
mapping (string => address) public affiliateCodeToAddr;
mapping (string => bool) public affiliateCodeExists;
mapping (address => string) public affiliateCodeOfAddr;
mapping (address => string) public isAffiliatedWith;
uint256 public premiumMemberFee;
uint256 public rate;
uint256 public dropUnitPrice;
Here's a breakdown of the above
- tokenTrialDrops - This mapping is used to check if a token has any free trial drops. By drops, we mean the number of transfers and by default, all non-native tokens have at least 100 free trial drops.
- isPremiumMember - This mapping checks if a user's wallet address is registered as a VIP member. This will be used to determine if a user is required to pay service fees.
- isAffiliate - This mapping checks if a user is registered as an affiliate partner using the user's address. This can be used by our affiliates so they can confirm that they are registered.
- affiliateCodeToAddr - This maps affiliate codes to the addresses of the associated affiliate partners. This is what is used to determine who to pay commissions to.
- affiliateCodeExists - This mapping is used for internal use only when registering a new affiliate partner. It is used to ensure that no two affiliates share the same code.
- affiliateCodeOfAddr - This mapping allows us and our affiliates to query the affiliate code of a partner by using their wallet address.
- isAffiliatedWith - This mapping is used to check if a user's address is affiliated with an affiliate partner. This is used to attribute sales to our partners so we know when commissions are due.
- premiumMemberFee - This is how much it costs to become a VIP member.
- rate - This specifies how many transfers one can do per 1 ETH/BNB/MATIC.
- dropUnitPrice - This specifies the cost of a single token transfer.
Now that we have covered the state variables, let's move on to the functions of the smart contract.
Smart Contract Functions
In the case that users accidentally pay too much in service fees, or too much to become a VIP member, the function below is used to automatically refund what is owed. This is used in the following functions becomePremiumMember, multiValueEthAirdrop and multiValueTokenAirdrop which we will address shortly.
function giveChange(uint256 _price) internal {
if(msg.value > _price) {
uint256 change = msg.value.sub(_price);
payable(msg.sender).transfer(change);
}
}
The below function ensures that the correct affiliate code is used and also prevents affiliate partners from claiming an already existing user came from their referral link. This is used in the following functions becomePremiumMember, multiValueEthAirdrop and multiValueTokenAirdrop which we will address shortly.
function processAffiliateCode(string memory _afCode) internal returns(string memory) {
if(stringsAreEqual(isAffiliatedWith[msg.sender], "void") || !isAffiliate[affiliateCodeToAddr[_afCode]]) {
isAffiliatedWith[msg.sender] = "void";
return "void";
}
if(!stringsAreEqual(_afCode, "") && stringsAreEqual(isAffiliatedWith[msg.sender],"") && affiliateCodeExists[_afCode]) {
if(affiliateCodeToAddr[_afCode] == msg.sender) {
return "void";
}
isAffiliatedWith[msg.sender] = _afCode;
}
if(stringsAreEqual(_afCode,"") && !stringsAreEqual(isAffiliatedWith[msg.sender],"")) {
_afCode = isAffiliatedWith[msg.sender];
}
if(stringsAreEqual(_afCode,"") || !affiliateCodeExists[_afCode]) {
isAffiliatedWith[msg.sender] = "void";
_afCode = "void";
}
return _afCode;
}
The function below is used to distribute commissions made from airdrop sales and is used by the multiValueEthAirdrop function and the multiValueTokenAirdrop function.
function distributeCommission(uint256 _drops, string memory _afCode) internal {
if(!stringsAreEqual(_afCode,"void") && isAffiliate[affiliateCodeToAddr[_afCode]]) {
uint256 profitSplit = _drops.mul(dropUnitPrice).div(2);
payable(owner).transfer(profitSplit);
payable(affiliateCodeToAddr[_afCode]).transfer(profitSplit);
emit CommissionPaid(affiliateCodeToAddr[_afCode], profitSplit);
} else {
payable(owner).transfer(_drops.mul(dropUnitPrice));
}
}
The function below allows users to become VIP members given that enough EHT/BNB/MATIC is sent with the transaction. As can be seen, this function takes an affiliate code as an argument which will be used to pay the due commission to the affiliate partner. Additionally, the commission in this function is hard-coded to be 20%, so we can never change this.
function becomePremiumMember(string memory _afCode) public payable returns(bool) {
require(!isPremiumMember[msg.sender], "Is already premiumMember member");
require(
msg.value >= premiumMemberFee,
string(abi.encodePacked(
"premiumMember fee is: ", uint2str(premiumMemberFee), ". Not enough ETH sent. ", uint2str(msg.value)
))
);
isPremiumMember[msg.sender] = true;
_afCode = processAffiliateCode(_afCode);
giveChange(premiumMemberFee);
if(!stringsAreEqual(_afCode,"void") && isAffiliate[affiliateCodeToAddr[_afCode]]) {
payable(owner).transfer(premiumMemberFee.mul(80).div(100));
uint256 commission = premiumMemberFee.mul(20).div(100);
payable(affiliateCodeToAddr[_afCode]).transfer(commission);
emit CommissionPaid(affiliateCodeToAddr[_afCode], commission);
} else {
payable(owner).transfer(premiumMemberFee);
}
emit NewPremiumMembership(msg.sender);
return true;
}
The below functions are used to airdrop ETH, BNB and MATIC. The function called _getTotalEthValue calculates the total amount of ETH/BNB/MATIC that is to be sent to all recipients and is used by the multiValueEthAirdrop function.
function _getTotalEthValue(uint256[] memory _values) internal pure returns(uint256) {
uint256 totalVal = 0;
for(uint i=0: i < _values.length: i++) {
totalVal = totalVal.add(_values[i]);
}
return totalVal;
}
function multiValueEthAirdrop(address[] memory _recipients, uint256[] memory _values, string memory _afCode)
public payable returns(bool) {
require(_recipients.length == _values.length, "Total number of recipients and values are not equal");
uint256 totalEthValue = _getTotalEthValue(_values);
uint256 price = _recipients.length.mul(dropUnitPrice);
uint256 totalCost = totalEthValue.add(price);
require(
msg.value >= totalCost || isPremiumMember[msg.sender],
"Not enough ETH sent with transaction!"
);
_afCode = processAffiliateCode(_afCode);
if(!isPremiumMember[msg.sender]) {
distributeCommission(_recipients.length, _afCode);
}
giveChange(totalCost);
for(uint i = 0; i < _recipients.length; i++) {
if(_recipients[i] != address(0) && _values[i] > 0) {
payable(_recipients[i]).transfer(_values[i]);
}
}
emit EthAirdrop(msg.sender, _recipients.length, totalEthValue);
return true;
}
The function below is used to airdrop tokens.
function multiValueTokenAirdrop(address _addressOfToken, address[] memory _recipients, uint256[] memory _values, string memory _afCode) public payable returns(bool) {
ERCInterface token = ERCInterface(_addressOfToken);
require(_recipients.length == _values.length, "Total number of recipients and values are not equal");
uint256 price = _recipients.length.mul(dropUnitPrice);
require(
msg.value >= price || tokenHasFreeTrial(_addressOfToken) || isPremiumMember[msg.sender],
"Not enough ETH sent with transaction!"
);
giveChange(price);
_afCode = processAffiliateCode(_afCode);
for(uint i = 0; i < _recipients.length; i++) {
if(_recipients[i] != address(0) && _values[i] > 0) {
token.transferFrom(msg.sender, _recipients[i], _values[i]);
}
}
if(tokenHasFreeTrial(_addressOfToken)) {
tokenTrialDrops[_addressOfToken] = tokenTrialDrops[_addressOfToken].add(_recipients.length);
} else {
if(!isPremiumMember[msg.sender]) {
distributeCommission(_recipients.length, _afCode);
}
}
emit TokenAirdrop(msg.sender, _addressOfToken, _recipients.length);
return true;
}