// SPDX-License-Identifier: Apache-2.0 pragma solidity >=0.5.0 <0.9.0; import {HederaResponseCodes} from '../../common/HederaResponseCodes.sol'; import {HederaTokenService} from '../../token-service/HederaTokenService.sol'; import {IHederaTokenService} from '../../token-service/IHederaTokenService.sol'; pragma experimental ABIEncoderV2; /// @title Airdrop Contract /// @notice Facilitates token airdrops for both fungible and non-fungible tokens /// @dev Implements HRC-904 standard for token airdrops /// /// Recipients will receive tokens in one of these ways: /// - Immediately if already associated with the token /// - Immediately with auto-association if they have available slots /// - As a pending airdrop requiring claim if they have "receiver signature required" /// - As a pending airdrop requiring claim if they have no available auto-association slots /// /// All transfer fees and auto-renewal rent costs are charged to the transaction submitter contract Airdrop is HederaTokenService { /// @notice Airdrops a fungible token from a sender to a single receiver /// @dev Builds airdrop struct and submits airdropTokens system contract call /// @param token The token address to airdrop /// @param sender The address sending the tokens /// @param receiver The address receiving the tokens /// @param amount The amount of tokens to transfer /// @return responseCode The response code from the airdrop operation (22 = success) function tokenAirdrop( address token, address sender, address receiver, int64 amount ) public payable returns (int64 responseCode) { IHederaTokenService.TokenTransferList[] memory tokenTransfers = new IHederaTokenService.TokenTransferList[](1); IHederaTokenService.TokenTransferList memory airdrop; airdrop.token = token; airdrop.transfers = createAccountTransferPair(sender, receiver, amount); tokenTransfers[0] = airdrop; responseCode = airdropTokens(tokenTransfers); if (responseCode != HederaResponseCodes.SUCCESS) { revert(); } return responseCode; } /// @notice Airdrops a non-fungible token from a sender to a single receiver /// @dev Builds NFT airdrop struct and submits airdropTokens system contract call /// @param token The NFT token address /// @param sender The address sending the NFT /// @param receiver The address receiving the NFT /// @param serial The serial number of the NFT to transfer /// @return responseCode The response code from the airdrop operation (22 = success) function nftAirdrop( address token, address sender, address receiver, int64 serial ) public payable returns (int64 responseCode) { IHederaTokenService.TokenTransferList[] memory tokenTransfers = new IHederaTokenService.TokenTransferList[](1); IHederaTokenService.TokenTransferList memory airdrop; airdrop.token = token; IHederaTokenService.NftTransfer memory nftTransfer = prepareNftTransfer( sender, receiver, serial ); IHederaTokenService.NftTransfer[] memory nftTransfers = new IHederaTokenService.NftTransfer[](1); nftTransfers[0] = nftTransfer; airdrop.nftTransfers = nftTransfers; tokenTransfers[0] = airdrop; responseCode = airdropTokens(tokenTransfers); if (responseCode != HederaResponseCodes.SUCCESS) { revert(); } return responseCode; } /// @notice Airdrops multiple fungible tokens of the same amount from a sender to a single receiver /// @dev Builds multiple token transfer structs and submits a single airdropTokens system contract call /// @param tokens Array of token addresses to airdrop /// @param sender The address sending all tokens /// @param receiver The address receiving all tokens /// @param amount The amount of each token to transfer /// @return responseCode The response code from the airdrop operation (22 = success) function multipleFtAirdrop( address[] memory tokens, address sender, address receiver, int64 amount ) public payable returns (int64 responseCode) { uint256 length = tokens.length; IHederaTokenService.TokenTransferList[] memory tokenTransfers = new IHederaTokenService.TokenTransferList[]( length ); for (uint256 i = 0; i < length; i++) { IHederaTokenService.TokenTransferList memory airdrop; airdrop.token = tokens[i]; airdrop.transfers = createAccountTransferPair(sender, receiver, amount); tokenTransfers[i] = airdrop; } responseCode = airdropTokens(tokenTransfers); if (responseCode != HederaResponseCodes.SUCCESS) { revert(); } return responseCode; } /// @notice Airdrops multiple NFTs from a sender to a single receiver /// @dev Builds multiple NFT transfer structs and submits a single airdropTokens system contract call /// @param nfts Array of NFT token addresses /// @param sender The address sending all NFTs /// @param receiver The address receiving all NFTs /// @param serials Array of serial numbers corresponding to each NFT /// @return responseCode The response code from the airdrop operation (22 = success) function multipleNftAirdrop( address[] memory nfts, address sender, address receiver, int64[] memory serials ) public payable returns (int64 responseCode) { uint256 length = nfts.length; IHederaTokenService.TokenTransferList[] memory tokenTransfers = new IHederaTokenService.TokenTransferList[]( length ); for (uint256 i = 0; i < length; i++) { IHederaTokenService.TokenTransferList memory airdrop; airdrop.token = nfts[i]; IHederaTokenService.NftTransfer[] memory nftTransfers = new IHederaTokenService.NftTransfer[](1); nftTransfers[0] = prepareNftTransfer(sender, receiver, serials[i]); airdrop.nftTransfers = nftTransfers; tokenTransfers[i] = airdrop; } responseCode = airdropTokens(tokenTransfers); if (responseCode != HederaResponseCodes.SUCCESS) { revert(); } return responseCode; } /// @notice Distributes the same amount of a fungible token from one sender to multiple receivers /// @dev Optimizes gas by building a single transfer list with multiple receivers /// @param token The token address to distribute /// @param sender The address sending the tokens /// @param receivers Array of addresses to receive the tokens /// @param amount The amount each receiver should get /// @return responseCode The response code from the airdrop operation (22 = success) function tokenAirdropDistribute( address token, address sender, address[] memory receivers, int64 amount ) public payable returns (int64 responseCode) { uint256 length = receivers.length + 1; IHederaTokenService.TokenTransferList[] memory tokenTransfers = new IHederaTokenService.TokenTransferList[](1); IHederaTokenService.TokenTransferList memory airdrop; airdrop.token = token; IHederaTokenService.AccountAmount memory senderAA; senderAA.accountID = sender; int64 totalAmount = 0; for (uint i = 0; i < receivers.length; i++) { totalAmount += amount; } senderAA.amount = -totalAmount; IHederaTokenService.AccountAmount[] memory transfers = new IHederaTokenService.AccountAmount[](length); transfers[0] = senderAA; for (uint i = 1; i < length; i++) { IHederaTokenService.AccountAmount memory receiverAA; receiverAA.accountID = receivers[i - 1]; receiverAA.amount = amount; transfers[i] = receiverAA; } airdrop.transfers = transfers; tokenTransfers[0] = airdrop; responseCode = airdropTokens(tokenTransfers); if (responseCode != HederaResponseCodes.SUCCESS) { revert(); } return responseCode; } /// @notice Distributes sequential NFTs from one sender to multiple receivers /// @dev Assigns sequential serial numbers starting from 1 to each receiver /// @param token The NFT token address /// @param sender The address sending the NFTs /// @param receivers Array of addresses to receive the NFTs /// @param serials Array of serial numbers to assign to each receiver /// @return responseCode The response code from the airdrop operation (22 = success) function nftAirdropDistribute( address token, address sender, address[] memory receivers, int64[] memory serials ) public payable returns (int64 responseCode) { uint256 length = receivers.length; IHederaTokenService.TokenTransferList[] memory tokenTransfers = new IHederaTokenService.TokenTransferList[](1); IHederaTokenService.TokenTransferList memory airdrop; airdrop.token = token; IHederaTokenService.NftTransfer[] memory nftTransfers = new IHederaTokenService.NftTransfer[](length); for (uint i = 0; i < length; i++) { nftTransfers[i] = prepareNftTransfer(sender, receivers[i], serials[i]); } airdrop.nftTransfers = nftTransfers; tokenTransfers[0] = airdrop; responseCode = airdropTokens(tokenTransfers); if (responseCode != HederaResponseCodes.SUCCESS) { revert(); } return responseCode; } /// @notice Performs a mixed airdrop of both fungible and non-fungible tokens /// @dev Combines multiple token types into a single airdropTokens call for gas efficiency /// @param token Array of fungible token addresses /// @param nft Array of NFT token addresses /// @param tokenSenders Array of addresses sending the fungible tokens /// @param tokenReceivers Array of addresses receiving the fungible tokens /// @param nftSenders Array of addresses sending the NFTs /// @param nftReceivers Array of addresses receiving the NFTs /// @param tokenAmount Amount of each fungible token to transfer /// @param serials Array of serial numbers for the NFTs /// @return responseCode The response code from the airdrop operation (22 = success) function mixedAirdrop( address[] memory token, address[] memory nft, address[] memory tokenSenders, address[] memory tokenReceivers, address[] memory nftSenders, address[] memory nftReceivers, int64 tokenAmount, int64[] memory serials ) public payable returns (int64 responseCode) { uint256 length = tokenSenders.length + nftSenders.length; IHederaTokenService.TokenTransferList[] memory tokenTransfers = new IHederaTokenService.TokenTransferList[]( length ); for (uint i = 0; i < tokenSenders.length; i++) { IHederaTokenService.TokenTransferList memory airdrop; airdrop.token = token[i]; airdrop.transfers = createAccountTransferPair( tokenSenders[i], tokenReceivers[i], tokenAmount ); tokenTransfers[i] = airdrop; } uint nftIndex = tokenSenders.length; for (uint v = 0; nftIndex < length; v++) { IHederaTokenService.TokenTransferList memory airdrop; airdrop.token = nft[v]; IHederaTokenService.NftTransfer[] memory nftTransfers = new IHederaTokenService.NftTransfer[](1); nftTransfers[0] = prepareNftTransfer( nftSenders[v], nftReceivers[v], serials[v] ); airdrop.nftTransfers = nftTransfers; tokenTransfers[nftIndex] = airdrop; nftIndex++; } responseCode = airdropTokens(tokenTransfers); if (responseCode != HederaResponseCodes.SUCCESS) { revert(); } return responseCode; } /// @notice Internal helper to prepare AccountAmount array for token transfers /// @dev Creates a transfer pair with negative amount for sender and positive for receiver /// @param sender The address sending tokens /// @param receiver The address receiving tokens /// @param amount The amount to transfer /// @return transfers Array containing the sender and receiver transfer details function createAccountTransferPair( address sender, address receiver, int64 amount ) internal pure returns (IHederaTokenService.AccountAmount[] memory transfers) { IHederaTokenService.AccountAmount memory aa1; aa1.accountID = sender; aa1.amount = -amount; IHederaTokenService.AccountAmount memory aa2; aa2.accountID = receiver; aa2.amount = amount; transfers = new IHederaTokenService.AccountAmount[](2); transfers[0] = aa1; transfers[1] = aa2; return transfers; } /// @notice Internal helper to prepare NFT transfer struct /// @dev Sets up sender, receiver and serial number for an NFT transfer /// @param sender The address sending the NFT /// @param receiver The address receiving the NFT /// @param serial The serial number of the NFT /// @return nftTransfer The prepared NFT transfer struct function prepareNftTransfer( address sender, address receiver, int64 serial ) internal pure returns (IHederaTokenService.NftTransfer memory nftTransfer) { nftTransfer.senderAccountID = sender; nftTransfer.receiverAccountID = receiver; nftTransfer.serialNumber = serial; return nftTransfer; } }