OmniaDisperse

0xbecacca1e44859363a2c3d31b0c514d6478bacd6

Verification
Verified
0.8.20+commit.a1b79de6
Type
Contract
8,690 bytes
ABI entries
17
4 read · 6 write
License
none

Contract information

Address
0xbecacca1e44859363a2c3d31b0c514d6478bacd6
Chain
Robinhood Chain (4663)
Compiler
0.8.20+commit.a1b79de6
Optimization
Disabled
Creation tx
0x0940cd4e1a…7fc0eead72

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (4)

MAX_PER_BATCH()uint256
feePerBatch()uint256
getFee()uint256
owner()address

Events (5)

EthDispersedFeeCollectedFeeUpdatedOwnershipTransferredTokenDispersed

ABI

[
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "_feePerBatch",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "totalRecipients",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "totalAmount",
        "type": "uint256"
      }
    ],
    "name": "EthDispersed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "payer",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "FeeCollected",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "oldFee",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "newFee",
        "type": "uint256"
      }
    ],
    "name": "FeeUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "oldOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferred",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "totalRecipients",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "totalAmount",
        "type": "uint256"
      }
    ],
    "name": "TokenDispersed",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "MAX_PER_BATCH",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address[]",
        "name": "recipients",
        "type": "address[]"
      },
      {
        "internalType": "uint256[]",
        "name": "amounts",
        "type": "uint256[]"
      }
    ],
    "name": "disperseEth",
    "outputs": [],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address[]",
        "name": "recipients",
        "type": "address[]"
      },
      {
        "internalType": "uint256[]",
        "name": "amounts",
        "type": "uint256[]"
      }
    ],
    "name": "disperseToken",
    "outputs": [],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "feePerBatch",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "getFee",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "newFee",
        "type": "uint256"
      }
    ],
    "name": "setFee",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "withdrawEth",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "withdrawToken",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]

Source code

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IERC20 {
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
    function transfer(address to, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
    function allowance(address owner, address spender) external view returns (uint256);
}

/// @title  OmniaDisperse — multisend ETH / ERC-20 in one transaction.
/// @notice Non-custodial: ETH is forwarded straight to each recipient and
///         tokens move via transferFrom directly from the sender. The
///         contract never holds user funds. feePerBatch (wei) is charged
///         per call and paid instantly to the owner; 0 = free.
/// @dev    Part of the OMNIA toolkit (deploy + airdrop).
///         One identical contract per supported network.
contract OmniaDisperse {
    // ------------------------------------------------------------------
    //  State
    // ------------------------------------------------------------------
    address public owner;
    uint256 public feePerBatch; // in wei
    uint256 public constant MAX_PER_BATCH = 500;

    uint256 private _locked;

    // ------------------------------------------------------------------
    //  Events
    // ------------------------------------------------------------------
    event EthDispersed(uint256 totalRecipients, uint256 totalAmount);
    event TokenDispersed(address indexed token, uint256 totalRecipients, uint256 totalAmount);
    event FeeCollected(address indexed payer, uint256 amount);
    event FeeUpdated(uint256 oldFee, uint256 newFee);
    event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);

    // ------------------------------------------------------------------
    //  Modifiers
    // ------------------------------------------------------------------
    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }

    modifier nonReentrant() {
        require(_locked == 0, "Reentrant");
        _locked = 1;
        _;
        _locked = 0;
    }

    /// @param _feePerBatch fee per call in wei (0 = no fee)
    constructor(uint256 _feePerBatch) {
        owner = msg.sender;
        feePerBatch = _feePerBatch;
        emit OwnershipTransferred(address(0), msg.sender);
    }

    function getFee() external view returns (uint256) {
        return feePerBatch;
    }

    // ------------------------------------------------------------------
    //  ETH multisend
    // ------------------------------------------------------------------
    function disperseEth(address[] calldata recipients, uint256[] calldata amounts)
        external
        payable
        nonReentrant
    {
        require(recipients.length == amounts.length, "Length mismatch");
        require(amounts.length > 0, "Empty list");
        require(amounts.length <= MAX_PER_BATCH, "Max 500 per batch");

        uint256 total;
        for (uint256 i = 0; i < amounts.length; i++) {
            total += amounts[i];
        }
        require(msg.value >= total + feePerBatch, "Insufficient ETH: fee + amounts required");

        if (feePerBatch > 0) {
            (bool fok, ) = owner.call{value: feePerBatch}("");
            require(fok, "Fee transfer failed");
            emit FeeCollected(msg.sender, feePerBatch);
        }

        for (uint256 i = 0; i < recipients.length; i++) {
            require(recipients[i] != address(0), "Zero address");
            (bool ok, ) = recipients[i].call{value: amounts[i]}("");
            require(ok, "ETH transfer failed");
        }

        uint256 refund = msg.value - feePerBatch - total;
        if (refund > 0) {
            (bool rok, ) = msg.sender.call{value: refund}("");
            require(rok, "Refund failed");
        }

        emit EthDispersed(recipients.length, total);
    }

    // ------------------------------------------------------------------
    //  ERC-20 multisend (sender must approve this contract first)
    // ------------------------------------------------------------------
    function disperseToken(address token, address[] calldata recipients, uint256[] calldata amounts)
        external
        payable
        nonReentrant
    {
        require(recipients.length == amounts.length, "Length mismatch");
        require(amounts.length > 0, "Empty list");
        require(amounts.length <= MAX_PER_BATCH, "Max 500 per batch");
        require(token != address(0), "Invalid token");
        require(msg.value >= feePerBatch, "Insufficient fee");

        if (feePerBatch > 0) {
            (bool fok, ) = owner.call{value: feePerBatch}("");
            require(fok, "Fee transfer failed");
            emit FeeCollected(msg.sender, feePerBatch);
        }

        uint256 total;
        for (uint256 i = 0; i < amounts.length; i++) {
            total += amounts[i];
        }
        require(
            IERC20(token).allowance(msg.sender, address(this)) >= total,
            "Insufficient allowance"
        );

        for (uint256 i = 0; i < recipients.length; i++) {
            require(recipients[i] != address(0), "Zero address");
            bool ok = IERC20(token).transferFrom(msg.sender, recipients[i], amounts[i]);
            require(ok, "Token transfer failed");
        }

        uint256 refund = msg.value - feePerBatch;
        if (refund > 0) {
            (bool rok, ) = msg.sender.call{value: refund}("");
            require(rok, "Refund failed");
        }

        emit TokenDispersed(token, recipients.length, total);
    }

    // ------------------------------------------------------------------
    //  Owner controls
    // ------------------------------------------------------------------
    function setFee(uint256 newFee) external onlyOwner {
        emit FeeUpdated(feePerBatch, newFee);
        feePerBatch = newFee;
    }

    function transferOwnership(address newOwner) external onlyOwner {
        require(newOwner != address(0), "Zero address");
        emit OwnershipTransferred(owner, newOwner);
        owner = newOwner;
    }

    /// Rescue ETH stuck in the contract (normally 0 — fees go straight to owner).
    function withdrawEth() external onlyOwner {
        uint256 bal = address(this).balance;
        require(bal > 0, "Nothing to withdraw");
        (bool ok, ) = owner.call{value: bal}("");
        require(ok, "Withdraw failed");
    }

    /// Rescue tokens stuck in the contract (normally 0).
    function withdrawToken(address token) external onlyOwner {
        uint256 bal = IERC20(token).balanceOf(address(this));
        require(bal > 0, "No tokens");
        require(IERC20(token).transfer(owner, bal), "Token transfer failed");
    }

    receive() external payable {}
}
Chain explorer1779msChain node89ms