Contract Name:
TokenSwapSpot
Contract Source Code:
File 1 of 1 : TokenSwapSpot
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface ITokenSwap {
function swapToken(
address tokenIn,
address tokenOut,
uint256 amountIn,
uint24 feeTier,
address payable reseller
) external payable;
function getPrice(address token1,address token2,uint256 feeTier) external view returns (uint256);
function WETH() external view returns (address);
}
interface IERC20 {
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
function allowance(address owner,address spender) external view returns (uint);
function approve(address spender, uint256 amount) external returns (bool);
}
interface IWETH {
function deposit() external payable;
function withdraw(uint wad) external;
}
contract TokenSwapSpot {
// Owner of the contract
address public owner;
// Oracle address to call for order processing
address public oracle;
// Address of the TokenSwap contract (Uniswap swap handler)
address public tokenSwapContract;
ITokenSwap tokenSwap;
address public WETH;
uint public oracleFee = 0.02 ether;
// Event for order placed
event OrderPlaced(address indexed user, address tokenIn, address tokenOut, uint256 price, uint256 amountIn);
// Event for swap triggered via oracle
event OrderTriggered(address indexed user, address tokenIn, address tokenOut, uint256 price, uint256 amountIn);
// Event for order cancellation
event OrderCancelled(address indexed user, address tokenIn, address tokenOut, uint256 price, uint256 amountIn);
// Struct representing a limit order
struct Order {
address user;
uint256 priceIn;
uint256 pairPriceId;
uint256 amountIn; // tokenInAmount
uint8 status; // 0=>open / 1=>withdrawed / 2=>cancelled
uint256 createdAt;
uint256 updatedAt;
}
uint256 public lastOrderId;
mapping(uint256 => Order) public orders;
mapping (address => uint[]) userOrderIds;
mapping (address =>uint) public userOrderCount;
struct PairPrice{
address tokenIn;
address tokenOut;
uint256 price;
uint256 totalAmountIn;
uint256 amountOut; // if 0 its open, if more it's done
uint256 totalWithdrawn;
}
uint256 public lastPairId;
mapping(uint256 => PairPrice) public pairPrices;
mapping(address => mapping(address => mapping(uint=>uint))) pairPriceIds;
// Modifier to restrict functions to the contract owner
modifier onlyOwner() {
require(msg.sender == owner, "Not the contract owner");
_;
}
// Modifier to restrict functions to the oracle
modifier onlyOracle() {
require(msg.sender == oracle, "Not the oracle");
_;
}
constructor(address _tokenSwapContract) {
owner = msg.sender;
oracle = msg.sender;
tokenSwapContract = _tokenSwapContract;
tokenSwap = ITokenSwap(tokenSwapContract);
WETH = tokenSwap.WETH();
}
// Function to place a limit order
function placeOrder(address tokenIn, address tokenOut, uint256 price, uint256 amountIn) external payable {
require(msg.value >= oracleFee,"Not enough oracle fee");
if(tokenIn==address(0)) tokenIn=WETH;
if(tokenOut==address(0)) tokenOut=WETH;
if (msg.value > oracleFee && tokenIn==address(WETH)) {
tokenIn = WETH;
amountIn = msg.value - oracleFee;
IWETH wethContract = IWETH(WETH);
wethContract.deposit{value: amountIn}();
//transfer oracle fee to oracle address
(bool sent, ) = oracle.call{value: oracleFee}("");
require(sent, "Failed to send oracle fee");
}else{
require(amountIn > 0, "Must specify an amount to swap");
//transfer token to contract
IERC20 token = IERC20(tokenIn);
token.transferFrom(msg.sender, address(this), amountIn);
//transfer oracle fee to oracle address
if(msg.value>0){
(bool sent, ) = oracle.call{value: msg.value}("");
require(sent, "Failed to send oracle fee");
}
}
uint256 pairPriceId = getPairPriceId(tokenIn,tokenOut,price);
pairPrices[pairPriceId].totalAmountIn += amountIn;
//approve tokenIn
IERC20 erc20 = IERC20(tokenIn);
if(erc20.allowance(address(this), tokenSwapContract)<amountIn)
erc20.approve(tokenSwapContract,type(uint256).max);
//place order
uint priceIn = tokenSwap.getPrice(tokenIn, tokenOut,3000);
Order memory newOrder = Order({
user: msg.sender,
priceIn: priceIn,
pairPriceId:pairPriceId,
amountIn: amountIn,
status:0,//pending
createdAt: block.timestamp,
updatedAt: 0
});
lastOrderId++;
orders[lastOrderId] = newOrder;
userOrderIds[msg.sender].push(lastOrderId);
userOrderCount[msg.sender]++;
emit OrderPlaced(msg.sender, tokenIn, tokenOut, price, amountIn);
}
// Function to cancel an order
function cancelOrder(uint256 orderIndex) external {
//validate inputs
require(orderIndex<=userOrderCount[msg.sender],"Order id is not valid");
uint256 orderId = userOrderIds[msg.sender][orderIndex];
require(orders[orderId].status==0 && orders[orderId].amountIn>0,"Order is not open");
//update order status
orders[orderId].status=2;
orders[orderId].updatedAt=block.timestamp;
//rolback the amounts
uint256 pairPriceId = orders[orderId].pairPriceId;
if(pairPrices[pairPriceId].tokenIn==WETH)
{
IWETH(WETH).withdraw(orders[orderId].amountIn);
(bool success, ) = msg.sender.call{value: orders[orderId].amountIn}("");
require(success, "Failed to send ETH");
}else
IERC20(pairPrices[pairPriceId].tokenIn).transfer(msg.sender, orders[orderId].amountIn);
pairPrices[pairPriceId].totalAmountIn -= orders[orderId].amountIn;
}
function withdrawOrder(uint256 orderIndex) public{
//validate inputs
require(orderIndex<=userOrderCount[msg.sender],"Order id is not valid");
uint256 orderId = userOrderIds[msg.sender][orderIndex];
require(orders[orderId].status==0 && orders[orderId].amountIn>0,"Order is not open");
//update order status
orders[orderId].status=1; //withdrawed
orders[orderId].updatedAt=block.timestamp;
//pay the swapped amounts
uint256 pairPriceId = orders[orderId].pairPriceId;
require(pairPrices[pairPriceId].amountOut>0,"Order is not placed yet.");
uint256 totalAmountIn = pairPrices[pairPriceId].totalAmountIn;
uint256 totalAmountOut = pairPrices[pairPriceId].amountOut;
uint256 myAmountIn = orders[orderId].amountIn;
uint256 ratio = myAmountIn * 10**18 / totalAmountIn;
uint256 myAmountOut = totalAmountOut * ratio / 10**18;
if(pairPrices[pairPriceId].tokenOut==WETH)
{
//IWETH(WETH).withdraw(myAmountOut); //already converted to ETH in swap contract
(bool success, ) = msg.sender.call{value: myAmountOut}("");
require(success, "Failed to send ETH");
}else
IERC20(pairPrices[pairPriceId].tokenOut).transfer(msg.sender, myAmountOut);
pairPrices[pairPriceId].totalWithdrawn += myAmountOut;
}
// Function to process orders in steps (batch processing)
function processPairPrice(uint256 pairPriceId) external onlyOracle {
//TODO:
//get price and check if it is ok
//??????????????????
//place swap order and get received token amount
uint256 firstBalance;
if(pairPrices[pairPriceId].tokenOut == WETH)
firstBalance = address(this).balance;
else
firstBalance = IERC20(pairPrices[pairPriceId].tokenOut).balanceOf(address(this));
tokenSwap.swapToken(
pairPrices[pairPriceId].tokenIn,
pairPrices[pairPriceId].tokenOut,
pairPrices[pairPriceId].totalAmountIn,
3000,
payable(oracle)
);
uint256 newBalance;
if(pairPrices[pairPriceId].tokenOut == WETH)
newBalance = address(this).balance;
else
newBalance = IERC20(pairPrices[pairPriceId].tokenOut).balanceOf(address(this));
uint256 income = newBalance - firstBalance;
require(income>0,"Failed to process swap");
//disable old pairPrice and update income
pairPrices[pairPriceId].amountOut = income;
pairPriceIds[pairPrices[pairPriceId].tokenIn][pairPrices[pairPriceId].tokenOut][pairPrices[pairPriceId].price]=0;
}
// Function to get all active orders for a user with pagination
struct TmpOrder {
uint userOrderIndex;
address user;
uint256 priceIn;
uint256 pairPriceId;
uint256 amountIn; // tokenInAmount
uint8 status; // 0=>open / 1=>withdrawed / 2=>cancelled
uint256 createdAt;
uint256 updatedAt;
address tokenIn;
address tokenOut;
uint256 price;
uint256 totalAmountIn;
uint256 amountOut; // if 0 its open, if more it's done
uint256 totalWithdrawn;
}
function getUserOrdersPaginated(address user,uint256 fromId, uint256 toId) external view returns (TmpOrder[] memory) {
if (toId > userOrderIds[user].length) {
toId = userOrderIds[user].length;
}
require(fromId <= toId, "Invalid pagination range");
uint256 size = toId - fromId;
TmpOrder[] memory paginatedOrders = new TmpOrder[](size);
for (uint256 i = 0; i < size; i++) {
uint256 orderIndex = userOrderIds[user][fromId + i];
uint256 pairPriceId = orders[orderIndex].pairPriceId;
TmpOrder memory _tmpOrder = TmpOrder({
userOrderIndex: fromId + i,
user: orders[orderIndex].user,
priceIn: orders[orderIndex].priceIn,
pairPriceId: orders[orderIndex].pairPriceId,
amountIn: orders[orderIndex].amountIn, // tokenInAmount
status: orders[orderIndex].status, // 0=>open / 1=>withdrawed / 2=>cancelled
createdAt: orders[orderIndex].createdAt,
updatedAt: orders[orderIndex].updatedAt,
tokenIn: pairPrices[pairPriceId].tokenIn,
tokenOut: pairPrices[pairPriceId].tokenOut,
price: pairPrices[pairPriceId].price,
totalAmountIn: pairPrices[pairPriceId].totalAmountIn,
amountOut: pairPrices[pairPriceId].amountOut,
totalWithdrawn: pairPrices[pairPriceId].totalWithdrawn
});
// Store the struct in the paginatedOrders array
paginatedOrders[i] = _tmpOrder;
}
return paginatedOrders;
}
// Function to set a new TokenSwap contract address
function setTokenSwapContract(address _tokenSwapContract) external onlyOwner {
tokenSwapContract = _tokenSwapContract;
tokenSwap = ITokenSwap(tokenSwapContract);
WETH = tokenSwap.WETH();
}
// Function to set the oracle address
function setOracle(address _oracle) external onlyOwner {
oracle = _oracle;
}
// Function to set the oracle address
function setOracleFee(uint _oracleFee) external onlyOwner {
oracleFee = _oracleFee;
}
// Helper function to round the price to a smaller step using base 2
function roundPriceBase2(uint256 price) private pure returns (uint256) {
uint256 magnitude = 1 << (log2(price) - 8); // Shift to create smaller rounding steps
uint256 roundedPrice = (price / magnitude) * magnitude;
return roundedPrice;
}
// Efficient log2 implementation (based on bit-shifting)
function log2(uint256 x) private pure returns (uint256) {
uint256 result = 0;
if (x >= 2**128) { x >>= 128; result += 128; }
if (x >= 2**64) { x >>= 64; result += 64; }
if (x >= 2**32) { x >>= 32; result += 32; }
if (x >= 2**16) { x >>= 16; result += 16; }
if (x >= 2**8) { x >>= 8; result += 8; }
if (x >= 2**4) { x >>= 4; result += 4; }
if (x >= 2**2) { x >>= 2; result += 2; }
if (x >= 2**1) { result += 1; }
return result;
}
function getPairPriceId(address tokenIn,address tokenOut,uint256 price) private returns(uint){
// Round the price to the nearest 0.1%
price = roundPriceBase2(price);
if(pairPriceIds[tokenIn][tokenOut][price]>0)
return pairPriceIds[tokenIn][tokenOut][price];
PairPrice memory _pairPrice = PairPrice({
tokenIn:tokenIn,
tokenOut:tokenOut,
price:price,
totalAmountIn:0,
amountOut:0, // if 0 its open, if more it's done
totalWithdrawn:0
});
lastPairId++;
pairPrices[lastPairId] = _pairPrice;
pairPriceIds[tokenIn][tokenOut][price] = lastPairId;
return lastPairId;
}
// Emergency withdraw ETH
function emergencyWithdrawETH(address payable to) external onlyOwner {
uint256 balance = address(this).balance;
require(balance > 0, "No ETH to withdraw");
to.transfer(balance);
}
// Emergency withdraw ERC20 tokens
function emergencyWithdrawToken(address token, address to) external onlyOwner {
IERC20 erc20 = IERC20(token);
uint256 balance = erc20.balanceOf(address(this));
require(balance > 0, "No token balance to withdraw");
erc20.transfer(to, balance);
}
// Fallback function to receive ETH
receive() external payable {}
}