// SPDX-License-Identifier: MIT pragma solidity 0.8.30; interface IAirdropToken { function balanceOf(address account) external view returns (uint256); function transfer(address recipient, uint256 amount) external returns (bool); } /// @notice One successful claim per address, not one per person. No Sybil protection. /// @dev Mainnet addresses are fixed. No proxy, upgrade, fee, allowance or arbitrary call. contract TSNXAirdrop { address public constant TOKEN = 0x66a9BaCd6D560383dA967c274999c06550cda07e; address public constant OWNER = 0x2249843586d79709b30C56347D348e596B502855; uint256 public constant AMOUNT = 10000 ether; uint256 public constant LIMIT = 1000; uint256 public constant TOTAL = AMOUNT * LIMIT; uint256 public claimedCount; bool public activated; bool public paused; mapping(address => bool) public claimed; bool private entered; event Activated(); event Paused(bool paused); event Claimed(address indexed recipient, uint256 amount, uint256 ordinal); event RecoveredBeforeActivation(uint256 amount); modifier onlyOwner() { require(msg.sender == OWNER, "Owner only"); _; } modifier lock() { require(!entered, "Reentry"); entered = true; _; entered = false; } constructor() { require(block.chainid == 56, "BNB Chain only"); require(msg.sender == OWNER, "Owner deployment only"); require(TOKEN.code.length > 0, "Missing token"); } function activate() external onlyOwner lock { require(!activated, "Already activated"); require(IAirdropToken(TOKEN).balanceOf(address(this)) >= TOTAL, "Fund full campaign first"); activated = true; emit Activated(); } function setPaused(bool value) external onlyOwner lock { require(activated, "Not activated"); paused = value; emit Paused(value); } function claim() external lock { require(activated && !paused, "Campaign closed"); require(!claimed[msg.sender], "Already claimed"); require(claimedCount < LIMIT, "Campaign full"); claimed[msg.sender] = true; claimedCount++; pay(msg.sender, AMOUNT); emit Claimed(msg.sender, AMOUNT, claimedCount); } /// @notice Once activated, owner cannot withdraw or redirect any campaign token. /// Unclaimed tokens remain here until claimed; there is no expiry or recovery after activation. function recoverBeforeActivation() external onlyOwner lock { require(!activated, "Campaign funds locked"); uint256 amount = IAirdropToken(TOKEN).balanceOf(address(this)); require(amount > 0, "Empty"); pay(OWNER, amount); emit RecoveredBeforeActivation(amount); } function pay(address recipient, uint256 amount) private { uint256 beforeBalance = IAirdropToken(TOKEN).balanceOf(recipient); require(IAirdropToken(TOKEN).transfer(recipient, amount), "Transfer failed"); require(IAirdropToken(TOKEN).balanceOf(recipient) == beforeBalance + amount, "Incorrect received amount"); } }