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": []
}
}}
Submitted on: 2025-10-27 13:14:43
Comments
Log in to comment.
No comments yet.