// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// Interface for interacting with AMM pairs like UniswapV2 or similar
interface IAMMPair {
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
function getReserves() external view returns (uint reserve0, uint reserve1, uint32 blockTimestampLast);
function token0() external view returns (address);
function token1() external view returns (address);
}
// Standard ERC20 interface for token interactions
interface IERC20 {
function balanceOf(address account) external view returns (uint);
function transferFrom(address sender, address recipient, uint amount) external returns (bool);
function transfer(address recipient, uint amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint);
}
// TokenSwapper contract that interacts with AMM pairs and ERC20 tokens
contract TokenSwapper {
address public owner;
event SwapExecuted(address indexed tokenIn, uint amountIn, uint amountOutMin, uint amountOut, address indexed recipient, bool success);
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "Caller is not the owner");
_;
}
// Function to calculate the output amount based on input, reserves, and an optional tax percentage
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut, uint taxPercent) public pure returns (uint amountOut) {
require(amountIn > 0, "TokenSwapper: INSUFFICIENT_INPUT_AMOUNT");
require(reserveIn > 0 && reserveOut > 0, "TokenSwapper: INSUFFICIENT_LIQUIDITY");
// Calculate the amount in after applying tax
uint amountInAfterTax = amountIn * (100 - taxPercent) / 100; // Apply tax percentage (for example, 10% tax would reduce amountIn by 10%)
uint amountInWithFee = amountInAfterTax * 997;
uint numerator = amountInWithFee * reserveOut;
uint denominator = reserveIn * 1000 + amountInWithFee;
amountOut = numerator / denominator;
}
// Function to get reserves for the swap
function getReserves(IAMMPair ammPair, bool isToken0In) internal view returns (uint reserveIn, uint reserveOut) {
(uint reserve0, uint reserve1,) = ammPair.getReserves();
return isToken0In ? (reserve0, reserve1) : (reserve1, reserve0);
}
// Function to determine the input and output tokens based on the swap direction
function getTokens(IAMMPair ammPair, bool isToken0In) internal view returns (address tokenIn, address tokenOut) {
return isToken0In ? (ammPair.token0(), ammPair.token1()) : (ammPair.token1(), ammPair.token0());
}
// Function to execute the swap on the AMM pair
function swap(address pair, uint amountIn, uint amountOutMin, bool isToken0In, uint taxPercent) external {
IAMMPair ammPair = IAMMPair(pair);
// Get reserves
(uint reserveIn, uint reserveOut) = getReserves(ammPair, isToken0In);
// Calculate the expected output amount with the tax applied
uint amountOut = getAmountOut(amountIn, reserveIn, reserveOut, taxPercent);
require(amountOut >= amountOutMin, "TokenSwapper: INSUFFICIENT_OUTPUT_AMOUNT");
// Determine the input and output tokens
(address tokenIn, address tokenOut) = getTokens(ammPair, isToken0In);
// Check the balance of the recipient before the swap
uint balanceBefore = IERC20(tokenOut).balanceOf(msg.sender);
// Transfer input tokens directly from the sender to the AMM pair
require(IERC20(tokenIn).transferFrom(msg.sender, pair, amountIn), "TokenSwapper: TRANSFER_FAILED");
// Execute the swap on the AMM pair
ammPair.swap(
isToken0In ? 0 : amountOut,
isToken0In ? amountOut : 0,
msg.sender, // Directly send the output tokens to msg.sender
""
);
// Check the balance of the recipient after the swap
uint balanceAfter = IERC20(tokenOut).balanceOf(msg.sender);
// Ensure the balance matches the expected amountOut
require(balanceAfter >= balanceBefore + amountOut, "TokenSwapper: BALANCE_MISMATCH");
// Emit the swap event
emit SwapExecuted(tokenIn, amountIn, amountOutMin, amountOut, msg.sender, true);
}
// Function to rescue tokens accidentally sent to the contract
function rescueTokens(address token, uint amount, address to) external onlyOwner {
require(IERC20(token).transfer(to, amount), "TokenSwapper: TRANSFER_FAILED");
}
// Function to transfer ownership of the contract
function transferOwnership(address newOwner) external onlyOwner {
require(newOwner != address(0), "New owner cannot be the zero address");
owner = newOwner;
}
}
{
"compilationTarget": {
"contracts/IAMM_swapper.sol": "TokenSwapper"
},
"evmVersion": "cancun",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": false,
"runs": 200
},
"remappings": []
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"tokenIn","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountIn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountOut","type":"uint256"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"bool","name":"success","type":"bool"}],"name":"SwapExecuted","type":"event"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"reserveIn","type":"uint256"},{"internalType":"uint256","name":"reserveOut","type":"uint256"},{"internalType":"uint256","name":"taxPercent","type":"uint256"}],"name":"getAmountOut","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"to","type":"address"}],"name":"rescueTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"pair","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"bool","name":"isToken0In","type":"bool"},{"internalType":"uint256","name":"taxPercent","type":"uint256"}],"name":"swap","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}]