// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; /** * @title BatchTransfer * @notice A contract to batch transfer multiple ERC20 tokens in a single transaction * @dev This contract uses SafeERC20 for safe token transfers and is protected against reentrancy */ contract BatchTransfer is ReentrancyGuard { using SafeERC20 for IERC20; // Events event TokenTransferred( address indexed token, address indexed from, address indexed to, uint256 amount ); event BatchTransferCompleted( address indexed from, address indexed to, uint256 tokenCount ); /** * @notice Transfer multiple tokens from one address to another * @dev Requires approval for each token beforehand * @param tokens Array of token addresses to transfer * @param from Source address (must have approved this contract) * @param to Destination address * @param amounts Array of amounts to transfer (corresponding to tokens array) */ function batchTransferFrom( address[] calldata tokens, address from, address to, uint256[] calldata amounts ) external nonReentrant { require(tokens.length == amounts.length, "Arrays length mismatch"); require(tokens.length > 0, "Empty arrays"); require(to != address(0), "Invalid destination"); require(from != address(0), "Invalid source"); for (uint256 i = 0; i < tokens.length; i++) { if (amounts[i] > 0) { IERC20 token = IERC20(tokens[i]); // Check allowance uint256 allowance = token.allowance(from, address(this)); require(allowance >= amounts[i], "Insufficient allowance"); // Transfer token.safeTransferFrom(from, to, amounts[i]); emit TokenTransferred(tokens[i], from, to, amounts[i]); } } emit BatchTransferCompleted(from, to, tokens.length); } /** * @notice Transfer a single token using transferFrom * @dev Requires approval beforehand * @param token Token address to transfer * @param from Source address * @param to Destination address * @param amount Amount to transfer */ function singleTransferFrom( address token, address from, address to, uint256 amount ) external nonReentrant { require(token != address(0), "Invalid token"); require(to != address(0), "Invalid destination"); require(from != address(0), "Invalid source"); require(amount > 0, "Amount must be greater than 0"); IERC20 tokenContract = IERC20(token); // Check allowance uint256 allowance = tokenContract.allowance(from, address(this)); require(allowance >= amount, "Insufficient allowance"); // Transfer tokenContract.safeTransferFrom(from, to, amount); emit TokenTransferred(token, from, to, amount); } /** * @notice Check allowances for multiple tokens * @param tokens Array of token addresses * @param owner Address of token owner * @return allowances Array of allowance amounts */ function checkAllowances( address[] calldata tokens, address owner ) external view returns (uint256[] memory allowances) { allowances = new uint256[](tokens.length); for (uint256 i = 0; i < tokens.length; i++) { allowances[i] = IERC20(tokens[i]).allowance(owner, address(this)); } return allowances; } /** * @notice Check balances for multiple tokens * @param tokens Array of token addresses * @param owner Address of token owner * @return balances Array of balance amounts */ function checkBalances( address[] calldata tokens, address owner ) external view returns (uint256[] memory balances) { balances = new uint256[](tokens.length); for (uint256 i = 0; i < tokens.length; i++) { balances[i] = IERC20(tokens[i]).balanceOf(owner); } return balances; } }