FeeSwapRouterV3
0xac33d51cf764859a678bcc9b255edbde69956a0b
Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
5,274 bytes
ABI entries
36
11 read · 9 write
License
none
Contract information
- Address
- 0xac33d51cf764859a678bcc9b255edbde69956a0b
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.24+commit.e11b9ed9
- Optimization
- Enabled
- Creator
- 0x0457a74fdd…1484C55933
- Creation tx
- 0x98f9f7fa4b…d246b6043d
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (11)
FEE_TIMELOCK() → uint256
MAX_FEE_BPS() → uint16
feeBps() → uint16
feeRecipient() → address
feeRecipientEta() → uint256
owner() → address
pendingFeeRecipient() → address
pendingOwner() → address
poolFee() → uint24
router() → address
weth() → address
Events (7)
FeeBpsUpdatedFeeRecipientCancelledFeeRecipientProposedFeeRecipientUpdatedFeeSwapOwnershipTransferStartedOwnershipTransferred
ABI
[
{
"inputs": [
{
"internalType": "contract ISwapRouterV3",
"name": "router_",
"type": "address"
},
{
"internalType": "address",
"name": "weth_",
"type": "address"
},
{
"internalType": "address",
"name": "feeRecipient_",
"type": "address"
},
{
"internalType": "uint24",
"name": "poolFee_",
"type": "uint24"
},
{
"internalType": "uint16",
"name": "feeBps_",
"type": "uint16"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [
{
"internalType": "address",
"name": "target",
"type": "address"
}
],
"name": "AddressEmptyCode",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "AddressInsufficientBalance",
"type": "error"
},
{
"inputs": [],
"name": "FailedInnerCall",
"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"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint16",
"name": "newFeeBps",
"type": "uint16"
}
],
"name": "FeeBpsUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "cancelled",
"type": "address"
}
],
"name": "FeeRecipientCancelled",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "next",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "eta",
"type": "uint256"
}
],
"name": "FeeRecipientProposed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "prev",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "next",
"type": "address"
}
],
"name": "FeeRecipientUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "trader",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": false,
"internalType": "bool",
"name": "buy",
"type": "bool"
},
{
"indexed": false,
"internalType": "uint256",
"name": "ethAmount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "fee",
"type": "uint256"
}
],
"name": "FeeSwap",
"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"
},
{
"inputs": [],
"name": "FEE_TIMELOCK",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_FEE_BPS",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "acceptOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "applyFeeRecipient",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "minTokensOut",
"type": "uint256"
}
],
"name": "buyWithFee",
"outputs": [
{
"internalType": "uint256",
"name": "tokensOut",
"type": "uint256"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "cancelFeeRecipient",
"outputs": [],
"stateMutability": "nonpayable",
"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": "feeRecipientEta",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pendingFeeRecipient",
"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": "poolFee",
"outputs": [
{
"internalType": "uint24",
"name": "",
"type": "uint24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "r",
"type": "address"
}
],
"name": "proposeFeeRecipient",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "router",
"outputs": [
{
"internalType": "contract ISwapRouterV3",
"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"
}
],
"name": "sellWithFee",
"outputs": [
{
"internalType": "uint256",
"name": "ethOut",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "newFeeBps",
"type": "uint16"
}
],
"name": "setFeeBps",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"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;
import "@openzeppelin/contracts/access/Ownable2Step.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./interfaces/IUniswapV3.sol";
/// @dev Swap subset of the canonical Uniswap V3 SwapRouter02 on Robinhood Chain.
/// SwapRouter02's exactInputSingle has no deadline field.
interface ISwapRouterV3 {
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);
}
/// @title FeeSwapRouterV3
/// @custom:mission We're building a transparent launchpad. Verified contracts, no
/// hidden owner over tokens, no token tax, liquidity you can never rug. Read the
/// code and verify everything yourself. Every launch is a drop. — rawbin
/// @notice Thin wrapper over Uniswap V3's SwapRouter02 that takes the Rawbin platform
/// fee on swaps made through the site (both Instant-DEX launches and migrated
/// tokens trade on V3 pools). Completely optional: anyone can still trade the
/// pool directly on Uniswap and pay no platform fee — this only prices in the
/// convenience of the UI. The token side always goes straight to the trader;
/// the fee is only ever taken from the ETH leg.
contract FeeSwapRouterV3 is Ownable2Step, ReentrancyGuard {
using SafeERC20 for IERC20;
ISwapRouterV3 public immutable router;
address public immutable weth;
uint24 public immutable poolFee; // Uniswap fee tier used for our pools (1% = 10000)
/// @notice Platform fee in basis points. Owner-adjustable, hard-capped at 3% — a
/// trader can verify the maximum they could ever be charged.
uint16 public feeBps;
uint16 public constant MAX_FEE_BPS = 300; // hard cap 3%
/// @notice Where the platform fee goes. Mutable behind a 24h timelock (propose ->
/// apply), so it can move to a multisig later without a redeploy.
address public feeRecipient;
address public pendingFeeRecipient;
uint256 public feeRecipientEta;
uint256 public constant FEE_TIMELOCK = 24 hours;
event FeeSwap(address indexed trader, address indexed token, bool buy, uint256 ethAmount, uint256 fee);
event FeeBpsUpdated(uint16 newFeeBps);
event FeeRecipientProposed(address indexed next, uint256 eta);
event FeeRecipientUpdated(address indexed prev, address indexed next);
event FeeRecipientCancelled(address indexed cancelled);
constructor(
ISwapRouterV3 router_,
address weth_,
address feeRecipient_,
uint24 poolFee_,
uint16 feeBps_
) Ownable(feeRecipient_) {
require(feeRecipient_ != address(0), "feeRecipient=0");
require(weth_ != address(0), "weth=0");
require(feeBps_ <= MAX_FEE_BPS, "fee too high");
router = router_;
weth = weth_;
feeRecipient = feeRecipient_;
poolFee = poolFee_;
feeBps = feeBps_;
}
// ------------------------------------------------------------------ admin
/// @notice Adjust the platform fee (immediate, capped). No fee can exceed 3%.
function setFeeBps(uint16 newFeeBps) external onlyOwner {
require(newFeeBps <= MAX_FEE_BPS, "fee too high");
feeBps = newFeeBps;
emit FeeBpsUpdated(newFeeBps);
}
function proposeFeeRecipient(address r) external onlyOwner {
require(r != address(0), "feeRecipient=0");
pendingFeeRecipient = r;
feeRecipientEta = block.timestamp + FEE_TIMELOCK;
emit FeeRecipientProposed(r, feeRecipientEta);
}
function applyFeeRecipient() external onlyOwner {
// forge-lint: disable-next-line(block-timestamp)
require(feeRecipientEta != 0 && block.timestamp >= feeRecipientEta, "timelock");
emit FeeRecipientUpdated(feeRecipient, pendingFeeRecipient);
feeRecipient = pendingFeeRecipient;
pendingFeeRecipient = address(0);
feeRecipientEta = 0;
}
function cancelFeeRecipient() external onlyOwner {
emit FeeRecipientCancelled(pendingFeeRecipient);
pendingFeeRecipient = address(0);
feeRecipientEta = 0;
}
/// @notice Buy `token` with ETH: platform fee off the top, remainder swapped via the pool.
function buyWithFee(address token, uint256 minTokensOut)
external payable nonReentrant returns (uint256 tokensOut)
{
require(msg.value > 0, "eth=0");
uint256 fee = (msg.value * feeBps) / 10_000;
uint256 swapAmount = msg.value - fee;
IWETH9(weth).deposit{value: swapAmount}();
IERC20(weth).forceApprove(address(router), swapAmount);
tokensOut = router.exactInputSingle(
ISwapRouterV3.ExactInputSingleParams({
tokenIn: weth,
tokenOut: token,
fee: poolFee,
recipient: msg.sender,
amountIn: swapAmount,
amountOutMinimum: minTokensOut,
sqrtPriceLimitX96: 0
})
);
if (fee > 0) {
(bool ok, ) = feeRecipient.call{value: fee}("");
require(ok, "fee transfer failed");
}
emit FeeSwap(msg.sender, token, true, msg.value, fee);
}
/// @notice Sell `token` for ETH via the pool; platform fee taken from the ETH proceeds.
function sellWithFee(address token, uint256 amountIn, uint256 minEthOut)
external nonReentrant returns (uint256 ethOut)
{
require(amountIn > 0, "amount=0");
IERC20(token).safeTransferFrom(msg.sender, address(this), amountIn);
IERC20(token).forceApprove(address(router), amountIn);
uint256 gross = router.exactInputSingle(
ISwapRouterV3.ExactInputSingleParams({
tokenIn: token,
tokenOut: weth,
fee: poolFee,
recipient: address(this),
amountIn: amountIn,
amountOutMinimum: 0, // slippage enforced on the NET amount below
sqrtPriceLimitX96: 0
})
);
IWETH9(weth).withdraw(gross);
uint256 fee = (gross * feeBps) / 10_000;
ethOut = gross - fee;
require(ethOut >= minEthOut, "slippage");
if (fee > 0) {
(bool okFee, ) = feeRecipient.call{value: fee}("");
require(okFee, "fee transfer failed");
}
(bool okUser, ) = msg.sender.call{value: ethOut}("");
require(okUser, "eth transfer failed");
emit FeeSwap(msg.sender, token, false, gross, fee);
}
/// @dev Receives ETH from unwrapping WETH during sellWithFee.
receive() external payable {}
}
Chain explorer5325msChain node87ms