FeeSplitter

0xa2da74831c34d396be9a42fbecc54c561184bcb1

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
5,375 bytes
ABI entries
49
20 read · 6 write
License
none

Contract information

Address
0xa2da74831c34d396be9a42fbecc54c561184bcb1
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0xe9c8f173ac…ea1ebd9862

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (20)

MAX_BPS()uint16
MAX_RECIPIENTS()uint256
accountedBalance(address)uint256
currentEpoch()uint256
currentShareBps(address)uint16
epochClosed(uint256)bool
epochDeposited(uint256, address)uint256
epochRoundingRecipient(uint256)address
epochShareBps(uint256, address)uint16
feeRouter()address
getRecipients(uint256)address[], uint16[]
owner()address
recipientAt(uint256, uint256)address, uint16
recipientCount(uint256)uint256
releasable(uint256, address, address)uint256
released(uint256, address, address)uint256
roundingRemainder(uint256, address)uint256
totalRecorded(address)uint256
totalReleased(address)uint256
unallocatedBalance(address)uint256

Events (6)

DepositRecordedEpochClosedEpochOpenedEpochRecipientConfiguredOwnershipTransferredPaymentReleased

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "feeRouter_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "owner_",
        "type": "address"
      },
      {
        "internalType": "address[]",
        "name": "recipients_",
        "type": "address[]"
      },
      {
        "internalType": "uint16[]",
        "name": "shares_",
        "type": "uint16[]"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      }
    ],
    "name": "DuplicateRecipient",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "EmptyRecipients",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidAmount",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidBasisPoints",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidRecipient",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidToken",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "LengthMismatch",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotFeeRouter",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NothingToRelease",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      }
    ],
    "name": "OwnableInvalidOwner",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "OwnableUnauthorizedAccount",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ReentrancyGuardReentrantCall",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "SafeERC20FailedOperation",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TooManyRecipients",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "UnsupportedToken",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAddress",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "epoch",
        "type": "uint256"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "DepositRecorded",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "epoch",
        "type": "uint256"
      }
    ],
    "name": "EpochClosed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "epoch",
        "type": "uint256"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "roundingRecipient",
        "type": "address"
      }
    ],
    "name": "EpochOpened",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "epoch",
        "type": "uint256"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "shareBps",
        "type": "uint16"
      }
    ],
    "name": "EpochRecipientConfigured",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferred",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "epoch",
        "type": "uint256"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "PaymentReleased",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "MAX_BPS",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "MAX_RECIPIENTS",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "accountedBalance",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "currentEpoch",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      }
    ],
    "name": "currentShareBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "deposit",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "epoch",
        "type": "uint256"
      }
    ],
    "name": "epochClosed",
    "outputs": [
      {
        "internalType": "bool",
        "name": "closed",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "epoch",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "epochDeposited",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "epoch",
        "type": "uint256"
      }
    ],
    "name": "epochRoundingRecipient",
    "outputs": [
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "epoch",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      }
    ],
    "name": "epochShareBps",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "bps",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "feeRouter",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "epoch",
        "type": "uint256"
      }
    ],
    "name": "getRecipients",
    "outputs": [
      {
        "internalType": "address[]",
        "name": "recipients",
        "type": "address[]"
      },
      {
        "internalType": "uint16[]",
        "name": "shares",
        "type": "uint16[]"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "epoch",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "index",
        "type": "uint256"
      }
    ],
    "name": "recipientAt",
    "outputs": [
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      },
      {
        "internalType": "uint16",
        "name": "bps",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "epoch",
        "type": "uint256"
      }
    ],
    "name": "recipientCount",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "epoch",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      }
    ],
    "name": "releasable",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "epoch",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "release",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "epoch",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      }
    ],
    "name": "releaseFor",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "epoch",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "recipient",
        "type": "address"
      }
    ],
    "name": "released",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "epoch",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "roundingRemainder",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "remainder",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address[]",
        "name": "recipients_",
        "type": "address[]"
      },
      {
        "internalType": "uint16[]",
        "name": "shares_",
        "type": "uint16[]"
      }
    ],
    "name": "setConfig",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "epoch",
        "type": "uint256"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "totalRecorded",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "totalReleased",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "unallocatedBalance",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  }
]

Source code

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

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

/// @notice Owner-configurable, epoch-based splitter for ERC20 fee receipts.
/// @dev The owner (intended to be a multisig) may replace recipients and
/// shares at any time. Each update opens a new epoch for future deposits;
/// prior deposits remain owed under their original immutable epoch.
///
/// The bound FeeRouter calls `deposit` atomically when routing a fee share.
/// Raw ERC20 transfers are intentionally not allocated. Accounting assumes
/// conventional, balance-conserving ERC20 assets such as WETH and LaunchToken.
contract FeeSplitter is Ownable, ReentrancyGuard {
    using SafeERC20 for IERC20;

    error EmptyRecipients();
    error LengthMismatch();
    error TooManyRecipients();
    error ZeroAddress();
    error InvalidRecipient();
    error DuplicateRecipient(address recipient);
    error InvalidBasisPoints();
    error InvalidToken();
    error InvalidAmount();
    error UnsupportedToken();
    error NotFeeRouter();
    error NothingToRelease();

    event EpochClosed(uint256 indexed epoch);
    event EpochOpened(uint256 indexed epoch, address indexed roundingRecipient);
    event EpochRecipientConfigured(uint256 indexed epoch, address indexed recipient, uint16 shareBps);
    event DepositRecorded(uint256 indexed epoch, address indexed token, uint256 amount);
    event PaymentReleased(uint256 indexed epoch, address indexed token, address indexed recipient, uint256 amount);

    uint16 public constant MAX_BPS = 10_000;
    uint256 public constant MAX_RECIPIENTS = 32;

    address public immutable feeRouter;
    uint256 public currentEpoch;

    mapping(uint256 epoch => bool closed) public epochClosed;
    mapping(uint256 epoch => address[] recipients) private _epochRecipients;
    mapping(uint256 epoch => mapping(address recipient => uint16 bps)) public epochShareBps;
    mapping(uint256 epoch => address recipient) public epochRoundingRecipient;

    mapping(uint256 epoch => mapping(address token => uint256 amount)) public epochDeposited;
    mapping(uint256 epoch => mapping(address token => mapping(address recipient => uint256 amount))) public released;

    /// @notice Total router-recorded balance still held across every epoch.
    mapping(address token => uint256 amount) public accountedBalance;
    mapping(address token => uint256 amount) public totalRecorded;
    mapping(address token => uint256 amount) public totalReleased;

    constructor(address feeRouter_, address owner_, address[] memory recipients_, uint16[] memory shares_)
        Ownable(owner_)
    {
        if (feeRouter_ == address(0)) revert ZeroAddress();
        feeRouter = feeRouter_;
        _openEpoch(recipients_, shares_);
    }

    /// @notice Changes the recipients and shares for future FeeRouter deposits.
    /// @dev The current epoch is closed but remains independently claimable.
    function setConfig(address[] calldata recipients_, uint16[] calldata shares_)
        external
        onlyOwner
        returns (uint256 epoch)
    {
        epochClosed[currentEpoch] = true;
        emit EpochClosed(currentEpoch);
        epoch = _openEpoch(recipients_, shares_);
    }

    /// @notice Pulls and records an exact fee deposit into the current epoch.
    /// @dev Only the immutable FeeRouter may call this function.
    function deposit(address token, uint256 amount) external nonReentrant {
        if (msg.sender != feeRouter) revert NotFeeRouter();
        if (token == address(0)) revert InvalidToken();
        if (amount == 0) revert InvalidAmount();

        IERC20 asset = IERC20(token);
        uint256 balanceBefore = asset.balanceOf(address(this));
        asset.safeTransferFrom(msg.sender, address(this), amount);
        uint256 balanceAfter = asset.balanceOf(address(this));
        if (balanceAfter < balanceBefore || balanceAfter - balanceBefore != amount) revert UnsupportedToken();

        epochDeposited[currentEpoch][token] += amount;
        accountedBalance[token] += amount;
        totalRecorded[token] += amount;

        emit DepositRecorded(currentEpoch, token, amount);
    }

    /// @notice Releases the caller's accrued share for one epoch and token.
    function release(uint256 epoch, address token) external nonReentrant returns (uint256 amount) {
        return _release(epoch, token, msg.sender);
    }

    /// @notice Releases `recipient`'s accrued share for one epoch and token, paid
    /// to `recipient`. Permissionless: the caller cannot redirect the payment
    /// (it always lands at `recipient`), so a keeper or anyone else may settle a
    /// recipient on its behalf. Reverts NothingToRelease when `recipient` is owed
    /// nothing, so it cannot be used to grief with dust.
    function releaseFor(uint256 epoch, address token, address recipient)
        external
        nonReentrant
        returns (uint256 amount)
    {
        if (recipient == address(0)) revert ZeroAddress();
        return _release(epoch, token, recipient);
    }

    /// @dev Shared release logic; the payout always goes to `recipient`, never to
    /// msg.sender, so `releaseFor` carries no redirect risk.
    function _release(uint256 epoch, address token, address recipient) private returns (uint256 amount) {
        if (token == address(0)) revert InvalidToken();
        amount = releasable(epoch, token, recipient);
        if (amount == 0) revert NothingToRelease();

        released[epoch][token][recipient] += amount;
        accountedBalance[token] -= amount;
        totalReleased[token] += amount;

        IERC20 asset = IERC20(token);
        uint256 balanceBefore = asset.balanceOf(address(this));
        asset.safeTransfer(recipient, amount);
        uint256 balanceAfter = asset.balanceOf(address(this));
        if (balanceAfter > balanceBefore || balanceBefore - balanceAfter != amount) revert UnsupportedToken();

        emit PaymentReleased(epoch, token, recipient, amount);
    }

    /// @notice Amount currently claimable by `recipient` for one epoch/token.
    /// @dev Once an epoch closes, its final integer-division remainder is owed
    /// to the epoch's last recipient so every recorded unit remains claimable.
    function releasable(uint256 epoch, address token, address recipient) public view returns (uint256) {
        uint16 bps = epochShareBps[epoch][recipient];
        if (bps == 0) return 0;

        uint256 entitlement = _mulBps(epochDeposited[epoch][token], bps);
        if (epochClosed[epoch] && recipient == epochRoundingRecipient[epoch]) {
            entitlement += roundingRemainder(epoch, token);
        }

        uint256 alreadyReleased = released[epoch][token][recipient];
        return entitlement > alreadyReleased ? entitlement - alreadyReleased : 0;
    }

    /// @notice Final rounding remainder for a closed epoch and token.
    function roundingRemainder(uint256 epoch, address token) public view returns (uint256 remainder) {
        if (!epochClosed[epoch]) return 0;

        uint256 deposited = epochDeposited[epoch][token];
        address[] storage recipients = _epochRecipients[epoch];
        uint256 allocated;
        uint256 count = recipients.length;
        for (uint256 i; i < count; ++i) {
            allocated += _mulBps(deposited, epochShareBps[epoch][recipients[i]]);
        }
        remainder = deposited - allocated;
    }

    /// @notice Unrecorded tokens sent directly rather than through FeeRouter.
    /// @dev Such balances do not affect any recipient's entitlement.
    function unallocatedBalance(address token) external view returns (uint256) {
        if (token == address(0)) revert InvalidToken();
        uint256 balance = IERC20(token).balanceOf(address(this));
        uint256 accounted = accountedBalance[token];
        return balance > accounted ? balance - accounted : 0;
    }

    function currentShareBps(address recipient) external view returns (uint16) {
        return epochShareBps[currentEpoch][recipient];
    }

    function recipientCount(uint256 epoch) external view returns (uint256) {
        return _epochRecipients[epoch].length;
    }

    function recipientAt(uint256 epoch, uint256 index) external view returns (address recipient, uint16 bps) {
        recipient = _epochRecipients[epoch][index];
        bps = epochShareBps[epoch][recipient];
    }

    function getRecipients(uint256 epoch) external view returns (address[] memory recipients, uint16[] memory shares) {
        recipients = _epochRecipients[epoch];
        uint256 count = recipients.length;
        shares = new uint16[](count);
        for (uint256 i; i < count; ++i) {
            shares[i] = epochShareBps[epoch][recipients[i]];
        }
    }

    function _openEpoch(address[] memory recipients_, uint16[] memory shares_) private returns (uint256 epoch) {
        uint256 count = recipients_.length;
        if (count == 0) revert EmptyRecipients();
        if (count != shares_.length) revert LengthMismatch();
        if (count > MAX_RECIPIENTS) revert TooManyRecipients();

        epoch = ++currentEpoch;
        uint256 totalBps;
        for (uint256 i; i < count; ++i) {
            address recipient = recipients_[i];
            uint16 bps = shares_[i];
            if (recipient == address(0)) revert ZeroAddress();
            if (recipient == address(this) || recipient == feeRouter || bps == 0) revert InvalidRecipient();
            if (epochShareBps[epoch][recipient] != 0) revert DuplicateRecipient(recipient);

            totalBps += bps;
            if (totalBps > MAX_BPS) revert InvalidBasisPoints();

            _epochRecipients[epoch].push(recipient);
            epochShareBps[epoch][recipient] = bps;
            emit EpochRecipientConfigured(epoch, recipient, bps);
        }
        if (totalBps != MAX_BPS) revert InvalidBasisPoints();

        epochRoundingRecipient[epoch] = recipients_[count - 1];
        emit EpochOpened(epoch, recipients_[count - 1]);
    }

    /// @dev Equivalent to floor(amount * bps / MAX_BPS) without risking
    /// overflow for arbitrary uint256 amounts.
    function _mulBps(uint256 amount, uint16 bps) private pure returns (uint256) {
        return (amount / MAX_BPS) * bps + ((amount % MAX_BPS) * bps) / MAX_BPS;
    }
}
Chain explorer4829msChain node78ms