// SPDX-License-Identifier: MIT pragma solidity 0.8.20; /// @title RichXchange Order Book — RICHX / ETHIC+ on RichX Chain (Chain ID 20260903) /// @notice Non-custodial limit order book. Base asset: native RICHX. Quote asset: ETHIC+ (RXC-20). /// An order's funds sit in this contract until the order is filled or cancelled by its maker. /// Nobody — including the deployer — can move anyone else's funds. No owner, no admin, no fees, /// no pause, no upgrade. Immutable after deployment. /// @dev price = quote-token base units paid per 1 RICHX (1e18 wei). /// Compile with solc 0.8.20, EVM "london", optimizer 200 runs. contract RichXOrderBook { struct Order { address maker; bool isBuy; // true: maker buys RICHX with ETHIC+ ; false: maker sells RICHX for ETHIC+ bool active; uint128 price; // ETHIC+ base units per 1e18 wei RICHX uint128 remaining; // RICHX wei still open uint128 escrow; // buy orders: ETHIC+ still held for this order ; sell orders: equals remaining uint64 time; } address public immutable quoteToken; // ETHIC+ uint256 public constant ONE = 1e18; // 1 RICHX Order[] internal _orders; uint256 public activeOrders; uint256 public tradeCount; event OrderPlaced(uint256 indexed id, address indexed maker, bool isBuy, uint256 price, uint256 amount); event OrderCancelled(uint256 indexed id, address indexed maker, uint256 refundedRichx, uint256 refundedQuote); event Trade(uint256 indexed id, address indexed maker, address indexed taker, bool takerBuys, uint256 price, uint256 amount, uint256 quoteAmount); uint256 private _lock = 1; modifier nonReentrant() { require(_lock == 1, "busy"); _lock = 2; _; _lock = 1; } constructor(address quote) { require(quote.code.length > 0, "quote not a contract"); quoteToken = quote; } // ------------------------------------------------------------------ trading /// @notice Buy RICHX at `price` or better. First fills the listed sell orders (best first, as given), /// then rests any remainder as a buy order (if `rest` is true). Requires ETHIC+ approval. function limitBuy(uint256 price, uint256 amount, uint256[] calldata takeIds, bool rest) external nonReentrant returns (uint256 filled, uint256 orderId) { require(price > 0 && price <= type(uint128).max && amount > 0 && amount <= type(uint128).max, "bad input"); uint256 left = amount; uint256 richxOut; for (uint256 i = 0; i < takeIds.length && left > 0; i++) { uint256 id = takeIds[i]; if (id >= _orders.length) continue; Order storage o = _orders[id]; if (!o.active || o.isBuy || o.price > price) continue; uint256 f = left < o.remaining ? left : o.remaining; uint256 q = f * o.price / ONE; if (q == 0) continue; o.remaining -= uint128(f); o.escrow -= uint128(f); if (o.remaining == 0) { o.active = false; activeOrders--; } left -= f; richxOut += f; tradeCount++; _pullQuote(msg.sender, o.maker, q); // taker pays maker directly emit Trade(id, o.maker, msg.sender, true, o.price, f, q); } filled = amount - left; orderId = type(uint256).max; if (left > 0 && rest) { uint256 q = left * price / ONE; require(q > 0, "order too small"); _pullQuote(msg.sender, address(this), q); // escrow orderId = _push(msg.sender, true, price, left, q); } if (richxOut > 0) _sendNative(msg.sender, richxOut); } /// @notice Sell the RICHX sent with this call at `price` or better. First fills the listed buy orders, /// then rests any remainder as a sell order (if `rest` is true) or refunds it. function limitSell(uint256 price, uint256[] calldata takeIds, bool rest) external payable nonReentrant returns (uint256 filled, uint256 orderId) { uint256 amount = msg.value; require(price > 0 && price <= type(uint128).max && amount > 0 && amount <= type(uint128).max, "bad input"); uint256 left = amount; uint256 quoteOut; for (uint256 i = 0; i < takeIds.length && left > 0; i++) { uint256 id = takeIds[i]; if (id >= _orders.length) continue; Order storage o = _orders[id]; if (!o.active || !o.isBuy || o.price < price) continue; uint256 f = left < o.remaining ? left : o.remaining; uint256 q = (f == o.remaining) ? o.escrow : f * o.price / ONE; // last fill takes all remaining escrow: no dust if (q == 0) continue; o.remaining -= uint128(f); o.escrow -= uint128(q); if (o.remaining == 0) { o.active = false; activeOrders--; } left -= f; quoteOut += q; tradeCount++; _sendNative(o.maker, f); // maker receives RICHX emit Trade(id, o.maker, msg.sender, false, o.price, f, q); } filled = amount - left; orderId = type(uint256).max; if (left > 0) { if (rest) orderId = _push(msg.sender, false, price, left, left); else _sendNative(msg.sender, left); } if (quoteOut > 0) _sendQuote(msg.sender, quoteOut); } /// @notice Cancel your own open order and get the unfilled funds back. function cancel(uint256 id) external nonReentrant { require(id < _orders.length, "no order"); Order storage o = _orders[id]; require(o.maker == msg.sender, "not yours"); require(o.active, "closed"); o.active = false; activeOrders--; uint256 esc = o.escrow; o.escrow = 0; o.remaining = 0; if (o.isBuy) { _sendQuote(msg.sender, esc); emit OrderCancelled(id, msg.sender, 0, esc); } else { _sendNative(msg.sender, esc); emit OrderCancelled(id, msg.sender, esc, 0); } } // ------------------------------------------------------------------ views function orderCount() external view returns (uint256) { return _orders.length; } function getOrder(uint256 id) external view returns (Order memory) { return _orders[id]; } /// @notice Orders `from` .. `from+count-1` (clamped), for building the book in a browser. function getOrders(uint256 from, uint256 count) external view returns (Order[] memory list) { uint256 n = _orders.length; if (from >= n) return new Order[](0); uint256 end = from + count > n ? n : from + count; list = new Order[](end - from); for (uint256 i = from; i < end; i++) list[i - from] = _orders[i]; } // ------------------------------------------------------------------ internals function _push(address maker, bool isBuy, uint256 price, uint256 amount, uint256 escrow) internal returns (uint256 id) { id = _orders.length; _orders.push(Order(maker, isBuy, true, uint128(price), uint128(amount), uint128(escrow), uint64(block.timestamp))); activeOrders++; emit OrderPlaced(id, maker, isBuy, price, amount); } function _sendNative(address to, uint256 v) internal { (bool ok, ) = to.call{value: v}(""); require(ok, "RICHX transfer failed"); } function _sendQuote(address to, uint256 v) internal { (bool ok, bytes memory r) = quoteToken.call(abi.encodeWithSelector(0xa9059cbb, to, v)); require(ok && (r.length == 0 || abi.decode(r, (bool))), "ETHIC+ transfer failed"); } /// @dev pulls exactly `v` ETHIC+ from `from` to `to`; rejects tokens that deliver less than asked. function _pullQuote(address from, address to, uint256 v) internal { uint256 b0 = _bal(to); (bool ok, bytes memory r) = quoteToken.call(abi.encodeWithSelector(0x23b872dd, from, to, v)); require(ok && (r.length == 0 || abi.decode(r, (bool))), "ETHIC+ transfer failed - approve first"); require(_bal(to) - b0 == v, "ETHIC+ amount mismatch"); } function _bal(address a) internal view returns (uint256) { (bool ok, bytes memory r) = quoteToken.staticcall(abi.encodeWithSelector(0x70a08231, a)); require(ok && r.length >= 32, "balance read failed"); return abi.decode(r, (uint256)); } receive() external payable { revert("use limitSell"); } }