UniversalUniswapV4Adapter
0xa687b664662b96b180346d699a6d5b42e9b05d31
Verification
Verified
v0.8.17+commit.8df45f5f
Type
Contract
5,773 bytes
ABI entries
12
2 read · 3 write
License
none
Contract information
- Address
- 0xa687b664662b96b180346d699a6d5b42e9b05d31
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.17+commit.8df45f5f
- Optimization
- Enabled
- Creator
- 0x399EfA78cA…Df9EFDf6Ad
- Creation tx
- 0x0c121bc932…1805bba5e8
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (2)
WETH() → address
poolManager() → address
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "poolManager_",
"type": "address"
},
{
"internalType": "address",
"name": "weth_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "ERC20TransferFailed",
"type": "error"
},
{
"inputs": [],
"name": "NativeTransferFailed",
"type": "error"
},
{
"inputs": [
{
"internalType": "PoolId",
"name": "poolId",
"type": "bytes32"
}
],
"name": "NotEnoughLiquidity",
"type": "error"
},
{
"inputs": [],
"name": "NotPoolManager",
"type": "error"
},
{
"inputs": [],
"name": "SafeTransferFailed",
"type": "error"
},
{
"inputs": [],
"name": "WETH",
"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": "to",
"type": "address"
},
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "bytes",
"name": "moreInfo",
"type": "bytes"
}
],
"name": "sellBase",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "bytes",
"name": "moreInfo",
"type": "bytes"
}
],
"name": "sellQuote",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "unlockCallback",
"outputs": [
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IAdapter} from "@interfaces/IAdapter.sol";
import {IERC20} from "@interfaces/IERC20.sol";
import {
BalanceDelta,
BalanceDeltaLibrary,
Currency,
CurrencyLibrary,
IHooks,
IPoolManager,
PoolId,
PoolKey,
PoolKeyLibrary,
equals,
lessThan
} from "@interfaces/IUniswapV4.sol";
import {IWETH} from "@interfaces/IWETH.sol";
import {SafeCallback} from "@libraries/SafeCallback.sol";
import {SafeERC20} from "@libraries/SafeERC20.sol";
import {TransientStateLibrary} from "@libraries/TransientStateLibrary.sol";
/// @title UniversalUniswapV4Adapter
/// @notice Adapter for all Uniswap V4 pools (vanilla or hooked: DX Terminal, Clanker, Zora, etc.).
/// @dev Uses the V4 singleton PoolManager via the unlock/settle/swap/take pattern.
/// Supports multi-hop swaps and native-ETH via WETH wrapping.
contract UniversalUniswapV4Adapter is IAdapter, SafeCallback {
using SafeERC20 for IERC20;
using CurrencyLibrary for Currency;
using BalanceDeltaLibrary for BalanceDelta;
using PoolKeyLibrary for PoolKey;
using TransientStateLibrary for IPoolManager;
address public immutable WETH;
uint160 internal constant MIN_SQRT_PRICE = 4295128739;
uint160 internal constant MAX_SQRT_PRICE = 1461446703485210103287273052203988822378723970342;
uint256 internal constant ADDRESS_MASK = 0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff;
error NotEnoughLiquidity(PoolId poolId);
struct PathKey {
Currency inputCurrency;
Currency intermediateCurrency;
uint24 fee;
int24 tickSpacing;
address hook;
bytes hookData;
}
constructor(address poolManager_, address weth_) SafeCallback(IPoolManager(poolManager_)) {
WETH = weth_;
}
// ──────────────────────── IAdapter ────────────────────────
function sellBase(address to, address, bytes memory moreInfo) external override {
uint256 payerOrigin;
assembly {
let size := calldatasize()
payerOrigin := calldataload(sub(size, 32))
}
_unlock(to, payerOrigin, moreInfo);
}
function sellQuote(address to, address, bytes memory moreInfo) external override {
uint256 payerOrigin;
assembly {
let size := calldatasize()
payerOrigin := calldataload(sub(size, 32))
}
_unlock(to, payerOrigin, moreInfo);
}
// ──────────────────────── Unlock ────────────────────────
function _unlock(address to, uint256 payerOrigin, bytes memory moreInfo) internal {
poolManager.unlock(abi.encode(to, payerOrigin, moreInfo));
}
function _unlockCallback(bytes calldata data) internal override returns (bytes memory) {
(address to, uint256 payerOrigin, bytes memory moreInfo) = abi.decode(data, (address, uint256, bytes));
PathKey[] memory pathKeys = abi.decode(moreInfo, (PathKey[]));
uint256 firstAmountIn;
if (pathKeys[0].inputCurrency.isAddressZero()) {
firstAmountIn = IERC20(WETH).balanceOf(address(this));
IWETH(WETH).withdraw(firstAmountIn);
} else {
firstAmountIn = pathKeys[0].inputCurrency.balanceOfSelf();
}
uint256 settledAmount = _settle(pathKeys[0].inputCurrency, firstAmountIn);
(uint256 actualAmountIn, uint256 actualAmountOut) = _swap(pathKeys, settledAmount);
require(actualAmountOut > 0, "Amount must be positive");
require(actualAmountIn <= settledAmount, "AmountIn exceeds settledAmount");
_refundUnused(pathKeys[0].inputCurrency, payerOrigin);
_takeOutput(pathKeys[pathKeys.length - 1].intermediateCurrency, to, actualAmountOut);
return "";
}
// ──────────────────────── Refund + Take ────────────────────────
function _refundUnused(Currency inputCurrency, uint256 payerOrigin) internal {
address _payerOrigin = address(uint160(payerOrigin & ADDRESS_MASK));
int256 inputDelta = poolManager.currencyDelta(address(this), inputCurrency);
if (inputDelta > 0) {
poolManager.take(inputCurrency, _payerOrigin, uint256(inputDelta));
}
}
function _takeOutput(Currency outputCurrency, address to, uint256 amount) internal {
if (outputCurrency.isAddressZero()) {
poolManager.take(outputCurrency, address(this), amount);
IWETH(WETH).deposit{value: amount}();
SafeERC20.safeTransfer(IERC20(WETH), to, amount);
} else {
poolManager.take(outputCurrency, to, amount);
}
}
// ──────────────────────── Swap ────────────────────────
function _getPoolAndSwapDirection(PathKey memory params)
internal
pure
returns (PoolKey memory poolKey, bool zeroForOne)
{
(Currency currency0, Currency currency1) = lessThan(params.inputCurrency, params.intermediateCurrency)
? (params.inputCurrency, params.intermediateCurrency)
: (params.intermediateCurrency, params.inputCurrency);
zeroForOne = equals(params.inputCurrency, currency0);
poolKey = PoolKey(currency0, currency1, params.fee, params.tickSpacing, IHooks(params.hook));
}
function _swap(PathKey[] memory pathKeys, uint256 firstAmountIn)
internal
returns (uint256 actualAmountIn, uint256 actualAmountOut)
{
BalanceDelta swapDelta;
int256 amountIn = int256(firstAmountIn);
for (uint256 i = 0; i < pathKeys.length; i++) {
(PoolKey memory poolKey, bool zeroForOne) = _getPoolAndSwapDirection(pathKeys[i]);
amountIn = -amountIn;
swapDelta = poolManager.swap(
poolKey,
IPoolManager.SwapParams({
zeroForOne: zeroForOne,
amountSpecified: amountIn,
sqrtPriceLimitX96: zeroForOne ? MIN_SQRT_PRICE + 1 : MAX_SQRT_PRICE - 1
}),
pathKeys[i].hookData
);
int128 amountSpecifiedActual = (zeroForOne == (amountIn < 0)) ? swapDelta.amount0() : swapDelta.amount1();
if (amountSpecifiedActual != amountIn) revert NotEnoughLiquidity(poolKey.toId());
amountIn = zeroForOne ? int256(swapDelta.amount1()) : int256(swapDelta.amount0());
if (i == 0) {
actualAmountIn =
zeroForOne ? uint256(-int256(swapDelta.amount0())) : uint256(-int256(swapDelta.amount1()));
}
if (i == pathKeys.length - 1) {
actualAmountOut = uint256(amountIn);
}
}
}
// ──────────────────────── Settle ────────────────────────
function _settle(Currency currency, uint256 amount) internal returns (uint256 paid) {
if (amount == 0) return 0;
poolManager.sync(currency);
if (currency.isAddressZero()) {
paid = poolManager.settle{value: amount}();
} else {
currency.transfer(address(poolManager), amount);
paid = poolManager.settle();
}
}
receive() external payable {}
}
Chain explorer2292msChain node73ms