RobinPumpFeeRouter
0xcc30e6ffd47f34154cf863e5b4ce01e161c221ab
Verification
Verified
v0.8.35+commit.47b9dedd
Type
Contract
6,796 bytes
ABI entries
22
7 read · 9 write
License
none
Contract information
- Address
- 0xcc30e6ffd47f34154cf863e5b4ce01e161c221ab
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.35+commit.47b9dedd
- Optimization
- Enabled
- Creator
- 0xd6c242b5b3…10caF849a2
- Creation tx
- 0xf92c772248…4ac4dd3ee7
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (7)
MAX_FEE_BPS() → uint16
feeBps() → uint16
feeRecipient() → address
owner() → address
v2Router() → address
v3Router() → address
weth() → address
Events (4)
FeeBpsSetFeeRecipientSetFeeRoutedOwnershipTransferred
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "_v2Router",
"type": "address"
},
{
"internalType": "address",
"name": "_v3Router",
"type": "address"
},
{
"internalType": "address",
"name": "_weth",
"type": "address"
},
{
"internalType": "address",
"name": "_feeRecipient",
"type": "address"
},
{
"internalType": "uint16",
"name": "_feeBps",
"type": "uint16"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint16",
"name": "oldBps",
"type": "uint16"
},
{
"indexed": false,
"internalType": "uint16",
"name": "newBps",
"type": "uint16"
}
],
"name": "FeeBpsSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "oldRecipient",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "newRecipient",
"type": "address"
}
],
"name": "FeeRecipientSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "trader",
"type": "address"
},
{
"indexed": false,
"internalType": "bool",
"name": "isBuy",
"type": "bool"
},
{
"indexed": false,
"internalType": "uint256",
"name": "feeEth",
"type": "uint256"
}
],
"name": "FeeRouted",
"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"
},
{
"inputs": [],
"name": "MAX_FEE_BPS",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "feeBps",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "feeRecipient",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "_feeBps",
"type": "uint16"
}
],
"name": "setFeeBps",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_feeRecipient",
"type": "address"
}
],
"name": "setFeeRecipient",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "amountOutMin",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
}
],
"name": "v2Buy",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "amountOutMin",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
}
],
"name": "v2BuyFeeOnTransfer",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "v2Router",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "amountIn",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "minEthOut",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
}
],
"name": "v2Sell",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "amountIn",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "minEthOut",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
}
],
"name": "v2SellFeeOnTransfer",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "tokenOut",
"type": "address"
},
{
"internalType": "uint24",
"name": "poolFee",
"type": "uint24"
},
{
"internalType": "uint256",
"name": "amountOutMin",
"type": "uint256"
}
],
"name": "v3Buy",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "v3Router",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "tokenIn",
"type": "address"
},
{
"internalType": "uint24",
"name": "poolFee",
"type": "uint24"
},
{
"internalType": "uint256",
"name": "amountIn",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "minEthOut",
"type": "uint256"
}
],
"name": "v3Sell",
"outputs": [],
"stateMutability": "nonpayable",
"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.24;
/**
* ███ RobinPumpFeeRouter — transparent platform-fee router for external Top Memes swaps ███
*
* Sits between the user and Uniswap V2/V3 for external meme-coin swaps only.
* RobinPump bonding-curve tokens go through the factory directly and are
* NEVER routed through this contract.
*
* Fee model:
* - On ETH-in (buy): fee = ethIn * feeBps / 10000 sent to feeRecipient,
* remaining ETH forwarded to Uniswap. Single user transaction.
* - On token-in (sell): full tokenIn amount swapped, fee deducted from ETH
* output, remainder sent to caller. Single user transaction.
*
* Owner powers are intentionally narrow:
* - setFeeBps: adjust fee rate, hard-capped at MAX_FEE_BPS (1%).
* - setFeeRecipient: change treasury address.
* - transferOwnership: hand off the role.
* Owner has NO power over any token, the bonding curve, or user funds beyond
* the routing flow described above.
*
* Safety properties:
* - Reentrancy-guarded on all external entry points.
* - Fee hard-capped at 1% (100 bps) in the contract — not just a convention.
* - No upgradability; no proxy.
* - V2/V3 router addresses are immutable after deployment.
*
* ⚠ UNAUDITED. Test on Robinhood Chain testnet (46630) before mainnet use.
*/
interface IUniswapV2Router {
function swapExactETHForTokens(
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external payable returns (uint256[] memory amounts);
function swapExactTokensForETH(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external payable;
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external;
}
interface IUniswapV3Router {
struct ExactInputSingleParams {
address tokenIn;
address tokenOut;
uint24 fee;
address recipient;
uint256 amountIn;
uint256 amountOutMinimum;
uint160 sqrtPriceLimitX96;
}
function exactInputSingle(ExactInputSingleParams calldata params)
external
payable
returns (uint256 amountOut);
}
interface IWETH9 {
function withdraw(uint256 amount) external;
}
interface IERC20Minimal {
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
contract RobinPumpFeeRouter {
/// @notice Hard cap: owner can never set feeBps above 1%.
uint16 public constant MAX_FEE_BPS = 100;
address public immutable v2Router;
address public immutable v3Router;
address public immutable weth;
address public owner;
address public feeRecipient;
uint16 public feeBps;
uint256 private _lock = 1;
modifier nonReentrant() {
require(_lock == 1, "reentrant");
_lock = 2;
_;
_lock = 1;
}
modifier onlyOwner() {
require(msg.sender == owner, "not owner");
_;
}
// ── Events ────────────────────────────────────────────────────────
event FeeRouted(address indexed trader, bool isBuy, uint256 feeEth);
event FeeBpsSet(uint16 oldBps, uint16 newBps);
event FeeRecipientSet(address oldRecipient, address newRecipient);
event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);
// ── Constructor ───────────────────────────────────────────────────
constructor(
address _v2Router,
address _v3Router,
address _weth,
address _feeRecipient,
uint16 _feeBps
) {
require(_v2Router != address(0), "v2Router zero");
require(_v3Router != address(0), "v3Router zero");
require(_weth != address(0), "weth zero");
require(_feeRecipient != address(0), "feeRecipient zero");
require(_feeBps <= MAX_FEE_BPS, "feeBps > cap");
v2Router = _v2Router;
v3Router = _v3Router;
weth = _weth;
feeRecipient = _feeRecipient;
feeBps = _feeBps;
owner = msg.sender;
}
// ── Owner setters ─────────────────────────────────────────────────
function setFeeBps(uint16 _feeBps) external onlyOwner {
require(_feeBps <= MAX_FEE_BPS, "feeBps > cap");
emit FeeBpsSet(feeBps, _feeBps);
feeBps = _feeBps;
}
function setFeeRecipient(address _feeRecipient) external onlyOwner {
require(_feeRecipient != address(0), "feeRecipient zero");
emit FeeRecipientSet(feeRecipient, _feeRecipient);
feeRecipient = _feeRecipient;
}
function transferOwnership(address newOwner) external onlyOwner {
require(newOwner != address(0), "newOwner zero");
emit OwnershipTransferred(owner, newOwner);
owner = newOwner;
}
// ── Internal helpers ──────────────────────────────────────────────
/// @dev Deducts platform fee from `amount`, sends to feeRecipient.
/// Returns (fee, remainder). If feeBps == 0, fee == 0 and the call
/// to feeRecipient is skipped (no gas wasted).
function _takeFeeEth(uint256 amount) internal returns (uint256 fee, uint256 rest) {
fee = (amount * feeBps) / 10000;
rest = amount - fee;
if (fee > 0) {
(bool ok, ) = feeRecipient.call{value: fee}("");
require(ok, "fee transfer failed");
}
}
/// @dev Approves `spender` for `token` to spend up to uint256.max if the
/// current allowance is less than `minAmount`. Uses max-approve to
/// avoid repeated approval transactions.
function _approveIfNeeded(address token, address spender, uint256 minAmount) internal {
if (IERC20Minimal(token).allowance(address(this), spender) < minAmount) {
require(IERC20Minimal(token).approve(spender, type(uint256).max), "approve failed");
}
}
// ── V2 Buy: ETH → token ───────────────────────────────────────────
/**
* @notice Swap ETH for tokens via Uniswap V2. Platform fee is taken from
* the incoming ETH; the remainder is forwarded to the V2 router.
* The Uniswap amountOutMin is enforced on the post-fee ETH, so
* the frontend must quote on post-fee ETH (ethIn * (10000 - feeBps)
* / 10000) for the displayed output to match what is received.
*
* @param token ERC-20 output token address.
* @param amountOutMin Minimum tokens to receive (Uniswap-enforced on
* post-fee ETH — set from the post-fee quote).
* @param deadline Uniswap deadline (unix seconds).
*/
function v2Buy(
address token,
uint256 amountOutMin,
uint256 deadline
) external payable nonReentrant {
require(msg.value > 0, "no eth");
(uint256 fee, uint256 ethForSwap) = _takeFeeEth(msg.value);
address[] memory path = new address[](2);
path[0] = weth;
path[1] = token;
IUniswapV2Router(v2Router).swapExactETHForTokens{value: ethForSwap}(
amountOutMin, path, msg.sender, deadline
);
emit FeeRouted(msg.sender, true, fee);
}
/**
* @notice Same as v2Buy but uses swapExactETHForTokensSupportingFeeOnTransferTokens.
* Use this for tokens with transfer taxes so the swap does not revert
* on the exact-amount check that the standard variant enforces.
*/
function v2BuyFeeOnTransfer(
address token,
uint256 amountOutMin,
uint256 deadline
) external payable nonReentrant {
require(msg.value > 0, "no eth");
(uint256 fee, uint256 ethForSwap) = _takeFeeEth(msg.value);
address[] memory path = new address[](2);
path[0] = weth;
path[1] = token;
IUniswapV2Router(v2Router).swapExactETHForTokensSupportingFeeOnTransferTokens{value: ethForSwap}(
amountOutMin, path, msg.sender, deadline
);
emit FeeRouted(msg.sender, true, fee);
}
// ── V2 Sell: token → ETH ──────────────────────────────────────────
/**
* @notice Swap tokens for ETH via Uniswap V2. Full tokenIn is swapped;
* platform fee is deducted from the ETH output.
*
* @param token ERC-20 input token (caller must have approved this contract).
* @param amountIn Token amount to sell.
* @param minEthOut Minimum ETH the CALLER receives AFTER the platform fee.
* Set this from postFeeQuote * (10000 - slippageBps) / 10000.
* @param deadline Uniswap deadline (unix seconds).
*/
function v2Sell(
address token,
uint256 amountIn,
uint256 minEthOut,
uint256 deadline
) external nonReentrant {
require(amountIn > 0, "no tokens");
require(
IERC20Minimal(token).transferFrom(msg.sender, address(this), amountIn),
"transferFrom failed"
);
_approveIfNeeded(token, v2Router, amountIn);
address[] memory path = new address[](2);
path[0] = token;
path[1] = weth;
uint256[] memory amounts = IUniswapV2Router(v2Router).swapExactTokensForETH(
amountIn, 0, path, address(this), deadline
);
uint256 ethOut = amounts[amounts.length - 1];
(uint256 fee, uint256 ethToUser) = _takeFeeEth(ethOut);
require(ethToUser >= minEthOut, "slippage: eth out too low");
(bool ok, ) = msg.sender.call{value: ethToUser}("");
require(ok, "eth transfer failed");
emit FeeRouted(msg.sender, false, fee);
}
/**
* @notice Same as v2Sell but uses swapExactTokensForETHSupportingFeeOnTransferTokens.
* Use this for tokens with transfer taxes.
*/
function v2SellFeeOnTransfer(
address token,
uint256 amountIn,
uint256 minEthOut,
uint256 deadline
) external nonReentrant {
require(amountIn > 0, "no tokens");
require(
IERC20Minimal(token).transferFrom(msg.sender, address(this), amountIn),
"transferFrom failed"
);
_approveIfNeeded(token, v2Router, amountIn);
address[] memory path = new address[](2);
path[0] = token;
path[1] = weth;
uint256 ethBefore = address(this).balance;
IUniswapV2Router(v2Router).swapExactTokensForETHSupportingFeeOnTransferTokens(
amountIn, 0, path, address(this), deadline
);
uint256 ethOut = address(this).balance - ethBefore;
(uint256 fee, uint256 ethToUser) = _takeFeeEth(ethOut);
require(ethToUser >= minEthOut, "slippage: eth out too low");
(bool ok, ) = msg.sender.call{value: ethToUser}("");
require(ok, "eth transfer failed");
emit FeeRouted(msg.sender, false, fee);
}
// ── V3 Buy: ETH → token ───────────────────────────────────────────
/**
* @notice Swap ETH for tokens via Uniswap V3 (SwapRouter02 exactInputSingle).
* Platform fee taken from incoming ETH; remainder forwarded to V3 router
* which wraps it to WETH and swaps. Output tokens go directly to the caller.
*
* @param tokenOut ERC-20 output token.
* @param poolFee V3 pool fee tier (e.g. 500, 3000, 10000).
* @param amountOutMin Minimum tokens out (Uniswap-enforced on post-fee ETH).
*/
function v3Buy(
address tokenOut,
uint24 poolFee,
uint256 amountOutMin
) external payable nonReentrant {
require(msg.value > 0, "no eth");
(uint256 fee, uint256 ethForSwap) = _takeFeeEth(msg.value);
IUniswapV3Router(v3Router).exactInputSingle{value: ethForSwap}(
IUniswapV3Router.ExactInputSingleParams({
tokenIn: weth,
tokenOut: tokenOut,
fee: poolFee,
recipient: msg.sender,
amountIn: ethForSwap,
amountOutMinimum: amountOutMin,
sqrtPriceLimitX96: 0
})
);
emit FeeRouted(msg.sender, true, fee);
}
// ── V3 Sell: token → ETH ──────────────────────────────────────────
/**
* @notice Swap tokens for ETH via Uniswap V3. Internally does:
* exactInputSingle(token → WETH, recipient = this)
* + WETH.withdraw()
* + platform fee deduction
* + ETH sent to caller.
* This replaces the frontend multicall(exactInputSingle + unwrapWETH9)
* pattern with a single contract call.
*
* @param tokenIn ERC-20 input token (caller must have approved this contract).
* @param poolFee V3 pool fee tier.
* @param amountIn Token amount to sell.
* @param minEthOut Minimum ETH the CALLER receives AFTER the platform fee.
*/
function v3Sell(
address tokenIn,
uint24 poolFee,
uint256 amountIn,
uint256 minEthOut
) external nonReentrant {
require(amountIn > 0, "no tokens");
require(
IERC20Minimal(tokenIn).transferFrom(msg.sender, address(this), amountIn),
"transferFrom failed"
);
_approveIfNeeded(tokenIn, v3Router, amountIn);
uint256 wethOut = IUniswapV3Router(v3Router).exactInputSingle(
IUniswapV3Router.ExactInputSingleParams({
tokenIn: tokenIn,
tokenOut: weth,
fee: poolFee,
recipient: address(this),
amountIn: amountIn,
amountOutMinimum: 0,
sqrtPriceLimitX96: 0
})
);
IWETH9(weth).withdraw(wethOut);
(uint256 fee, uint256 ethToUser) = _takeFeeEth(wethOut);
require(ethToUser >= minEthOut, "slippage: eth out too low");
(bool ok, ) = msg.sender.call{value: ethToUser}("");
require(ok, "eth transfer failed");
emit FeeRouted(msg.sender, false, fee);
}
/// @dev Accepts ETH from WETH.withdraw() during v3Sell, and any other
/// incidental ETH sends. The contract is designed to hold zero ETH
/// between transactions — any stray balance is swept on the next sell.
receive() external payable {}
}
Chain explorer1839msChain node78ms