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"
}
]