Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/lp-lockers/ClankerLpLockerFeeConversion.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import {IClanker} from "../interfaces/IClanker.sol";
import {IClankerFeeLocker} from "../interfaces/IClankerFeeLocker.sol";
import {IClankerLpLocker} from "../interfaces/IClankerLpLocker.sol";
import {IClankerHook} from "./interfaces/IClankerHook.sol";
import {IClankerLpLockerFeeConversion} from "./interfaces/IClankerLpLockerFeeConversion.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {IPermit2} from "@uniswap/permit2/src/interfaces/IPermit2.sol";
import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";
import {IUniversalRouter} from "@uniswap/universal-router/contracts/interfaces/IUniversalRouter.sol";
import {Commands} from "@uniswap/universal-router/contracts/libraries/Commands.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
import {BalanceDelta, BalanceDeltaLibrary} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {IPositionManager} from "@uniswap/v4-periphery/src/interfaces/IPositionManager.sol";
import {IV4Router} from "@uniswap/v4-periphery/src/interfaces/IV4Router.sol";
import {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol";
import {LiquidityAmounts} from "@uniswap/v4-periphery/src/libraries/LiquidityAmounts.sol";
contract ClankerLpLockerFeeConversion is IClankerLpLockerFeeConversion, ReentrancyGuard, Ownable {
using TickMath for int24;
using BalanceDeltaLibrary for BalanceDelta;
string public constant version = "1";
uint256 public constant BASIS_POINTS = 10_000;
uint256 public constant MAX_REWARD_PARTICIPANTS = 7;
uint256 public constant MAX_LP_POSITIONS = 7;
IPositionManager public immutable positionManager;
IPoolManager public immutable poolManager;
IPermit2 public immutable permit2;
IClankerFeeLocker public immutable feeLocker;
IUniversalRouter public immutable universalRouter;
address public immutable factory;
// guard to stop recursive collection calls
bool internal _inCollect;
mapping(address token => TokenRewardInfo tokenRewardInfo) internal _tokenRewards;
mapping(address token => FeeIn[] feePreference) public feePreferences;
constructor(
address owner_,
address factory_, // Address of the clanker factory
address feeLocker_,
address positionManager_, // Address of the position manager
address permit2_, // address of the permit2 contract
address universalRouter_, // address of the universal router
address poolManager_ // address of the pool manager
) Ownable(owner_) {
factory = factory_;
feeLocker = IClankerFeeLocker(feeLocker_);
positionManager = IPositionManager(positionManager_);
permit2 = IPermit2(permit2_);
universalRouter = IUniversalRouter(universalRouter_);
poolManager = IPoolManager(poolManager_);
}
modifier onlyFactory() {
if (msg.sender != factory) {
revert Unauthorized();
}
_;
}
function tokenRewards(address token) external view returns (TokenRewardInfo memory) {
return _tokenRewards[token];
}
function placeLiquidity(
IClanker.LockerConfig memory lockerConfig,
IClanker.PoolConfig memory poolConfig,
PoolKey memory poolKey,
uint256 poolSupply,
address token
) external onlyFactory nonReentrant returns (uint256 positionId) {
// decode the extra locker data
FeeIn[] memory lpFeeConversionPreferences = abi.decode(
lockerConfig.lockerData, (IClankerLpLockerFeeConversion.LpFeeConversionInfo)
).feePreference;
// ensure that we don't already have a reward for this token
if (_tokenRewards[token].positionId != 0) {
revert TokenAlreadyHasRewards();
}
// create the reward info
TokenRewardInfo memory tokenRewardInfo = TokenRewardInfo({
token: token,
poolKey: poolKey,
positionId: 0, // set below
numPositions: lockerConfig.tickLower.length,
rewardBps: lockerConfig.rewardBps,
rewardAdmins: lockerConfig.rewardAdmins,
rewardRecipients: lockerConfig.rewardRecipients
});
// check that all arrays are the same length
if (
tokenRewardInfo.rewardBps.length != tokenRewardInfo.rewardAdmins.length
|| tokenRewardInfo.rewardBps.length != tokenRewardInfo.rewardRecipients.length
|| tokenRewardInfo.rewardBps.length != lpFeeConversionPreferences.length
) {
revert MismatchedRewardArrays();
}
// check that the number of reward participants is not greater than the max
if (tokenRewardInfo.rewardBps.length > MAX_REWARD_PARTICIPANTS) {
revert TooManyRewardParticipants();
}
// check that there is at least one reward
if (tokenRewardInfo.rewardBps.length == 0) {
revert NoRewardRecipients();
}
// check that the reward amounts add up to 10000
uint16 totalRewards = 0;
for (uint256 i = 0; i < tokenRewardInfo.rewardBps.length; i++) {
totalRewards += tokenRewardInfo.rewardBps[i];
if (tokenRewardInfo.rewardBps[i] == 0) {
revert ZeroRewardAmount();
}
}
if (totalRewards != BASIS_POINTS) {
revert InvalidRewardBps();
}
// check that no address is the zero address
for (uint256 i = 0; i < tokenRewardInfo.rewardBps.length; i++) {
if (
tokenRewardInfo.rewardAdmins[i] == address(0)
|| tokenRewardInfo.rewardRecipients[i] == address(0)
) {
revert ZeroRewardAddress();
}
}
// pull in the token and mint liquidity
IERC20(token).transferFrom(msg.sender, address(this), poolSupply);
positionId = _mintLiquidity(poolConfig, lockerConfig, poolKey, poolSupply, token);
// store the reward info
tokenRewardInfo.positionId = positionId;
_tokenRewards[token] = tokenRewardInfo;
// store the fee preference
feePreferences[token] = lpFeeConversionPreferences;
emit InitialFeePreferences(token, lpFeeConversionPreferences);
emit TokenRewardAdded({
token: tokenRewardInfo.token,
poolKey: tokenRewardInfo.poolKey,
poolSupply: poolSupply,
positionId: tokenRewardInfo.positionId,
numPositions: tokenRewardInfo.numPositions,
rewardBps: tokenRewardInfo.rewardBps,
rewardAdmins: tokenRewardInfo.rewardAdmins,
rewardRecipients: tokenRewardInfo.rewardRecipients,
tickLower: lockerConfig.tickLower,
tickUpper: lockerConfig.tickUpper,
positionBps: lockerConfig.positionBps
});
}
function _mintLiquidity(
IClanker.PoolConfig memory poolConfig,
IClanker.LockerConfig memory lockerConfig,
PoolKey memory poolKey,
uint256 poolSupply,
address token
) internal returns (uint256 positionId) {
// check that all position infos are the same length
if (
lockerConfig.tickLower.length != lockerConfig.tickUpper.length
|| lockerConfig.tickLower.length != lockerConfig.positionBps.length
) {
revert MismatchedPositionInfos();
}
// ensure that there is at least one position
if (lockerConfig.tickLower.length == 0) {
revert NoPositions();
}
// ensure that the max number of positions is not exceeded
if (lockerConfig.tickLower.length > MAX_LP_POSITIONS) {
revert TooManyPositions();
}
// make sure the locker position config is valid
uint256 positionBpsTotal = 0;
for (uint256 i = 0; i < lockerConfig.tickLower.length; i++) {
if (lockerConfig.tickLower[i] > lockerConfig.tickUpper[i]) {
revert TicksBackwards();
}
if (
lockerConfig.tickLower[i] < TickMath.MIN_TICK
|| lockerConfig.tickUpper[i] > TickMath.MAX_TICK
) {
revert TicksOutOfTickBounds();
}
if (
lockerConfig.tickLower[i] % poolConfig.tickSpacing != 0
|| lockerConfig.tickUpper[i] % poolConfig.tickSpacing != 0
) {
revert TicksNotMultipleOfTickSpacing();
}
if (lockerConfig.tickLower[i] < poolConfig.tickIfToken0IsClanker) {
revert TickRangeLowerThanStartingTick();
}
positionBpsTotal += lockerConfig.positionBps[i];
}
if (positionBpsTotal != BASIS_POINTS) {
revert InvalidPositionBps();
}
bool token0IsClanker = token < poolConfig.pairedToken;
// encode actions
bytes[] memory params = new bytes[](lockerConfig.tickLower.length + 1);
bytes memory actions;
int24 startingTick =
token0IsClanker ? poolConfig.tickIfToken0IsClanker : -poolConfig.tickIfToken0IsClanker;
for (uint256 i = 0; i < lockerConfig.tickLower.length; i++) {
// add mint action
actions = abi.encodePacked(actions, uint8(Actions.MINT_POSITION));
// determine token amount for this position
uint256 tokenAmount = poolSupply * lockerConfig.positionBps[i] / BASIS_POINTS;
uint256 amount0 = token0IsClanker ? tokenAmount : 0;
uint256 amount1 = token0IsClanker ? 0 : tokenAmount;
// determine tick bounds for this position
int24 tickLower_ =
token0IsClanker ? lockerConfig.tickLower[i] : -lockerConfig.tickLower[i];
int24 tickUpper_ =
token0IsClanker ? lockerConfig.tickUpper[i] : -lockerConfig.tickUpper[i];
int24 tickLower = token0IsClanker ? tickLower_ : tickUpper_;
int24 tickUpper = token0IsClanker ? tickUpper_ : tickLower_;
uint160 lowerSqrtPrice = TickMath.getSqrtPriceAtTick(tickLower);
uint160 upperSqrtPrice = TickMath.getSqrtPriceAtTick(tickUpper);
// determine liquidity amount
uint256 liquidity = LiquidityAmounts.getLiquidityForAmounts(
startingTick.getSqrtPriceAtTick(), lowerSqrtPrice, upperSqrtPrice, amount0, amount1
);
params[i] = abi.encode(
poolKey,
tickLower, // tick lower
tickUpper, // tick upper
liquidity, // liquidity
amount0, // amount0Max
amount1, // amount1Max
address(this), // recipient of position
abi.encode(address(this))
);
}
// add settle action
actions = abi.encodePacked(actions, uint8(Actions.SETTLE_PAIR));
params[lockerConfig.tickLower.length] = abi.encode(poolKey.currency0, poolKey.currency1);
// approvals
{
IERC20(token).approve(address(permit2), poolSupply);
permit2.approve(
token, address(positionManager), uint160(poolSupply), uint48(block.timestamp)
);
}
// grab position id we're about to mint
positionId = positionManager.nextTokenId();
// add liquidity
positionManager.modifyLiquidities(abi.encode(actions, params), block.timestamp);
}
// collect rewards while pool is unlocked (e.g. in an afterSwap hook)
function collectRewardsWithoutUnlock(address token) external {
_collectRewards(token, true);
}
// collect rewards while pool is locked
function collectRewards(address token) external {
_collectRewards(token, false);
}
function _mevModuleOperating(address token) internal view returns (bool) {
// check if the mev module has expired on the token's pool,
// if it has not, we need to skip the collection as the swap-back
// can be blocked by the mev module
PoolId poolId = PoolIdLibrary.toId(_tokenRewards[token].poolKey);
// if the mev module is disabled, the swap backs cannot be blocked
if (!IClankerHook(address(_tokenRewards[token].poolKey.hooks)).mevModuleEnabled(poolId)) {
return false;
}
// if the mev module is enabled, check if the pool is older than the max mev module expiry time.
// if it is, the mev module will not be triggered and the swap backs cannot be blocked
uint256 poolCreationTimestamp =
IClankerHook(address(_tokenRewards[token].poolKey.hooks)).poolCreationTimestamp(poolId);
if (
poolCreationTimestamp
+ IClankerHook(address(_tokenRewards[token].poolKey.hooks)).MAX_MEV_MODULE_DELAY()
<= block.timestamp
) {
return false;
}
// mev module is enabled and not expired, the swap backs can be blocked
return true;
}
// Collect rewards for a token
function _collectRewards(address token, bool withoutUnlock) internal {
if (_inCollect) {
// stop recursive call
return;
}
// check if the mev module is expired
if (_mevModuleOperating(token)) {
// do not perform collection if mev module is still operating
return;
}
_inCollect = true;
// get the reward info
TokenRewardInfo memory tokenRewardInfo = _tokenRewards[token];
// collect the rewards
(uint256 amount0, uint256 amount1) = _bringFeesIntoContract(
tokenRewardInfo.poolKey,
tokenRewardInfo.positionId,
tokenRewardInfo.numPositions,
withoutUnlock
);
IERC20 rewardToken0 = IERC20(Currency.unwrap(tokenRewardInfo.poolKey.currency0));
IERC20 rewardToken1 = IERC20(Currency.unwrap(tokenRewardInfo.poolKey.currency1));
uint256[] memory rewards0 = new uint256[](tokenRewardInfo.rewardBps.length);
uint256[] memory rewards1 = new uint256[](tokenRewardInfo.rewardBps.length);
uint256 amount0_actualized = 0;
uint256 amount1_actualized = 0;
// handle fees for token0
if (amount0 > 0) {
(uint256[] memory _rewards0, uint256[] memory _rewards1) =
_handleFees(token, address(rewardToken0), amount0, withoutUnlock);
for (uint256 i = 0; i < tokenRewardInfo.rewardBps.length; i++) {
rewards0[i] += _rewards0[i];
rewards1[i] += _rewards1[i];
amount0_actualized += _rewards0[i];
amount1_actualized += _rewards1[i];
}
}
// handle fees for token1
if (amount1 > 0) {
(uint256[] memory _rewards0, uint256[] memory _rewards1) =
_handleFees(token, address(rewardToken1), amount1, withoutUnlock);
for (uint256 i = 0; i < tokenRewardInfo.rewardBps.length; i++) {
rewards0[i] += _rewards0[i];
rewards1[i] += _rewards1[i];
amount0_actualized += _rewards0[i];
amount1_actualized += _rewards1[i];
}
}
_inCollect = false;
// emit the claim event
emit ClaimedRewards(
tokenRewardInfo.token, amount0_actualized, amount1_actualized, rewards0, rewards1
);
}
// handle fees for a token
function _handleFees(address token, address rewardToken, uint256 amount, bool withoutUnlock)
internal
returns (uint256[] memory, uint256[] memory)
{
TokenRewardInfo memory tokenRewardInfo = _tokenRewards[token];
uint256[] memory rewards0 = new uint256[](tokenRewardInfo.rewardBps.length);
uint256[] memory rewards1 = new uint256[](tokenRewardInfo.rewardBps.length);
// if the rewardToken is the clanker, we want to swap for recipients who
// want their fee in the paired token
//
// conversely, if the rewardToken is the paired token, we want to swap for
// recipients who want their fee in the clanker token
FeeIn toSwap = token == rewardToken ? FeeIn.Paired : FeeIn.Clanker;
address tokenToSwapInto =
token == rewardToken ? _getPairedToken(token, tokenRewardInfo.poolKey) : token;
bool rewardTokenIsToken0 = rewardToken == Currency.unwrap(tokenRewardInfo.poolKey.currency0);
// get the reward info
FeeIn[] memory feePreference = feePreferences[token];
// determine bps and token amount to swap while distributing the non-swapped portion
uint256 tokenToSwap = amount;
uint256 bpsToSwapTotal = 0;
uint256[] memory toSwapIndexes = new uint256[](feePreference.length);
uint256[] memory toDistributeIndexes = new uint256[](feePreference.length);
uint256 toSwapCount = 0;
uint256 toDistributeCount = 0;
// determine breakdown of bps to swap and distribute
for (uint256 i = 0; i < feePreference.length; i++) {
if (feePreference[i] == toSwap) {
bpsToSwapTotal += tokenRewardInfo.rewardBps[i];
toSwapIndexes[toSwapCount] = i;
toSwapCount++;
} else {
toDistributeIndexes[toDistributeCount] = i;
toDistributeCount++;
}
}
// determine how to handle dust. if there is no recipient requesting a swap,
// then we handle the dust in the last index of the distribute loop
uint256 distributeLoop = toSwapCount == 0 ? toDistributeCount - 1 : toDistributeCount;
// send the non-swapped portion to the recipients in the fee locker
for (uint256 i = 0; i < distributeLoop; i++) {
uint256 tokenToDistribute =
tokenRewardInfo.rewardBps[toDistributeIndexes[i]] * amount / BASIS_POINTS;
if (tokenToDistribute == 0) {
continue;
}
tokenToSwap -= tokenToDistribute;
SafeERC20.forceApprove(IERC20(rewardToken), address(feeLocker), tokenToDistribute);
feeLocker.storeFees(
tokenRewardInfo.rewardRecipients[toDistributeIndexes[i]],
address(rewardToken),
tokenToDistribute
);
rewardTokenIsToken0
? rewards0[toDistributeIndexes[i]] += tokenToDistribute
: rewards1[toDistributeIndexes[i]] += tokenToDistribute;
}
if (toSwapCount == 0 && tokenToSwap > 0) {
SafeERC20.forceApprove(IERC20(rewardToken), address(feeLocker), tokenToSwap);
feeLocker.storeFees(
tokenRewardInfo.rewardRecipients[toDistributeIndexes[distributeLoop]],
address(rewardToken),
tokenToSwap
);
rewardTokenIsToken0
? rewards0[toDistributeIndexes[distributeLoop]] += tokenToSwap
: rewards1[toDistributeIndexes[distributeLoop]] += tokenToSwap;
}
// swap the remaining reward token
uint256 swapAmountOut = 0;
if (toSwapCount > 0) {
swapAmountOut = withoutUnlock
? _uniSwapUnlocked(
tokenRewardInfo.poolKey, address(rewardToken), tokenToSwapInto, uint128(tokenToSwap)
)
: _uniSwapLocked(
tokenRewardInfo.poolKey, address(rewardToken), tokenToSwapInto, uint128(tokenToSwap)
);
// record amount distributed so far for dust handling
uint256 swapDistributed = 0;
// force approve the fee locker to the swap amount out
SafeERC20.forceApprove(IERC20(tokenToSwapInto), address(feeLocker), swapAmountOut);
// distribute the swapped portion to the recipients in the fee locker
for (uint256 i = 0; i < toSwapCount - 1; i++) {
uint256 tokenToDistribute =
tokenRewardInfo.rewardBps[toSwapIndexes[i]] * swapAmountOut / BASIS_POINTS;
if (tokenToDistribute == 0) {
continue;
}
swapDistributed += tokenToDistribute;
feeLocker.storeFees(
tokenRewardInfo.rewardRecipients[toSwapIndexes[i]],
address(tokenToSwapInto),
tokenToDistribute
);
rewardTokenIsToken0
? rewards1[toSwapIndexes[i]] += tokenToDistribute
: rewards0[toSwapIndexes[i]] += tokenToDistribute;
}
// distribute the fees and dust to the last swap recipient
uint256 tokenToDistribute = swapAmountOut - swapDistributed;
if (tokenToDistribute != 0) {
feeLocker.storeFees(
tokenRewardInfo.rewardRecipients[toSwapIndexes[toSwapCount - 1]],
address(tokenToSwapInto),
tokenToDistribute
);
rewardTokenIsToken0
? rewards1[toSwapIndexes[toSwapCount - 1]] += tokenToDistribute
: rewards0[toSwapIndexes[toSwapCount - 1]] += tokenToDistribute;
}
emit FeesSwapped(token, rewardToken, tokenToSwap, tokenToSwapInto, swapAmountOut);
}
return (rewards0, rewards1);
}
function _bringFeesIntoContract(
PoolKey memory poolKey,
uint256 positionId,
uint256 numPositions,
bool withoutUnlock
) internal returns (uint256 amount0, uint256 amount1) {
bytes memory actions;
bytes[] memory params = new bytes[](numPositions + 1);
for (uint256 i = 0; i < numPositions; i++) {
actions = abi.encodePacked(actions, uint8(Actions.DECREASE_LIQUIDITY));
/// @dev collecting fees is achieved with liquidity=0, the second parameter
params[i] = abi.encode(positionId + i, 0, 0, 0, abi.encode());
}
Currency currency0 = poolKey.currency0;
Currency currency1 = poolKey.currency1;
actions = abi.encodePacked(actions, uint8(Actions.TAKE_PAIR));
params[numPositions] = abi.encode(currency0, currency1, address(this));
uint256 balance0Before = IERC20(Currency.unwrap(currency0)).balanceOf(address(this));
uint256 balance1Before = IERC20(Currency.unwrap(currency1)).balanceOf(address(this));
// when claiming from the hook, we need to call modifyLiquiditiesWithoutUnlock since
// the pool will be in an unlocked state
if (withoutUnlock) {
positionManager.modifyLiquiditiesWithoutUnlock(actions, params);
} else {
positionManager.modifyLiquidities(abi.encode(actions, params), block.timestamp);
}
uint256 balance0After = IERC20(Currency.unwrap(currency0)).balanceOf(address(this));
uint256 balance1After = IERC20(Currency.unwrap(currency1)).balanceOf(address(this));
return (balance0After - balance0Before, balance1After - balance1Before);
}
// perform a swap on the pool directly while it is unlocked
function _uniSwapUnlocked(
PoolKey memory poolKey,
address tokenIn,
address tokenOut,
uint128 amountIn
) internal returns (uint256) {
bool zeroForOne = tokenIn < tokenOut;
// Build swap request
IPoolManager.SwapParams memory swapParams = IPoolManager.SwapParams({
zeroForOne: zeroForOne,
amountSpecified: -int256(int128(amountIn)),
sqrtPriceLimitX96: zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1
});
// record before token balance
uint256 tokenOutBefore = IERC20(tokenOut).balanceOf(address(this));
// Execute the swap
BalanceDelta delta = poolManager.swap(poolKey, swapParams, abi.encode());
// determine swap outcomes
int128 deltaOut = delta.amount0() < 0 ? delta.amount1() : delta.amount0();
// pay the input token
poolManager.sync(Currency.wrap(tokenIn));
Currency.wrap(tokenIn).transfer(address(poolManager), amountIn);
poolManager.settle();
// take out the converted token
poolManager.take(Currency.wrap(tokenOut), address(this), uint256(uint128(deltaOut)));
uint256 tokenOutAfter = IERC20(tokenOut).balanceOf(address(this));
return tokenOutAfter - tokenOutBefore;
}
// perform a swap using the universal router which handles the unlocking of the pool
function _uniSwapLocked(
PoolKey memory poolKey,
address tokenIn,
address tokenOut,
uint128 amountIn
) internal returns (uint256) {
// initiate a swap command
bytes memory commands = abi.encodePacked(uint8(Commands.V4_SWAP));
// Encode V4Router actions
bytes memory actions = abi.encodePacked(
uint8(Actions.SWAP_EXACT_IN_SINGLE), uint8(Actions.SETTLE_ALL), uint8(Actions.TAKE_ALL)
);
bytes[] memory params = new bytes[](3);
// First parameter: SWAP_EXACT_IN_SINGLE
params[0] = abi.encode(
IV4Router.ExactInputSingleParams({
poolKey: poolKey,
zeroForOne: tokenIn < tokenOut, // swapping tokenIn -> tokenOut
amountIn: amountIn, // amount of tokenIn to swap
amountOutMinimum: 0, // minimum amount we expect to receive
hookData: bytes("") // no hook data needed, assuming we're using simple hooks
})
);
// Second parameter: SETTLE_ALL
params[1] = abi.encode(tokenIn, uint256(amountIn));
// Third parameter: TAKE_ALL
params[2] = abi.encode(tokenOut, 1);
// Combine actions and params into inputs
bytes[] memory inputs = new bytes[](1);
inputs[0] = abi.encode(actions, params);
// approvals
SafeERC20.forceApprove(IERC20(tokenIn), address(permit2), amountIn);
permit2.approve(tokenIn, address(universalRouter), amountIn, uint48(block.timestamp));
// Execute the swap
uint256 tokenOutBefore = IERC20(tokenOut).balanceOf(address(this));
universalRouter.execute(commands, inputs, block.timestamp);
uint256 tokenOutAfter = IERC20(tokenOut).balanceOf(address(this));
return tokenOutAfter - tokenOutBefore;
}
function _getPairedToken(address token, PoolKey memory poolKey)
internal
view
returns (address)
{
return Currency.unwrap(poolKey.currency0) == token
? Currency.unwrap(poolKey.currency1)
: Currency.unwrap(poolKey.currency0);
}
// Replace the reward recipient
function updateRewardRecipient(address token, uint256 rewardIndex, address newRecipient)
external
{
TokenRewardInfo storage tokenRewardInfo = _tokenRewards[token];
// Only admin can replace the reward recipient
if (msg.sender != tokenRewardInfo.rewardAdmins[rewardIndex]) {
revert Unauthorized();
}
// Add the new recipient
address oldRecipient = tokenRewardInfo.rewardRecipients[rewardIndex];
tokenRewardInfo.rewardRecipients[rewardIndex] = newRecipient;
emit RewardRecipientUpdated(token, rewardIndex, oldRecipient, newRecipient);
}
function updateFeePreference(address token, uint256 rewardIndex, FeeIn newFeePreference)
external
{
TokenRewardInfo storage tokenRewardInfo = _tokenRewards[token];
// Only admin can update the fee preference
if (msg.sender != tokenRewardInfo.rewardAdmins[rewardIndex]) {
revert Unauthorized();
}
// grab the old fee preference
FeeIn oldFeePreference = feePreferences[token][rewardIndex];
// update the fee preference
feePreferences[token][rewardIndex] = newFeePreference;
emit FeePreferenceUpdated(token, rewardIndex, oldFeePreference, newFeePreference);
}
// Replace the reward admin
function updateRewardAdmin(address token, uint256 rewardIndex, address newAdmin) external {
TokenRewardInfo storage tokenRewardInfo = _tokenRewards[token];
// Only admin can replace the reward recipient
if (msg.sender != tokenRewardInfo.rewardAdmins[rewardIndex]) {
revert Unauthorized();
}
// Add the new recipient
address oldAdmin = tokenRewardInfo.rewardAdmins[rewardIndex];
tokenRewardInfo.rewardAdmins[rewardIndex] = newAdmin;
emit RewardAdminUpdated(token, rewardIndex, oldAdmin, newAdmin);
}
// Enable contract to receive LP Tokens
function onERC721Received(address, address from, uint256 id, bytes calldata)
external
returns (bytes4)
{
// Only Clanker Factory can send NFTs here
if (from != factory) {
revert Unauthorized();
}
emit Received(from, id);
return IERC721Receiver.onERC721Received.selector;
}
// Withdraw ETH from the contract
function withdrawETH(address recipient) public onlyOwner nonReentrant {
payable(recipient).transfer(address(this).balance);
}
// Withdraw ERC20 tokens from the contract
function withdrawERC20(address token, address recipient) public onlyOwner nonReentrant {
IERC20 token_ = IERC20(token);
SafeERC20.safeTransfer(token_, recipient, token_.balanceOf(address(this)));
}
function supportsInterface(bytes4 interfaceId) external pure returns (bool) {
return interfaceId == type(IERC721Receiver).interfaceId
|| interfaceId == type(IClankerLpLocker).interfaceId;
}
}
"
},
"src/interfaces/IClanker.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import {IOwnerAdmins} from "./IOwnerAdmins.sol";
import {PoolId} from "@uniswap/v4-core/src/types/PoolId.sol";
interface IClanker is IOwnerAdmins {
struct TokenConfig {
address tokenAdmin;
string name;
string symbol;
bytes32 salt;
string image;
string metadata;
string context;
uint256 originatingChainId;
}
struct PoolConfig {
address hook;
address pairedToken;
int24 tickIfToken0IsClanker;
int24 tickSpacing;
bytes poolData;
}
struct LockerConfig {
address locker;
// reward info
address[] rewardAdmins;
address[] rewardRecipients;
uint16[] rewardBps;
// liquidity placement info
int24[] tickLower;
int24[] tickUpper;
uint16[] positionBps;
bytes lockerData;
}
struct ExtensionConfig {
address extension;
uint256 msgValue;
uint16 extensionBps;
bytes extensionData;
}
struct DeploymentConfig {
TokenConfig tokenConfig;
PoolConfig poolConfig;
LockerConfig lockerConfig;
MevModuleConfig mevModuleConfig;
ExtensionConfig[] extensionConfigs;
}
struct MevModuleConfig {
address mevModule;
bytes mevModuleData;
}
struct DeploymentInfo {
address token;
address hook;
address locker;
address[] extensions;
}
/// @notice When the factory is deprecated
error Deprecated();
/// @notice When the token is not found to collect rewards for
error NotFound();
/// @notice When the function is only valid on the originating chain
error OnlyOriginatingChain();
/// @notice When the function is only valid on a non-originating chain
error OnlyNonOriginatingChains();
/// @notice When the hook is invalid
error InvalidHook();
/// @notice When the locker is invalid
error InvalidLocker();
/// @notice When the extension contract is invalid
error InvalidExtension();
/// @notice When the hook is not enabled
error HookNotEnabled();
/// @notice When the locker is not enabled
error LockerNotEnabled();
/// @notice When the extension contract is not enabled
error ExtensionNotEnabled();
/// @notice When the mev module is not enabled
error MevModuleNotEnabled();
/// @notice When the token is not paired to the pool
error ExtensionMsgValueMismatch();
/// @notice When the maximum number of extensions is exceeded
error MaxExtensionsExceeded();
/// @notice When the extension supply percentage is exceeded
error MaxExtensionBpsExceeded();
/// @notice When the mev module is invalid
error InvalidMevModule();
/// @notice When the team fee recipient is not set
error TeamFeeRecipientNotSet();
event TokenCreated(
address msgSender,
address indexed tokenAddress,
address indexed tokenAdmin,
string tokenImage,
string tokenName,
string tokenSymbol,
string tokenMetadata,
string tokenContext,
int24 startingTick,
address poolHook,
PoolId poolId,
address pairedToken,
address locker,
address mevModule,
uint256 extensionsSupply,
address[] extensions
);
event ExtensionTriggered(address extension, uint256 extensionSupply, uint256 msgValue);
event SetDeprecated(bool deprecated);
event SetExtension(address extension, bool enabled);
event SetHook(address hook, bool enabled);
event SetMevModule(address mevModule, bool enabled);
event SetLocker(address locker, address hook, bool enabled);
event SetTeamFeeRecipient(address oldTeamFeeRecipient, address newTeamFeeRecipient);
event ClaimTeamFees(address indexed token, address indexed recipient, uint256 amount);
function deprecated() external view returns (bool);
function deployToken(DeploymentConfig memory deploymentConfig)
external
payable
returns (address tokenAddress);
function tokenDeploymentInfo(address token) external view returns (DeploymentInfo memory);
}
"
},
"src/interfaces/IClankerFeeLocker.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
interface IClankerFeeLocker {
error NoFeesToClaim();
error Unauthorized();
event StoreTokens(
address indexed sender,
address indexed feeOwner,
address indexed token,
uint256 balance,
uint256 amount
);
event ClaimTokensPermissioned(
address indexed feeOwner, address indexed token, address recipient, uint256 amountClaimed
);
event ClaimTokens(address indexed feeOwner, address indexed token, uint256 amountClaimed);
event AddDepositor(address indexed depositor);
function storeFees(address feeOwner, address token, uint256 amount) external;
function claim(address feeOwner, address token) external;
function addDepositor(address depositor) external;
function availableFees(address feeOwner, address token) external view returns (uint256);
function supportsInterface(bytes4 interfaceId) external pure returns (bool);
}
"
},
"src/interfaces/IClankerLpLocker.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import {IClanker} from "../interfaces/IClanker.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
interface IClankerLpLocker {
struct TokenRewardInfo {
address token;
PoolKey poolKey;
uint256 positionId;
uint256 numPositions;
uint16[] rewardBps;
address[] rewardAdmins;
address[] rewardRecipients;
}
event TokenRewardAdded(
address token,
PoolKey poolKey,
uint256 poolSupply,
uint256 positionId,
uint256 numPositions,
uint16[] rewardBps,
address[] rewardAdmins,
address[] rewardRecipients,
int24[] tickLower,
int24[] tickUpper,
uint16[] positionBps
);
event ClaimedRewards(
address indexed token,
uint256 amount0,
uint256 amount1,
uint256[] rewards0,
uint256[] rewards1
);
// pull rewards from the uniswap v4 pool into the locker
function collectRewards(address token) external;
// pull rewards from the uniswap v4 pool into the locker while
// the pool is unlocked
function collectRewardsWithoutUnlock(address token) external;
// take liqudity from the factory and place it into a pool
function placeLiquidity(
IClanker.LockerConfig memory lockerConfig,
IClanker.PoolConfig memory poolConfig,
PoolKey memory poolKey,
uint256 poolSupply,
address token
) external returns (uint256 tokenId);
// get the reward info for a token
function tokenRewards(address token) external view returns (TokenRewardInfo memory);
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
"
},
"src/lp-lockers/interfaces/IClankerHook.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import {IClanker} from "../../interfaces/IClanker.sol";
import {PoolId} from "@uniswap/v4-core/src/types/PoolId.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
interface IClankerHook {
error ETHPoolNotAllowed();
error OnlyFactory();
error UnsupportedInitializePath();
error PastCreationTimestamp();
error MevModuleEnabled();
error WethCannotBeClanker();
event PoolCreatedOpen(
address indexed pairedToken,
address indexed clanker,
PoolId poolId,
int24 tickIfToken0IsClanker,
int24 tickSpacing
);
event PoolCreatedFactory(
address indexed pairedToken,
address indexed clanker,
PoolId poolId,
int24 tickIfToken0IsClanker,
int24 tickSpacing,
address locker,
address mevModule
);
// note: is not emitted when a mev module expires
event MevModuleDisabled(PoolId);
event ClaimProtocolFees(address indexed token, uint256 amount);
// initialize a pool on the hook for a token
function initializePool(
address clanker,
address pairedToken,
int24 tickIfToken0IsClanker,
int24 tickSpacing,
address locker,
address mevModule,
bytes calldata poolData
) external returns (PoolKey memory);
// initialize a pool not via the factory
function initializePoolOpen(
address clanker,
address pairedToken,
int24 tickIfToken0IsClanker,
int24 tickSpacing,
bytes calldata poolData
) external returns (PoolKey memory);
// turn a pool's mev module on if it exists
function initializeMevModule(PoolKey calldata poolKey, bytes calldata mevModuleData) external;
// note: added these to make the code work, but the original IClankerHook interface does not have them
function mevModuleEnabled(PoolId poolId) external view returns (bool);
function poolCreationTimestamp(PoolId poolId) external view returns (uint256);
function MAX_MEV_MODULE_DELAY() external view returns (uint256);
function supportsInterface(bytes4 interfaceId) external pure returns (bool);
}
"
},
"src/lp-lockers/interfaces/IClankerLpLockerFeeConversion.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import {IClankerLpLocker} from "../../interfaces/IClankerLpLocker.sol";
import {IClankerLpLockerMultiple} from "./IClankerLpLockerMultiple.sol";
interface IClankerLpLockerFeeConversion is IClankerLpLockerMultiple {
enum FeeIn {
Both,
Paired,
Clanker
}
struct LpFeeConversionInfo {
FeeIn[] feePreference;
}
event FeePreferenceUpdated(
address indexed token,
uint256 indexed rewardIndex,
FeeIn oldFeePreference,
FeeIn indexed newFeePreference
);
event FeesSwapped(
address indexed token,
address indexed rewardToken,
uint256 amountSwapped,
address indexed swappedToken,
uint256 amountOut
);
event InitialFeePreferences(address indexed token, FeeIn[] feePreference);
function feePreferences(address token, uint256 index) external view returns (FeeIn);
}
"
},
"lib/openzeppelin-contracts/contracts/access/Ownable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
"
},
"lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
"
},
"lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC-20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
/**
* @dev An operation with an ERC-20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*
* IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
* smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
* this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
* that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*
* NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
* only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
* set here.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
safeTransfer(token, to, value);
} else if (!token.transferAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
* has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* Reverts if the returned value is other than `true`.
*/
function transferFromAndCallRelaxed(
IERC1363 token,
address from,
address to,
uint256 value,
bytes memory data
) internal {
if (to.code.length == 0) {
safeTransferFrom(token, from, to, value);
} else if (!token.transferFromAndCall(from, to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
* code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
* targeting contracts.
*
* NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
* Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
* once without retrying, and relies on the returned value to be true.
*
* Reverts if the returned value is other than `true`.
*/
function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
if (to.code.length == 0) {
forceApprove(token, to, value);
} else if (!token.approveAndCall(to, value, data)) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
// bubble errors
if iszero(success) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
returnSize := returndatasize()
returnValue := mload(0)
}
if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
bool success;
uint256 returnSize;
uint256 returnValue;
assembly ("memory-safe") {
success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
returnSize := returndatasize()
returnValue := mload(0)
}
return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
}
}
"
},
"lib/openzeppelin-contracts/contracts/token/ERC721/IERC721Receiver.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/IERC721Receiver.sol)
pragma solidity ^0.8.20;
/**
* @title ERC-721 token receiver interface
* @dev Interface for any contract that wants to support safeTransfers
* from ERC-721 asset contracts.
*/
interface IERC721Receiver {
/**
* @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
* by `operator` from `from`, this function is called.
*
* It must return its Solidity selector to confirm the token transfer.
* If any other value is returned or the interface is not implemented by the recipient, the transfer will be
* reverted.
*
* The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
*/
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
"
},
"lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
* consider using {ReentrancyGuardTransient} instead.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
constructor() {
_status = NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be NOT_ENTERED
if (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
_status = ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == ENTERED;
}
}
"
},
"lib/permit2/src/interfaces/IPermit2.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {ISignatureTransfer} from "./ISignatureTransfer.sol";
import {IAllowanceTransfer} from "./IAllowanceTransfer.sol";
/// @notice Permit2 handles signature-based transfers in SignatureTransfer and allowance-based transfers in AllowanceTransfer.
/// @dev Users must approve Permit2 before calling any of the transfer functions.
interface IPermit2 is ISignatureTransfer, IAllowanceTransfer {
// IPermit2 unifies the two interfaces so users have maximal flexibility with their approval.
}
"
},
"lib/v4-core/src/types/PoolId.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {PoolKey} from "./PoolKey.sol";
type PoolId is bytes32;
/// @notice Library for computing the ID of a pool
library PoolIdLibrary {
/// @notice Returns value equal to keccak256(abi.encode(poolKey))
function toId(PoolKey memory poolKey) internal pure returns (PoolId poolId) {
assembly ("memory-safe") {
// 0xa0 represents the total size of the poolKey struct (5 slots of 32 bytes)
poolId := keccak256(poolKey, 0xa0)
}
}
}
"
},
"lib/universal-router/contracts/interfaces/IUniversalRouter.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;
interface IUniversalRouter {
/// @notice Thrown when a required command has failed
error ExecutionFailed(uint256 commandIndex, bytes message);
/// @notice Thrown when attempting to send ETH directly to the contract
error ET
Submitted on: 2025-10-07 22:31:22
Comments
Log in to comment.
No comments yet.