FeeRouter
0xba3a36b648bce86c62a82134c38fbfc51a174c8e
Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
6,385 bytes
ABI entries
51
17 read · 12 write
License
none
Contract information
- Address
- 0xba3a36b648bce86c62a82134c38fbfc51a174c8e
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.24+commit.e11b9ed9
- Optimization
- Enabled
- Creator
- 0x0d45254857…2E1FA4aF74
- Creation tx
- 0xbbaa3a8b16…d3c217a576
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (17)
BPS() → uint16
DEAD() → address
burnBps() → uint16
buybackBps() → uint16
indexBps() → uint16
indexManager() → address
isKeeper(address) → bool
maxSlippageBps() → uint16
memedex() → address
opsBps() → uint16
opsWallet() → address
owner() → address
pendingOwner() → address
router() → address
treasury() → address
treasuryBps() → uint16
weth() → address
Events (11)
BurnBpsSetBuybackDistributedKeeperSetMaxSlippageSetMemedexSetOwnershipTransferStartedOwnershipTransferredRouterSetSplitsSetTargetsSet
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "initialOwner",
"type": "address"
},
{
"internalType": "address",
"name": "weth_",
"type": "address"
},
{
"internalType": "address",
"name": "router_",
"type": "address"
},
{
"internalType": "address",
"name": "indexManager_",
"type": "address"
},
{
"internalType": "address",
"name": "treasury_",
"type": "address"
},
{
"internalType": "address",
"name": "opsWallet_",
"type": "address"
},
{
"internalType": "uint16",
"name": "indexBps_",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "buybackBps_",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "treasuryBps_",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "opsBps_",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "maxSlippageBps_",
"type": "uint16"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "BadSplit",
"type": "error"
},
{
"inputs": [],
"name": "MemedexAlreadySet",
"type": "error"
},
{
"inputs": [],
"name": "NotKeeper",
"type": "error"
},
{
"inputs": [],
"name": "NothingToDistribute",
"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": "ZeroAddress",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint16",
"name": "bps",
"type": "uint16"
}
],
"name": "BurnBpsSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "wethIn",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "memedexBought",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "burned",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "toTreasury",
"type": "uint256"
}
],
"name": "Buyback",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "wethTotal",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "toIndex",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "buyback",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "toTreasury",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "toOps",
"type": "uint256"
}
],
"name": "Distributed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "keeper",
"type": "address"
},
{
"indexed": false,
"internalType": "bool",
"name": "enabled",
"type": "bool"
}
],
"name": "KeeperSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint16",
"name": "bps",
"type": "uint16"
}
],
"name": "MaxSlippageSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "memedex",
"type": "address"
}
],
"name": "MemedexSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferStarted",
"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": "address",
"name": "router",
"type": "address"
}
],
"name": "RouterSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint16",
"name": "indexBps",
"type": "uint16"
},
{
"indexed": false,
"internalType": "uint16",
"name": "buybackBps",
"type": "uint16"
},
{
"indexed": false,
"internalType": "uint16",
"name": "treasuryBps",
"type": "uint16"
},
{
"indexed": false,
"internalType": "uint16",
"name": "opsBps",
"type": "uint16"
}
],
"name": "SplitsSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "indexManager",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "treasury",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "opsWallet",
"type": "address"
}
],
"name": "TargetsSet",
"type": "event"
},
{
"inputs": [],
"name": "BPS",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "DEAD",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "acceptOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "burnBps",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "buybackBps",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "distribute",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "indexBps",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "indexManager",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "isKeeper",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "maxSlippageBps",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "memedex",
"outputs": [
{
"internalType": "contract IERC20",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "opsBps",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "opsWallet",
"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": "pendingOwner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "rescue",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "router",
"outputs": [
{
"internalType": "contract IUniswapV2Router",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "bps",
"type": "uint16"
}
],
"name": "setBurnBps",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "keeper",
"type": "address"
},
{
"internalType": "bool",
"name": "enabled",
"type": "bool"
}
],
"name": "setKeeper",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "bps",
"type": "uint16"
}
],
"name": "setMaxSlippage",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "memedex_",
"type": "address"
}
],
"name": "setMemedex",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "router_",
"type": "address"
}
],
"name": "setRouter",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "indexBps_",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "buybackBps_",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "treasuryBps_",
"type": "uint16"
},
{
"internalType": "uint16",
"name": "opsBps_",
"type": "uint16"
}
],
"name": "setSplits",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "indexManager_",
"type": "address"
},
{
"internalType": "address",
"name": "treasury_",
"type": "address"
},
{
"internalType": "address",
"name": "opsWallet_",
"type": "address"
}
],
"name": "setTargets",
"outputs": [],
"stateMutability": "nonpayable",
"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": "treasuryBps",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "weth",
"outputs": [
{
"internalType": "contract IERC20",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {Governed} from "./access/Governed.sol";
import {IUniswapV2Router} from "./interfaces/IUniswapV2Router.sol";
import {IWETH} from "./interfaces/IWETH.sol";
/// @title FeeRouter
/// @notice The flap.sh vault beneficiary: receives the trade tax (as native ETH on RH Chain, or WETH) and
/// splits it four ways — memecoin index buys, MEMEDEX buyback (burn + Treasury), WETH reserve, ops.
/// Splits are bps that must sum to 10000. Any native ETH received is auto-wrapped to WETH on distribute.
contract FeeRouter is Governed, ReentrancyGuard {
using SafeERC20 for IERC20;
uint16 public constant BPS = 10_000;
IERC20 public immutable weth;
IERC20 public memedex; // set once AFTER the flap token is created (its address isn't known at deploy)
IUniswapV2Router public router;
address public indexManager;
address public treasury;
address public opsWallet;
uint16 public indexBps;
uint16 public buybackBps;
uint16 public treasuryBps;
uint16 public opsBps;
uint16 public maxSlippageBps; // guard on the buyback swap
uint16 public burnBps = 6600; // portion of each MEMEDEX buyback that is BURNED (rest → treasury). Default 66%.
address public constant DEAD = 0x000000000000000000000000000000000000dEaD;
event SplitsSet(uint16 indexBps, uint16 buybackBps, uint16 treasuryBps, uint16 opsBps);
event TargetsSet(address indexManager, address treasury, address opsWallet);
event Distributed(uint256 wethTotal, uint256 toIndex, uint256 buyback, uint256 toTreasury, uint256 toOps);
event RouterSet(address router);
event MemedexSet(address memedex);
event MaxSlippageSet(uint16 bps);
event BurnBpsSet(uint16 bps);
event Buyback(uint256 wethIn, uint256 memedexBought, uint256 burned, uint256 toTreasury);
error BadSplit();
error ZeroAddress();
error NothingToDistribute();
error MemedexAlreadySet();
constructor(
address initialOwner,
address weth_,
address router_,
address indexManager_,
address treasury_,
address opsWallet_,
uint16 indexBps_,
uint16 buybackBps_,
uint16 treasuryBps_,
uint16 opsBps_,
uint16 maxSlippageBps_
) Governed(initialOwner) {
if (
weth_ == address(0) || router_ == address(0) || indexManager_ == address(0)
|| treasury_ == address(0) || opsWallet_ == address(0)
) revert ZeroAddress();
if (uint256(indexBps_) + buybackBps_ + treasuryBps_ + opsBps_ != BPS) revert BadSplit();
weth = IERC20(weth_);
router = IUniswapV2Router(router_);
indexManager = indexManager_;
treasury = treasury_;
opsWallet = opsWallet_;
indexBps = indexBps_;
buybackBps = buybackBps_;
treasuryBps = treasuryBps_;
opsBps = opsBps_;
maxSlippageBps = maxSlippageBps_;
}
// --- admin -------------------------------------------------------------
function setSplits(uint16 indexBps_, uint16 buybackBps_, uint16 treasuryBps_, uint16 opsBps_)
external
onlyOwner
{
if (uint256(indexBps_) + buybackBps_ + treasuryBps_ + opsBps_ != BPS) revert BadSplit();
indexBps = indexBps_;
buybackBps = buybackBps_;
treasuryBps = treasuryBps_;
opsBps = opsBps_;
emit SplitsSet(indexBps_, buybackBps_, treasuryBps_, opsBps_);
}
function setTargets(address indexManager_, address treasury_, address opsWallet_) external onlyOwner {
if (indexManager_ == address(0) || treasury_ == address(0) || opsWallet_ == address(0)) {
revert ZeroAddress();
}
indexManager = indexManager_;
treasury = treasury_;
opsWallet = opsWallet_;
emit TargetsSet(indexManager_, treasury_, opsWallet_);
}
function setRouter(address router_) external onlyOwner {
if (router_ == address(0)) revert ZeroAddress();
router = IUniswapV2Router(router_);
emit RouterSet(router_);
}
/// @notice Wire the MEMEDEX token address once the flap token exists. Set-once. Until it is set, the
/// buyback slice is routed to the Treasury as WETH instead of buying MEMEDEX.
function setMemedex(address memedex_) external onlyOwner {
if (memedex_ == address(0)) revert ZeroAddress();
if (address(memedex) != address(0)) revert MemedexAlreadySet();
memedex = IERC20(memedex_);
emit MemedexSet(memedex_);
}
function setMaxSlippage(uint16 bps) external onlyOwner {
if (bps > BPS) revert BadSplit();
maxSlippageBps = bps;
emit MaxSlippageSet(bps);
}
/// @notice Portion (bps) of each MEMEDEX buyback that is burned (sent to dead); the rest goes to Treasury.
function setBurnBps(uint16 bps) external onlyOwner {
if (bps > BPS) revert BadSplit();
burnBps = bps;
emit BurnBpsSet(bps);
}
// --- core --------------------------------------------------------------
/// @notice Accept native ETH from flap's Tax Splitter (RH Chain pays in ETH). Wrapped on distribute().
receive() external payable {}
/// @notice Split the contract's entire WETH balance per the configured bps. Keeper-callable.
function distribute() external onlyKeeper nonReentrant {
// flap delivers the tax in the payment token (ETH on RH Chain). Wrap any native ETH to WETH first.
uint256 nativeBal = address(this).balance;
if (nativeBal > 0) IWETH(address(weth)).deposit{value: nativeBal}();
uint256 bal = weth.balanceOf(address(this));
if (bal == 0) revert NothingToDistribute();
uint256 toIndex = (bal * indexBps) / BPS;
uint256 buyback = (bal * buybackBps) / BPS;
uint256 toTreasury = (bal * treasuryBps) / BPS;
// ops gets the remainder so the sum is always exactly `bal` (no dust stranded).
uint256 toOps = bal - toIndex - buyback - toTreasury;
if (toIndex > 0) weth.safeTransfer(indexManager, toIndex);
if (toTreasury > 0) weth.safeTransfer(treasury, toTreasury);
if (toOps > 0) weth.safeTransfer(opsWallet, toOps);
if (buyback > 0) {
// Before the flap token is wired, park the buyback slice in the Treasury (as WETH) rather than
// reverting — so fee routing works from the moment the token trades.
if (address(memedex) == address(0)) {
weth.safeTransfer(treasury, buyback);
} else {
_buyback(buyback);
}
}
emit Distributed(bal, toIndex, buyback, toTreasury, toOps);
}
/// @dev Buy MEMEDEX with WETH, burn `burnBps` of it (to dead) and send the rest to the Treasury.
/// Slippage-guarded. NOTE: the FeeRouter should be tax-exempt on the flap MEMEDEX token, else the buy
/// pays MEMEDEX's own buy tax (it recycles back via the Tax Splitter, but wastes gas/slippage).
function _buyback(uint256 wethIn) private {
address[] memory path = new address[](2);
path[0] = address(weth);
path[1] = address(memedex);
uint256[] memory quote = router.getAmountsOut(wethIn, path);
uint256 minOut = (quote[1] * (BPS - maxSlippageBps)) / BPS;
uint256 beforeBal = memedex.balanceOf(address(this));
IERC20(address(weth)).forceApprove(address(router), wethIn);
router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
wethIn, minOut, path, address(this), block.timestamp
);
uint256 bought = memedex.balanceOf(address(this)) - beforeBal;
uint256 toBurn = (bought * burnBps) / BPS;
uint256 toTreasury = bought - toBurn;
if (toBurn > 0) memedex.safeTransfer(DEAD, toBurn); // permanent burn → deflationary
if (toTreasury > 0) memedex.safeTransfer(treasury, toTreasury); // dry powder
emit Buyback(wethIn, bought, toBurn, toTreasury);
}
/// @notice Recover tokens accidentally sent here (not WETH, which is the working balance).
function rescue(address token, address to, uint256 amount) external onlyOwner {
if (to == address(0)) revert ZeroAddress();
IERC20(token).safeTransfer(to, amount);
}
}
Chain explorer2977msChain node73ms