Blockchain / Global / Address

Contract

0xD6a461F2042CEC8FdEAB30cDF3F8a108E4e7C40B

Balance
0 SOMI
Transactions sent
1
Type
Contract · 6,216 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

and 1 more files

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

import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

/**
 * @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 is ReentrancyGuard {
    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;

    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 {}
}

Other files

  • @openzeppelin/contracts/utils/ReentrancyGuard.sol
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol)
    
    pragma solidity ^0.8.20;
    
    /**
     * @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;
    
        /**
         * @dev Unauthorized reentrant call.
         */
        error ReentrancyGuardReentrantCall();
    
        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
            if (_status == ENTERED) {
                revert ReentrancyGuardReentrantCall();
            }
    
            // 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;
        }
    }
    
ABI · 16 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": "ReentrancyGuardReentrantCall",
    "type": "error",
    "inputs": []
  },
  {
    "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"
  }
]