AirdropDistributor
0x79b73f360f84a5edc239df65df6eb181abe6036f
Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
3,583 bytes
ABI entries
32
10 read · 4 write
License
none
Contract information
- Address
- 0x79b73f360f84a5edc239df65df6eb181abe6036f
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.24+commit.e11b9ed9
- Optimization
- Enabled
- Creator
- 0xe26cAd83DF…A452826a6F
- Creation tx
- 0x6a6b276a44…92b5b1adc4
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (10)
MAX_BATCH_SIZE() → uint256
batchCount() → uint256
campaignHash() → bytes32
distributedAmount() → uint256
distributedRecipients() → uint256
owner() → address
paused() → bool
processed(address) → bool
processedStatuses(address[]) → bool[]
token() → address
Events (4)
BatchAirdroppedOwnershipTransferredPauseChangedTokenRecovered
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "tokenAddress",
"type": "address"
},
{
"internalType": "bytes32",
"name": "manifestHash",
"type": "bytes32"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
}
],
"name": "AlreadyProcessed",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "supplied",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "maximum",
"type": "uint256"
}
],
"name": "BatchTooLarge",
"type": "error"
},
{
"inputs": [],
"name": "EmptyBatch",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "available",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "required",
"type": "uint256"
}
],
"name": "InsufficientTokenBalance",
"type": "error"
},
{
"inputs": [],
"name": "InvalidAddress",
"type": "error"
},
{
"inputs": [],
"name": "InvalidAmount",
"type": "error"
},
{
"inputs": [],
"name": "LengthMismatch",
"type": "error"
},
{
"inputs": [],
"name": "NativeCurrencyNotAccepted",
"type": "error"
},
{
"inputs": [],
"name": "NotOwner",
"type": "error"
},
{
"inputs": [],
"name": "Paused",
"type": "error"
},
{
"inputs": [],
"name": "ReentrantCall",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "TokenTransferFailed",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "batchNumber",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "recipientCount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "totalAmount",
"type": "uint256"
}
],
"name": "BatchAirdropped",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "bool",
"name": "paused",
"type": "bool"
}
],
"name": "PauseChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "TokenRecovered",
"type": "event"
},
{
"inputs": [],
"name": "MAX_BATCH_SIZE",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address[]",
"name": "recipients",
"type": "address[]"
},
{
"internalType": "uint256[]",
"name": "amounts",
"type": "uint256[]"
}
],
"name": "airdrop",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "batchCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "campaignHash",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "distributedAmount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "distributedRecipients",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "paused",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "processed",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address[]",
"name": "recipients",
"type": "address[]"
}
],
"name": "processedStatuses",
"outputs": [
{
"internalType": "bool[]",
"name": "statuses",
"type": "bool[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "tokenAddress",
"type": "address"
},
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "recoverToken",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bool",
"name": "shouldPause",
"type": "bool"
}
],
"name": "setPaused",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "token",
"outputs": [
{
"internalType": "contract IERC20AirdropAsset",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
interface IERC20AirdropAsset {
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
}
/// @title Airdrop Distributor
/// @notice A transparent, non-upgradeable batch distributor for variable ERC-20 allocations.
/// @dev The campaign hash commits to the off-chain snapshot and every recipient allocation.
contract AirdropDistributor {
error AlreadyProcessed(address recipient);
error BatchTooLarge(uint256 supplied, uint256 maximum);
error EmptyBatch();
error InsufficientTokenBalance(uint256 available, uint256 required);
error InvalidAddress();
error InvalidAmount();
error LengthMismatch();
error NativeCurrencyNotAccepted();
error NotOwner();
error Paused();
error ReentrantCall();
error TokenTransferFailed(address token, address recipient, uint256 amount);
event BatchAirdropped(uint256 indexed batchNumber, uint256 recipientCount, uint256 totalAmount);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
event PauseChanged(bool paused);
event TokenRecovered(address indexed token, address indexed recipient, uint256 amount);
uint256 public constant MAX_BATCH_SIZE = 150;
IERC20AirdropAsset public immutable token;
bytes32 public immutable campaignHash;
address public owner;
bool public paused;
uint256 public batchCount;
uint256 public distributedRecipients;
uint256 public distributedAmount;
mapping(address => bool) public processed;
uint256 private _entered = 1;
modifier onlyOwner() {
if (msg.sender != owner) revert NotOwner();
_;
}
modifier nonReentrant() {
if (_entered != 1) revert ReentrantCall();
_entered = 2;
_;
_entered = 1;
}
constructor(address tokenAddress, bytes32 manifestHash) {
if (tokenAddress == address(0) || tokenAddress.code.length == 0) revert InvalidAddress();
token = IERC20AirdropAsset(tokenAddress);
campaignHash = manifestHash;
owner = msg.sender;
emit OwnershipTransferred(address(0), msg.sender);
}
/// @notice Sends each recipient the corresponding amount committed in the campaign plan.
function airdrop(address[] calldata recipients, uint256[] calldata amounts) external onlyOwner nonReentrant {
if (paused) revert Paused();
uint256 length = recipients.length;
if (length == 0) revert EmptyBatch();
if (length != amounts.length) revert LengthMismatch();
if (length > MAX_BATCH_SIZE) revert BatchTooLarge(length, MAX_BATCH_SIZE);
uint256 required;
for (uint256 i; i < length; ) {
if (recipients[i] == address(0) || recipients[i] == address(this)) revert InvalidAddress();
if (amounts[i] == 0) revert InvalidAmount();
required += amounts[i];
unchecked {
++i;
}
}
uint256 available = token.balanceOf(address(this));
if (available < required) revert InsufficientTokenBalance(available, required);
for (uint256 i; i < length; ) {
address recipient = recipients[i];
if (processed[recipient]) revert AlreadyProcessed(recipient);
processed[recipient] = true;
_safeTransfer(address(token), recipient, amounts[i]);
unchecked {
++i;
}
}
unchecked {
++batchCount;
distributedRecipients += length;
distributedAmount += required;
}
emit BatchAirdropped(batchCount, length, required);
}
function processedStatuses(address[] calldata recipients) external view returns (bool[] memory statuses) {
uint256 length = recipients.length;
statuses = new bool[](length);
for (uint256 i; i < length; ) {
statuses[i] = processed[recipients[i]];
unchecked {
++i;
}
}
}
function setPaused(bool shouldPause) external onlyOwner {
paused = shouldPause;
emit PauseChanged(shouldPause);
}
function transferOwnership(address newOwner) external onlyOwner {
if (newOwner == address(0)) revert InvalidAddress();
address previousOwner = owner;
owner = newOwner;
emit OwnershipTransferred(previousOwner, newOwner);
}
function recoverToken(address tokenAddress, address recipient, uint256 amount) external onlyOwner nonReentrant {
if (tokenAddress == address(0) || recipient == address(0)) revert InvalidAddress();
if (amount == 0) revert InvalidAmount();
_safeTransfer(tokenAddress, recipient, amount);
emit TokenRecovered(tokenAddress, recipient, amount);
}
function _safeTransfer(address tokenAddress, address recipient, uint256 amount) private {
(bool success, bytes memory returnData) = tokenAddress.call(
abi.encodeWithSelector(IERC20AirdropAsset.transfer.selector, recipient, amount)
);
if (!success || (returnData.length != 0 && !abi.decode(returnData, (bool)))) {
revert TokenTransferFailed(tokenAddress, recipient, amount);
}
}
receive() external payable {
revert NativeCurrencyNotAccepted();
}
}
Chain explorer3527msChain node74ms