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
- Creator
- 0xE9C1a82D6b…eACCe42A1c
- 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