// SPDX-License-Identifier: MIT pragma solidity 0.8.30; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; /// @notice Immutable fixed supply. No owner, tax, additional mint or blacklist. contract MeridaxToken is ERC20Burnable { constructor(string memory name_, string memory symbol_, uint256 supply_, address recipient_) ERC20(name_, symbol_) { require(bytes(name_).length > 0 && bytes(name_).length <= 60, "Name length"); require(bytes(symbol_).length > 0 && bytes(symbol_).length <= 12, "Symbol length"); require(supply_ >= 1e18 && supply_ <= 1e33 && recipient_ != address(0), "Supply or recipient"); _mint(recipient_, supply_); } } /// @notice Permanent native-ETH constant product market. Not a price floor. /// @dev No LP withdrawal, upgrade, pause, owner transfer or token mint methods. contract MeridaxMarket is ReentrancyGuard { MeridaxToken public immutable token; address public immutable creator; uint256 public constant FEE_BPS = 30; uint256 public reserveETH; uint256 public reserveTokens; uint256 public creatorFees; event MarketCreated(address indexed token, address indexed creator, uint256 supply, uint256 seedETH); event Trade(address indexed trader, bool isBuy, uint256 amountIn, uint256 amountOut, uint256 ethReserve, uint256 tokenReserve); event FeesClaimed(address indexed recipient, uint256 amount); constructor(string memory name_, string memory symbol_, uint256 supply_, address creator_) payable { require(msg.value >= 1e13 && msg.value <= 10000 ether, "Seed 0.00001-10000 ETH"); require(creator_ != address(0), "Creator required"); creator = creator_; token = new MeridaxToken(name_, symbol_, supply_, address(this)); reserveETH = msg.value; reserveTokens = supply_; emit MarketCreated(address(token), creator_, supply_, msg.value); } function quoteBuy(uint256 amount) public view returns(uint256 out, uint256 fee) { fee = Math.mulDiv(amount, FEE_BPS, 10000); uint256 net = amount - fee; out = Math.mulDiv(reserveTokens, net, reserveETH + net); } function quoteSell(uint256 amount) public view returns(uint256 out, uint256 fee) { uint256 gross = Math.mulDiv(reserveETH, amount, reserveTokens + amount); fee = Math.mulDiv(gross, FEE_BPS, 10000); out = gross - fee; } function buy(uint256 minimumOut, uint256 deadline) external payable nonReentrant returns(uint256 out) { require(block.timestamp <= deadline && msg.value > 0, "Expired or zero input"); uint256 fee; (out, fee) = quoteBuy(msg.value); require(out > 0 && out >= minimumOut, "Slippage or zero output"); reserveETH += msg.value - fee; reserveTokens -= out; creatorFees += fee; require(token.transfer(msg.sender, out), "Transfer failed"); emit Trade(msg.sender, true, msg.value, out, reserveETH, reserveTokens); } function sell(uint256 amount, uint256 minimumOut, uint256 deadline) external nonReentrant returns(uint256 out) { require(block.timestamp <= deadline && amount > 0, "Expired or zero input"); uint256 fee; (out, fee) = quoteSell(amount); require(out > 0 && out >= minimumOut, "Slippage or zero output"); reserveETH -= out + fee; reserveTokens += amount; creatorFees += fee; require(token.transferFrom(msg.sender, address(this), amount), "Transfer failed"); (bool ok,) = msg.sender.call{value:out}(""); require(ok, "ETH transfer failed"); emit Trade(msg.sender, false, amount, out, reserveETH, reserveTokens); } function claimFees() external nonReentrant { require(msg.sender == creator, "Creator only"); uint256 amount = creatorFees; require(amount > 0, "No fees"); creatorFees = 0; (bool ok,) = creator.call{value:amount}(""); require(ok, "ETH transfer failed"); emit FeesClaimed(creator, amount); } }