FeeRouter

0x6cd6b454c2c7727a65dec8693022f2361b703a7e

Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
2,378 bytes
ABI entries
29
7 read · 7 write
License
none

Contract information

Address
0x6cd6b454c2c7727a65dec8693022f2361b703a7e
Chain
Robinhood Chain (4663)
Compiler
v0.8.26+commit.8a97fa7a
Optimization
Enabled
Creation tx
0x77106ecf58…0c03b8cbe1

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (7)

MAX_BPS()uint16
locker()address
owner()address
protocolShare()uint16
tokenTreasuryShare()uint16
treasury()address
weth()address

Events (6)

FeesDistributedLockerUpdatedOwnershipTransferredProtocolShareUpdatedTokenTreasuryShareUpdatedTreasuryUpdated

ABI

[
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "weth_",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "treasury_",
        "type": "address"
      },
      {
        "internalType": "uint16",
        "name": "protocolShare_",
        "type": "uint16"
      },
      {
        "internalType": "uint16",
        "name": "tokenTreasuryShare_",
        "type": "uint16"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [],
    "name": "EthTransferFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "InvalidBasisPoints",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotLocker",
    "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": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "SafeERC20FailedOperation",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "ZeroAddress",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "feeWallet",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "creatorTokenAmount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "creatorPairAmount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "protocolTokenAmount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "protocolPairAmount",
        "type": "uint256"
      }
    ],
    "name": "FeesDistributed",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "locker",
        "type": "address"
      }
    ],
    "name": "LockerUpdated",
    "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": false,
        "internalType": "uint16",
        "name": "protocolShareBps",
        "type": "uint16"
      }
    ],
    "name": "ProtocolShareUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "uint16",
        "name": "tokenTreasuryShareBps",
        "type": "uint16"
      }
    ],
    "name": "TokenTreasuryShareUpdated",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "treasury",
        "type": "address"
      }
    ],
    "name": "TreasuryUpdated",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "MAX_BPS",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "pairToken",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "tokenAmount",
        "type": "uint256"
      },
      {
        "internalType": "uint256",
        "name": "pairAmount",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "feeWallet",
        "type": "address"
      }
    ],
    "name": "distribute",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "locker",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "protocolShare",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "locker_",
        "type": "address"
      }
    ],
    "name": "setLocker",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint16",
        "name": "bps",
        "type": "uint16"
      }
    ],
    "name": "setProtocolShare",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "uint16",
        "name": "bps",
        "type": "uint16"
      }
    ],
    "name": "setTokenTreasuryShare",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "treasury_",
        "type": "address"
      }
    ],
    "name": "setTreasury",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "tokenTreasuryShare",
    "outputs": [
      {
        "internalType": "uint16",
        "name": "",
        "type": "uint16"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "treasury",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "weth",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]

Source code

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

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

/// @notice Splits LP fees collected by the LauncherLocker between the token
/// creator (feeWallet) and the protocol treasury. Pair-token fees paid in WETH
/// are unwrapped to native ETH before payout.
contract FeeRouter is Ownable {
    using SafeERC20 for IERC20;

    error InvalidBasisPoints();
    error ZeroAddress();
    error EthTransferFailed();
    error NotLocker();

    event FeesDistributed(
        address indexed token,
        address indexed feeWallet,
        uint256 creatorTokenAmount,
        uint256 creatorPairAmount,
        uint256 protocolTokenAmount,
        uint256 protocolPairAmount
    );
    event TreasuryUpdated(address treasury);
    event ProtocolShareUpdated(uint16 protocolShareBps);
    event TokenTreasuryShareUpdated(uint16 tokenTreasuryShareBps);
    event LockerUpdated(address locker);

    uint16 public constant MAX_BPS = 10_000;

    address public immutable weth;
    address public treasury;
    address public locker;
    /// @notice Protocol share of pair-token (ETH) fees, in bps.
    uint16 public protocolShare;
    /// @notice Protocol share of launched-token fees, in bps.
    uint16 public tokenTreasuryShare;

    constructor(address weth_, address treasury_, uint16 protocolShare_, uint16 tokenTreasuryShare_)
        Ownable(msg.sender)
    {
        if (weth_ == address(0) || treasury_ == address(0)) revert ZeroAddress();
        if (protocolShare_ > MAX_BPS || tokenTreasuryShare_ > MAX_BPS) revert InvalidBasisPoints();
        weth = weth_;
        treasury = treasury_;
        protocolShare = protocolShare_;
        tokenTreasuryShare = tokenTreasuryShare_;
    }

    receive() external payable {}

    function setTreasury(address treasury_) external onlyOwner {
        if (treasury_ == address(0)) revert ZeroAddress();
        treasury = treasury_;
        emit TreasuryUpdated(treasury_);
    }

    function setProtocolShare(uint16 bps) external onlyOwner {
        if (bps > MAX_BPS) revert InvalidBasisPoints();
        protocolShare = bps;
        emit ProtocolShareUpdated(bps);
    }

    function setTokenTreasuryShare(uint16 bps) external onlyOwner {
        if (bps > MAX_BPS) revert InvalidBasisPoints();
        tokenTreasuryShare = bps;
        emit TokenTreasuryShareUpdated(bps);
    }

    /// @notice One-time wiring of the locker (set at deployment).
    function setLocker(address locker_) external onlyOwner {
        if (locker_ == address(0)) revert ZeroAddress();
        locker = locker_;
        emit LockerUpdated(locker_);
    }

    /// @notice Distribute fees previously transferred to this contract by the locker.
    /// @dev Only callable by the locker, within the same tx as the collect.
    function distribute(address token, address pairToken, uint256 tokenAmount, uint256 pairAmount, address feeWallet)
        external
    {
        if (msg.sender != locker) revert NotLocker();

        uint256 protocolTokenAmt = (tokenAmount * tokenTreasuryShare) / MAX_BPS;
        uint256 creatorTokenAmt = tokenAmount - protocolTokenAmt;
        uint256 protocolPairAmt = (pairAmount * protocolShare) / MAX_BPS;
        uint256 creatorPairAmt = pairAmount - protocolPairAmt;

        if (protocolTokenAmt > 0) IERC20(token).safeTransfer(treasury, protocolTokenAmt);
        if (creatorTokenAmt > 0) IERC20(token).safeTransfer(feeWallet, creatorTokenAmt);

        if (pairToken == weth && pairAmount > 0) {
            IWETH9(weth).withdraw(pairAmount);
            if (protocolPairAmt > 0) _sendEth(treasury, protocolPairAmt);
            if (creatorPairAmt > 0) _sendEth(feeWallet, creatorPairAmt);
        } else {
            if (protocolPairAmt > 0) IERC20(pairToken).safeTransfer(treasury, protocolPairAmt);
            if (creatorPairAmt > 0) IERC20(pairToken).safeTransfer(feeWallet, creatorPairAmt);
        }

        emit FeesDistributed(token, feeWallet, creatorTokenAmt, creatorPairAmt, protocolTokenAmt, protocolPairAmt);
    }

    function _sendEth(address to, uint256 amount) private {
        (bool ok,) = to.call{value: amount}("");
        if (!ok) revert EthTransferFailed();
    }
}
Chain explorer1551msChain node83ms