// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; /** * @notice Batch ERC-20 drops for EI matrix mainnet top-ups (cWUSDT / cWUSDC). * @dev Multicall3 transferFrom is not viable on mainnet cW* (allowance does not persist for MC3). * Operator prefunds this contract, then calls dropPrefunded() — ~100 recipients per tx. * msg.sender must be owner (deployer EOA). */ contract EiMatrixMainnetBatchDrop { using SafeERC20 for IERC20; address public immutable owner; event BatchDrop(address indexed token, address indexed operator, uint256 recipientCount, uint256 totalAmount); constructor() { owner = msg.sender; } modifier onlyOwner() { require(msg.sender == owner, "EiMatrixBatchDrop: not owner"); _; } /// @dev Transfer `amounts[i]` from this contract's token balance to `recipients[i]`. function dropPrefunded( address token, address[] calldata recipients, uint256[] calldata amounts ) external onlyOwner { uint256 n = recipients.length; require(n == amounts.length, "EiMatrixBatchDrop: length mismatch"); uint256 total; for (uint256 i; i < n; ) { uint256 amount = amounts[i]; total += amount; IERC20(token).safeTransfer(recipients[i], amount); unchecked { ++i; } } emit BatchDrop(token, msg.sender, n, total); } /// @dev Pull total from owner via transferFrom then distribute (one tx if allowance to this contract works). function pullAndDrop( address token, address[] calldata recipients, uint256[] calldata amounts ) external onlyOwner { uint256 n = recipients.length; require(n == amounts.length, "EiMatrixBatchDrop: length mismatch"); uint256 total; for (uint256 i; i < n; ) { total += amounts[i]; unchecked { ++i; } } IERC20(token).safeTransferFrom(owner, address(this), total); for (uint256 i; i < n; ) { IERC20(token).safeTransfer(recipients[i], amounts[i]); unchecked { ++i; } } emit BatchDrop(token, msg.sender, n, total); } /// @dev Recover stray tokens (operator only). function sweep(address token, address to, uint256 amount) external onlyOwner { IERC20(token).safeTransfer(to, amount); } }