ConcentratedLiquidityAdapter
0x31aa702b208b698bdf9e1c750af230b5af64d655
Verification
Verified
v0.8.30+commit.73712a01
Type
Contract
8,262 bytes
ABI entries
15
1 read · 1 write
License
none
Contract information
- Address
- 0x31aa702b208b698bdf9e1c750af230b5af64d655
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.30+commit.73712a01
- Optimization
- Enabled
- Creator
- 0x540Ef2956C…614476c1A6
- Creation tx
- 0x8187a64ab2…bcd60848f8
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (1)
revShareRouter() → address
Events (1)
TokenPlatformFeePaid
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "router_",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "ApprovalFailed",
"type": "error"
},
{
"inputs": [],
"name": "Expired",
"type": "error"
},
{
"inputs": [],
"name": "InvalidAddress",
"type": "error"
},
{
"inputs": [],
"name": "InvalidInput",
"type": "error"
},
{
"inputs": [],
"name": "NativeTransferFailed",
"type": "error"
},
{
"inputs": [],
"name": "OnlyRouter",
"type": "error"
},
{
"inputs": [],
"name": "ReentrantCall",
"type": "error"
},
{
"inputs": [],
"name": "TransferFailed",
"type": "error"
},
{
"inputs": [],
"name": "TransferFromFailed",
"type": "error"
},
{
"inputs": [],
"name": "UnsupportedOperation",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "payer",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "TokenPlatformFeePaid",
"type": "event"
},
{
"inputs": [
{
"components": [
{
"internalType": "enum IRevShareAdapter.Operation",
"name": "operation",
"type": "uint8"
},
{
"internalType": "address",
"name": "payer",
"type": "address"
},
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "address[]",
"name": "tokens",
"type": "address[]"
},
{
"internalType": "uint256[]",
"name": "amounts",
"type": "uint256[]"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
},
{
"internalType": "uint16",
"name": "platformFeeBps",
"type": "uint16"
},
{
"internalType": "address",
"name": "feeRecipient",
"type": "address"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"internalType": "struct IRevShareAdapter.Request",
"name": "request",
"type": "tuple"
}
],
"name": "execute",
"outputs": [
{
"internalType": "bytes",
"name": "result",
"type": "bytes"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "revShareRouter",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
import {BaseAdapter} from "./BaseAdapter.sol";
import {SafeTransferLib} from "../libraries/SafeTransferLib.sol";
interface IConcentratedPositionManager {
struct MintParams {
address token0; address token1; uint24 fee; int24 tickLower; int24 tickUpper;
uint256 amount0Desired; uint256 amount1Desired; uint256 amount0Min; uint256 amount1Min;
address recipient; uint256 deadline;
}
struct IncreaseLiquidityParams {
uint256 tokenId; uint256 amount0Desired; uint256 amount1Desired;
uint256 amount0Min; uint256 amount1Min; uint256 deadline;
}
struct DecreaseLiquidityParams {
uint256 tokenId; uint128 liquidity; uint256 amount0Min; uint256 amount1Min; uint256 deadline;
}
struct CollectParams { uint256 tokenId; address recipient; uint128 amount0Max; uint128 amount1Max; }
function createAndInitializePoolIfNecessary(address, address, uint24, uint160)
external payable returns (address);
function mint(MintParams calldata) external payable returns (uint256, uint128, uint256, uint256);
function increaseLiquidity(IncreaseLiquidityParams calldata)
external payable returns (uint128, uint256, uint256);
function decreaseLiquidity(DecreaseLiquidityParams calldata) external payable returns (uint256, uint256);
function collect(CollectParams calldata) external payable returns (uint256, uint256);
function refundETH() external payable;
}
interface IConcentratedSwapRouter {
struct ExactInputSingleParams {
address tokenIn; address tokenOut; uint24 fee; address recipient;
uint256 amountIn; uint256 amountOutMinimum; uint160 sqrtPriceLimitX96;
}
function exactInputSingle(ExactInputSingleParams calldata) external payable returns (uint256);
}
interface IDeadlineConcentratedSwapRouter {
struct ExactInputSingleParams {
address tokenIn; address tokenOut; uint24 fee; address recipient; uint256 deadline;
uint256 amountIn; uint256 amountOutMinimum; uint160 sqrtPriceLimitX96;
}
function exactInputSingle(ExactInputSingleParams calldata) external payable returns (uint256);
}
interface IConcentratedWrappedNative {
function deposit() external payable;
function withdraw(uint256 amount) external;
}
/// @notice Adapter for Uniswap V3-compatible position managers and swap routers.
/// @dev Compatible with SushiSwap V3 by setting `swapRouterUsesDeadline` for its router ABI.
contract ConcentratedLiquidityAdapter is BaseAdapter {
using SafeTransferLib for address;
struct Venue {
address factory;
address positionManager;
address swapRouter;
address wrappedNative;
bool swapRouterUsesDeadline;
}
struct PoolConfig { Venue venue; address token0; address token1; uint24 fee; uint160 sqrtPriceX96; }
struct SwapConfig {
Venue venue; address tokenIn; address tokenOut; uint24 fee;
uint256 minAmountOut; uint160 sqrtPriceLimitX96; bool nativeIn; bool nativeOut;
}
struct MintConfig {
Venue venue; address token0; address token1; uint24 fee; int24 tickLower; int24 tickUpper;
uint256 amount0Min; uint256 amount1Min;
}
struct PositionConfig {
Venue venue; uint256 tokenId; uint128 liquidity;
uint256 amount0Min; uint256 amount1Min; uint128 amount0Max; uint128 amount1Max;
}
constructor(address router_) BaseAdapter(router_) {}
function execute(Request calldata request)
external payable override onlyRouter nonReentrant returns (bytes memory result)
{
_validate(request);
if (request.operation == Operation.CreatePool) return _createPool(request);
if (request.operation == Operation.Swap) return _swap(request);
if (request.operation == Operation.AddLiquidity) return _mint(request);
if (request.operation == Operation.IncreaseLiquidity) return _increase(request);
if (request.operation == Operation.RemoveLiquidity) return _decreaseAndCollect(request);
if (request.operation == Operation.CollectFees) return _collect(request);
revert UnsupportedOperation();
}
function _createPool(Request calldata request) private returns (bytes memory) {
if (msg.value != 0 || request.tokens.length != 0 || request.amounts.length != 0) revert InvalidInput();
PoolConfig memory config = abi.decode(request.data, (PoolConfig));
_validateVenue(config.venue);
address pool = IConcentratedPositionManager(config.venue.positionManager)
.createAndInitializePoolIfNecessary(config.token0, config.token1, config.fee, config.sqrtPriceX96);
return abi.encode(pool);
}
function _swap(Request calldata request) private returns (bytes memory) {
if (request.amounts.length != 1) revert InvalidInput();
SwapConfig memory config = abi.decode(request.data, (SwapConfig));
_validateVenue(config.venue);
if (
(config.nativeIn && config.nativeOut)
|| (config.nativeIn && config.tokenIn != config.venue.wrappedNative)
|| (config.nativeOut && config.tokenOut != config.venue.wrappedNative)
) revert InvalidInput();
uint256 wrappedBalanceBefore = SafeTransferLib.balanceOf(
config.venue.wrappedNative, address(this)
);
uint256 amountIn;
if (config.nativeIn) {
if (request.tokens.length != 0 || msg.value != request.amounts[0]) revert InvalidInput();
amountIn = request.amounts[0];
IConcentratedWrappedNative(config.venue.wrappedNative).deposit{value: amountIn}();
} else {
if (
msg.value != 0 || request.tokens.length != 1
|| request.tokens[0] != config.tokenIn
) revert InvalidInput();
amountIn = _pullSwapInput(request, config.tokenIn, request.amounts[0]);
}
address swapRecipient = config.nativeOut ? address(this) : request.recipient;
uint256 nativeOutputBalanceBefore = config.nativeOut
? SafeTransferLib.balanceOf(config.venue.wrappedNative, address(this))
: 0;
uint256 amountOut = _performSwap(config, amountIn, swapRecipient, request.deadline);
if (config.nativeOut) {
_deliverNativeOutput(
config.venue.wrappedNative,
request.recipient,
nativeOutputBalanceBefore,
amountOut,
config.minAmountOut
);
}
if (config.nativeIn) {
_refundWrappedNative(config.venue.wrappedNative, request.payer, wrappedBalanceBefore);
} else {
_refund(config.tokenIn, request.payer);
}
_refundNative(request.payer);
return abi.encode(amountOut);
}
function _performSwap(
SwapConfig memory config,
uint256 amountIn,
address recipient,
uint256 deadline
) private returns (uint256 amountOut) {
config.tokenIn.forceApprove(config.venue.swapRouter, amountIn);
if (config.venue.swapRouterUsesDeadline) {
amountOut = IDeadlineConcentratedSwapRouter(config.venue.swapRouter).exactInputSingle(
IDeadlineConcentratedSwapRouter.ExactInputSingleParams({
tokenIn: config.tokenIn, tokenOut: config.tokenOut, fee: config.fee,
recipient: recipient, deadline: deadline, amountIn: amountIn,
amountOutMinimum: config.minAmountOut, sqrtPriceLimitX96: config.sqrtPriceLimitX96
})
);
} else {
amountOut = IConcentratedSwapRouter(config.venue.swapRouter).exactInputSingle(
IConcentratedSwapRouter.ExactInputSingleParams({
tokenIn: config.tokenIn, tokenOut: config.tokenOut, fee: config.fee,
recipient: recipient, amountIn: amountIn,
amountOutMinimum: config.minAmountOut, sqrtPriceLimitX96: config.sqrtPriceLimitX96
})
);
}
config.tokenIn.forceApprove(config.venue.swapRouter, 0);
}
function _deliverNativeOutput(
address wrappedNative,
address recipient,
uint256 balanceBefore,
uint256 reportedAmountOut,
uint256 minimumAmountOut
) private {
uint256 received = SafeTransferLib.balanceOf(wrappedNative, address(this)) - balanceBefore;
if (received != reportedAmountOut || received < minimumAmountOut) revert InvalidInput();
IConcentratedWrappedNative(wrappedNative).withdraw(received);
SafeTransferLib.safeTransferNative(recipient, received);
}
function _refundWrappedNative(address wrappedNative, address payer, uint256 balanceBefore)
private
{
uint256 balanceAfter = SafeTransferLib.balanceOf(wrappedNative, address(this));
if (balanceAfter > balanceBefore) {
wrappedNative.safeTransfer(payer, balanceAfter - balanceBefore);
}
}
function _mint(Request calldata request) private returns (bytes memory) {
if (msg.value != 0 || request.tokens.length != 2 || request.amounts.length != 2) revert InvalidInput();
MintConfig memory config = abi.decode(request.data, (MintConfig));
_validateVenue(config.venue);
if (request.tokens[0] != config.token0 || request.tokens[1] != config.token1) revert InvalidInput();
_fundPosition(config.venue.positionManager, request);
(uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1) =
IConcentratedPositionManager(config.venue.positionManager).mint(
IConcentratedPositionManager.MintParams({
token0: config.token0, token1: config.token1, fee: config.fee,
tickLower: config.tickLower, tickUpper: config.tickUpper,
amount0Desired: request.amounts[0], amount1Desired: request.amounts[1],
amount0Min: config.amount0Min, amount1Min: config.amount1Min,
recipient: request.recipient, deadline: request.deadline
})
);
_clearAndRefund(config.venue.positionManager, request);
return abi.encode(tokenId, liquidity, amount0, amount1);
}
function _increase(Request calldata request) private returns (bytes memory) {
if (msg.value != 0 || request.tokens.length != 2 || request.amounts.length != 2) revert InvalidInput();
PositionConfig memory config = abi.decode(request.data, (PositionConfig));
_validateVenue(config.venue);
_fundPosition(config.venue.positionManager, request);
(uint128 liquidity, uint256 amount0, uint256 amount1) =
IConcentratedPositionManager(config.venue.positionManager).increaseLiquidity(
IConcentratedPositionManager.IncreaseLiquidityParams({
tokenId: config.tokenId, amount0Desired: request.amounts[0],
amount1Desired: request.amounts[1], amount0Min: config.amount0Min,
amount1Min: config.amount1Min, deadline: request.deadline
})
);
_clearAndRefund(config.venue.positionManager, request);
return abi.encode(liquidity, amount0, amount1);
}
function _decreaseAndCollect(Request calldata request) private returns (bytes memory) {
if (msg.value != 0 || request.tokens.length != 0 || request.amounts.length != 0) revert InvalidInput();
PositionConfig memory config = abi.decode(request.data, (PositionConfig));
_validateVenue(config.venue);
(uint256 principal0, uint256 principal1) =
IConcentratedPositionManager(config.venue.positionManager).decreaseLiquidity(
IConcentratedPositionManager.DecreaseLiquidityParams({
tokenId: config.tokenId, liquidity: config.liquidity,
amount0Min: config.amount0Min, amount1Min: config.amount1Min,
deadline: request.deadline
})
);
(uint256 collected0, uint256 collected1) = _collectPosition(config, request.recipient);
return abi.encode(principal0, principal1, collected0, collected1);
}
function _collect(Request calldata request) private returns (bytes memory) {
if (msg.value != 0 || request.tokens.length != 0 || request.amounts.length != 0) revert InvalidInput();
PositionConfig memory config = abi.decode(request.data, (PositionConfig));
_validateVenue(config.venue);
(uint256 amount0, uint256 amount1) = _collectPosition(config, request.recipient);
return abi.encode(amount0, amount1);
}
function _collectPosition(PositionConfig memory config, address recipient)
private returns (uint256 amount0, uint256 amount1)
{
return IConcentratedPositionManager(config.venue.positionManager).collect(
IConcentratedPositionManager.CollectParams({
tokenId: config.tokenId, recipient: recipient,
amount0Max: config.amount0Max == 0 ? type(uint128).max : config.amount0Max,
amount1Max: config.amount1Max == 0 ? type(uint128).max : config.amount1Max
})
);
}
function _fundPosition(address manager, Request calldata request) private {
for (uint256 i; i < 2; ++i) {
_pull(request.tokens[i], request.payer, request.amounts[i]);
request.tokens[i].forceApprove(manager, request.amounts[i]);
}
}
function _clearAndRefund(address manager, Request calldata request) private {
for (uint256 i; i < 2; ++i) {
request.tokens[i].forceApprove(manager, 0);
_refund(request.tokens[i], request.payer);
}
}
function _validateVenue(Venue memory venue) private view {
_requireContract(venue.positionManager);
_requireContract(venue.swapRouter);
if (venue.factory == address(0) || venue.wrappedNative == address(0)) revert InvalidAddress();
}
}
Chain explorer6126msChain node81ms