QuiverRouter
0xa5ced4a472586b79c8d744f1b50b8d5a703b1b5d
Verification
Verified
v0.8.26+commit.8a97fa7a
Type
Contract
3,982 bytes
ABI entries
13
5 read · 3 write
License
none
Contract information
- Address
- 0xa5ced4a472586b79c8d744f1b50b8d5a703b1b5d
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.26+commit.8a97fa7a
- Optimization
- Enabled
- Creator
- 0x7817F4e44a…d51e5Ea846
- Creation tx
- 0xa6bab71ed5…aaf79ea7ee
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (5)
LP_FEE() → uint24
TICK_SPACING() → int24
hook() → address
poolManager() → address
weth() → address
ABI
[
{
"inputs": [
{
"internalType": "contract IPoolManager",
"name": "pm",
"type": "address"
},
{
"internalType": "contract IWETH",
"name": "weth_",
"type": "address"
},
{
"internalType": "address",
"name": "hook_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "ReentrancyGuardReentrantCall",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "SafeERC20FailedOperation",
"type": "error"
},
{
"inputs": [],
"name": "TooLittleReceived",
"type": "error"
},
{
"inputs": [],
"name": "LP_FEE",
"outputs": [
{
"internalType": "uint24",
"name": "",
"type": "uint24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "TICK_SPACING",
"outputs": [
{
"internalType": "int24",
"name": "",
"type": "int24"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "minOut",
"type": "uint256"
}
],
"name": "buy",
"outputs": [
{
"internalType": "uint256",
"name": "out",
"type": "uint256"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "hook",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "poolManager",
"outputs": [
{
"internalType": "contract IPoolManager",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "amountIn",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "minOut",
"type": "uint256"
}
],
"name": "sell",
"outputs": [
{
"internalType": "uint256",
"name": "out",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "raw",
"type": "bytes"
}
],
"name": "unlockCallback",
"outputs": [
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "weth",
"outputs": [
{
"internalType": "contract IWETH",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
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 {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {IUnlockCallback} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import {SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
interface IWETH is IERC20 {
function deposit() external payable;
function withdraw(uint256) external;
}
/// @title QuiverRouter
/// @notice Native-ETH trading front door for Quiver V4 pools. Buyers send ETH,
/// which the router wraps to WETH and swaps for the token; sellers send
/// the token and receive ETH. The per-pool tax is applied automatically
/// by the hook during the swap. Stateless and permissionless.
contract QuiverRouter is IUnlockCallback, ReentrancyGuard {
using SafeERC20 for IERC20;
IPoolManager public immutable poolManager;
IWETH public immutable weth;
address public immutable hook;
uint24 public constant LP_FEE = 0;
int24 public constant TICK_SPACING = 60;
struct Order {
PoolKey key;
bool zeroForOne;
uint256 amountIn;
address recipient;
bool payerIsSelf; // true when the router already holds the input (WETH buy)
address payer;
}
error TooLittleReceived();
constructor(IPoolManager pm, IWETH weth_, address hook_) {
poolManager = pm;
weth = weth_;
hook = hook_;
}
function _key(address token) internal view returns (PoolKey memory key, bool tokenIsC0) {
tokenIsC0 = token < address(weth);
key = PoolKey({
currency0: Currency.wrap(tokenIsC0 ? token : address(weth)),
currency1: Currency.wrap(tokenIsC0 ? address(weth) : token),
fee: LP_FEE,
tickSpacing: TICK_SPACING,
hooks: IHooks(hook)
});
}
/// @notice Buy `token` with the ETH sent; output goes to the caller.
function buy(address token, uint256 minOut) external payable nonReentrant returns (uint256 out) {
require(msg.value > 0, "no eth");
weth.deposit{value: msg.value}();
(PoolKey memory key, bool tokenIsC0) = _key(token);
// Spend WETH -> receive token. WETH is the non-token side.
bool zeroForOne = !tokenIsC0; // WETH is currency0 when token is currency1
bytes memory res = poolManager.unlock(
abi.encode(Order(key, zeroForOne, msg.value, msg.sender, true, address(this)))
);
out = abi.decode(res, (uint256));
if (out < minOut) revert TooLittleReceived();
}
/// @notice Sell `amountIn` of `token` for ETH; ETH goes to the caller.
function sell(address token, uint256 amountIn, uint256 minOut) external nonReentrant returns (uint256 out) {
require(amountIn > 0, "no amount");
(PoolKey memory key, bool tokenIsC0) = _key(token);
// Spend token -> receive WETH. token side depends on orientation.
bool zeroForOne = tokenIsC0; // token is currency0 -> zeroForOne
bytes memory res = poolManager.unlock(
abi.encode(Order(key, zeroForOne, amountIn, address(this), false, msg.sender))
);
out = abi.decode(res, (uint256));
if (out < minOut) revert TooLittleReceived();
weth.withdraw(out);
(bool ok, ) = payable(msg.sender).call{value: out}("");
require(ok, "eth xfer");
}
function unlockCallback(bytes calldata raw) external override returns (bytes memory) {
require(msg.sender == address(poolManager), "not pm");
Order memory o = abi.decode(raw, (Order));
BalanceDelta delta = poolManager.swap(
o.key,
SwapParams({
zeroForOne: o.zeroForOne,
amountSpecified: -int256(o.amountIn),
sqrtPriceLimitX96: o.zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1
}),
""
);
Currency inC = o.zeroForOne ? o.key.currency0 : o.key.currency1;
Currency outC = o.zeroForOne ? o.key.currency1 : o.key.currency0;
int128 inDelta = o.zeroForOne ? delta.amount0() : delta.amount1();
int128 outDelta = o.zeroForOne ? delta.amount1() : delta.amount0();
// Pay the input.
uint256 owed = uint256(uint128(-inDelta));
poolManager.sync(inC);
if (o.payerIsSelf) {
IERC20(Currency.unwrap(inC)).safeTransfer(address(poolManager), owed);
} else {
IERC20(Currency.unwrap(inC)).safeTransferFrom(o.payer, address(poolManager), owed);
}
poolManager.settle();
// Take the output.
uint256 out = uint256(uint128(outDelta));
poolManager.take(outC, o.recipient, out);
return abi.encode(out);
}
receive() external payable {
require(msg.sender == address(weth), "direct eth");
}
}
Chain explorer1582msChain node84ms