Multicall3Router
0x6222f99443a0d75bd96d40f2904606f60f37cdc2
Verification
Verified
v0.8.25+commit.b61c2a91
Type
Contract
4,795 bytes
ABI entries
15
2 read · 6 write
License
none
Contract information
- Address
- 0x6222f99443a0d75bd96d40f2904606f60f37cdc2
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.25+commit.b61c2a91
- Optimization
- Enabled
- Creator
- 0x4e59b44847…26c0B4956C
- Creation tx
- 0xbd789b7634…a5224e3264
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (2)
multicall3() → address
permit2() → address
Events (1)
WheelxRouter
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "_multicall3",
"type": "address"
},
{
"internalType": "contract IPermit2",
"name": "_permit2",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "ArrayLengthsMismatch",
"type": "error"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "returnData",
"type": "bytes"
}
],
"name": "CallFailed",
"type": "error"
},
{
"inputs": [],
"name": "InvalidOffset",
"type": "error"
},
{
"inputs": [],
"name": "InvalidTarget",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "request_id",
"type": "bytes32"
}
],
"name": "WheelxRouter",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "refundTo",
"type": "address"
}
],
"name": "cleanupERC20",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address[]",
"name": "tokens",
"type": "address[]"
},
{
"internalType": "address[]",
"name": "recipients",
"type": "address[]"
},
{
"internalType": "uint256[]",
"name": "amounts",
"type": "uint256[]"
}
],
"name": "cleanupErc20s",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "multicall3",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "permit2",
"outputs": [
{
"internalType": "contract IPermit2",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "safeApproveIfBelow",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "safeApprovePermit2",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "calls",
"type": "bytes"
},
{
"internalType": "address",
"name": "refundTo",
"type": "address"
},
{
"internalType": "bytes32",
"name": "request_id",
"type": "bytes32"
}
],
"name": "safeMultiCall",
"outputs": [
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "sellToken",
"type": "address"
},
{
"internalType": "uint256",
"name": "bps",
"type": "uint256"
},
{
"internalType": "address",
"name": "pool",
"type": "address"
},
{
"internalType": "uint256",
"name": "offset",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "sellToPool",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
import {IRouter} from "./libs/IRouter.sol";
import { IPermit2 } from "@uniswap/permit2/src/interfaces/IPermit2.sol";
library Panic {
function panic(uint256 code) internal pure {
assembly ("memory-safe") {
mstore(0x00, 0x4e487b71) // selector for `Panic(uint256)`
mstore(0x20, code)
revert(0x1c, 0x24)
}
}
// https://docs.soliditylang.org/en/latest/control-structures.html#panic-via-assert-and-error-via-require
uint8 internal constant GENERIC = 0x00;
uint8 internal constant ASSERT_FAIL = 0x01;
uint8 internal constant ARITHMETIC_OVERFLOW = 0x11;
uint8 internal constant DIVISION_BY_ZERO = 0x12;
uint8 internal constant ENUM_CAST = 0x21;
uint8 internal constant CORRUPT_STORAGE_ARRAY = 0x22;
uint8 internal constant POP_EMPTY_ARRAY = 0x31;
uint8 internal constant ARRAY_OUT_OF_BOUNDS = 0x32;
uint8 internal constant OUT_OF_MEMORY = 0x41;
uint8 internal constant ZERO_FUNCTION_POINTER = 0x51;
}
library Revert {
function _revert(bytes memory reason) internal pure {
assembly ("memory-safe") {
revert(add(reason, 0x20), mload(reason))
}
}
function maybeRevert(bool success, bytes memory reason) internal pure {
if (!success) {
_revert(reason);
}
}
}
library SafeApproveLib {
function safeApprove(IERC20 token, address to, uint256 amount) internal {
assembly ("memory-safe") {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
// Storing `amount` clobbers the upper bits of the free memory pointer, but those bits
// can never be set without running into an OOG, so it's safe. We'll restore them to
// zero at the end.
mstore(0x00, 0x095ea7b3000000000000000000000000) // Selector for `approve(address,uint256)`, with `to`'s padding.
// Calldata starts at offset 16 and is 68 bytes long (2 * 32 + 4).
// If there is returndata (optional) we copy the first 32 bytes into the first slot of memory.
if iszero(call(gas(), token, 0x00, 0x10, 0x44, 0x00, 0x20)) {
let ptr := and(0xffffffffffffffffffffffff, mload(0x40))
returndatacopy(ptr, 0x00, returndatasize())
revert(ptr, returndatasize())
}
// We check that the call either returned exactly 1 [true] (can't just be non-zero
// data), or had no return data.
if iszero(or(and(eq(mload(0x00), 0x01), lt(0x1f, returndatasize())), iszero(returndatasize()))) {
mstore(0x00, 0x3e3f8f73) // Selector for `ApproveFailed()`
revert(0x1c, 0x04)
}
mstore(0x34, 0x00) // Restore the part of the free memory pointer that was overwritten.
}
}
function safeApproveIfBelow(IERC20 token, address spender, uint256 amount) internal {
uint256 allowance = token.allowance(address(this), spender);
if (allowance < amount) {
if (allowance != 0) {
safeApprove(token, spender, 0);
}
safeApprove(token, spender, type(uint256).max);
}
}
}
contract Multicall3Router is IRouter {
using Revert for bool;
using SafeTransferLib for address;
using SafeApproveLib for IERC20;
uint256 internal constant BASIS = 10_000;
error CallFailed(bytes returnData);
error InvalidOffset();
error InvalidTarget();
error ArrayLengthsMismatch();
event WheelxRouter(bytes32 indexed request_id);
address public immutable multicall3;
IPermit2 public immutable permit2;
// Set the Multicall3 address (e.g., 0xcA11bde05977b3631167028862bE2a173976CA11)
constructor(address _multicall3, IPermit2 _permit2) {
multicall3 = _multicall3;
permit2 = _permit2;
}
// Allow ETH transfers
receive() external payable {}
// a safer version of multicall3, which makes sure remaining eth is sent away.
function safeMultiCall(
bytes calldata calls,
address refundTo,
bytes32 request_id
) external payable returns (bytes memory) {
// perform multicall
(bool success, bytes memory returnData) = multicall3.delegatecall(calls);
if (!success) {
revert CallFailed(returnData);
}
if (address(this).balance > 0) {
// If refundTo is address(0), refund to msg.sender
address refundAddr = refundTo == address(0) ? msg.sender : refundTo;
uint256 amount = address(this).balance;
refundAddr.safeTransferETH(amount);
}
emit WheelxRouter(request_id);
return returnData;
}
function sellToPool(address sellToken, uint256 bps, address pool, uint256 offset, bytes memory data) external {
bool success;
bytes memory returnData;
uint256 value;
if (sellToken == address(0)) {
value = (address(this).balance * bps) / BASIS;
if (data.length == 0) {
if (offset != 0) revert InvalidOffset();
(success, returnData) = payable(pool).call{value: value}("");
success.maybeRevert(returnData);
return;
} else {
if ((offset += 32) > data.length) {
Panic.panic(Panic.ARRAY_OUT_OF_BOUNDS);
}
assembly ("memory-safe") {
mstore(add(data, offset), value)
}
}
} else {
uint256 amount = (IERC20(sellToken).balanceOf(address(this)) * bps) / BASIS;
if ((offset += 32) > data.length) {
Panic.panic(Panic.ARRAY_OUT_OF_BOUNDS);
}
assembly ("memory-safe") {
mstore(add(data, offset), amount)
}
if (address(sellToken) != pool) {
IERC20(sellToken).safeApproveIfBelow(pool, amount);
}
}
(success, returnData) = payable(pool).call{value: value}(data);
success.maybeRevert(returnData);
// forbid sending data to EOAs
if (returnData.length == 0 && pool.code.length == 0) revert InvalidTarget();
}
function cleanupERC20(address token, address refundTo) external {
// Check the router's balance for the token
uint256 balance = IERC20(token).balanceOf(address(this));
// Transfer the token to the refundTo address
if (balance > 0) {
IERC20(token).transfer(refundTo, balance);
}
}
function safeApproveIfBelow(address token, address spender, uint256 amount) external {
IERC20(token).safeApproveIfBelow(spender, amount);
}
function safeApprovePermit2(address token, address spender, uint256 amount) external {
// Approve the spender to spend the specified amount of tokens through permit2
IERC20(token).safeApproveIfBelow(address(permit2), amount);
(uint256 allowance, uint48 expiration,) = permit2.allowance(address(this), token, spender);
if (allowance < amount || expiration <= block.timestamp) {
permit2.approve(token, spender, type(uint160).max, type(uint48).max);
}
}
function cleanupErc20s(
address[] calldata tokens,
address[] calldata recipients,
uint256[] calldata amounts
) public virtual {
// Revert if array lengths do not match
if (
tokens.length != amounts.length ||
amounts.length != recipients.length
) {
revert ArrayLengthsMismatch();
}
for (uint256 i; i < tokens.length; i++) {
address token = tokens[i];
address recipient = recipients[i];
// Get the amount to transfer
uint256 amount = amounts[i] == 0
? IERC20(token).balanceOf(address(this))
: amounts[i];
// Transfer the token to the recipient address
token.safeTransfer(recipient, amount);
}
}
}
Chain explorer4776msChain node84ms