Skip to main content
PulseScanner.io

Address

0x8e1093cd44f4e6fc7e4f07c52152cee0cc0dfaee
Current Holdings
$0.00
TXs sent
not counted
First Active
2026-04-13
block 26,271,827
Last Active
129 days ago
block 26,492,582
Funded By
0xd597…bdf5

Net worth historyi

6 snapshots · to block 27,548,869coverage change 26 Augcoverage change 27 Augcoverage change 27 Augcoverage change 27 Augcoverage change 27 Aug
exact matchTradeTerminalRoutersolc 0.8.26+commit.8a97fa7aruntime exact · creation exact
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

interface IERC20 {
    function balanceOf(address account) external view returns (uint256);
    function transfer(address to, uint256 amount) external returns (bool);
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
    function approve(address spender, uint256 amount) external returns (bool);
}

interface IWPLS {
    function deposit() external payable;
    function withdraw(uint256 amount) external;
    function balanceOf(address account) external view returns (uint256);
    function transfer(address to, uint256 amount) external returns (bool);
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
    function approve(address spender, uint256 amount) external returns (bool);
}

interface IUniswapV2Router {
    function getAmountsOut(uint256 amountIn, address[] calldata path)
        external view returns (uint256[] memory amounts);

    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external;
}

contract TradeTerminalRouter {

    uint256 public constant FEE_BPS_MAX      = 100;
    uint256 public constant BPS_DENOM        = 10_000;
    uint256 public constant SLIPPAGE_MAX     = 5_000;
    uint256 public constant SLIPPAGE_DEFAULT = 200;
    uint256 public constant DEX_MAX          = 10;

    address public immutable WPLS;

    address public owner;
    address public pendingOwner;
    address public platformTreasury;
    uint256 public feeBps;

    address[] public dexRouters;
    mapping(address => bool)   public isDexRouter;
    mapping(address => string) public dexName;

    bool private _locked;

    struct BestRoute {
        address   router;
        address[] path;
        uint256   amountOut;
    }

    event TradeExecuted(
        address indexed tokenIn,
        address indexed tokenOut,
        uint256 amountIn,
        uint256 amountOut,
        address indexed router,
        address[] path,
        uint256 timestamp
    );

    event FeeCollected(address indexed token, uint256 amount);

    event RouterAdded(address indexed router, string name);
    event RouterRemoved(address indexed router);
    event FeeUpdated(uint256 oldFee, uint256 newFee);
    event TreasuryUpdated(address oldTreasury, address newTreasury);
    event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);
    event OwnershipTransferStarted(address indexed currentOwner, address indexed pendingOwner);

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

    modifier nonReentrant() {
        require(!_locked, "REENTRANT");
        _locked = true;
        _;
        _locked = false;
    }

    modifier validDeadline(uint256 deadline) {
        require(block.timestamp <= deadline, "EXPIRED");
        _;
    }

    constructor(
        address _wpls,
        address _treasury,
        uint256 _feeBps,
        address[] memory _dexRouters,
        string[] memory  _dexNames
    ) {
        require(_wpls     != address(0),                "INVALID_WPLS");
        require(_treasury != address(0),                "INVALID_TREASURY");
        require(_feeBps   <= FEE_BPS_MAX,               "FEE_TOO_HIGH");
        require(_wpls     != _treasury,                 "WPLS_EQ_TREASURY");
        require(_dexRouters.length == _dexNames.length, "LENGTH_MISMATCH");
        require(_dexRouters.length > 0,                 "NO_ROUTERS");
        require(_dexRouters.length <= DEX_MAX,          "TOO_MANY_ROUTERS");

        WPLS             = _wpls;
        owner            = msg.sender;
        platformTreasury = _treasury;
        feeBps           = _feeBps;

        for (uint256 i = 0; i < _dexRouters.length; i++) {
            require(_dexRouters[i] != address(0), "INVALID_ROUTER");
            require(!isDexRouter[_dexRouters[i]], "DUPLICATE_ROUTER");
            _addRouter(_dexRouters[i], _dexNames[i]);
        }
    }

    function swapPLSForToken(
        address tokenOut,
        uint256 slippageBps,
        address to,
        uint256 deadline
    ) external payable nonReentrant validDeadline(deadline) {
        require(tokenOut != address(0), "INVALID_TOKEN");
        require(tokenOut != WPLS,       "USE_WRAP_FUNCTION");
        require(msg.value > 0,          "ZERO_IN");
        require(to       != address(0), "INVALID_TO");

        uint256 fee    = (msg.value * feeBps) / BPS_DENOM;
        uint256 netPLS = msg.value - fee;
        require(netPLS > 0, "ZERO_NET");

        IWPLS(WPLS).deposit{value: netPLS}();

        BestRoute memory route = _findBestRoute(WPLS, tokenOut, netPLS);
        require(route.router != address(0), "NO_LIQUIDITY");

        if (fee > 0) {
            _sendPLSToTreasury(fee);
            emit FeeCollected(address(0), fee);
        }

        uint256 amountOutMin = _calcAmountOutMin(route.amountOut, slippageBps);
        _safeApprove(WPLS, route.router, netPLS);

        uint256 balBefore = IERC20(tokenOut).balanceOf(address(this));
        IUniswapV2Router(route.router)
            .swapExactTokensForTokensSupportingFeeOnTransferTokens(
                netPLS, amountOutMin, route.path, address(this), deadline
            );

        uint256 actualOut = IERC20(tokenOut).balanceOf(address(this)) - balBefore;
        require(actualOut > 0, "ZERO_OUT");

        require(IERC20(tokenOut).transfer(to, actualOut), "TOKEN_SEND_FAILED");

        emit TradeExecuted(address(0), tokenOut, msg.value, actualOut, route.router, route.path, block.timestamp);
    }

    function swapTokenForPLS(
        address tokenIn,
        uint256 amountIn,
        uint256 slippageBps,
        address payable to,
        uint256 deadline
    ) external nonReentrant validDeadline(deadline) {
        require(tokenIn  != address(0), "INVALID_TOKEN");
        require(tokenIn  != WPLS,       "USE_UNWRAP_FUNCTION");
        require(amountIn > 0,           "ZERO_IN");
        require(to       != address(0), "INVALID_TO");

        uint256 balBefore = IERC20(tokenIn).balanceOf(address(this));
        require(IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn), "TRANSFER_FAILED");
        uint256 netIn = IERC20(tokenIn).balanceOf(address(this)) - balBefore;
        require(netIn > 0, "ZERO_NET_IN");

        BestRoute memory route = _findBestRoute(tokenIn, WPLS, netIn);
        require(route.router != address(0), "NO_LIQUIDITY");

        uint256 amountOutMin = _calcAmountOutMin(route.amountOut, slippageBps);
        _safeApprove(tokenIn, route.router, netIn);

        uint256 wplsBefore = IWPLS(WPLS).balanceOf(address(this));
        IUniswapV2Router(route.router)
            .swapExactTokensForTokensSupportingFeeOnTransferTokens(
                netIn, amountOutMin, route.path, address(this), deadline
            );

        uint256 wplsOut = IWPLS(WPLS).balanceOf(address(this)) - wplsBefore;
        require(wplsOut > 0, "ZERO_WPLS_OUT");

        uint256 plsBefore = address(this).balance;
        IWPLS(WPLS).withdraw(wplsOut);
        uint256 plsOut = address(this).balance - plsBefore;
        require(plsOut > 0, "ZERO_PLS_OUT");

        uint256 fee    = (plsOut * feeBps) / BPS_DENOM;
        uint256 netPLS = plsOut - fee;
        require(netPLS > 0, "ZERO_NET_PLS");

        if (fee > 0) {
            _sendPLSToTreasury(fee);
            emit FeeCollected(address(0), fee);
        }

        _sendPLS(to, netPLS);

        emit TradeExecuted(tokenIn, address(0), amountIn, netPLS, route.router, route.path, block.timestamp);
    }

    function swapWPLSForToken(
        address tokenOut,
        uint256 amountIn,
        uint256 slippageBps,
        address to,
        uint256 deadline
    ) external nonReentrant validDeadline(deadline) {
        require(tokenOut != address(0), "INVALID_TOKEN");
        require(tokenOut != WPLS,       "SAME_TOKEN");
        require(amountIn > 0,           "ZERO_IN");
        require(to       != address(0), "INVALID_TO");

        require(IWPLS(WPLS).transferFrom(msg.sender, address(this), amountIn), "TRANSFER_FAILED");

        uint256 fee  = (amountIn * feeBps) / BPS_DENOM;
        uint256 net  = amountIn - fee;
        require(net > 0, "ZERO_NET");

        BestRoute memory route = _findBestRoute(WPLS, tokenOut, net);
        require(route.router != address(0), "NO_LIQUIDITY");

        if (fee > 0) {
            require(IWPLS(WPLS).transfer(platformTreasury, fee), "FEE_FAILED");
            emit FeeCollected(WPLS, fee);
        }

        uint256 amountOutMin = _calcAmountOutMin(route.amountOut, slippageBps);
        _safeApprove(WPLS, route.router, net);

        uint256 balBefore = IERC20(tokenOut).balanceOf(address(this));
        IUniswapV2Router(route.router)
            .swapExactTokensForTokensSupportingFeeOnTransferTokens(
                net, amountOutMin, route.path, address(this), deadline
            );

        uint256 actualOut = IERC20(tokenOut).balanceOf(address(this)) - balBefore;
        require(actualOut > 0, "ZERO_OUT");

        require(IERC20(tokenOut).transfer(to, actualOut), "TOKEN_SEND_FAILED");

        emit TradeExecuted(WPLS, tokenOut, amountIn, actualOut, route.router, route.path, block.timestamp);
    }

    function swapTokenForWPLS(
        address tokenIn,
        uint256 amountIn,
        uint256 slippageBps,
        address to,
        uint256 deadline
    ) external nonReentrant validDeadline(deadline) {
        require(tokenIn  != address(0), "INVALID_TOKEN");
        require(tokenIn  != WPLS,       "SAME_TOKEN");
        require(amountIn > 0,           "ZERO_IN");
        require(to       != address(0), "INVALID_TO");

        uint256 balBefore = IERC20(tokenIn).balanceOf(address(this));
        require(IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn), "TRANSFER_FAILED");
        uint256 netIn = IERC20(tokenIn).balanceOf(address(this)) - balBefore;
        require(netIn > 0, "ZERO_NET_IN");

        BestRoute memory route = _findBestRoute(tokenIn, WPLS, netIn);
        require(route.router != address(0), "NO_LIQUIDITY");

        uint256 amountOutMin = _calcAmountOutMin(route.amountOut, slippageBps);
        _safeApprove(tokenIn, route.router, netIn);

        uint256 wplsBefore = IWPLS(WPLS).balanceOf(address(this));
        IUniswapV2Router(route.router)
            .swapExactTokensForTokensSupportingFeeOnTransferTokens(
                netIn, amountOutMin, route.path, address(this), deadline
            );

        uint256 wplsOut = IWPLS(WPLS).balanceOf(address(this)) - wplsBefore;
        require(wplsOut > 0, "ZERO_WPLS_OUT");

        uint256 fee     = (wplsOut * feeBps) / BPS_DENOM;
        uint256 netWPLS = wplsOut - fee;
        require(netWPLS > 0, "ZERO_NET_WPLS");

        if (fee > 0) {
            require(IWPLS(WPLS).transfer(platformTreasury, fee), "FEE_FAILED");
            emit FeeCollected(WPLS, fee);
        }

        require(IWPLS(WPLS).transfer(to, netWPLS), "WPLS_SEND_FAILED");

        emit TradeExecuted(tokenIn, WPLS, amountIn, netWPLS, route.router, route.path, block.timestamp);
    }

    function swapTokenForToken(
        address tokenIn,
        address tokenOut,
        uint256 amountIn,
        uint256 slippageBps,
        address to,
        uint256 deadline
    ) external nonReentrant validDeadline(deadline) {
        require(tokenIn  != address(0), "INVALID_TOKEN_IN");
        require(tokenOut != address(0), "INVALID_TOKEN_OUT");
        require(tokenIn  != tokenOut,   "SAME_TOKEN");
        require(amountIn > 0,           "ZERO_IN");
        require(to       != address(0), "INVALID_TO");

        uint256 balBefore = IERC20(tokenIn).balanceOf(address(this));
        require(IERC20(tokenIn).transferFrom(msg.sender, address(this), amountIn), "TRANSFER_FAILED");
        uint256 netIn = IERC20(tokenIn).balanceOf(address(this)) - balBefore;
        require(netIn > 0, "ZERO_NET_IN");

        BestRoute memory route = _findBestRoute(tokenIn, tokenOut, netIn);
        require(route.router != address(0), "NO_LIQUIDITY");

        uint256 amountOutMin = _calcAmountOutMin(route.amountOut, slippageBps);
        _safeApprove(tokenIn, route.router, netIn);

        uint256 outBalBefore = IERC20(tokenOut).balanceOf(address(this));
        IUniswapV2Router(route.router)
            .swapExactTokensForTokensSupportingFeeOnTransferTokens(
                netIn, amountOutMin, route.path, address(this), deadline
            );

        uint256 actualOut = IERC20(tokenOut).balanceOf(address(this)) - outBalBefore;
        require(actualOut > 0, "ZERO_OUT");

        require(IERC20(tokenOut).transfer(to, actualOut), "TOKEN_SEND_FAILED");

        emit TradeExecuted(tokenIn, tokenOut, amountIn, actualOut, route.router, route.path, block.timestamp);
    }

    function quoteSwap(
        uint8   swapType,
        uint256 amountIn,
        address tokenIn,
        address tokenOut,
        uint256 slippageBps,
        uint256 taxBpsHint
    ) external view returns (
        uint256 fee,
        uint256 netIn,
        uint256 estimatedOut,
        uint256 amountOutMin
    ) {
        require(taxBpsHint < BPS_DENOM, "TAX_HINT_TOO_HIGH");

        if (swapType == 0) {
            fee          = (amountIn * feeBps) / BPS_DENOM;
            netIn        = amountIn - fee;
            BestRoute memory r = _findBestRoute(WPLS, tokenOut, netIn);
            uint256 grossOut = r.amountOut;
            estimatedOut = taxBpsHint > 0
                ? (grossOut * (BPS_DENOM - taxBpsHint)) / BPS_DENOM
                : grossOut;
            amountOutMin = _calcAmountOutMin(estimatedOut, slippageBps);

        } else if (swapType == 1) {
            uint256 grossIn = taxBpsHint > 0
                ? (amountIn * (BPS_DENOM - taxBpsHint)) / BPS_DENOM
                : amountIn;
            netIn        = grossIn;
            BestRoute memory r = _findBestRoute(tokenIn, WPLS, netIn);
            uint256 plsEstimate = r.amountOut;
            fee          = (plsEstimate * feeBps) / BPS_DENOM;
            estimatedOut = plsEstimate - fee;
            amountOutMin = _calcAmountOutMin(r.amountOut, slippageBps);

        } else if (swapType == 2) {
            fee          = (amountIn * feeBps) / BPS_DENOM;
            netIn        = amountIn - fee;
            BestRoute memory r = _findBestRoute(WPLS, tokenOut, netIn);
            uint256 grossOut = r.amountOut;
            estimatedOut = taxBpsHint > 0
                ? (grossOut * (BPS_DENOM - taxBpsHint)) / BPS_DENOM
                : grossOut;
            amountOutMin = _calcAmountOutMin(estimatedOut, slippageBps);

        } else if (swapType == 3) {
            uint256 grossIn = taxBpsHint > 0
                ? (amountIn * (BPS_DENOM - taxBpsHint)) / BPS_DENOM
                : amountIn;
            netIn        = grossIn;
            BestRoute memory r = _findBestRoute(tokenIn, WPLS, netIn);
            fee          = (r.amountOut * feeBps) / BPS_DENOM;
            estimatedOut = r.amountOut - fee;
            amountOutMin = _calcAmountOutMin(r.amountOut, slippageBps);

        } else {
            fee          = 0;
            netIn        = taxBpsHint > 0
                ? (amountIn * (BPS_DENOM - taxBpsHint)) / BPS_DENOM
                : amountIn;
            BestRoute memory r = _findBestRoute(tokenIn, tokenOut, netIn);
            estimatedOut = r.amountOut;
            amountOutMin = _calcAmountOutMin(estimatedOut, slippageBps);
        }
    }

    function getBestRoute(
        address tokenIn,
        address tokenOut,
        uint256 amountIn
    ) external view returns (
        address bestRouter,
        address[] memory bestPath,
        uint256 bestOut
    ) {
        BestRoute memory r = _findBestRoute(tokenIn, tokenOut, amountIn);
        return (r.router, r.path, r.amountOut);
    }

    function getAllQuotes(
        address tokenIn,
        address tokenOut,
        uint256 amountIn
    ) external view returns (
        address[] memory routers,
        uint256[] memory directAmounts,
        uint256[] memory viaWplsAmounts,
        string[]  memory names
    ) {
        uint256 len    = dexRouters.length;
        routers        = new address[](len);
        directAmounts  = new uint256[](len);
        viaWplsAmounts = new uint256[](len);
        names          = new string[](len);

        address[] memory directPath = new address[](2);
        directPath[0] = tokenIn;
        directPath[1] = tokenOut;

        address[] memory viaPath = new address[](3);
        viaPath[0] = tokenIn;
        viaPath[1] = WPLS;
        viaPath[2] = tokenOut;

        bool canVia = (tokenIn != WPLS && tokenOut != WPLS && tokenIn != tokenOut);

        for (uint256 i = 0; i < len; i++) {
            routers[i] = dexRouters[i];
            names[i]   = dexName[dexRouters[i]];

            if (tokenIn != tokenOut) {
                try IUniswapV2Router(dexRouters[i]).getAmountsOut(amountIn, directPath)
                    returns (uint256[] memory out)
                { directAmounts[i] = out[1]; } catch {}
            }

            if (canVia) {
                try IUniswapV2Router(dexRouters[i]).getAmountsOut(amountIn, viaPath)
                    returns (uint256[] memory out)
                { viaWplsAmounts[i] = out[2]; } catch {}
            }
        }
    }

    function dexRouterCount() external view returns (uint256) {
        return dexRouters.length;
    }

    function _calcAmountOutMin(
        uint256 estimatedOut,
        uint256 userSlippage
    ) internal pure returns (uint256) {
        uint256 slip = userSlippage == 0 ? SLIPPAGE_DEFAULT : userSlippage;
        if (slip > SLIPPAGE_MAX) slip = SLIPPAGE_MAX;
        return (estimatedOut * (BPS_DENOM - slip)) / BPS_DENOM;
    }

    function _findBestRoute(
        address tokenIn,
        address tokenOut,
        uint256 amountIn
    ) internal view returns (BestRoute memory best) {
        if (amountIn == 0) return best;

        address[] memory directPath = new address[](2);
        directPath[0] = tokenIn;
        directPath[1] = tokenOut;

        address[] memory viaPath = new address[](3);
        viaPath[0] = tokenIn;
        viaPath[1] = WPLS;
        viaPath[2] = tokenOut;

        bool canDirect = (tokenIn != tokenOut);
        bool canVia    = (tokenIn != WPLS && tokenOut != WPLS && tokenIn != tokenOut);

        for (uint256 i = 0; i < dexRouters.length; i++) {
            address dex = dexRouters[i];

            if (canDirect) {
                try IUniswapV2Router(dex).getAmountsOut(amountIn, directPath)
                    returns (uint256[] memory out)
                {
                    if (out[1] > best.amountOut) {
                        best = BestRoute(dex, directPath, out[1]);
                    }
                } catch {}
            }

            if (canVia) {
                try IUniswapV2Router(dex).getAmountsOut(amountIn, viaPath)
                    returns (uint256[] memory out)
                {
                    if (out[2] > best.amountOut) {
                        best = BestRoute(dex, viaPath, out[2]);
                    }
                } catch {}
            }
        }
    }

    function _safeApprove(address token, address spender, uint256 amount) internal {
        require(IERC20(token).approve(spender, 0),      "APPROVE_RESET_FAILED");
        require(IERC20(token).approve(spender, amount), "APPROVE_FAILED");
    }

    function _sendPLSToTreasury(uint256 amount) internal {
        (bool ok, ) = platformTreasury.call{value: amount}("");
        require(ok, "TREASURY_FEE_FAILED");
    }

    function _sendPLS(address payable to, uint256 amount) internal {
        (bool ok, ) = to.call{value: amount}("");
        if (!ok) {
            IWPLS(WPLS).deposit{value: amount}();
            require(IWPLS(WPLS).transfer(to, amount), "WPLS_FALLBACK_FAILED");
        }
    }

    function _addRouter(address router, string memory name) internal {
        dexRouters.push(router);
        isDexRouter[router] = true;
        dexName[router]     = name;
        emit RouterAdded(router, name);
    }

    function addDexRouter(address router, string calldata name) external onlyOwner {
        require(router != address(0),        "INVALID_ROUTER");
        require(!isDexRouter[router],        "ALREADY_ADDED");
        require(dexRouters.length < DEX_MAX, "DEX_CAP_REACHED");
        _addRouter(router, name);
    }

    function removeDexRouter(address router) external onlyOwner {
        require(isDexRouter[router], "NOT_FOUND");
        isDexRouter[router] = false;
        delete dexName[router];

        uint256 len = dexRouters.length;
        for (uint256 i = 0; i < len; i++) {
            if (dexRouters[i] == router) {
                dexRouters[i] = dexRouters[len - 1];
                dexRouters.pop();
                break;
            }
        }
        emit RouterRemoved(router);
    }

    function setFee(uint256 _feeBps) external onlyOwner {
        require(_feeBps <= FEE_BPS_MAX, "FEE_TOO_HIGH");
        emit FeeUpdated(feeBps, _feeBps);
        feeBps = _feeBps;
    }

    function setTreasury(address _treasury) external onlyOwner {
        require(_treasury != address(0), "INVALID_TREASURY");
        require(_treasury != WPLS,       "TREASURY_IS_WPLS");
        emit TreasuryUpdated(platformTreasury, _treasury);
        platformTreasury = _treasury;
    }

    function transferOwnership(address newOwner) external onlyOwner {
        require(newOwner != address(0), "INVALID_OWNER");
        pendingOwner = newOwner;
        emit OwnershipTransferStarted(owner, newOwner);
    }

    function acceptOwnership() external {
        require(msg.sender == pendingOwner, "NOT_PENDING_OWNER");
        emit OwnershipTransferred(owner, pendingOwner);
        owner        = pendingOwner;
        pendingOwner = address(0);
    }

    function rescueToken(address token, uint256 amount) external onlyOwner nonReentrant {
        require(IERC20(token).transfer(owner, amount), "RESCUE_FAILED");
    }

    function rescuePLS() external onlyOwner nonReentrant {
        uint256 bal = address(this).balance;
        require(bal > 0, "NO_PLS");
        (bool ok, ) = owner.call{value: bal}("");
        require(ok, "RESCUE_FAILED");
    }

    receive() external payable {
        require(msg.sender == WPLS, "ONLY_WPLS");
    }
}