Blockchain / Global / Address

Contract

0x9614e7b5dEf94FA75708615aefa6cBC85C83e2D4

Balance
0 SOMI
Transactions sent
1
Type
Contract · 6,370 bytes of code

SomiverseSwapRouter Verified

Compiler
solc 0.8.24+commit.e11b9ed9
Language
Solidity
Optimization
Yes, 200 runs
EVM version
paris
License
—
Verified
2026-10-12

contracts/SomiverseSwapRouter.sol

Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/**
 * @title SomiverseSwapRouter
 * @notice Proxy contract that collects platform fee and forwards the
 *         remaining amount to the QuickSwap Algebra V4 SwapRouter.
 *
 * Deployment args:
 *   _router   – QuickSwap Algebra V4 SwapRouter address
 *   _wsomi    – WSOMI (WETH9) contract address
 *   _treasury – wallet that receives the platform fee
 */

interface IAlgebraSwapRouter {
    struct ExactInputSingleParams {
        address tokenIn;
        address tokenOut;
        address deployer;
        address recipient;
        uint256 deadline;
        uint256 amountIn;
        uint256 amountOutMinimum;
        uint160 limitSqrtPrice;
    }

    /// @notice Multi-hop path encoded as:
    ///   abi.encodePacked(tokenIn, deployer0, tokenMid, deployer1, tokenOut)
    ///   for a 2-hop (3-pair) swap.
    struct ExactInputParams {
        bytes   path;
        address recipient;
        uint256 deadline;
        uint256 amountIn;
        uint256 amountOutMinimum;
    }

    function exactInputSingle(
        ExactInputSingleParams calldata params
    ) external payable returns (uint256 amountOut);

    function exactInput(
        ExactInputParams calldata params
    ) external payable returns (uint256 amountOut);
}

interface IERC20 {
    function transferFrom(address, address, uint256) external returns (bool);
    function transfer(address, uint256) external returns (bool);
    function approve(address, uint256) external returns (bool);
    function balanceOf(address) external view returns (uint256);
}

interface IWETH {
    function withdraw(uint256) external;
}

contract SomiverseSwapRouter {
    address public immutable router;
    address public immutable wsomi;
    address public immutable treasury;
    address public owner;

    uint256 public constant FEE_BPS = 250;           // 2.5 %
    uint256 public constant BPS_DENOMINATOR = 10_000;

    uint256 private _locked = 1;

    modifier nonReentrant() {
        require(_locked == 1, "reentrant call");
        _locked = 2;
        _;
        _locked = 1;
    }

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

    constructor(address _router, address _wsomi, address _treasury) {
        require(_router   != address(0), "zero router");
        require(_wsomi    != address(0), "zero wsomi");
        require(_treasury != address(0), "zero treasury");
        router   = _router;
        wsomi    = _wsomi;
        treasury = _treasury;
        owner    = msg.sender;
    }

    function transferOwnership(address newOwner) external onlyOwner {
        require(newOwner != address(0), "zero address");
        owner = newOwner;
    }

    /* ───────────────────────── Native SOMI → ERC-20 ───────────────────────── */

    /// @notice Swap native SOMI for an ERC-20 token.
    function swapNativeToToken(
        address tokenOut,
        address deployer,
        uint256 deadline,
        uint256 amountOutMin,
        uint160 limitSqrtPrice
    ) external payable nonReentrant returns (uint256 amountOut) {
        uint256 fee     = (msg.value * FEE_BPS) / BPS_DENOMINATOR;
        uint256 swapAmt = msg.value - fee;

        // Fee → treasury
        (bool ok, ) = treasury.call{value: fee}("");
        require(ok, "fee transfer failed");

        // Swap (router wraps native SOMI automatically)
        amountOut = IAlgebraSwapRouter(router).exactInputSingle{value: swapAmt}(
            IAlgebraSwapRouter.ExactInputSingleParams({
                tokenIn:           wsomi,
                tokenOut:          tokenOut,
                deployer:          deployer,
                recipient:         msg.sender,
                deadline:          deadline,
                amountIn:          swapAmt,
                amountOutMinimum:  amountOutMin,
                limitSqrtPrice:    limitSqrtPrice
            })
        );
    }

    /* ──────────────── ERC-20 → ERC-20  /  ERC-20 → native SOMI ──────────── */

    /// @notice Swap an ERC-20 for another ERC-20 (or native SOMI when
    ///         unwrapToNative is true). Caller must approve this contract for
    ///         amountIn beforehand.
    function swapTokenToToken(
        address tokenIn,
        address tokenOut,
        address deployer,
        uint256 deadline,
        uint256 amountIn,
        uint256 amountOutMin,
        uint160 limitSqrtPrice,
        bool    unwrapToNative
    ) external nonReentrant returns (uint256 amountOut) {
        // Pull full amount from caller
        IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);

        uint256 fee     = (amountIn * FEE_BPS) / BPS_DENOMINATOR;
        uint256 swapAmt = amountIn - fee;

        // Fee → treasury
        IERC20(tokenIn).transfer(treasury, fee);

        // Approve router for the swap portion only
        IERC20(tokenIn).approve(router, swapAmt);

        address recipient = unwrapToNative ? address(this) : msg.sender;

        amountOut = IAlgebraSwapRouter(router).exactInputSingle(
            IAlgebraSwapRouter.ExactInputSingleParams({
                tokenIn:           tokenIn,
                tokenOut:          tokenOut,
                deployer:          deployer,
                recipient:         recipient,
                deadline:          deadline,
                amountIn:          swapAmt,
                amountOutMinimum:  amountOutMin,
                limitSqrtPrice:    limitSqrtPrice
            })
        );

        // Reset approval to prevent leftover allowance
        IERC20(tokenIn).approve(router, 0);

        // If output is native SOMI, unwrap WSOMI → native and send to caller
        if (unwrapToNative) {
            IWETH(wsomi).withdraw(amountOut);
            (bool ok, ) = msg.sender.call{value: amountOut}("");
            require(ok, "native send failed");
        }
    }

    /* ───────────────── Native SOMI → ERC-20  (multi-hop) ────────────────── */

    /// @notice Swap native SOMI for an ERC-20 using a multi-hop path.
    /// @param path   ABI-packed path: tokenIn, deployer0, tokenMid, deployer1, tokenOut
    ///               The first address in path MUST be WSOMI.
    /// @param deadline       Unix timestamp deadline.
    /// @param amountOutMin   Minimum tokens to receive (slippage guard).
    function swapNativeToTokenMultiHop(
        bytes  calldata path,
        uint256 deadline,
        uint256 amountOutMin
    ) external payable nonReentrant returns (uint256 amountOut) {
        require(path.length >= 40, "path too short");
        uint256 fee     = (msg.value * FEE_BPS) / BPS_DENOMINATOR;
        uint256 swapAmt = msg.value - fee;

        // Fee → treasury
        (bool ok, ) = treasury.call{value: fee}("");
        require(ok, "fee transfer failed");

        // Swap via multi-hop (router wraps native SOMI automatically)
        amountOut = IAlgebraSwapRouter(router).exactInput{value: swapAmt}(
            IAlgebraSwapRouter.ExactInputParams({
                path:             path,
                recipient:        msg.sender,
                deadline:         deadline,
                amountIn:         swapAmt,
                amountOutMinimum: amountOutMin
            })
        );
    }

    /* ─────────── ERC-20 → ERC-20 / ERC-20 → native SOMI (multi-hop) ──────── */

    /// @notice Swap an ERC-20 for another ERC-20 (or native SOMI when
    ///         unwrapToNative is true) using a multi-hop path.
    ///         Caller must approve this contract for amountIn beforehand.
    /// @param path         ABI-packed path: tokenIn, deployer0, tokenMid, deployer1, tokenOut
    /// @param amountIn     Total input amount (fee is deducted from this).
    /// @param amountOutMin Minimum output amount (slippage guard).
    /// @param tokenIn      First token address in the path (for pull & fee).
    /// @param unwrapToNative  If true, unwraps WSOMI output to native SOMI.
    function swapTokenToTokenMultiHop(
        bytes   calldata path,
        address tokenIn,
        uint256 deadline,
        uint256 amountIn,
        uint256 amountOutMin,
        bool    unwrapToNative
    ) external nonReentrant returns (uint256 amountOut) {
        // Validate path is long enough and starts with tokenIn
        require(path.length >= 40, "path too short");
        address firstToken;
        assembly {
            firstToken := shr(96, calldataload(path.offset))
        }
        require(firstToken == tokenIn, "path/tokenIn mismatch");

        // Pull full amount from caller
        IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn);

        uint256 fee     = (amountIn * FEE_BPS) / BPS_DENOMINATOR;
        uint256 swapAmt = amountIn - fee;

        // Fee → treasury
        IERC20(tokenIn).transfer(treasury, fee);

        // Approve router for the swap portion only
        IERC20(tokenIn).approve(router, swapAmt);

        address recipient = unwrapToNative ? address(this) : msg.sender;

        amountOut = IAlgebraSwapRouter(router).exactInput(
            IAlgebraSwapRouter.ExactInputParams({
                path:             path,
                recipient:        recipient,
                deadline:         deadline,
                amountIn:         swapAmt,
                amountOutMinimum: amountOutMin
            })
        );

        // Reset approval to prevent leftover allowance
        IERC20(tokenIn).approve(router, 0);

        // If output is native SOMI, unwrap WSOMI → native and send to caller
        if (unwrapToNative) {
            IWETH(wsomi).withdraw(amountOut);
            (bool ok, ) = msg.sender.call{value: amountOut}("");
            require(ok, "native send failed");
        }
    }

    /* ─────────────────────────── Admin / Recovery ───────────────────────── */

    /// @notice Withdraw any native SOMI accidentally stuck in this contract.
    function withdrawStuckNative() external onlyOwner nonReentrant {
        uint256 bal = address(this).balance;
        require(bal > 0, "no native balance");
        (bool ok, ) = owner.call{value: bal}("");
        require(ok, "transfer failed");
    }

    /// @notice Withdraw any ERC-20 tokens accidentally stuck in this contract.
    function withdrawStuckToken(address token) external onlyOwner nonReentrant {
        uint256 bal = IERC20(token).balanceOf(address(this));
        require(bal > 0, "no token balance");
        IERC20(token).transfer(owner, bal);
    }

    /// @dev Accept native SOMI (needed for WETH.withdraw callback)
    receive() external payable {}
}
ABI · 15 entries
json
[
  {
    "type": "constructor",
    "inputs": [
      {
        "name": "_router",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "_wsomi",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "_treasury",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "name": "BPS_DENOMINATOR",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "FEE_BPS",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "owner",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "router",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "swapNativeToToken",
    "type": "function",
    "inputs": [
      {
        "name": "tokenOut",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "deployer",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "deadline",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "amountOutMin",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "limitSqrtPrice",
        "type": "uint160",
        "internalType": "uint160"
      }
    ],
    "outputs": [
      {
        "name": "amountOut",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "payable"
  },
  {
    "name": "swapNativeToTokenMultiHop",
    "type": "function",
    "inputs": [
      {
        "name": "path",
        "type": "bytes",
        "internalType": "bytes"
      },
      {
        "name": "deadline",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "amountOutMin",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "outputs": [
      {
        "name": "amountOut",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "payable"
  },
  {
    "name": "swapTokenToToken",
    "type": "function",
    "inputs": [
      {
        "name": "tokenIn",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "tokenOut",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "deployer",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "deadline",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "amountIn",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "amountOutMin",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "limitSqrtPrice",
        "type": "uint160",
        "internalType": "uint160"
      },
      {
        "name": "unwrapToNative",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "outputs": [
      {
        "name": "amountOut",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "name": "swapTokenToTokenMultiHop",
    "type": "function",
    "inputs": [
      {
        "name": "path",
        "type": "bytes",
        "internalType": "bytes"
      },
      {
        "name": "tokenIn",
        "type": "address",
        "internalType": "address"
      },
      {
        "name": "deadline",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "amountIn",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "amountOutMin",
        "type": "uint256",
        "internalType": "uint256"
      },
      {
        "name": "unwrapToNative",
        "type": "bool",
        "internalType": "bool"
      }
    ],
    "outputs": [
      {
        "name": "amountOut",
        "type": "uint256",
        "internalType": "uint256"
      }
    ],
    "stateMutability": "nonpayable"
  },
  {
    "name": "transferOwnership",
    "type": "function",
    "inputs": [
      {
        "name": "newOwner",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "treasury",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "name": "withdrawStuckNative",
    "type": "function",
    "inputs": [],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "withdrawStuckToken",
    "type": "function",
    "inputs": [
      {
        "name": "token",
        "type": "address",
        "internalType": "address"
      }
    ],
    "outputs": [],
    "stateMutability": "nonpayable"
  },
  {
    "name": "wsomi",
    "type": "function",
    "inputs": [],
    "outputs": [
      {
        "name": "",
        "type": "address",
        "internalType": "address"
      }
    ],
    "stateMutability": "view"
  },
  {
    "type": "receive",
    "stateMutability": "payable"
  }
]