UniswapV3DexModule

Description:

Decentralized Finance (DeFi) protocol contract providing Swap, Factory functionality.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "contracts/swap/UniswapV3DexModule.sol": {
      "content": "// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.30;\r
\r
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";\r
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";\r
\r
/**\r
 * @title  UniswapV3DexModule\r
 * @author Andrei Averin — CTO dsf.finance\r
 * @notice Uniswap V3 module for UniversalRouter\r
 * @dev    - Supports 1-hop and 2-hop routes (via hub tokens).\r
 *         - Uses a QuoterV2-compatible contract for pricing (prefers StaticQuoterV2).\r
 *         - Executes swaps via Uniswap V3 ISwapRouter's exactInput.\r
 */\r
\r
/* ──────────── External interfaces ──────────── */\r
\r
interface IUniswapV3Factory {\r
    function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address pool);\r
}\r
\r
interface ISwapRouterV3 {\r
    /// @notice Parameters for exactInput swap\r
    struct ExactInputParams {\r
        bytes   path;\r
        address recipient;\r
        uint256 deadline;\r
        uint256 amountIn;\r
        uint256 amountOutMinimum;\r
    }\r
\r
    function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut);\r
}\r
\r
/**\r
 * @title   IQuoterV2\r
 * @notice  Quoter interface aligned with a view-return implementation (e.g. StaticQuoterV2),\r
 *          while still compatible with Uniswap's revert-data returning QuoterV2 via try/catch.\r
 */\r
interface IQuoterV2 {\r
    /// @notice Params for quoting a single v3 hop\r
    struct QuoteExactInputSingleParams {\r
        address tokenIn;\r
        address tokenOut;\r
        uint24  fee;\r
        uint256 amountIn;\r
        uint160 sqrtPriceLimitX96; // 0 = no limit\r
    }\r
\r
    /**\r
     * @notice  Quote an exact-input multi-hop path.\r
     * @dev     Implementations may return via normal return or revert(data).\r
     * @param   path      V3-encoded path\r
     * @param   amountIn  Input amount\r
     * @return  amountOut Quoted output amount\r
     * @return  sqrtPriceX96AfterList sqrt(P) after each hop\r
     * @return  initializedTicksCrossedList ticks crossed per hop\r
     * @return  gasEstimate Auxiliary value (0 for static quoters)\r
     */\r
    function quoteExactInput(bytes memory path, uint256 amountIn)\r
        external\r
        view\r
        returns (\r
            uint256 amountOut,\r
            uint160[] memory sqrtPriceX96AfterList,\r
            uint32[]  memory initializedTicksCrossedList,\r
            uint256   gasEstimate\r
        );\r
\r
    /**\r
     * @notice  Quote a single-hop exact-input swap.\r
     * @param   params    QuoteExactInputSingleParams\r
     * @return  amountOut Quoted output\r
     * @return  sqrtPriceX96After sqrt(P) after swap\r
     * @return  initializedTicksCrossed Ticks crossed\r
     * @return  gasEstimate Auxiliary value (0 for static quoters)\r
     */\r
    function quoteExactInputSingle(QuoteExactInputSingleParams memory params)\r
        external\r
        view\r
        returns (\r
            uint256 amountOut,\r
            uint160 sqrtPriceX96After,\r
            uint32  initializedTicksCrossed,\r
            uint256 gasEstimate\r
        );\r
}\r
\r
/* ──────────── Aggregator interfaces ───────────── */\r
\r
/**\r
 * @title IDexModule\r
 * @notice Generic DEX module interface used by an aggregator/UR.\r
 */\r
interface IDexModule {\r
    /**\r
     * @notice  Compute the best 1-hop and 2-hop routes.\r
     * @param   tokenIn    Input token\r
     * @param   tokenOut   Output token\r
     * @param   amountIn   Input amount\r
     * @return  best1HopRoute Serialized 1-hop route\r
     * @return  amountOut1Hop Quoted 1-hop output\r
     * @return  best2HopRoute Serialized 2-hop route\r
     * @return  amountOut2Hop Quoted 2-hop output\r
     */\r
    function getBestRoute(\r
        address tokenIn,\r
        address tokenOut,\r
        uint256 amountIn\r
    ) external view returns (\r
        DexRoute memory best1HopRoute,\r
        uint256 amountOut1Hop,\r
        DexRoute memory best2HopRoute,\r
        uint256 amountOut2Hop\r
    );\r
\r
    /**\r
     * @notice  Execute a previously returned route with a slippage check based on a percentage.\r
     * @param   route     Serialized route\r
     * @param   to        Recipient of the final tokens\r
     * @param   percent   Slippage tolerance (e.g., 9900 for 1% slippage, where 10000 is 0% slippage)\r
     * @return  amountOut Actual output received\r
     */\r
    function swapRoute(\r
        DexRoute calldata route,\r
        address to,\r
        uint256 percent\r
    ) external returns (\r
        uint256 amountOut\r
    );\r
\r
    /**\r
     * @notice  Simulate a route (1–2 hops) encoded as {DexRoute}.\r
     * @param   route Serialized route\r
     * @param   percent Dummy parameter (or for future use)\r
     * @return  amountOut Quoted total output amount\r
     */\r
    function simulateRoute(\r
        DexRoute calldata route,\r
        uint256 percent\r
    ) external view returns (uint256 amountOut);\r
}\r
\r
/// @notice Serialized route container; each hop is ABI-encoded\r
struct DexRoute {\r
    /// @dev Each element = abi.encode(tokenIn, tokenOut, pool, fee, amountIn)\r
    bytes[] data;\r
}\r
\r
/// @notice Quote for a hop or a complete route\r
struct Quote {\r
    address tokenIn;\r
    address tokenOut;\r
    address pool;      // v3 pool\r
    uint24  fee;       // v3 fee tier\r
    uint256 amountIn;\r
    uint256 amountOut;\r
}\r
\r
/// @notice Decoded hop for UI/debugging\r
struct RouteStep {\r
    address tokenIn;\r
    address tokenOut;\r
    address pool;\r
    uint24  fee;\r
    uint256 amountIn;\r
}\r
\r
/**\r
 * @notice Full route option with serialized route, hop quotes and final amountOut\r
 * @dev    Used for debugging/inspection (e.g., in getAllRoutes).\r
 */\r
struct RouteOption {\r
    DexRoute route;\r
    Quote[]  quotes;     // 1 or 2 elements\r
    uint256  amountOut;  // final out\r
}\r
\r
/* ──────────── Constants & small utils ─────────── */\r
\r
/**\r
 * @title   V3Path\r
 * @notice  Helpers for V3 path encoding\r
 */\r
library V3Path {\r
    /**\r
     * @notice  ABI-pack a fee tier (uint24) to 3-byte representation used in v3 paths.\r
     * @param   v Fee tier\r
     * @return  b 3-byte packed fee\r
     */\r
    function _u24(uint24 v) internal pure returns (bytes memory b) {\r
        b = abi.encodePacked(bytes1(uint8(v >> 16)), bytes1(uint8(v >> 8)), bytes1(uint8(v)));\r
    }\r
}\r
\r
/* ────────────────── Dex module ────────────────── */\r
\r
/**\r
 * @title   UniswapV3DexModule\r
 * @notice  Specific implementation of IDexModule for Uniswap V3.\r
 * @dev     Static contract, no administrative functions and no owner.\r
 *          Supports 1-hop and 2-hop routes (via WETH/USDC),\r
 *          takes quotes from a QuoterV2-compatible contract,\r
 *          executes swaps via Uniswap V3 Router.\r
 */\r
contract UniswapV3DexModule is IDexModule {\r
    using SafeERC20 for IERC20;\r
    using V3Path    for uint24;\r
\r
    /// @notice  Uniswap V3 factory & router (mainnet)\r
    IUniswapV3Factory public constant FACTORY = IUniswapV3Factory(0x1F98431c8aD98523631AE4a59f267346ea31F984);\r
    ISwapRouterV3     public constant ROUTER  = ISwapRouterV3(    0xE592427A0AEce92De3Edee1F18E0157C05861564);\r
\r
    /// @notice  Quoter contract (prefer StaticQuoterV2)\r
    IQuoterV2 public immutable QUOTER;\r
\r
    /// @notice  Common hub tokens used for 2-hop discovery\r
    address public constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;\r
    address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;\r
\r
    /// @notice  Fee tiers probed everywhere\r
    uint24[4] private ALL_FEES = [100, 500, 3000, 10000];\r
\r
    /**\r
     * @notice  Create the module with a given QuoterV2-compatible address.\r
     * @param   quoter Address of the quoter (e.g., StaticQuoterV2 or Uniswap's QuoterV2)\r
     */\r
    constructor(address quoter) {\r
        require(quoter != address(0), "V3: quoter=0");\r
        QUOTER = IQuoterV2(quoter);\r
    }\r
\r
    /* ───────────────── External VIEW ──────────────── */\r
\r
    /**\r
     * @inheritdoc  IDexModule\r
     */\r
    function getBestRoute(\r
        address tokenIn,\r
        address tokenOut,\r
        uint256 amountIn\r
    ) external view override returns (\r
        DexRoute memory best1HopRoute,\r
        uint256 amountOut1Hop,\r
        DexRoute memory best2HopRoute,\r
        uint256 amountOut2Hop\r
    ) {\r
        require(tokenIn != address(0) && tokenOut != address(0), "UniswapV3: zero addr");\r
        require(tokenIn != tokenOut, "UniswapV3: same token");\r
        require(amountIn > 0, "UniswapV3: zero amountIn");\r
\r
        // 1-hop\r
        (bool ok1, Quote[] memory best1, uint256 out1) = _bestOneHop(tokenIn, tokenOut, amountIn);\r
\r
        // 2-hop via hubs\r
        (bool ok2, Quote[] memory best2, uint256 out2) = _bestTwoHop(tokenIn, tokenOut, amountIn);\r
\r
        if (ok1) {\r
            best1HopRoute = DexRoute({ data: _encodeRouteFromQuotes(best1) });\r
            amountOut1Hop = out1;\r
        } else {\r
            // Return empty route and zero amount if no 1-hop route found\r
            best1HopRoute = DexRoute({ data: new bytes[](0) });\r
            amountOut1Hop = 0;\r
        }\r
\r
        if (ok2) {\r
            best2HopRoute = DexRoute({ data: _encodeRouteFromQuotes(best2) });\r
            amountOut2Hop = out2;\r
        } else {\r
            // Return empty route and zero amount if no 2-hop route found\r
            best2HopRoute = DexRoute({ data: new bytes[](0) });\r
            amountOut2Hop = 0;\r
        }\r
\r
        require(amountOut1Hop > 0 || amountOut2Hop > 0, "UniswapV3: no route");\r
    }\r
\r
    /**\r
     * @notice  Enumerate all viable 1-hop and 2-hop routes for debugging/inspection.\r
     * @dev     Not intended for production routing; this is a convenience view for UIs/tests.\r
     * @param   tokenIn  Input token\r
     * @param   tokenOut Output token\r
     * @param   amountIn Input amount\r
     * @return  routes   Array of routes, each represented only by its Quote[] list\r
     */\r
    function getAllRoutes(address tokenIn, address tokenOut, uint256 amountIn)\r
        external\r
        view\r
        returns (Quote[][] memory routes)\r
    {\r
        require(tokenIn != address(0) && tokenOut != address(0), "UniswapV3: zero addr");\r
        require(tokenIn != tokenOut, "UniswapV3: same token");\r
        require(amountIn > 0, "UniswapV3: zero amountIn");\r
\r
        RouteOption[] memory oneHop = _collectOneHopOptions(tokenIn, tokenOut, amountIn);\r
        RouteOption[] memory twoHop = _collectTwoHopOptions(tokenIn, tokenOut, amountIn);\r
\r
        uint256 total = oneHop.length + twoHop.length;\r
        routes = new Quote[][](total);\r
\r
        uint256 k;\r
        for (uint256 i; i < oneHop.length; ++i) {\r
            routes[k++] = _copyQuotes(oneHop[i].quotes);\r
        }\r
        for (uint256 j; j < twoHop.length; ++j) {\r
            routes[k++] = _copyQuotes(twoHop[j].quotes);\r
        }\r
    }\r
\r
    /**\r
     * @inheritdoc IDexModule\r
     */\r
    function simulateRoute(\r
        DexRoute calldata route,\r
        uint256 percent\r
    ) external view override returns (uint256 amountOut) {\r
        require(route.data.length == 1 || route.data.length == 2, "UniswapV3: bad route");\r
        require(percent <= 100, "UniswapV3: percent > 100");\r
        \r
        // Read the original amountIn from the route (the full quoted amount)\r
        (, , , , uint256 originalAmountIn) =\r
            abi.decode(route.data[0], (address, address, address, uint24, uint256));\r
        require(originalAmountIn > 0, "UniswapV3: zero original amountIn");\r
\r
        // Calculate the actual amount to simulate (amountIn * percent / 100)\r
        uint256 actualAmountIn = (originalAmountIn * percent) / 100;\r
        require(actualAmountIn > 0, "UniswapV3: zero actual amountIn");\r
        \r
        // Use the calculated amount for quoting\r
        bytes memory path = _buildPath(route);\r
        (amountOut,, ,) = _safeQuote(path, actualAmountIn);\r
    }\r
\r
    /**\r
     * @notice  Simulate a route (1–2 hops) encoded as {DexRoute}\r
     * @param   route Serialized route (each hop ABI-encoded as (tokenIn, tokenOut, pool, fee, amountIn))\r
     * @return  amountOut Quoted total output amount\r
     */\r
    function quoteExactInput(DexRoute calldata route)\r
        external\r
        view\r
        returns (uint256 amountOut)\r
    {\r
        require(route.data.length == 1 || route.data.length == 2, "UniswapV3: bad route");\r
        (,,,, uint256 amountIn) =\r
            abi.decode(route.data[0], (address, address, address, uint24, uint256));\r
        bytes memory path = _buildPath(route);\r
        (amountOut,, ,) = _safeQuote(path, amountIn);\r
    }\r
\r
    /**\r
     * @notice  Decode a serialized route into human-readable steps (for UI/debugging)\r
     * @param   route Serialized route\r
     * @return  steps Array of RouteStep with resolved fields\r
     */\r
    function decodeRoute(DexRoute calldata route)\r
        external\r
        pure\r
        returns (RouteStep[] memory steps)\r
    {\r
        uint256 len = route.data.length;\r
        steps = new RouteStep[](len);\r
        for (uint256 n; n < len; ++n) {\r
            (address tokenIn, address tokenOut, address pool, uint24 fee, uint256 amountIn) =\r
                abi.decode(route.data[n], (address, address, address, uint24, uint256));\r
\r
            steps[n] = RouteStep({\r
                tokenIn:  tokenIn,\r
                tokenOut: tokenOut,\r
                pool:     pool,\r
                fee:      fee,\r
                amountIn: amountIn\r
            });\r
        }\r
    }\r
\r
    /* ──────────── External STATE-CHANGING ─────────── */\r
\r
    /**\r
     * @inheritdoc IDexModule\r
     */\r
    function swapRoute(\r
        DexRoute calldata route,\r
        address to,\r
        uint256 percent // Percentage (0-100) of amountIn from the route to be swapped\r
    ) external override returns (uint256 amountOut) {\r
        require(to != address(0), "UniswapV3: bad recipient");\r
        require(route.data.length == 1 || route.data.length == 2, "UniswapV3: only 1-2 hops");\r
        require(percent <= 100, "UniswapV3: percent > 100");\r
\r
        // Read the first hop to know amountIn/tokenIn\r
        (address tokenIn,, , , uint256 originalAmountIn) =\r
            abi.decode(route.data[0], (address, address, address, uint24, uint256));\r
        require(originalAmountIn > 0, "UniswapV3: zero amountIn");\r
\r
        uint256 amountIn = (originalAmountIn * percent) / 100;\r
        require(amountIn > 0, "UniswapV3: zero actual amountIn");\r
\r
        uint256 balBefore = IERC20(tokenIn).balanceOf(address(this));\r
        _pullToken(tokenIn, amountIn);\r
        uint256 received = IERC20(tokenIn).balanceOf(address(this)) - balBefore;\r
        require(received == amountIn, "UniswapV3: FOT not supported");\r
        _smartApprove(tokenIn, address(ROUTER), amountIn);\r
\r
        bytes memory path = _buildPath(route);\r
\r
        amountOut = ROUTER.exactInput(\r
            ISwapRouterV3.ExactInputParams({\r
                path: path,\r
                recipient: to,\r
                deadline: block.timestamp,\r
                amountIn: amountIn,\r
                amountOutMinimum: 0\r
            })\r
        );\r
    }\r
\r
    /* ──────────────── Internal VIEW ─────────────── */\r
\r
    /**\r
     * @notice Collect all viable 1-hop options across ALL_FEES.\r
     * @dev    Returns only options that both exist (pool!=0) and quote to >0.\r
     * @param  tokenIn  Input token\r
     * @param  tokenOut Output token\r
     * @param  amountIn Input amount\r
     * @return opts     Dense array of valid RouteOption instances\r
     */\r
    function _collectOneHopOptions(\r
        address tokenIn,\r
        address tokenOut,\r
        uint256 amountIn\r
    ) internal view returns (RouteOption[] memory opts) {\r
        RouteOption[] memory tempOpts = new RouteOption[](ALL_FEES.length);\r
        uint256 found = 0;\r
\r
        (address a, address b) = _sortTokens(tokenIn, tokenOut);\r
\r
        for (uint256 i = 0; i < ALL_FEES.length; i++) {\r
            uint24 fee = ALL_FEES[i];\r
            address pool = FACTORY.getPool(a, b, fee);\r
            if (pool == address(0)) {\r
                continue;\r
            }\r
\r
            (uint256 out,, ,) = _safeQuoteSingle(IQuoterV2.QuoteExactInputSingleParams({\r
                tokenIn: tokenIn,\r
                tokenOut: tokenOut,\r
                fee: fee,\r
                amountIn: amountIn,\r
                sqrtPriceLimitX96: 0\r
            }));\r
            if (out == 0) {\r
                continue;\r
            }\r
\r
            // Correctly build and assign the DexRoute for the single hop\r
            bytes[] memory routeData = new bytes[](1);\r
            routeData[0] = abi.encode(tokenIn, tokenOut, pool, fee, amountIn);\r
\r
            // Correctly build and assign the Quote array\r
            Quote[] memory qs = new Quote[](1);\r
            qs[0] = Quote({\r
                tokenIn: tokenIn,\r
                tokenOut: tokenOut,\r
                pool: pool,\r
                fee: fee,\r
                amountIn: amountIn,\r
                amountOut: out\r
            });\r
\r
            // Assign the complete RouteOption struct\r
            tempOpts[found] = RouteOption({\r
                route: DexRoute({ data: routeData }),\r
                quotes: qs,\r
                amountOut: out\r
            });\r
            unchecked { ++found; }\r
        }\r
\r
        // Trim the temporary array to the final size\r
        opts = new RouteOption[](found);\r
        for (uint256 k = 0; k < found; k++) {\r
            opts[k] = tempOpts[k];\r
        }\r
    }\r
\r
    /**\r
     * @notice  Collect all viable 2-hop options across HUB_TOKENS × ALL_FEES × ALL_FEES.\r
     * @param   tokenIn  Input token\r
     * @param   tokenOut Output token\r
     * @param   amountIn Input amount\r
     * @return  opts     Dense array of valid RouteOption instances\r
     */\r
    function _collectTwoHopOptions(\r
        address tokenIn,\r
        address tokenOut,\r
        uint256 amountIn\r
    ) internal view returns (RouteOption[] memory opts) {\r
        RouteOption[] memory tmp = new RouteOption[](\r
            2 * ALL_FEES.length * ALL_FEES.length\r
        );\r
        uint256 found;\r
\r
        for (uint256 h = 0; h < 2; ++h) {\r
            address hub = _hubAt(h);\r
            if (hub == tokenIn || hub == tokenOut) continue;\r
\r
            RouteOption[] memory part = _collectTwoHopForHub(tokenIn, tokenOut, amountIn, hub);\r
            for (uint256 p = 0; p < part.length; ++p) {\r
                tmp[found] = part[p];\r
                unchecked { ++found; }\r
            }\r
        }\r
\r
        opts = new RouteOption[](found);\r
        for (uint256 k = 0; k < found; ++k) {\r
            opts[k] = tmp[k];\r
        }\r
    }\r
\r
    /**\r
     * @notice  Collect 2-hop options for a specific hub across ALL_FEES × ALL_FEES.\r
     * @param   tokenIn  Input token\r
     * @param   tokenOut Output token\r
     * @param   amountIn Input amount\r
     * @param   hub      Hub token address\r
     * @return  opts     Dense array of valid RouteOption instances for this hub\r
     */\r
    function _collectTwoHopForHub(\r
        address tokenIn,\r
        address tokenOut,\r
        uint256 amountIn,\r
        address hub\r
    ) internal view returns (RouteOption[] memory opts) {\r
        RouteOption[] memory tmp = new RouteOption[](ALL_FEES.length * ALL_FEES.length);\r
        uint256 found;\r
\r
        for (uint256 i = 0; i < ALL_FEES.length; ++i) {\r
            for (uint256 j = 0; j < ALL_FEES.length; ++j) {\r
                RouteOption memory opt = _buildTwoHopOption(\r
                    tokenIn, tokenOut, hub, ALL_FEES[i], ALL_FEES[j], amountIn\r
                );\r
                if (opt.amountOut == 0) continue;\r
\r
                tmp[found] = opt;\r
                unchecked { ++found; }\r
            }\r
        }\r
\r
        opts = new RouteOption[](found);\r
        for (uint256 k = 0; k < found; ++k) {\r
            opts[k] = tmp[k];\r
        }\r
    }\r
\r
    /**\r
     * @notice  Try to build a valid 2-hop option (tokenIn → hub → tokenOut) for specific fee pair.\r
     * @dev     Returns zeroed struct if either pool is missing or any quote is zero.\r
     * @param   tokenIn  Input token\r
     * @param   tokenOut Output token\r
     * @param   hub      Hub token\r
     * @param   fee0     Fee tier for tokenIn→hub\r
     * @param   fee1     Fee tier for hub→tokenOut\r
     * @param   amountIn Input amount\r
     * @return  opt      Fully-populated RouteOption or zeroed if invalid\r
     */\r
    function _buildTwoHopOption(\r
        address tokenIn,\r
        address tokenOut,\r
        address hub,\r
        uint24  fee0,\r
        uint24  fee1,\r
        uint256 amountIn\r
    ) internal view returns (RouteOption memory opt) {\r
        address pool0 = FACTORY.getPool(\r
            tokenIn < hub ? tokenIn : hub,\r
            tokenIn < hub ? hub     : tokenIn,\r
            fee0\r
        );\r
        if (pool0 == address(0)) {\r
            return opt;\r
        }\r
\r
        address pool1 = FACTORY.getPool(\r
            hub < tokenOut ? hub     : tokenOut,\r
            hub < tokenOut ? tokenOut: hub,\r
            fee1\r
        );\r
        if (pool1 == address(0)) {\r
            return opt;\r
        }\r
\r
        (uint256 hop0Out,, ,) = _safeQuoteSingle(IQuoterV2.QuoteExactInputSingleParams({\r
            tokenIn: tokenIn,\r
            tokenOut: hub,\r
            fee: fee0,\r
            amountIn: amountIn,\r
            sqrtPriceLimitX96: 0\r
        }));\r
        if (hop0Out == 0) {\r
            return opt;\r
        }\r
\r
        (uint256 finalOut,, ,) = _safeQuote(\r
            abi.encodePacked(tokenIn, fee0._u24(), hub, fee1._u24(), tokenOut),\r
            amountIn\r
        );\r
        if (finalOut == 0) {\r
            return opt;\r
        }\r
\r
        Quote[] memory qs = new Quote[](2);\r
\r
        qs[0] = Quote({\r
            tokenIn:   tokenIn,\r
            tokenOut:  hub,\r
            pool:      pool0,\r
            fee:       fee0,\r
            amountIn:  amountIn,\r
            amountOut: hop0Out\r
        });\r
\r
        qs[1] = Quote({\r
            tokenIn:   hub,\r
            tokenOut:  tokenOut,\r
            pool:      pool1,\r
            fee:       fee1,\r
            amountIn:  hop0Out,\r
            amountOut: finalOut\r
        });\r
\r
        bytes[] memory routeData = new bytes[](2);\r
        routeData[0] = abi.encode(tokenIn, hub, pool0, fee0, amountIn);\r
        routeData[1] = abi.encode(hub, tokenOut, pool1, fee1, hop0Out);\r
\r
        opt = RouteOption({\r
            route: DexRoute({ data: routeData }),\r
            quotes: qs,\r
            amountOut: finalOut\r
        });\r
    }\r
\r
    /* ──────────────── Internal HELPERS ─────────────── */\r
\r
    /**\r
     * @notice  Build a v3 path from a serialized route (1–2 hop)\r
     * @dev     Path = tokenIn | fee0 | tokenMid? | fee1? | tokenOut\r
     * @param   route DexRoute (bytes representation of hops)\r
     * @return  path Constructed v3 path ready for router/quoter\r
     */\r
    function _buildPath(DexRoute calldata route) internal pure returns (bytes memory path) {\r
        uint256 hops = route.data.length;\r
        require(hops >= 1 && hops <= 2, "UniswapV3: bad hops");\r
\r
        // read first token\r
        (address tokenIn,, , ,) = abi.decode(route.data[0], (address, address, address, uint24, uint256));\r
        path = abi.encodePacked(tokenIn);\r
\r
        for (uint256 i; i < hops; ++i) {\r
            (, address tokenOut,, uint24 fee,) =\r
                abi.decode(route.data[i], (address, address, address, uint24, uint256));\r
            path = bytes.concat(path, fee._u24(), abi.encodePacked(tokenOut));\r
        }\r
    }\r
\r
    /**\r
     * @notice  Transfer the specified amount of tokens from the caller to this contract.\r
     * @dev     Uses   SafeERC20.safeTransferFrom.\r
     * @param   token  ERC20 address\r
     * @param   amount Amount to transfer\r
     */\r
    function _pullToken(address token, uint256 amount) internal {\r
        IERC20(token).safeTransferFrom(msg.sender, address(this), amount);\r
    }\r
\r
    /**\r
     * @notice  Safely set allowance for a spender, handling non-standard ERC20s.\r
     * @param   token    ERC20 token address\r
     * @param   spender  Allowance spender\r
     * @param   amount   Minimum required allowance amount\r
     */\r
    function _smartApprove (address token, address spender, uint256 amount) internal {\r
        if (IERC20(token).allowance(address(this), spender) < amount) {\r
            IERC20(token).forceApprove(spender, type(uint256).max);\r
        }\r
    }\r
\r
    /**\r
     * @notice  Sort two token addresses ascending\r
     * @param   x First  token\r
     * @param   y Second token\r
     * @return  a Lower  address (token0)\r
     * @return  b Higher address (token1)\r
     */\r
    function _sortTokens(address x, address y) internal pure returns (address a, address b) {\r
        require(x != address(0) && y != address(0), "UniswapV3: zero addr");\r
        require(x != y, "UniswapV3: same token");\r
        (a, b) = x < y ? (x, y) : (y, x);\r
    }\r
\r
    /* ───────────────── Quote helpers ───────────────── */\r
\r
    /**\r
     * @notice  Try to quote a multi-hop path via a view-return quoter first; fallback to Uniswap's revert-data format\r
     * @param   path        V3-encoded path\r
     * @param   amountIn    Input amount\r
     * @return  amountOut   Quoted output\r
     * @return  s           Sqrt(P) after each hop (may be empty if v1-only)\r
     * @return  t           Initialized ticks crossed per hop (may be empty if v1-only)\r
     * @return  gasEstimate Compatibility value (0 for StaticQuoterV2)\r
     */\r
    function _safeQuote(bytes memory path, uint256 amountIn)\r
        internal\r
        view\r
        returns (uint256 amountOut, uint160[] memory s, uint32[] memory t, uint256 gasEstimate)\r
    {\r
        // Fast path: StaticQuoterV2 returns normally\r
        try QUOTER.quoteExactInput(path, amountIn) returns (\r
            uint256 a,\r
            uint160[] memory s2,\r
            uint32[]  memory t2,\r
            uint256   g\r
        ) {\r
            return (a, s2, t2, g);\r
        } catch (bytes memory ret) {\r
            if (ret.length > 4) {\r
                bytes memory data = _stripSelector(ret);\r
                uint256 a2 = _decodeAmountOutLoose(data);\r
                if (a2 > 0) {\r
                    return (a2, new uint160[](0), new uint32[](0), 0);\r
                }\r
            }\r
            // Corrected default return for dynamic arrays\r
            return (0, new uint160[](0), new uint32[](0), 0);\r
        }\r
    }\r
\r
    /**\r
     * @notice  Quote a single-hop swap with a QuoterV2-compatible contract.\r
     * @dev     Tries normal view-return first; falls back to decoding revert(data).\r
     * @param   params      IQuoterV2.QuoteExactInputSingleParams\r
     * @return  amountOut   Quoted output\r
     * @return  spAfter     Sqrt(P) after swap (0 if v1-only)\r
     * @return  ticks       ticks crossed (0 if v1-only)\r
     * @return  gasEstimate Compatibility value (0 for StaticQuoterV2)\r
     */\r
    function _safeQuoteSingle(IQuoterV2.QuoteExactInputSingleParams memory params)\r
        internal\r
        view\r
        returns (uint256 amountOut, uint160 spAfter, uint32 ticks, uint256 gasEstimate)\r
    {\r
        // Fast path: StaticQuoterV2\r
        try QUOTER.quoteExactInputSingle(params) returns (uint256 a, uint160 s, uint32 c, uint256 g) {\r
            return (a, s, c, g);\r
        } catch (bytes memory ret) {\r
            if (ret.length > 4) {\r
                bytes memory data = _stripSelector(ret);\r
                uint256 a2 = _decodeAmountOutLoose(data);\r
                if (a2 > 0) {\r
                    return (a2, 0, 0, 0);\r
                }\r
            }\r
            return (0, 0, 0, 0);\r
        }\r
    }\r
\r
    /* ────────── Revert-data decoding helpers ───────── */\r
\r
    /**\r
     * @notice  Strip the first 4 bytes (function selector) from revert data\r
     * @param   revertData Raw revert data\r
     * @return  out        Revert payload without selector\r
     */\r
    function _stripSelector(bytes memory revertData) private pure returns (bytes memory out) {\r
        if (revertData.length <= 4) return "";\r
        out = new bytes(revertData.length - 4);\r
        for (uint256 i = 0; i < out.length; ++i) {\r
            out[i] = revertData[i + 4];\r
        }\r
    }\r
\r
    /**\r
     * @notice  Leniently extracts the first 32 bytes (amountOut) from revert payload.\r
     * @dev     Works for both full-tuple and amount-only QuoterV2 encodings.\r
     */\r
    function _decodeAmountOutLoose(bytes memory data) internal pure returns (uint256 out) {\r
        if (data.length < 32) return 0;\r
        assembly { out := mload(add(data, 32)) } // first 32 bytes = amountOut\r
    }\r
\r
    /**\r
     * @notice  Shallow-copy a memory array of Quote.\r
     * @param   src Source array\r
     * @return  dst New array with the same elements\r
     */\r
    function _copyQuotes(Quote[] memory src) private pure returns (Quote[] memory dst) {\r
        uint256 n = src.length;\r
        dst = new Quote[](n);\r
        for (uint256 i; i < n; ++i) {\r
            dst[i] = src[i];\r
        }\r
    }\r
\r
    /**\r
     * @notice  Turn Quote[] into the serialized bytes[] format used by swapRoute.\r
     * @dev     Each hop encodes (tokenIn, tokenOut, pool, fee, amountIn).\r
     * @param   qs   Quotes (len 1 or 2)\r
     * @return  data bytes[] suitable for DexRoute\r
     */\r
    function _encodeRouteFromQuotes(Quote[] memory qs) internal pure returns (bytes[] memory data) {\r
        uint256 hops = qs.length;\r
        require(hops == 1 || hops == 2, "UniswapV3: only 1-2 hops");\r
\r
        data = new bytes[](hops);\r
\r
        // hop0: amountIn = original input amount\r
        data[0] = abi.encode(\r
            qs[0].tokenIn,\r
            qs[0].tokenOut,\r
            qs[0].pool,\r
            qs[0].fee,\r
            qs[0].amountIn\r
        );\r
\r
        if (hops == 2) {\r
            require(qs[0].tokenOut == qs[1].tokenIn, "UniswapV3: hops mismatch");\r
\r
            uint256 hop1In = qs[1].amountIn;\r
            if (hop1In == 0) {\r
                hop1In = qs[0].amountOut;\r
                require(hop1In > 0, "UniswapV3: hop1 amountIn=0");\r
            }\r
\r
            data[1] = abi.encode(\r
                qs[1].tokenIn,\r
                qs[1].tokenOut,\r
                qs[1].pool,\r
                qs[1].fee,\r
                hop1In\r
            );\r
        }\r
    }\r
\r
    /**\r
     * @notice  Find the best single-hop route across ALL_FEES\r
     * @dev     uses quoteExactInputSingle to avoid path-encoding overhead\r
     * @param   tokenIn  Input token\r
     * @param   tokenOut Output token\r
     * @param   amountIn Input amount\r
     * @return  ok       True if a valid 1-hop route was found\r
     * @return  best     Quotes array (length 1) for the best route\r
     * @return  bestOut  Final quoted output\r
     */\r
    function _bestOneHop(\r
        address tokenIn,\r
        address tokenOut,\r
        uint256 amountIn\r
    ) internal view returns (bool ok, Quote[] memory best, uint256 bestOut) {\r
        (address a, address b) = _sortTokens(tokenIn, tokenOut);\r
\r
        for (uint256 i; i < ALL_FEES.length; ) {\r
            uint24 fee = ALL_FEES[i];\r
\r
            address pool = FACTORY.getPool(a, b, fee);\r
            if (pool != address(0)) {\r
                (uint256 out,, ,) = _safeQuoteSingle(IQuoterV2.QuoteExactInputSingleParams({\r
                    tokenIn: tokenIn,\r
                    tokenOut: tokenOut,\r
                    fee: fee,\r
                    amountIn: amountIn,\r
                    sqrtPriceLimitX96: 0\r
                }));\r
\r
                if (out > bestOut) {\r
                    ok = true;\r
                    bestOut = out;\r
                    best = new Quote[](1);\r
                    best[0] = Quote({\r
                        tokenIn: tokenIn,\r
                        tokenOut: tokenOut,\r
                        pool: pool,\r
                        fee: fee,\r
                        amountIn: amountIn,\r
                        amountOut: out\r
                    });\r
                }\r
            }\r
            unchecked { ++i; }\r
        }\r
    }\r
\r
    /**\r
     * @notice  Find the best two-hop route via configured hubs.\r
     * @param   tokenIn  Input token\r
     * @param   tokenOut Output token\r
     * @param   amountIn Input amount\r
     * @return  ok       True if a valid 2-hop route was found\r
     * @return  best     Quotes array (length 2) for the best route\r
     * @return  bestOut  Final quoted output\r
     */\r
    function _bestTwoHop(address tokenIn, address tokenOut, uint256 amountIn)\r
        internal\r
        view\r
        returns (bool ok, Quote[] memory best, uint256 bestOut)\r
    {\r
        address bestHub;\r
        uint24  bestFee0;\r
        uint24  bestFee1;\r
\r
        for (uint256 h; h < 2; ) {\r
            address hub = _hubAt(h);\r
            if (hub != tokenIn && hub != tokenOut) {\r
                (uint24 f0, uint24 f1, uint256 outCand) = _bestTwoHopForHub(tokenIn, hub, tokenOut, amountIn);\r
                if (outCand > bestOut) {\r
                    ok       = true;\r
                    bestOut  = outCand;\r
                    bestHub  = hub;\r
                    bestFee0 = f0;\r
                    bestFee1 = f1;\r
                }\r
            }\r
            unchecked { ++h; }\r
        }\r
\r
        if (ok) {\r
            address pool0 = _getPool(tokenIn, bestHub,  bestFee0);\r
            address pool1 = _getPool(bestHub,  tokenOut, bestFee1);\r
\r
            (uint256 hop0Out,, ,) = _safeQuoteSingle(IQuoterV2.QuoteExactInputSingleParams({\r
                tokenIn: tokenIn,\r
                tokenOut: bestHub,\r
                fee:     bestFee0,\r
                amountIn: amountIn,\r
                sqrtPriceLimitX96: 0\r
            }));\r
            require(hop0Out > 0, "UniswapV3: hop0 quote=0");\r
\r
            Quote[] memory q = new Quote[](2);\r
            q[0] = Quote({\r
                tokenIn: tokenIn,\r
                tokenOut: bestHub,\r
                pool: pool0,\r
                fee: bestFee0,\r
                amountIn: amountIn,\r
                amountOut: hop0Out\r
            });\r
            q[1] = Quote({\r
                tokenIn: bestHub,\r
                tokenOut: tokenOut,\r
                pool: pool1,\r
                fee: bestFee1,\r
                amountIn: hop0Out,\r
                amountOut: bestOut\r
            });\r
\r
            best = q;\r
        }\r
    }\r
\r
    /**\r
     * @notice  Small hub selector to keep the loop tight and avoid reading HUB_TOKENS in memory.\r
     * @param   i Index in {0,1}\r
     * @return  The hub address (0→WETH, 1→USDC)\r
     */\r
    function _hubAt(uint256 i) internal pure returns (address) {\r
        if (i == 0) return WETH;\r
        return USDC;\r
    }\r
\r
    /**\r
     * @notice  For a given hub, find the best fee pair (fee0, fee1) and its output.\r
     * @dev     Single full-path quote per (fee0, fee1) pair; pools existence checked upfront.\r
     * @param   tokenIn  Input token\r
     * @param   hub      Hub token\r
     * @param   tokenOut Output token\r
     * @param   amountIn Input amount\r
     * @return  bestFee0 Best fee for tokenIn→hub\r
     * @return  bestFee1 Best fee for hub→tokenOut\r
     * @return  bestOut  Best final output for this hub\r
     */\r
    function _bestTwoHopForHub(\r
        address tokenIn,\r
        address hub,\r
        address tokenOut,\r
        uint256 amountIn\r
    ) internal view returns (uint24 bestFee0, uint24 bestFee1, uint256 bestOut) {\r
        for (uint256 i; i < ALL_FEES.length; ) {\r
            uint24 fee0 = ALL_FEES[i];\r
            address pool0 = _getPool(tokenIn, hub, fee0);\r
            if (pool0 != address(0)) {\r
                for (uint256 j; j < ALL_FEES.length; ) {\r
                    uint24 fee1 = ALL_FEES[j];\r
                    address pool1 = _getPool(hub, tokenOut, fee1);\r
                    if (pool1 != address(0)) {\r
                        bytes memory path = abi.encodePacked(tokenIn, fee0._u24(), hub, fee1._u24(), tokenOut);\r
                        (uint256 outTmp,, ,) = _safeQuote(path, amountIn);\r
                        if (outTmp > bestOut) { bestOut = outTmp; bestFee0 = fee0; bestFee1 = fee1; }\r
                    }\r
                    unchecked { ++j; }\r
                }\r
            }\r
            unchecked { ++i; }\r
        }\r
    }\r
\r
    /**\r
     * @notice  Thin wrapper that sorts tokens and calls FACTORY.getPool.\r
     * @param   x   Token A\r
     * @param   y   Token B\r
     * @param   fee Fee tier\r
     * @return  Pool address or zero if missing\r
     */\r
    function _getPool(address x, address y, uint24 fee) internal view returns (address) {\r
        (address a, address b) = _sortTokens(x, y);\r
        return FACTORY.getPool(a, b, fee);\r
    }\r
}\r
"
    },
    "@openzeppelin/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);
    }
}
"
    },
    "@openzeppelin/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)

pragma solidity >=0.4.16;

/**
 * @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);
}
"
    },
    "@openzeppelin/contracts/interfaces/IERC1363.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC1363.sol)

pragma solidity >=0.6.2;

import {IERC20} from "./IERC20.sol";
import {IERC165} from "./IERC165.sol";

/**
 * @title IERC1363
 * @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
 *
 * Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
 * after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
 */
interface IERC1363 is IERC20, IERC165 {
    /*
     * Note: the ERC-165 identifier for this interface is 0xb0202a11.
     * 0xb0202a11 ===
     *   bytes4(keccak256('transferAndCall(address,uint256)')) ^
     *   bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
     *   bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
     *   bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
     *   bytes4(keccak256('approveAndCall(address,uint256)')) ^
     *   bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
     */

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferAndCall(address to, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @param data Additional data with no specified format, sent in call to `to`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param from The address which you want to send tokens from.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferFromAndCall(address from, address to, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param from The address which you want to send tokens from.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @param data Additional data with no specified format, sent in call to `to`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function approveAndCall(address spender, uint256 value) external returns (bool);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     * @param data Additional data with no specified format, sent in call to `spender`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}
"
    },
    "@openzeppelin/contracts/interfaces/IERC165.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC165.sol)

pragma solidity >=0.4.16;

import {IERC165} from "../utils/introspection/IERC165.sol";
"
    },
    "@openzeppelin/contracts/interfaces/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC20.sol)

pragma solidity >=0.4.16;

import {IERC20} from "../token/ERC20/IERC20.sol";
"
    },
    "@openzeppelin/contracts/utils/introspection/IERC165.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/introspection/IERC165.sol)

pragma solidity >=0.4.16;

/**
 * @dev Interface of the ERC-165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[ERC].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
"
    }
  },
  "settings": {
    "optimizer": {
      "enabled": true,
      "runs": 200
    },
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "abi"
        ]
      }
    },
    "remappings": []
  }
}}

Tags:
ERC20, ERC165, DeFi, Swap, Factory|addr:0xf544a4cd898608d0fb142188c4f2ea43b5a34e0f|verified:true|block:23532075|tx:0xb374e2f292fded90e658c4ca64ec53b5f3b550d5ecf57826a94734ae4da657a5|first_check:1759918685

Submitted on: 2025-10-08 12:18:06

Comments

Log in to comment.

No comments yet.