V3Utils

0xb4acbc082b5e7ded571c98ee4257778a9d784b36

Verification
Verified
v0.8.15+commit.e14f2714
Type
Contract
24,351 bytes
ABI entries
55
11 read · 16 write
License
none

Contract information

Address
0xb4acbc082b5e7ded571c98ee4257778a9d784b36
Chain
Robinhood Chain (4663)
Compiler
v0.8.15+commit.e14f2714
Optimization
Enabled
Creation tx
0x2ab817bf5f…f283624666

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (11)

ADMIN_ROLE()bytes32
DEFAULT_ADMIN_ROLE()bytes32
FEE_TAKER()address
WETH()address
WITHDRAWER_ROLE()bytes32
getMaxFeeX64(uint8)uint64
getRoleAdmin(bytes32)bytes32
hasRole(bytes32, address)bool
paused()bool
supportsInterface(bytes4)bool
swapRouter()address

Events (11)

ChangeRangeCompoundFeesDeductFeesPausedRoleAdminChangedRoleGrantedRoleRevokedSwapAndIncreaseLiquiditySwapAndMintUnpausedWithdrawAndCollectAndSwap

ABI

[
  {
    "inputs": [],
    "name": "AmountError",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "CollectError",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "EtherSendFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "GetPositionFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NoEtherToken",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NoFees",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotSupportedAction",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotSupportedProtocol",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "NotWETH",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "SameToken",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "SelfSend",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "SlippageError",
    "type": "error"
  },
  {
    "inputs": [
      {
        "internalType": "bytes",
        "name": "swapData",
        "type": "bytes"
      },
      {
        "internalType": "uint256",
        "name": "index",
        "type": "uint256"
      }
    ],
    "name": "SwapFailed",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TooMuchEtherSent",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TooMuchFee",
    "type": "error"
  },
  {
    "inputs": [],
    "name": "TransferError",
    "type": "error"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "nfpm",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "newTokenId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "newLiquidity",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "token0Added",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "token1Added",
        "type": "uint256"
      }
    ],
    "name": "ChangeRange",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "nfpm",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint128",
        "name": "liquidity",
        "type": "uint128"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount0",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount1",
        "type": "uint256"
      }
    ],
    "name": "CompoundFees",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "nfpm",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "userAddress",
        "type": "address"
      },
      {
        "components": [
          {
            "internalType": "address",
            "name": "token0",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "token1",
            "type": "address"
          },
          {
            "internalType": "address",
            "name": "token2",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "amount0",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "amount1",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "amount2",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "feeAmount0",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "feeAmount1",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "feeAmount2",
            "type": "uint256"
          },
          {
            "internalType": "uint64",
            "name": "feeX64",
            "type": "uint64"
          },
          {
            "internalType": "enum Common.FeeType",
            "name": "feeType",
            "type": "uint8"
          }
        ],
        "indexed": false,
        "internalType": "struct Common.DeductFeesEventData",
        "name": "data",
        "type": "tuple"
      }
    ],
    "name": "DeductFees",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "Paused",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "previousAdminRole",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "newAdminRole",
        "type": "bytes32"
      }
    ],
    "name": "RoleAdminChanged",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "sender",
        "type": "address"
      }
    ],
    "name": "RoleGranted",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "account",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "sender",
        "type": "address"
      }
    ],
    "name": "RoleRevoked",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "nfpm",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint128",
        "name": "liquidity",
        "type": "uint128"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount0",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount1",
        "type": "uint256"
      }
    ],
    "name": "SwapAndIncreaseLiquidity",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "nfpm",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint128",
        "name": "liquidity",
        "type": "uint128"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount0",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount1",
        "type": "uint256"
      }
    ],
    "name": "SwapAndMint",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": false,
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "Unpaused",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "nfpm",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "WithdrawAndCollectAndSwap",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "ADMIN_ROLE",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "DEFAULT_ADMIN_ROLE",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "FEE_TAKER",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "WETH",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "WITHDRAWER_ROLE",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "contract INonfungiblePositionManager",
        "name": "_nfpm",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      },
      {
        "components": [
          {
            "internalType": "enum V3Utils.WhatToDo",
            "name": "whatToDo",
            "type": "uint8"
          },
          {
            "internalType": "enum Nfpm.Protocol",
            "name": "protocol",
            "type": "uint8"
          },
          {
            "internalType": "address",
            "name": "targetToken",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "amountRemoveMin0",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "amountRemoveMin1",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "amountIn0",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "amountOut0Min",
            "type": "uint256"
          },
          {
            "internalType": "bytes",
            "name": "swapData0",
            "type": "bytes"
          },
          {
            "internalType": "uint256",
            "name": "amountIn1",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "amountOut1Min",
            "type": "uint256"
          },
          {
            "internalType": "bytes",
            "name": "swapData1",
            "type": "bytes"
          },
          {
            "internalType": "int24",
            "name": "tickLower",
            "type": "int24"
          },
          {
            "internalType": "int24",
            "name": "tickUpper",
            "type": "int24"
          },
          {
            "internalType": "bool",
            "name": "compoundFees",
            "type": "bool"
          },
          {
            "internalType": "uint128",
            "name": "liquidity",
            "type": "uint128"
          },
          {
            "internalType": "uint256",
            "name": "amountAddMin0",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "amountAddMin1",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "deadline",
            "type": "uint256"
          },
          {
            "internalType": "address",
            "name": "recipient",
            "type": "address"
          },
          {
            "internalType": "bool",
            "name": "unwrap",
            "type": "bool"
          },
          {
            "internalType": "uint64",
            "name": "liquidityFeeX64",
            "type": "uint64"
          },
          {
            "internalType": "uint64",
            "name": "performanceFeeX64",
            "type": "uint64"
          },
          {
            "internalType": "uint64",
            "name": "gasFeeX64",
            "type": "uint64"
          }
        ],
        "internalType": "struct V3Utils.Instructions",
        "name": "instructions",
        "type": "tuple"
      }
    ],
    "name": "execute",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "enum Common.FeeType",
        "name": "feeType",
        "type": "uint8"
      }
    ],
    "name": "getMaxFeeX64",
    "outputs": [
      {
        "internalType": "uint64",
        "name": "",
        "type": "uint64"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      }
    ],
    "name": "getRoleAdmin",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "grantRole",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "hasRole",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "router",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "admin",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "feeTaker",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "_weth",
        "type": "address"
      },
      {
        "internalType": "address[]",
        "name": "whitelistedNfpms",
        "type": "address[]"
      }
    ],
    "name": "initialize",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      },
      {
        "internalType": "bytes",
        "name": "data",
        "type": "bytes"
      }
    ],
    "name": "onERC721Received",
    "outputs": [
      {
        "internalType": "bytes4",
        "name": "",
        "type": "bytes4"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "pause",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "paused",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "renounceRole",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes32",
        "name": "role",
        "type": "bytes32"
      },
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "revokeRole",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "feeTaker",
        "type": "address"
      }
    ],
    "name": "setFeeTaker",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "enum Common.FeeType",
        "name": "feeType",
        "type": "uint8"
      },
      {
        "internalType": "uint64",
        "name": "feex64",
        "type": "uint64"
      }
    ],
    "name": "setMaxFeeX64",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address[]",
        "name": "nfpms",
        "type": "address[]"
      },
      {
        "internalType": "bool",
        "name": "isWhitelist",
        "type": "bool"
      }
    ],
    "name": "setWhitelistNfpm",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "bytes4",
        "name": "interfaceId",
        "type": "bytes4"
      }
    ],
    "name": "supportsInterface",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "components": [
          {
            "internalType": "enum Nfpm.Protocol",
            "name": "protocol",
            "type": "uint8"
          },
          {
            "internalType": "contract INonfungiblePositionManager",
            "name": "nfpm",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "tokenId",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "amount0",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "amount1",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "amount2",
            "type": "uint256"
          },
          {
            "internalType": "address",
            "name": "recipient",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "deadline",
            "type": "uint256"
          },
          {
            "internalType": "contract IERC20",
            "name": "swapSourceToken",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "amountIn0",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "amountOut0Min",
            "type": "uint256"
          },
          {
            "internalType": "bytes",
            "name": "swapData0",
            "type": "bytes"
          },
          {
            "internalType": "uint256",
            "name": "amountIn1",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "amountOut1Min",
            "type": "uint256"
          },
          {
            "internalType": "bytes",
            "name": "swapData1",
            "type": "bytes"
          },
          {
            "internalType": "uint256",
            "name": "amountAddMin0",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "amountAddMin1",
            "type": "uint256"
          },
          {
            "internalType": "uint64",
            "name": "protocolFeeX64",
            "type": "uint64"
          },
          {
            "internalType": "uint64",
            "name": "gasFeeX64",
            "type": "uint64"
          }
        ],
        "internalType": "struct Common.SwapAndIncreaseLiquidityParams",
        "name": "params",
        "type": "tuple"
      }
    ],
    "name": "swapAndIncreaseLiquidity",
    "outputs": [
      {
        "components": [
          {
            "internalType": "uint128",
            "name": "liquidity",
            "type": "uint128"
          },
          {
            "internalType": "uint256",
            "name": "added0",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "added1",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "feeAmount0",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "feeAmount1",
            "type": "uint256"
          }
        ],
        "internalType": "struct Common.SwapAndIncreaseLiquidityResult",
        "name": "result",
        "type": "tuple"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "components": [
          {
            "internalType": "enum Nfpm.Protocol",
            "name": "protocol",
            "type": "uint8"
          },
          {
            "internalType": "contract INonfungiblePositionManager",
            "name": "nfpm",
            "type": "address"
          },
          {
            "internalType": "contract IERC20",
            "name": "token0",
            "type": "address"
          },
          {
            "internalType": "contract IERC20",
            "name": "token1",
            "type": "address"
          },
          {
            "internalType": "uint24",
            "name": "fee",
            "type": "uint24"
          },
          {
            "internalType": "int24",
            "name": "tickSpacing",
            "type": "int24"
          },
          {
            "internalType": "int24",
            "name": "tickLower",
            "type": "int24"
          },
          {
            "internalType": "int24",
            "name": "tickUpper",
            "type": "int24"
          },
          {
            "internalType": "uint64",
            "name": "protocolFeeX64",
            "type": "uint64"
          },
          {
            "internalType": "uint64",
            "name": "gasFeeX64",
            "type": "uint64"
          },
          {
            "internalType": "uint256",
            "name": "amount0",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "amount1",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "amount2",
            "type": "uint256"
          },
          {
            "internalType": "address",
            "name": "recipient",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "deadline",
            "type": "uint256"
          },
          {
            "internalType": "contract IERC20",
            "name": "swapSourceToken",
            "type": "address"
          },
          {
            "internalType": "uint256",
            "name": "amountIn0",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "amountOut0Min",
            "type": "uint256"
          },
          {
            "internalType": "bytes",
            "name": "swapData0",
            "type": "bytes"
          },
          {
            "internalType": "uint256",
            "name": "amountIn1",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "amountOut1Min",
            "type": "uint256"
          },
          {
            "internalType": "bytes",
            "name": "swapData1",
            "type": "bytes"
          },
          {
            "internalType": "uint256",
            "name": "amountAddMin0",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "amountAddMin1",
            "type": "uint256"
          },
          {
            "internalType": "address",
            "name": "poolDeployer",
            "type": "address"
          }
        ],
        "internalType": "struct Common.SwapAndMintParams",
        "name": "params",
        "type": "tuple"
      }
    ],
    "name": "swapAndMint",
    "outputs": [
      {
        "components": [
          {
            "internalType": "uint256",
            "name": "tokenId",
            "type": "uint256"
          },
          {
            "internalType": "uint128",
            "name": "liquidity",
            "type": "uint128"
          },
          {
            "internalType": "uint256",
            "name": "added0",
            "type": "uint256"
          },
          {
            "internalType": "uint256",
            "name": "added1",
            "type": "uint256"
          }
        ],
        "internalType": "struct Common.SwapAndMintResult",
        "name": "result",
        "type": "tuple"
      }
    ],
    "stateMutability": "payable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "swapRouter",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "unpause",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "contract IERC20[]",
        "name": "tokens",
        "type": "address[]"
      },
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      }
    ],
    "name": "withdrawERC20",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "contract INonfungiblePositionManager",
        "name": "nfpm",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "tokenId",
        "type": "uint256"
      },
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      }
    ],
    "name": "withdrawERC721",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      }
    ],
    "name": "withdrawNative",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "stateMutability": "payable",
    "type": "receive"
  }
]

Source code

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "./Common.sol";

/// @title v3Utils v1.0
/// @notice Utility functions for Uniswap V3 positions
/// This is a completely ownerless/stateless contract - does not hold any ERC20 or NFTs.
contract V3Utils is IERC721Receiver, Common {
    /// @notice Action which should be executed on provided NFT
    enum WhatToDo {
        CHANGE_RANGE,
        WITHDRAW_AND_COLLECT_AND_SWAP,
        COMPOUND_FEES
    }

    /// @notice Complete description of what should be executed on provided NFT - different fields are used depending on specified WhatToDo
    struct Instructions {
        // what action to perform on provided Uniswap v3 position
        WhatToDo whatToDo;
        // protocol to provide lp
        Nfpm.Protocol protocol;
        // target token for swaps (if this is address(0) no swaps are executed)
        address targetToken;
        // for removing liquidity slippage
        uint256 amountRemoveMin0;
        uint256 amountRemoveMin1;
        // amountIn0 is used for swap and also as minAmount0 for decreased liquidity + collected fees
        uint256 amountIn0;
        // if token0 needs to be swapped to targetToken - set values
        uint256 amountOut0Min;
        bytes swapData0; // encoded data from 0x api call (address,bytes) - allowanceTarget,data
        // amountIn1 is used for swap and also as minAmount1 for decreased liquidity + collected fees
        uint256 amountIn1;
        // if token1 needs to be swapped to targetToken - set values
        uint256 amountOut1Min;
        bytes swapData1; // encoded data from 0x api call (address,bytes) - allowanceTarget,data
        // for creating new positions with CHANGE_RANGE
        int24 tickLower;
        int24 tickUpper;
        bool compoundFees;
        // remove liquidity amount for COMPOUND_FEES (in this case should be probably 0) / CHANGE_RANGE / WITHDRAW_AND_COLLECT_AND_SWAP
        uint128 liquidity;
        // for adding liquidity slippage
        uint256 amountAddMin0;
        uint256 amountAddMin1;
        // for all uniswap deadlineable functions
        uint256 deadline;
        // left over tokens will be sent to this address
        address recipient;
        // if tokenIn or tokenOut is WETH - unwrap
        bool unwrap;
        // protocol fees
        uint64 liquidityFeeX64;
        uint64 performanceFeeX64;
        uint64 gasFeeX64;
    }

    /// @notice Execute instruction by pulling approved NFT instead of direct safeTransferFrom call from owner
    /// @param tokenId Token to process
    /// @param instructions Instructions to execute
    function execute(INonfungiblePositionManager _nfpm, uint256 tokenId, Instructions calldata instructions)
        external
        whenNotPaused
    {
        // must be approved beforehand
        _nfpm.safeTransferFrom(msg.sender, address(this), tokenId, abi.encode(instructions));
    }

    /// @notice ERC721 callback function. Called on safeTransferFrom and does manipulation as configured in encoded Instructions parameter.
    /// At the end the NFT (and any newly minted NFT) is returned to sender. The leftover tokens are sent to instructions.recipient.
    function onERC721Received(address, address from, uint256 tokenId, bytes calldata data)
        external
        override
        whenNotPaused
        returns (bytes4)
    {
        // not allowed to send to itself
        if (from == address(this)) {
            revert SelfSend();
        }

        require(_isWhitelistedNfpm(msg.sender));
        INonfungiblePositionManager nfpm = INonfungiblePositionManager(msg.sender);

        Instructions memory instructions = abi.decode(data, (Instructions));

        Position memory position = _getPosition(nfpm, instructions.protocol, tokenId);

        uint256 amount0;
        uint256 amount1;
        {
            (uint256 feeAmount0, uint256 feeAmount1) = (0, 0);

            (amount0, amount1, feeAmount0, feeAmount1) = _decreaseLiquidityAndCollectFees(
                DecreaseAndCollectFeesParams(
                    nfpm,
                    instructions.recipient,
                    IERC20(position.token0),
                    IERC20(position.token1),
                    tokenId,
                    instructions.liquidity,
                    instructions.deadline,
                    instructions.amountRemoveMin0,
                    instructions.amountRemoveMin1,
                    instructions.compoundFees
                )
            );

            // deduct fees
            {
                DeductFeesParams memory _deductFeesParams = DeductFeesParams(
                    amount0 - feeAmount0, // only liquidity tokens, not including fees
                    amount1 - feeAmount1,
                    0,
                    instructions.gasFeeX64,
                    FeeType.GAS_FEE,
                    msg.sender,
                    tokenId,
                    instructions.recipient,
                    position.token0,
                    position.token1,
                    address(0)
                );
                uint256 gasFeeAmount0;
                uint256 gasFeeAmount1;
                if (instructions.gasFeeX64 > 0) {
                    (,,, gasFeeAmount0, gasFeeAmount1,) = _deductFees(_deductFeesParams, true);
                }

                _deductFeesParams.feeX64 = instructions.liquidityFeeX64;
                _deductFeesParams.feeType = FeeType.LIQUIDITY_FEE;
                uint256 liquidityFeeAmount0;
                uint256 liquidityFeeAmount1;
                if (instructions.liquidityFeeX64 > 0) {
                    (,,, liquidityFeeAmount0, liquidityFeeAmount1,) = _deductFees(_deductFeesParams, true);
                }

                _deductFeesParams.amount0 = feeAmount0;
                _deductFeesParams.amount1 = feeAmount1;
                _deductFeesParams.feeX64 = instructions.performanceFeeX64;
                _deductFeesParams.feeType = FeeType.PERFORMANCE_FEE;
                uint256 performanceFeeAmount0;
                uint256 performanceFeeAmount1;
                if (instructions.performanceFeeX64 > 0) {
                    (,,, performanceFeeAmount0, performanceFeeAmount1,) = _deductFees(_deductFeesParams, true);
                }

                amount0 -= (liquidityFeeAmount0 + gasFeeAmount0);
                amount1 -= (liquidityFeeAmount1 + gasFeeAmount1);

                // if compound fees, amount for next action is deducted from performance fees
                // otherwise, we exclude collected fees from the amounts
                if (instructions.compoundFees) {
                    amount0 -= performanceFeeAmount0;
                    amount1 -= performanceFeeAmount1;
                } else {
                    amount0 -= feeAmount0;
                    amount1 -= feeAmount1;
                    ReturnLeftoverTokensParams memory _returnLeftoverTokensParams;

                    _returnLeftoverTokensParams.to = instructions.recipient;
                    _returnLeftoverTokensParams.token0 = IERC20(position.token0);
                    _returnLeftoverTokensParams.token1 = IERC20(position.token1);
                    _returnLeftoverTokensParams.total0 = feeAmount0;
                    _returnLeftoverTokensParams.total1 = feeAmount1;
                    _returnLeftoverTokensParams.added0 = performanceFeeAmount0;
                    _returnLeftoverTokensParams.added1 = performanceFeeAmount1;
                    _returnLeftoverTokensParams.unwrap = instructions.unwrap;

                    _returnLeftoverTokens(_returnLeftoverTokensParams);
                }
            }
        }

        // check if enough tokens are available for swaps
        if (amount0 < instructions.amountIn0 || amount1 < instructions.amountIn1) {
            revert AmountError();
        }

        if (instructions.whatToDo == WhatToDo.COMPOUND_FEES) {
            SwapAndIncreaseLiquidityResult memory result;
            if (instructions.targetToken == position.token0) {
                result = _swapAndIncrease(
                    SwapAndIncreaseLiquidityParams(
                        instructions.protocol,
                        nfpm,
                        tokenId,
                        amount0,
                        amount1,
                        0,
                        instructions.recipient,
                        instructions.deadline,
                        IERC20(position.token1),
                        instructions.amountIn1,
                        instructions.amountOut1Min,
                        instructions.swapData1,
                        0,
                        0,
                        "",
                        instructions.amountAddMin0,
                        instructions.amountAddMin1,
                        0,
                        0
                    ),
                    IERC20(position.token0),
                    IERC20(position.token1),
                    instructions.unwrap
                );
            } else if (instructions.targetToken == position.token1) {
                result = _swapAndIncrease(
                    SwapAndIncreaseLiquidityParams(
                        instructions.protocol,
                        nfpm,
                        tokenId,
                        amount0,
                        amount1,
                        0,
                        instructions.recipient,
                        instructions.deadline,
                        IERC20(position.token0),
                        0,
                        0,
                        "",
                        instructions.amountIn0,
                        instructions.amountOut0Min,
                        instructions.swapData0,
                        instructions.amountAddMin0,
                        instructions.amountAddMin1,
                        0,
                        0
                    ),
                    IERC20(position.token0),
                    IERC20(position.token1),
                    instructions.unwrap
                );
            } else {
                // no swap is done here
                result = _swapAndIncrease(
                    SwapAndIncreaseLiquidityParams(
                        instructions.protocol,
                        nfpm,
                        tokenId,
                        amount0,
                        amount1,
                        0,
                        instructions.recipient,
                        instructions.deadline,
                        IERC20(address(0)),
                        0,
                        0,
                        "",
                        0,
                        0,
                        "",
                        instructions.amountAddMin0,
                        instructions.amountAddMin1,
                        0,
                        0
                    ),
                    IERC20(position.token0),
                    IERC20(position.token1),
                    instructions.unwrap
                );
            }
            emit CompoundFees(address(nfpm), tokenId, result.liquidity, result.added0, result.added1);
        } else if (instructions.whatToDo == WhatToDo.CHANGE_RANGE) {
            SwapAndMintResult memory result;
            if (instructions.targetToken == position.token0) {
                result = _swapAndMint(
                    SwapAndMintParams(
                        instructions.protocol,
                        nfpm,
                        IERC20(position.token0),
                        IERC20(position.token1),
                        position.fee,
                        position.tickSpacing,
                        instructions.tickLower,
                        instructions.tickUpper,
                        0,
                        0,
                        amount0,
                        amount1,
                        0,
                        instructions.recipient,
                        instructions.deadline,
                        IERC20(position.token1),
                        instructions.amountIn1,
                        instructions.amountOut1Min,
                        instructions.swapData1,
                        0,
                        0,
                        "",
                        instructions.amountAddMin0,
                        instructions.amountAddMin1,
                        position.deployer
                    ),
                    instructions.unwrap
                );
            } else if (instructions.targetToken == position.token1) {
                result = _swapAndMint(
                    SwapAndMintParams(
                        instructions.protocol,
                        nfpm,
                        IERC20(position.token0),
                        IERC20(position.token1),
                        position.fee,
                        position.tickSpacing,
                        instructions.tickLower,
                        instructions.tickUpper,
                        0,
                        0,
                        amount0,
                        amount1,
                        0,
                        instructions.recipient,
                        instructions.deadline,
                        IERC20(position.token0),
                        0,
                        0,
                        "",
                        instructions.amountIn0,
                        instructions.amountOut0Min,
                        instructions.swapData0,
                        instructions.amountAddMin0,
                        instructions.amountAddMin1,
                        position.deployer
                    ),
                    instructions.unwrap
                );
            } else {
                // no swap is done here
                result = _swapAndMint(
                    SwapAndMintParams(
                        instructions.protocol,
                        nfpm,
                        IERC20(position.token0),
                        IERC20(position.token1),
                        position.fee,
                        position.tickSpacing,
                        instructions.tickLower,
                        instructions.tickUpper,
                        0,
                        0,
                        amount0,
                        amount1,
                        0,
                        instructions.recipient,
                        instructions.deadline,
                        IERC20(address(0)),
                        0,
                        0,
                        "",
                        0,
                        0,
                        "",
                        instructions.amountAddMin0,
                        instructions.amountAddMin1,
                        position.deployer
                    ),
                    instructions.unwrap
                );
            }

            emit ChangeRange(msg.sender, tokenId, result.tokenId, result.liquidity, result.added0, result.added1);
        } else if (instructions.whatToDo == WhatToDo.WITHDRAW_AND_COLLECT_AND_SWAP) {
            uint256 targetAmount;
            if (position.token0 != instructions.targetToken) {
                (uint256 amountInDelta, uint256 amountOutDelta) = _swap(
                    IERC20(position.token0),
                    IERC20(instructions.targetToken),
                    amount0,
                    instructions.amountOut0Min,
                    instructions.swapData0,
                    0
                );
                if (amountInDelta < amount0) {
                    _transferToken(
                        instructions.recipient, IERC20(position.token0), amount0 - amountInDelta, instructions.unwrap
                    );
                }
                targetAmount += amountOutDelta;
            } else {
                targetAmount += amount0;
            }
            if (position.token1 != instructions.targetToken) {
                (uint256 amountInDelta, uint256 amountOutDelta) = _swap(
                    IERC20(position.token1),
                    IERC20(instructions.targetToken),
                    amount1,
                    instructions.amountOut1Min,
                    instructions.swapData1,
                    1
                );
                if (amountInDelta < amount1) {
                    _transferToken(
                        instructions.recipient, IERC20(position.token1), amount1 - amountInDelta, instructions.unwrap
                    );
                }
                targetAmount += amountOutDelta;
            } else {
                targetAmount += amount1;
            }

            // send complete target amount
            if (targetAmount != 0 && instructions.targetToken != address(0)) {
                _transferToken(
                    instructions.recipient, IERC20(instructions.targetToken), targetAmount, instructions.unwrap
                );
            }

            emit WithdrawAndCollectAndSwap(address(nfpm), tokenId, instructions.targetToken, targetAmount);
        } else {
            revert NotSupportedAction();
        }

        // return token to owner (this line guarantees that token is returned to originating owner)
        nfpm.transferFrom(address(this), from, tokenId);

        return IERC721Receiver.onERC721Received.selector;
    }

    /// @notice Does 1 or 2 swaps from swapSourceToken to token0 and token1 and adds as much as possible liquidity to a newly minted position.
    /// Newly minted NFT and leftover tokens are returned to recipient
    function swapAndMint(SwapAndMintParams calldata params)
        external
        payable
        whenNotPaused
        returns (SwapAndMintResult memory result)
    {
        if (params.token0 == params.token1) {
            revert SameToken();
        }
        require(_isWhitelistedNfpm(address(params.nfpm)));

        // validate if amount2 is enough for action
        if (
            params.swapSourceToken != params.token0 && params.swapSourceToken != params.token1
                && params.amountIn0 + params.amountIn1 > params.amount2
        ) {
            revert AmountError();
        }
        _prepareSwap(
            params.token0, params.token1, params.swapSourceToken, params.amount0, params.amount1, params.amount2
        );
        SwapAndMintParams memory _params = params;

        DeductFeesEventData memory liquidityFeeEventData;
        DeductFeesEventData memory gasFeeEventData;

        uint256 feeAmount0;
        uint256 feeAmount1;
        uint256 feeAmount2;
        if (params.protocolFeeX64 > 0) {
            // since we do not have the tokenId here, we need to emit event later
            (,,, feeAmount0, feeAmount1, feeAmount2) = _deductFees(
                DeductFeesParams(
                    params.amount0,
                    params.amount1,
                    params.amount2,
                    params.protocolFeeX64,
                    FeeType.LIQUIDITY_FEE,
                    address(params.nfpm),
                    0,
                    params.recipient,
                    address(params.token0),
                    address(params.token1),
                    address(params.swapSourceToken)
                ),
                false
            );
            liquidityFeeEventData = DeductFeesEventData({
                token0: address(params.token0),
                token1: address(params.token1),
                token2: address(params.swapSourceToken),
                amount0: params.amount0,
                amount1: params.amount1,
                amount2: params.amount2,
                feeAmount0: feeAmount0,
                feeAmount1: feeAmount1,
                feeAmount2: feeAmount2,
                feeX64: params.protocolFeeX64,
                feeType: FeeType.LIQUIDITY_FEE
            });

            _params.amount0 -= feeAmount0;
            _params.amount1 -= feeAmount1;
            _params.amount2 -= feeAmount2;
        }
        if (params.gasFeeX64 > 0) {
            // since we do not have the tokenId here, we need to emit event later
            (,,, feeAmount0, feeAmount1, feeAmount2) = _deductFees(
                DeductFeesParams(
                    params.amount0,
                    params.amount1,
                    params.amount2,
                    params.gasFeeX64,
                    FeeType.GAS_FEE,
                    address(params.nfpm),
                    0,
                    params.recipient,
                    address(params.token0),
                    address(params.token1),
                    address(params.swapSourceToken)
                ),
                false
            );

            gasFeeEventData = DeductFeesEventData({
                token0: address(params.token0),
                token1: address(params.token1),
                token2: address(params.swapSourceToken),
                amount0: params.amount0,
                amount1: params.amount1,
                amount2: params.amount2,
                feeAmount0: feeAmount0,
                feeAmount1: feeAmount1,
                feeAmount2: feeAmount2,
                feeX64: params.gasFeeX64,
                feeType: FeeType.GAS_FEE
            });
            _params.amount0 -= feeAmount0;
            _params.amount1 -= feeAmount1;
            _params.amount2 -= feeAmount2;
        }

        result = _swapAndMint(_params, msg.value != 0);
        if (params.protocolFeeX64 > 0) {
            emit DeductFees(address(params.nfpm), result.tokenId, params.recipient, liquidityFeeEventData);
        }
        if (params.gasFeeX64 > 0) {
            emit DeductFees(address(params.nfpm), result.tokenId, params.recipient, gasFeeEventData);
        }
    }

    /// @notice Does 1 or 2 swaps from swapSourceToken to token0 and token1 and adds as much as possible liquidity to any existing position (no need to be position owner).
    // Sends any leftover tokens to recipient.
    function swapAndIncreaseLiquidity(SwapAndIncreaseLiquidityParams calldata params)
        external
        payable
        whenNotPaused
        returns (SwapAndIncreaseLiquidityResult memory result)
    {
        require(_isWhitelistedNfpm(address(params.nfpm)));
        address owner = params.nfpm.ownerOf(params.tokenId);
        require(owner == msg.sender);

        Position memory position = _getPosition(params.nfpm, params.protocol, params.tokenId);

        // validate if amount2 is enough for action
        if (
            address(params.swapSourceToken) != position.token0 && address(params.swapSourceToken) != position.token1
                && params.amountIn0 + params.amountIn1 > params.amount2
        ) {
            revert AmountError();
        }

        _prepareSwap(
            IERC20(position.token0),
            IERC20(position.token1),
            params.swapSourceToken,
            params.amount0,
            params.amount1,
            params.amount2
        );
        SwapAndIncreaseLiquidityParams memory _params = params;
        uint256 feeAmount0;
        uint256 feeAmount1;
        uint256 feeAmount2;
        if (params.protocolFeeX64 > 0) {
            (,,, feeAmount0, feeAmount1, feeAmount2) = _deductFees(
                DeductFeesParams(
                    params.amount0,
                    params.amount1,
                    params.amount2,
                    params.protocolFeeX64,
                    FeeType.LIQUIDITY_FEE,
                    address(params.nfpm),
                    params.tokenId,
                    params.recipient,
                    position.token0,
                    position.token1,
                    address(params.swapSourceToken)
                ),
                true
            );
            _params.amount0 -= feeAmount0;
            _params.amount1 -= feeAmount1;
            _params.amount2 -= feeAmount2;
        }
        if (params.gasFeeX64 > 0) {
            (,,, feeAmount0, feeAmount1, feeAmount2) = _deductFees(
                DeductFeesParams(
                    params.amount0,
                    params.amount1,
                    params.amount2,
                    params.gasFeeX64,
                    FeeType.GAS_FEE,
                    address(params.nfpm),
                    params.tokenId,
                    params.recipient,
                    position.token0,
                    position.token1,
                    address(params.swapSourceToken)
                ),
                true
            );
            _params.amount0 -= feeAmount0;
            _params.amount1 -= feeAmount1;
            _params.amount2 -= feeAmount2;
        }

        result = _swapAndIncrease(_params, IERC20(position.token0), IERC20(position.token1), msg.value != 0);
    }

    // needed for WETH unwrapping
    receive() external payable {}
}
Chain explorer1471msChain node86ms