IDOGUCashcatAirdrop

0x030bd8ac3d24c8e4781e24f43c135dd8fbc37b10

Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
3,356 bytes
ABI entries
31
10 read · 4 write
License
mit

Contract information

Address
0x030bd8ac3d24c8e4781e24f43c135dd8fbc37b10
Chain
Robinhood Chain (4663)
Compiler
v0.8.24+commit.e11b9ed9
Optimization
Enabled
Creation tx
0xa4b7140da9…4a85ebebfc

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": "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": "amountEach",
        "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": "amountEach",
        "type": "uint256"
      }
    ],
    "name": "airdropEqual",
    "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 IERC20AirdropToken",
        "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 IERC20AirdropToken {
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
}

/// @title IDOGU CASHCAT Holder Airdrop
/// @notice A transparent, non-upgradeable, one-purpose batch distributor.
/// @dev The campaign hash commits to the off-chain snapshot manifest used by the local dashboard.
contract IDOGUCashcatAirdrop {
    error AlreadyProcessed(address recipient);
    error BatchTooLarge(uint256 supplied, uint256 maximum);
    error EmptyBatch();
    error InsufficientTokenBalance(uint256 available, uint256 required);
    error InvalidAddress();
    error InvalidAmount();
    error NativeCurrencyNotAccepted();
    error NotOwner();
    error Paused();
    error ReentrantCall();
    error TokenTransferFailed(address token, address recipient, uint256 amount);

    event BatchAirdropped(uint256 indexed batchNumber, uint256 recipientCount, uint256 amountEach, 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;

    IERC20AirdropToken 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 = IERC20AirdropToken(tokenAddress);
        campaignHash = manifestHash;
        owner = msg.sender;
        emit OwnershipTransferred(address(0), msg.sender);
    }

    /// @notice Sends the same token amount to each unprocessed recipient.
    function airdropEqual(address[] calldata recipients, uint256 amountEach) external onlyOwner nonReentrant {
        if (paused) revert Paused();
        uint256 length = recipients.length;
        if (length == 0) revert EmptyBatch();
        if (length > MAX_BATCH_SIZE) revert BatchTooLarge(length, MAX_BATCH_SIZE);
        if (amountEach == 0) revert InvalidAmount();

        uint256 required = length * amountEach;
        uint256 available = token.balanceOf(address(this));
        if (available < required) revert InsufficientTokenBalance(available, required);

        for (uint256 i; i < length; ) {
            address recipient = recipients[i];
            if (recipient == address(0) || recipient == address(this)) revert InvalidAddress();
            if (processed[recipient]) revert AlreadyProcessed(recipient);

            processed[recipient] = true;
            _safeTransfer(address(token), recipient, amountEach);

            unchecked {
                ++i;
            }
        }

        unchecked {
            ++batchCount;
            distributedRecipients += length;
            distributedAmount += required;
        }

        emit BatchAirdropped(batchCount, length, amountEach, required);
    }

    /// @notice Returns processing status for a dashboard-sized group of addresses.
    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);
    }

    /// @notice Recovers leftover campaign tokens or unrelated tokens accidentally sent here.
    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(IERC20AirdropToken.transfer.selector, recipient, amount)
        );
        if (!success || (returnData.length != 0 && !abi.decode(returnData, (bool)))) {
            revert TokenTransferFailed(tokenAddress, recipient, amount);
        }
    }

    receive() external payable {
        revert NativeCurrencyNotAccepted();
    }
}
Chain explorer2952msChain node73ms