MultiswapRouter

Description:

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

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "hopium/uniswap/main/multiswap-router.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "hopium/common/interface/imDirectory.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

import "hopium/uniswap/types/multiswap.sol";
import "hopium/common/types/bips.sol";

import "hopium/common/lib/transfer-helpers.sol";
import "hopium/common/interface-ext/iWeth.sol";
import "hopium/common/lib/permit2.sol";

import "hopium/uniswap/lib/oracleLibrary.sol";
import "hopium/uniswap/interface/imPoolFinder.sol";

import "hopium/uniswap/interface-ext/uniswapV2.sol";
import "hopium/uniswap/interface-ext/uniswapV3.sol";
import "hopium/uniswap/interface-ext/universal-router.sol";

/* ----------------------------------------------------- */
/*                  Custom errors (cheaper)              */
/* ----------------------------------------------------- */
error ZeroAddress();
error InvalidInput();
error WeightsInvalid();
error NoPoolFound();

/* ----------------------------------------------------- */
/*                        Storage                        */
/* ----------------------------------------------------- */
abstract contract Storage {
    address public immutable wethAddress;

    IPermit2 public immutable permit2; 

    IUniswapV2Factory public immutable v2Factory;
    IUniswapV3Factory public immutable v3Factory;

    IUniswapV2Router02 public immutable v2Router;   // read-only getAmountsOut
    IUniversalRouter    public immutable uRouter;   // actual swaps

    constructor(
        address _v2Router,
        address _v3Factory,
        address _universalRouter,
        address _permit2
    ) {
        if (_v3Factory == address(0)) revert ZeroAddress();
        if (_v2Router  == address(0)) revert ZeroAddress();
        if (_universalRouter == address(0)) revert ZeroAddress();
        if (_permit2 == address(0)) revert ZeroAddress();

        v3Factory = IUniswapV3Factory(_v3Factory);
        v2Router  = IUniswapV2Router02(_v2Router);
        uRouter   = IUniversalRouter(_universalRouter);

        address _weth = IUniswapV2Router02(_v2Router).WETH();
        wethAddress = _weth;
        v2Factory   = IUniswapV2Factory(IUniswapV2Router02(_v2Router).factory());

        permit2 = IPermit2(_permit2);
    }
}

/* ----------------------------------------------------- */
/*                      Quote helpers                    */
/* ----------------------------------------------------- */
error QuoteUnavailable();
abstract contract QuoteHelpers is Storage, ImDirectory {
    function _applySlippage(uint256 quoteOut, uint32 slippageBps) internal pure returns (uint256) {
        if (slippageBps > HUNDRED_PERCENT_BIPS) revert WeightsInvalid();
        return (quoteOut * (HUNDRED_PERCENT_BIPS - slippageBps)) / HUNDRED_PERCENT_BIPS;
    }

    /// @dev V2 minOut calc using Router.getAmountsOut (read-only use)
    /// @dev V2 minOut calc using Router.getAmountsOut (cheap view).
    ///      For fee-on-transfer tokens, this is only an estimate (the router can’t know transfer fees).
    function _calcMinOutV2(
        address tokenInAddress,
        address tokenOutAddress,
        uint256 amountIn,
        uint32  slippageBps
    ) internal view returns (uint256 minOut) {
        if (amountIn == 0) return 0;
        if (tokenInAddress == address(0) || tokenOutAddress == address(0)) revert ZeroAddress();
        if (slippageBps > HUNDRED_PERCENT_BIPS) revert WeightsInvalid();

        address[] memory path = new address[](2);
        path[0] = tokenInAddress;
        path[1] = tokenOutAddress;

        uint256[] memory amounts;
        // some routers may revert if pair doesn’t exist; catch and signal unavailability
        try v2Router.getAmountsOut(amountIn, path) returns (uint256[] memory outAmts) {
            amounts = outAmts;
        } catch {
            revert QuoteUnavailable();
        }

        if (amounts.length < 2) revert QuoteUnavailable();
        uint256 quoteOut = amounts[1];
        if (quoteOut == 0) revert QuoteUnavailable();

        minOut = _applySlippage(quoteOut, slippageBps);
    }

    /// @dev V3 minOut calc using pool TWAP/spot — no Quoter simulation gas.
    ///      - Applies fee-on-input (1e6 denominator) before pricing.
    ///      - Uses a short TWAP (e.g., 15s), falling back to spot tick if oracle is too young.
    ///      NOTE: Ignores your own price impact; use conservative slippage for large trades.
    function _calcMinOutV3(
        address tokenInAddress,
        address tokenOutAddress,
        uint24  fee,           // 500, 3000, 10000, etc.
        uint256 amountIn,
        uint32  slippageBps
    ) internal view returns (uint256 minOut) {
        if (amountIn == 0) return 0;
        if (tokenInAddress == address(0) || tokenOutAddress == address(0)) revert ZeroAddress();
        if (slippageBps > HUNDRED_PERCENT_BIPS) revert WeightsInvalid();

        address pool = v3Factory.getPool(tokenInAddress, tokenOutAddress, fee);
        if (pool == address(0)) revert NoPoolFound();

        // Uniswap V3 takes fee from amountIn
        uint256 amountAfterFee = (amountIn * (1_000_000 - uint256(fee))) / 1_000_000;
        if (amountAfterFee == 0) revert QuoteUnavailable();

        // Cap to uint128 as required by OracleLibrary.getQuoteAtTick
        uint128 amt128 = amountAfterFee > type(uint128).max ? type(uint128).max : uint128(amountAfterFee);

        // Choose a short, cheap TWAP window (adjust if you like)
        uint32 secondsAgo = 15;

        // Derive a tick to quote at: prefer TWAP, fall back to spot
        int24 tickForQuote;
        {
            uint32[] memory secondsAgos = new uint32[](2);
            secondsAgos[0] = secondsAgo; // older
            secondsAgos[1] = 0;          // now

            // try TWAP via external call (try/catch allowed)
            try IUniswapV3Pool(pool).observe(secondsAgos) returns (
                int56[] memory tickCumulatives,
                uint160[] memory /* secondsPerLiquidityCumulativeX128s */
            ) {
                tickForQuote = OracleLibrary.meanTickFromCumulatives(
                    tickCumulatives[0],
                    tickCumulatives[1],
                    secondsAgo
                );
            } catch {
                // not enough observations (young/idle pool) — use spot tick
                (, int24 spotTick, , , , , ) = IUniswapV3Pool(pool).slot0();
                tickForQuote = spotTick;
            }
        }

        uint256 quoteOut = OracleLibrary.getQuoteAtTick(
            tickForQuote,
            amt128,
            tokenInAddress,
            tokenOutAddress
        );
        if (quoteOut == 0) revert QuoteUnavailable();

        // Apply user slippage tolerance
        minOut = _applySlippage(quoteOut, slippageBps);
    }
}

/* ----------------------------------------------------- */
/*                 Universal Router swapper              */
/* ----------------------------------------------------- */
abstract contract URSwapHelpers is QuoteHelpers {
    // Command bytes (from Uniswap’s Commands.sol)
    uint256 constant CMD_V3_SWAP_EXACT_IN = 0x00;
    uint256 constant CMD_V2_SWAP_EXACT_IN = 0x08;

    /* ---------------------- Packing helpers ----------------------- */
    // inputs for V3_SWAP_EXACT_IN = abi.encode(recipient, amountIn, amountOutMin, path, payerIsUser)
    function _packV3ExactInInput(
        address tokenInAddress,
        address tokenOutAddress,
        uint24 fee,
        uint256 amountIn,
        uint256 amountOutMin,
        address recipientAddress
    ) internal returns (bytes memory input) {
        TransferHelpers.approveMaxIfNeeded(tokenInAddress, address(permit2), amountIn);
        Permit2.approveMaxIfNeededPermit2(address(permit2), tokenInAddress, address(uRouter), uint160(amountIn));
        bytes memory path = abi.encodePacked(tokenInAddress, fee, tokenOutAddress);
        input = abi.encode(recipientAddress, amountIn, amountOutMin, path, true);
    }

    // inputs for V2_SWAP_EXACT_IN = abi.encode(recipient, amountIn, amountOutMin, path[], payerIsUser)
    function _packV2ExactInInput(
        address tokenInAddress,
        address tokenOutAddress,
        uint256 amountIn,
        uint256 amountOutMin,
        address recipientAddress
    ) internal returns (bytes memory input) {
        TransferHelpers.approveMaxIfNeeded(tokenInAddress, address(permit2), amountIn);
        Permit2.approveMaxIfNeededPermit2(address(permit2), tokenInAddress, address(uRouter), uint160(amountIn));
        address[] memory path = new address[](2);
        path[0] = tokenInAddress;
        path[1] = tokenOutAddress;
        input = abi.encode(recipientAddress, amountIn, amountOutMin, path, true);
    }

    error NoSwaps();
    // Execute a built batch
    function _executeBatch(bytes memory commands, bytes[] memory inputs, uint256 count) internal {
        if (count == 0) revert NoSwaps();

        // Trim inputs array length if over-allocated
        if (inputs.length != count) {
            bytes[] memory trimmed = new bytes[](count);
            unchecked {
                for (uint256 i = 0; i < count; ++i) trimmed[i] = inputs[i];
            }
            inputs = trimmed;
        }

        // Trim commands length in-place (cheapest)
        assembly {
            mstore(commands, count)
        }

        uRouter.execute(commands, inputs);
    }
}

/* ----------------------------------------------------- */
/*            Token <-> WETH packed-leg builders         */
/* ----------------------------------------------------- */
abstract contract TokenEthSwapHelpers is URSwapHelpers, ImPoolFinder {
    /// @notice Build a WETH->token swap leg (no execute).
    function _packWethToTokenSwap(
        address tokenAddress,
        uint256 wethAmount,
        address recipientAddress,
        uint32 slippageBps
    ) internal returns (bytes1 cmd, bytes memory packedInput) {
        if (wethAmount == 0) return (bytes1(0), bytes(""));
         Pool memory pool = getPoolFinder().getBestWethPoolAndUpdateIfStale(tokenAddress);
        if (pool.poolAddress == address(0)) revert NoPoolFound();

        uint256 minOut;
        if (pool.isV3Pool == 1) {
            minOut = _calcMinOutV3(wethAddress, tokenAddress, pool.poolFee, wethAmount, slippageBps);
            packedInput = _packV3ExactInInput(wethAddress, tokenAddress, pool.poolFee, wethAmount, minOut, recipientAddress);
            cmd = bytes1(uint8(CMD_V3_SWAP_EXACT_IN));
        } else {
            minOut = _calcMinOutV2(wethAddress, tokenAddress, wethAmount, slippageBps);
            packedInput = _packV2ExactInInput(wethAddress, tokenAddress, wethAmount, minOut, recipientAddress);
            cmd = bytes1(uint8(CMD_V2_SWAP_EXACT_IN));
        }
    }

    /// @notice Build a token->WETH swap leg (no execute). Assumes tokens are already held by this contract.
    function _packTokenToWethSwap(
        address tokenAddress,
        uint256 amountIn,
        address recipientAddress,
        uint32 slippageBps
    ) internal returns (bytes1 cmd, bytes memory packedInput) {
        if (amountIn == 0) return (bytes1(0), bytes(""));
        if (tokenAddress == address(0) || tokenAddress == wethAddress) revert BadToken();

        Pool memory pool = getPoolFinder().getBestWethPoolAndUpdateIfStale(tokenAddress);
        if (pool.poolAddress == address(0)) revert NoPoolFound();

        uint256 minOut;
        if (pool.isV3Pool == 1) {
            minOut = _calcMinOutV3(tokenAddress, wethAddress, pool.poolFee, amountIn, slippageBps);
            packedInput = _packV3ExactInInput(tokenAddress, wethAddress, pool.poolFee, amountIn, minOut, recipientAddress);
            cmd = bytes1(uint8(CMD_V3_SWAP_EXACT_IN));
        } else {
            minOut = _calcMinOutV2(tokenAddress, wethAddress, amountIn, slippageBps);
            packedInput = _packV2ExactInInput(tokenAddress, wethAddress, amountIn, minOut, recipientAddress);
            cmd = bytes1(uint8(CMD_V2_SWAP_EXACT_IN));
        }
    }
}

/* ----------------------------------------------------- */
/*            Only two loop helpers as requested         */
/* ----------------------------------------------------- */
abstract contract BatchBuilders is TokenEthSwapHelpers {

    struct EthToTokenBatch {
        bytes commands;
        bytes[] inputs;
        uint256 count;
        uint256 allocatedWeth;
        uint256 sumBips;
        uint256 directWethToSend;
    }
    /// @dev Builds the UR commands+inputs for ETH->tokens (handles direct WETH transfers here).
    function _buildEthToTokensBatch(
        MultiTokenOutput[] calldata outputs,
        address recipientAddress,
        uint32 slippageBps,
        uint256 totalEth
    ) internal returns (EthToTokenBatch memory batch) {
        address _weth = wethAddress;
        uint256 len = outputs.length;

        // Pre-allocate worst-case and write commands by index (no repeated copies).
        batch.inputs = new bytes[](len);
        batch.commands = new bytes(len);

        unchecked {
            for (uint256 i = 0; i < len; ++i) {
                address outToken = outputs[i].tokenAddress;
                if (outToken == address(0)) revert BadToken();

                uint256 weight = outputs[i].weightBips;
                batch.sumBips += weight;

                // skip zero-weight slices early
                if (weight == 0) continue;

                uint256 slice = (totalEth * weight) / HUNDRED_PERCENT_BIPS;
                if (slice == 0) continue;

                batch.allocatedWeth += slice;

                if (outToken == _weth) {
                    // Defer sending; we need WETH wrapped first.
                    batch.directWethToSend += slice;
                    continue;
                }

                (bytes1 cmd, bytes memory packed) =
                    _packWethToTokenSwap(outToken, slice, recipientAddress, slippageBps);

                batch.commands[batch.count] = cmd;
                batch.inputs[batch.count] = packed;
                ++batch.count;
            }
        }
    }

    struct TokenToEthBatch {
        bytes commands;
        bytes[] inputs;
        uint256 count;
    }

    error PreTransferInsufficient();
    /// @dev Single-pass build for tokens->ETH. Assumes tokens are unique.
    ///      Pulls each token (fee-on-transfer aware) and immediately appends a swap leg if needed.
    function _buildTokensToEthBatch(
        MultiTokenInput[] calldata inputsIn,
        uint32 slippageBps,
        bool preTransferred
    ) internal returns (TokenToEthBatch memory batch) {
        address _weth = wethAddress;
        uint256 len = inputsIn.length;

        // Pre-allocate worst case; we'll trim in _executeBatch.
        batch.inputs = new bytes[](len);
        batch.commands = new bytes(len);

        unchecked {
            for (uint256 i = 0; i < len; ++i) {
                address token = inputsIn[i].tokenAddress;
                uint256 amtIn = inputsIn[i].amount;
                if (amtIn == 0) continue;
                if (token == address(0)) revert BadToken();

                bool isWeth = (token == _weth);
                uint256 received;

                if (preTransferred) {
                    // Ensure the contract already holds the required amount
                    uint256 bal = IERC20(token).balanceOf(address(this));
                    if (bal < amtIn) revert PreTransferInsufficient();

                    if (isWeth) {
                        // No swap leg for WETH itself; we'll unwrap at the end
                        continue;
                    } else {
                        // Use the declared amount as "received"
                        received = amtIn;
                    }
                } else {
                    uint256 pulledAmount = TransferHelpers.receiveToken(token, amtIn, msg.sender);

                    if (isWeth) {
                        // Pulled WETH; no swap leg needed
                        continue;
                    } else {
                        // Pull token (fee-on-transfer aware)
                        received = pulledAmount;
                        if (received == 0) continue;
                    }
                }

                // Build packed swap leg for non-WETH tokens
                (bytes1 cmd, bytes memory packed) =
                    _packTokenToWethSwap(token, received, address(this), slippageBps);

                batch.commands[batch.count] = cmd;
                batch.inputs[batch.count] = packed;
                ++batch.count;
            }
        }
    }
}

/* ----------------------------------------------------- */
/*                        Router                          */
/* ----------------------------------------------------- */
contract MultiswapRouter is BatchBuilders, ReentrancyGuard {

    constructor(
        address _directory,
        address _v2Router,
        address _v3Factory,
        address _universalRouter,
        address _permit2
    )
        ImDirectory(_directory)
        Storage(_v2Router, _v3Factory, _universalRouter, _permit2)
    {}

    /* ────────────────────────────────────────────────────────────────
                        ETH → Multiple Tokens (WEIGHTED)
       - Wrap once
       - Build all swap legs via _buildEthToTokensBatch
       - Single Universal Router execute
    ───────────────────────────────────────────────────────────────── */
    function swapEthToMultipleTokens(
        MultiTokenOutput[] calldata outputs,
        address recipientAddress,
        uint32 slippageBps
    ) external payable nonReentrant {
        if (recipientAddress == address(0)) revert ZeroAddress();
        uint256 msgValue = msg.value;
        if (outputs.length == 0) revert InvalidInput();
        if (msgValue == 0) revert InvalidInput();

        EthToTokenBatch memory batch =
            _buildEthToTokensBatch(outputs, recipientAddress, slippageBps, msgValue);

        // Validate weights after single pass
        uint256 sum = batch.sumBips;
        if (sum == 0 || sum > HUNDRED_PERCENT_BIPS) revert WeightsInvalid();

        // Wrap exactly once for all legs (including direct-WETH)
        uint256 alloc = batch.allocatedWeth;
        if (alloc > 0) TransferHelpers.wrapETH(wethAddress, alloc);

        // Execute swaps in one UR call
        _executeBatch(batch.commands, batch.inputs, batch.count);

        // Now send any direct WETH slice(s)
        uint256 direct = batch.directWethToSend;
        if (direct > 0) {
            TransferHelpers.sendToken(wethAddress, recipientAddress, direct);
        }

        // Refund any leftover ETH dust
        uint256 leftover = address(this).balance;
        if (leftover > 0) {
            TransferHelpers.sendEth(msg.sender, leftover);
        }
    }


    /* ────────────────────────────────────────────────────────────────
                        Multiple Tokens → ETH (BATCH)
       - Assume tokens are UNIQUE
       - Single-pass build via _buildTokensToEthBatchUnique
       - Single execute, then unwrap once & send
    ───────────────────────────────────────────────────────────────── */
    function swapMultipleTokensToEth(
        MultiTokenInput[] calldata inputsIn,
        address payable recipientAddress,
        uint32 slippageBps,
        bool preTransferred
    ) external nonReentrant {
        if (recipientAddress == address(0)) revert ZeroAddress();
        if (inputsIn.length == 0) revert InvalidInput();

        TokenToEthBatch memory batch =
            _buildTokensToEthBatch(inputsIn, slippageBps, preTransferred);

        _executeBatch(batch.commands, batch.inputs, batch.count);

        uint256 wethBal = IERC20(wethAddress).balanceOf(address(this));
        if (wethBal == 0) revert NoSwaps();

        TransferHelpers.unwrapAndSendWETH(wethAddress,wethBal, recipientAddress);
    }

    function recoverAsset(address tokenAddress, address toAddress) external onlyOwner {
        TransferHelpers.recoverAsset(tokenAddress, toAddress);
    }
   

    receive() external payable {} // to receive ETH for wrap/unwrap only
}
"
    },
    "hopium/uniswap/interface-ext/universal-router.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IUniversalRouter {
    function execute(bytes calldata commands, bytes[] calldata inputs) external payable;
}"
    },
    "hopium/uniswap/interface-ext/uniswapV3.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IUniswapV3Factory {
    function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address pool);
}

interface IUniswapV3Pool {
    function slot0()
        external
        view
        returns (
            uint160 sqrtPriceX96,
            int24 tick,
            uint16 observationIndex,
            uint16 observationCardinality,
            uint16 observationCardinalityNext,
            uint8 feeProtocol,
            bool unlocked
        );
    
    function observe(uint32[] calldata secondsAgos)
        external
        view
        returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s);
}"
    },
    "hopium/uniswap/interface-ext/uniswapV2.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IUniswapV2Factory {
    function getPair(address tokenA, address tokenB) external view returns (address pair);
}

interface IUniswapV2Pair {
    function token0() external view returns (address);
    function token1() external view returns (address);
    function getReserves() external view returns (uint112 r0, uint112 r1, uint32 t);
}

interface IUniswapV2Router02 {
    function WETH() external view returns (address);
    function factory() external view returns (address);

    function getAmountsOut(
        uint amountIn,
        address[] memory path
    ) external view returns (uint[] memory amounts);
}"
    },
    "hopium/uniswap/interface/imPoolFinder.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;

import "hopium/uniswap/types/pool.sol";
import "hopium/common/interface/imDirectory.sol";

interface IPoolFinder {
    function getBestWethPool(address tokenAddress) external view returns (Pool memory pool);
    function getBestWethPoolAndUpdateIfStale(address tokenAddress) external returns (Pool memory pool);
    function getBestWethPoolAndForceUpdate(address tokenAddress) external returns (Pool memory out);
    function emitPoolChangedEventOnWethUsdPool(address poolAddress, bool isV3Pool) external;
}

abstract contract ImPoolFinder is ImDirectory {

    function getPoolFinder() internal view virtual returns (IPoolFinder) {
        return IPoolFinder(fetchFromDirectory("pool-finder"));
    }
}"
    },
    "hopium/uniswap/lib/oracleLibrary.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "hopium/common/lib/full-math.sol";

library OracleLibrary {

    function getQuoteAtTick(
        int24 tick,
        uint128 baseAmount,
        address baseToken,
        address quoteToken
    ) internal pure returns (uint256 quoteAmount) {
        uint160 sqrtRatioX96 = TickMath.getSqrtRatioAtTick(tick);

        // Calculate quoteAmount with better precision if it doesn't overflow when multiplied by itself
        if (sqrtRatioX96 <= type(uint128).max) {
            uint256 ratioX192 = uint256(sqrtRatioX96) * sqrtRatioX96;
            quoteAmount = baseToken < quoteToken
                ? FullMath.mulDiv(ratioX192, baseAmount, 1 << 192)
                : FullMath.mulDiv(1 << 192, baseAmount, ratioX192);
        } else {
            uint256 ratioX128 = FullMath.mulDiv(sqrtRatioX96, sqrtRatioX96, 1 << 64);
            quoteAmount = baseToken < quoteToken
                ? FullMath.mulDiv(ratioX128, baseAmount, 1 << 128)
                : FullMath.mulDiv(1 << 128, baseAmount, ratioX128);
        }
    }

    function meanTickFromDelta(int56 tickCumulativesDelta, uint32 secondsAgo)
        internal
        pure
        returns (int24 meanTick)
    {
        require(secondsAgo != 0, "BP");

        // Cast secondsAgo to a signed type for mixed signed/unsigned ops
        int56 secondsAgoSigned = int56(int32(secondsAgo));

        // Truncate toward zero first
        int56 q = tickCumulativesDelta / secondsAgoSigned;
        int56 r = tickCumulativesDelta % secondsAgoSigned;

        meanTick = int24(q);

        // Adjust to round toward -infinity
        if (tickCumulativesDelta < 0 && r != 0) {
            unchecked { meanTick -= 1; }
        }
    }

    /// @notice Convenience overload: compute mean tick from start/end cumulatives.
    function meanTickFromCumulatives(int56 tickCumulativeStart, int56 tickCumulativeEnd, uint32 secondsAgo)
        internal
        pure
        returns (int24)
    {
        return meanTickFromDelta(tickCumulativeEnd - tickCumulativeStart, secondsAgo);
    }

}

library TickMath {
    /// @dev The minimum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**-128
    int24 internal constant MIN_TICK = -887272;
    /// @dev The maximum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**128
    int24 internal constant MAX_TICK = -MIN_TICK;

    /// @notice Calculates sqrt(1.0001^tick) * 2^96
    /// @dev Throws if |tick| > max tick
    /// @param tick The input tick for the above formula
    /// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the ratio of the two assets (token1/token0)
    /// at the given tick
    function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) {
        uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick));
        require(absTick <= uint256(uint24(MAX_TICK)), 'T');

        uint256 ratio = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000;
        if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128;
        if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
        if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
        if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128;
        if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
        if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
        if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
        if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
        if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
        if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
        if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
        if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
        if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
        if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
        if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128;
        if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
        if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128;
        if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128;
        if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128;

        if (tick > 0) ratio = type(uint256).max / ratio;

        // this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96.
        // we then downcast because we know the result always fits within 160 bits due to our tick input constraint
        // we round up in the division so getTickAtSqrtRatio of the output price is always consistent
        sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1));
    }
}"
    },
    "hopium/common/lib/permit2.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;

import "hopium/common/interface-ext/iPermit2.sol";

// Constants for “infinite” style approvals
uint160 constant PERMIT2_MAX_AMOUNT = type(uint160).max;
uint48  constant PERMIT2_MAX_EXPIRY = type(uint48).max;

error BadToken();
library Permit2 {

    function approveMaxIfNeededPermit2(
        address permit2Addr,
        address token,
        address spender,
        uint160 amount
    ) internal {
        if (token == address(0) || spender == address(0)) revert BadToken();

        // 2) Permit2 internal allowance (owner=this, spender=`spender`)
        (uint160 currentAmt, uint48 expiry, ) = IPermit2(permit2Addr).allowance(address(this), token, spender);

        bool amountOk = (currentAmt >= amount);

        // Expired if nonzero expiry and already passed; Permit2 may return 0 for “unset”
        bool isExpired = (expiry != 0 && expiry <= block.timestamp);

        if (!amountOk || isExpired) {
            IPermit2(permit2Addr).approve(token, spender, PERMIT2_MAX_AMOUNT, PERMIT2_MAX_EXPIRY);
        }
    }
}"
    },
    "hopium/common/interface-ext/iWeth.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;

import "hopium/common/interface-ext/iErc20.sol";

interface IWETH is IERC20 {
    function deposit() external payable;
    function withdraw(uint256) external;
}"
    },
    "hopium/common/lib/transfer-helpers.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;

import "hopium/common/interface-ext/iErc20.sol";
import "hopium/common/interface-ext/iWeth.sol";

library TransferHelpers {
    /* ----------------------------- Custom Errors ----------------------------- */
    error ZeroAmount();
    error InsufficientETH();
    error ETHSendFailed();
    error AllowanceTooLow();
    error NothingReceived();

    /* ----------------------------- Internal Helpers -------------------------- */

    function safeTransfer(
        address token,
        address to,
        uint256 value
    ) internal {
        (bool success, bytes memory data) =
            token.call(abi.encodeWithSelector(IERC20.transfer.selector, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), "TRANSFER_FAILED");
    }

    function safeTransferFrom(
        address token,
        address from,
        address to,
        uint256 value
    ) internal {
        (bool success, bytes memory data) =
            token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), "TRANSFER_FROM_FAILED");
    }

    function safeApprove(address token, address spender, uint256 value) internal {
        (bool success, bytes memory data) =
            token.call(abi.encodeWithSelector(IERC20.approve.selector, spender, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), "APPROVE_FAILED");
    }

    /* ------------------------------- ETH helpers ----------------------------- */

    function sendEth(address to, uint256 amount) internal {
        if (amount == 0) revert ZeroAmount();
        if (address(this).balance < amount) revert InsufficientETH();

        (bool ok, ) = payable(to).call{value: amount}("");
        if (!ok) revert ETHSendFailed();
    }

    /* ------------------------------- WETH helpers ----------------------------- */

    function wrapETH(address wethAddress, uint256 amt) internal {
        if (address(this).balance < amt) revert InsufficientETH();
        IWETH(wethAddress).deposit{value: amt}();
    }

    error ETHSendFailure();
    function unwrapAndSendWETH(address wethAddress, uint256 amt, address payable to) internal {
        IWETH(wethAddress).withdraw(amt);
        (bool ok, ) = to.call{value: amt}("");
        if (!ok) revert ETHSendFailure();
    }

    /* ------------------------------ Token helpers ---------------------------- */

    function sendToken(address token, address to, uint256 amount) internal {
        if (amount == 0) revert ZeroAmount();
        safeTransfer(token, to, amount);
    }

    function receiveToken(address token, uint256 amount, address from) internal returns (uint256 received) {
        if (amount == 0) revert ZeroAmount();

        uint256 currentAllowance = IERC20(token).allowance(from, address(this));
        if (currentAllowance < amount) revert AllowanceTooLow();

        uint256 balBefore = IERC20(token).balanceOf(address(this));
        safeTransferFrom(token, from, address(this), amount);
        uint256 balAfter = IERC20(token).balanceOf(address(this));

        unchecked {
            received = balAfter - balBefore;
        }
        if (received == 0) revert NothingReceived();
    }

    function approveMaxIfNeeded(address token, address spender, uint256 amount) internal {
        uint256 current = IERC20(token).allowance(address(this), spender);
        if (current < amount) {
            if (current != 0) safeApprove(token, spender, 0);
            safeApprove(token, spender, type(uint256).max);
        }
    }

    /* ------------------------------ ETH + Token helpers ---------------------------- */

    function sendEthOrToken(address token, address to) internal {
        if (token == address(0)) {
            sendEth(to, address(this).balance);
        } else {
            uint256 bal = IERC20(token).balanceOf(address(this));
            if (bal > 0) safeTransfer(token, to, bal);
        }
    }

    function recoverAsset(address token, address to) internal {
        sendEthOrToken(token, to);
    }
}
"
    },
    "hopium/common/types/bips.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;

uint256 constant HUNDRED_PERCENT_BIPS = 10_000;
"
    },
    "hopium/uniswap/types/multiswap.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;

struct MultiTokenInput {
    address tokenAddress;
    uint256 amount; // For tokens->ETH: amount of the ERC20 to sell
}

struct MultiTokenOutput {
    address tokenAddress;
    uint16  weightBips; // Weight in basis points (must sum to <= 10_000)
}"
    },
    "@openzeppelin/contracts/security/ReentrancyGuard.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)

pragma solidity ^0.8.0;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be _NOT_ENTERED
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _status == _ENTERED;
    }
}
"
    },
    "hopium/common/interface/imDirectory.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;

/// @notice Interface used by the registry to talk to the external directory.
interface IDirectory {
    function owner() external view returns (address);
    function fetchFromDirectory(string memory _key) external view returns (address);
}

abstract contract ImDirectory {
    IDirectory public Directory;

    constructor(address _directory) {
        _setDirectory(_directory); // no modifier here
    }

    function changeDirectoryAddress(address _directory) external onlyOwner {
        _setDirectory(_directory);
    }

    function _setDirectory(address _directory) internal {
        require(_directory != address(0), "Directory cannot be zero address");
        require(_directory.code.length > 0, "Directory must be a contract");

        // Sanity check the interface
        try IDirectory(_directory).owner() returns (address) {
            Directory = IDirectory(_directory);
        } catch {
            revert("Directory address does not implement owner()");
        }
    }

    modifier onlyOwner() {
        require(msg.sender == Directory.owner(), "Caller is not the owner");
        _;
    }

    function owner() public view returns (address) {
        return Directory.owner();
    }

    function fetchFromDirectory(string memory _key) public view returns (address) {
        return Directory.fetchFromDirectory(_key);
    }
}
"
    },
    "hopium/common/lib/full-math.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;

library FullMath {
    /// @dev Full-precision multiply-divide: floor(a * b / denominator)
    ///      Reverts if denominator == 0 or the result overflows uint256.
    /// @notice Based on Uniswap v3’s FullMath.mulDiv().
    function mulDiv(
        uint256 a,
        uint256 b,
        uint256 denominator
    ) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = a * b
            uint256 prod0; // Least-significant 256 bits
            uint256 prod1; // Most-significant 256 bits
            assembly {
                let mm := mulmod(a, b, not(0))
                prod0 := mul(a, b)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // No overflow: do simple division
            if (prod1 == 0) return prod0 / denominator;

            require(denominator > prod1, "mulDiv overflow");

            ///////////////////////////////////////////////
            //  Make division exact  (subtract remainder)
            ///////////////////////////////////////////////
            uint256 remainder;
            assembly {
                remainder := mulmod(a, b, denominator)
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            ///////////////////////////////////////////////
            //  Factor powers of two out of denominator
            ///////////////////////////////////////////////
            uint256 twos = denominator & (~denominator + 1);
            assembly {
                denominator := div(denominator, twos)
                prod0 := div(prod0, twos)
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Combine high and low products
            prod0 |= prod1 * twos;

            ///////////////////////////////////////////////
            //  Compute modular inverse of denominator mod 2²⁵⁶
            ///////////////////////////////////////////////
            uint256 inv = (3 * denominator) ^ 2;
            inv *= 2 - denominator * inv; // inverse mod 2⁸
            inv *= 2 - denominator * inv; // mod 2¹⁶
            inv *= 2 - denominator * inv; // mod 2³²
            inv *= 2 - denominator * inv; // mod 2⁶⁴
            inv *= 2 - denominator * inv; // mod 2¹²⁸
            inv *= 2 - denominator * inv; // mod 2²⁵⁶

            ///////////////////////////////////////////////
            //  Multiply by modular inverse to finish division
            ///////////////////////////////////////////////
            result = prod0 * inv;
            return result;
        }
    }
}"
    },
    "hopium/uniswap/types/pool.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;

struct Pool {
    address poolAddress; // 20
    uint24  poolFee;     // 3
    uint8   isV3Pool;    // 1 (0/1)
    uint64  lastCached;  // 8
}"
    },
    "hopium/common/interface-ext/iErc20.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;

interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address to, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}"
    },
    "hopium/common/interface-ext/iPermit2.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;

interface IPermit2 {
    function approve(address token, address spender, uint160 amount, uint48 expiration) external;
    function allowance(address owner, address token, address spender)
        external
        view
        returns (uint160 amount, uint48 expiration, uint48 nonce);
}"
    }
  },
  "settings": {
    "optimizer": {
      "enabled": true,
      "runs": 200
    },
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    },
    "remappings": []
  }
}}

Tags:
ERC20, DeFi, Swap, Factory, Oracle|addr:0xb96781071c0991345817889e65e8dcb419ad2204|verified:true|block:23668191|tx:0x4e0a3b74ca7108c51b891de2fed5cab57b9ccb0506b615251b56937d2d93a1c5|first_check:1761567282

Submitted on: 2025-10-27 13:14:43

Comments

Log in to comment.

No comments yet.