Robinhood MainnetChain ID: 4663
Contract detail

Batcher

Batcher is a verified Robinhood Chain smart contract at 0x77d5311d…68ca4014 with 0 indexed transactions.

Address type
Smart contract
Transactions
0
History coverage
No indexed activity found
Emitted events
4
Verification
Verified
Compiler
v0.8.20+commit.a1b79de6
Verification match
full
Creation transaction
0x077e2c81…540b3f68
Created at block
#144
Language
solidity
EVM version
paris
License
none
Compiler optimization
Enabled · 1000 runs
Verified contract

Contract source code

// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.20;

import '@openzeppelin/contracts/access/Ownable2Step.sol';
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
import '@openzeppelin/contracts/utils/ReentrancyGuard.sol';

/// @notice Thrown when attempting a batch transfer with an empty recipients list
/// @dev This error indicates an invalid attempt to perform a batch transfer without any recipients
error EmptyRecipientsList();

/// @notice Thrown when the number of recipients doesn't match the number of values/amounts
/// @dev Arrays must be of equal length for batch transfers to work correctly
error UnequalRecipientsAndValues();

/// @notice Thrown when the batch size exceeds the configured limit
/// @param provided The number of recipients in the attempted batch
/// @param limit The maximum allowed number of recipients
error TooManyRecipients(uint256 provided, uint256 limit);

/// @title Batcher - Batch transfer contract for ETH and ERC20 tokens
/// @notice Allows batch transfers of ETH and ERC20 tokens to multiple recipients in a single transaction
/// @dev Implements reentrancy protection and configurable gas limits for transfers

contract Batcher is Ownable2Step, ReentrancyGuard {
  using SafeERC20 for IERC20;

  event BatchTransfer(address sender, address recipient, uint256 value);
  event TransferGasLimitChange(
    uint256 prevTransferGasLimit,
    uint256 newTransferGasLimit
  );
  event BatchTransferLimitChange(
    uint256 prevBatchTransferLimit,
    uint256 newBatchTransferLimit
  );
  event NativeBatchTransferLimitChange(
    uint256 prevNativeBatchTransferLimit,
    uint256 newNativeBatchTransferLimit
  );
  event RecoveryInitiated(address to, uint256 value, bytes data);

  uint256 public transferGasLimit;
  uint256 public batchTransferLimit;
  uint256 public nativeBatchTransferLimit;

  /// @notice Contract constructor
  /// @param _transferGasLimit Gas limit for individual transfers
  /// @param _batchTransferLimit Maximum number of ERC20 transfers allowed in a batch
  /// @param _nativeBatchTransferLimit Maximum number of native coin transfers allowed in a batch
  /// @dev Sets initial values for transfer limits and initializes the reentrancy guard
  constructor(uint256 _transferGasLimit, uint256 _batchTransferLimit, uint256 _nativeBatchTransferLimit) Ownable(msg.sender) {
    transferGasLimit = _transferGasLimit;
    batchTransferLimit = _batchTransferLimit;
    nativeBatchTransferLimit = _nativeBatchTransferLimit;
    emit TransferGasLimitChange(0, transferGasLimit);
    emit BatchTransferLimitChange(0, batchTransferLimit);
    emit NativeBatchTransferLimitChange(0, nativeBatchTransferLimit);
  }

  /// @notice Batch transfer ETH to multiple recipients
  /// @param recipients The list of recipients to send to
  /// @param values The list of values to send to recipients.
  /// @dev Total value sent must match msg.value exactly
  /// @dev Reverts if any transfer fails or if arrays are mismatched
  function batch(address[] calldata recipients, uint256[] calldata values)
    external
    payable
    nonReentrant
  {
    if (recipients.length == 0) revert EmptyRecipientsList();
    if (recipients.length != values.length) revert UnequalRecipientsAndValues();
    if (recipients.length > nativeBatchTransferLimit) revert TooManyRecipients(recipients.length, nativeBatchTransferLimit);

    uint256 totalSent = 0;

    // Try to send all given amounts to all given recipients
    // Revert everything if any transfer fails
    for (uint256 i = 0; i < recipients.length; i++) {
      require(recipients[i] != address(0), 'Invalid recipient address');
      emit BatchTransfer(msg.sender, recipients[i], values[i]);
      (bool success, ) = recipients[i].call{
        value: values[i],
        gas: transferGasLimit
      }('');
      require(success, 'Send failed');

      totalSent += values[i];
    }

    require(totalSent == msg.value, 'Total sent out must equal total received');
  }

  /// @notice Batch transfer ERC20 tokens from sender to multiple recipients
  /// @param token Address of the ERC20 token contract
  /// @param recipients Array of recipient addresses
  /// @param amounts Array of token amounts to transfer to each recipient
  /// @dev Requires prior approval for token spending
  /// @dev Uses SafeERC20 for secure token transfers
  function batchTransferFrom(
    address token,
    address[] calldata recipients,
    uint256[] calldata amounts
  ) external nonReentrant {
    if (recipients.length == 0) revert EmptyRecipientsList();
    if (recipients.length != amounts.length) revert UnequalRecipientsAndValues();
    if (recipients.length > batchTransferLimit) revert TooManyRecipients(recipients.length, batchTransferLimit);

    IERC20 safeToken = IERC20(token);
    for (uint256 i = 0; i < recipients.length; i++) {
      safeToken.safeTransferFrom(msg.sender, recipients[i], amounts[i]);
    }
  }

  /// @notice Recover any ETH or tokens accidentally sent to the contract
  /// @param to Destination address for recovery
  /// @param value Amount of ETH to send
  /// @param data Calldata for the recovery transaction
  /// @return bytes The return data from the recovery call
  /// @dev Only callable by contract owner
  function recover(
    address to,
    uint256 value,
    bytes calldata data
  ) external onlyOwner returns (bytes memory) {
    require(to != address(0), "Recovery to zero address not allowed");
    emit RecoveryInitiated(to, value, data);
    (bool success, bytes memory returnData) = to.call{ value: value }(data);
    require(success, 'Recover failed');
    return returnData;
  }

  /// @notice Update the gas limit for individual transfers
  /// @param newTransferGasLimit New gas limit value
  /// @dev Minimum value of 2300 required for basic ETH transfers
  /// @dev Only callable by contract owner
  function changeTransferGasLimit(uint256 newTransferGasLimit)
    external
    onlyOwner
  {
    require(newTransferGasLimit >= 2300, 'Transfer gas limit too low');
    emit TransferGasLimitChange(transferGasLimit, newTransferGasLimit);
    transferGasLimit = newTransferGasLimit;
  }

  /// @notice Update the maximum number of transfers allowed in a batch
  /// @param newBatchTransferLimit New maximum batch size
  /// @dev Must be greater than zero
  /// @dev Only callable by contract owner
  function changeBatchTransferLimit(uint256 newBatchTransferLimit)
    external
    onlyOwner
  {
    require(newBatchTransferLimit > 0, 'Batch transfer limit too low');
    emit BatchTransferLimitChange(batchTransferLimit, newBatchTransferLimit);
    batchTransferLimit = newBatchTransferLimit;
  }

  /// @notice Update the maximum number of transfers for native coin allowed in a batch
  /// @param newNativeBatchTransferLimit New maximum batch size for native coin transfers
  /// @dev Must be greater than zero
  /// @dev Only callable by contract owner
  function changeNativeBatchTransferLimit(uint256 newNativeBatchTransferLimit)
    external
    onlyOwner
  {
    require(newNativeBatchTransferLimit > 0, 'Native batch transfer limit too low');
    emit NativeBatchTransferLimitChange(nativeBatchTransferLimit, newNativeBatchTransferLimit);
    nativeBatchTransferLimit = newNativeBatchTransferLimit;
  }
}