Address
0x8e1093cd44f4e6fc7e4f07c52152cee0cc0dfaeeCurrent 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
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");
}
}