Trading & Liquidity
MonoSwap is the decentralized exchange (DEX) powering all token trading in the Monolythium ecosystem. It follows the Uniswap V2 constant-product AMM model, with native LYTH support via the WLYTH wrapper and a manipulation-resistant TWAP oracle.
Architecture
MonoSwap consists of four core contracts:
+------------------+ +------------------+ +------------------+
| MonoRouter | ----> | MonoFactory | ----> | MonoPair(s) |
| | | | | |
| - Swap routing | | - Pair creation | | - Token reserves |
| - Liquidity ops | | - Pair registry | | - LP token mint |
| - WLYTH wrapping | | - CREATE2 deploy | | - Price accum. |
+------------------+ +------------------+ +------------------+
| |
v v
+------------------+ +------------------+
| WLYTH | | TWAPOracle |
| | | |
| - Wrap/unwrap | | - Ring buffer |
| native LYTH | | - Deviation caps |
+------------------+ | - Warm-up period |
+------------------+
How the AMM Works
Constant Product Formula
Every MonoPair holds reserves of two tokens and maintains the invariant:
x * y = k
Where x and y are the reserves of each token, and k is a constant that only changes when liquidity is added or removed. This formula determines the exchange rate between the two tokens at any given moment.
Swap Fee
Every swap incurs a 0.3% fee that remains in the pool, increasing k over time and rewarding liquidity providers:
amountInWithFee = amountIn * 997
numerator = amountInWithFee * reserveOut
denominator = (reserveIn * 1000) + amountInWithFee
amountOut = numerator / denominator
The 0.3% fee is not extracted as protocol revenue -- it accrues entirely to LP token holders.
Price Impact
Larger trades move the price more. The price impact depends on the trade size relative to the pool reserves:
Pool: 1,000 LYTH / 100,000 TOKEN
Spot price: 1 LYTH = 100 TOKEN
Buy 10 LYTH worth of TOKEN:
amountOut = (10 * 997 * 100000) / (1000 * 1000 + 10 * 997)
amountOut = 987.16 TOKEN (vs 1000 at spot price)
Price impact: ~1.3%
Buy 100 LYTH worth of TOKEN:
amountOut = 9066.11 TOKEN (vs 10000 at spot price)
Price impact: ~9.3%
Swapping Tokens
Token-to-Token Swaps
The MonoRouter handles all swap routing. For a direct swap between two tokens that have a pair:
function swapExactTokensForTokens(
uint256 amountIn, // Tokens to sell
uint256 amountOutMin, // Minimum tokens to receive (slippage protection)
address[] calldata path, // [tokenIn, tokenOut]
address to, // Recipient address
uint256 deadline // Transaction deadline (reverts if expired)
) external returns (uint256[] memory amounts);
There is also a reverse function to specify an exact output amount:
function swapTokensForExactTokens(
uint256 amountOut, // Exact tokens to receive
uint256 amountInMax, // Maximum tokens willing to spend
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
Native LYTH Swaps
Since the AMM works with ERC-20 tokens, native LYTH is automatically wrapped/unwrapped by the router:
// Swap LYTH -> Token (send native LYTH as msg.value)
function swapExactLYTHForTokens(
uint256 amountOutMin,
address[] calldata path, // path[0] must be WLYTH
address to,
uint256 deadline
) external payable returns (uint256[] memory amounts);
// Swap Token -> LYTH (receive native LYTH)
function swapExactTokensForLYTH(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path, // path[last] must be WLYTH
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
The WLYTH contract follows the standard WETH9 pattern -- a 1:1 wrapper that converts native LYTH to an ERC-20 (deposit()) and back (withdraw()).
Multi-Hop Swaps
If no direct pair exists between two tokens, the router can route through intermediate pairs. For example, to swap TOKEN_A for TOKEN_B when only TOKEN_A/WLYTH and WLYTH/TOKEN_B pairs exist:
path = [TOKEN_A, WLYTH, TOKEN_B]
Step 1: TOKEN_A -> WLYTH (via TOKEN_A/WLYTH pair)
Step 2: WLYTH -> TOKEN_B (via WLYTH/TOKEN_B pair)
Each hop incurs the 0.3% fee independently.
Slippage Protection
Every swap function accepts a minimum output amount (or maximum input amount). If the trade cannot be executed within these bounds, the transaction reverts:
amountOutMin = expectedOut * (1 - slippageTolerance)
Example with 1% slippage:
Expected output: 1000 TOKEN
amountOutMin: 990 TOKEN
If actual output < 990 TOKEN -> transaction reverts
Deadline Protection
All router functions accept a deadline parameter (Unix timestamp). If block.timestamp > deadline, the transaction reverts with Expired(). This prevents stale transactions from executing at unfavorable prices long after submission.
Providing Liquidity
Adding Liquidity
Liquidity providers deposit equal value of both tokens in a pair and receive LP tokens representing their share of the pool:
// Add liquidity with two ERC-20 tokens
function addLiquidity(
address tokenA,
address tokenB,
uint256 amountADesired, // Maximum tokenA to deposit
uint256 amountBDesired, // Maximum tokenB to deposit
uint256 amountAMin, // Minimum tokenA to deposit
uint256 amountBMin, // Minimum tokenB to deposit
address to, // LP token recipient
uint256 deadline
) external returns (uint256 amountA, uint256 amountB, uint256 liquidity);
// Add liquidity with native LYTH + ERC-20 token
function addLiquidityLYTH(
address token,
uint256 amountTokenDesired,
uint256 amountTokenMin,
uint256 amountLYTHMin,
address to,
uint256 deadline
) external payable returns (uint256 amountToken, uint256 amountLYTH, uint256 liquidity);
If the pair does not exist, the router automatically calls MonoFactory.createPair() to create it. The first liquidity provider sets the initial price ratio.
LP Tokens
When you add liquidity, you receive MONO-LP tokens (ERC-20) proportional to your share of the pool:
For the first deposit:
liquidity = sqrt(amountA * amountB) - MINIMUM_LIQUIDITY
For subsequent deposits:
liquidity = min(
amountA * totalSupply / reserveA,
amountB * totalSupply / reserveB
)
The MINIMUM_LIQUIDITY (1000 wei) is permanently locked on the first deposit to prevent division-by-zero attacks.
Removing Liquidity
LP token holders can withdraw their proportional share of the pool at any time:
// Remove liquidity to two ERC-20 tokens
function removeLiquidity(
address tokenA,
address tokenB,
uint256 liquidity, // LP tokens to burn
uint256 amountAMin, // Minimum tokenA to receive
uint256 amountBMin, // Minimum tokenB to receive
address to,
uint256 deadline
) external returns (uint256 amountA, uint256 amountB);
// Remove liquidity to ERC-20 + native LYTH
function removeLiquidityLYTH(
address token,
uint256 liquidity,
uint256 amountTokenMin,
uint256 amountLYTHMin,
address to,
uint256 deadline
) external returns (uint256 amountToken, uint256 amountLYTH);
The amounts returned are proportional to the LP tokens burned relative to the total supply:
amountA = liquidity * balanceA / totalSupply
amountB = liquidity * balanceB / totalSupply
Since swap fees accumulate in the pool, withdrawing will typically return more tokens than originally deposited (minus any impermanent loss).
Impermanent Loss
Providing liquidity exposes you to impermanent loss when the price ratio of the pair changes. The loss is "impermanent" because it reverses if the price returns to the original ratio. However, if you withdraw at a different ratio, the loss becomes permanent.
Price change Impermanent loss
+/- 25% ~0.6%
+/- 50% ~2.0%
+/- 100% ~5.7%
+/- 200% ~13.4%
+/- 500% ~25.5%
The 0.3% swap fees earned on every trade help offset impermanent loss.
TWAP Oracle
MonoSwap includes a manipulation-resistant Time-Weighted Average Price (TWAP) oracle via the TWAPOracle contract.
How It Works
Each MonoPair maintains cumulative price accumulators that are updated on every interaction (swap, mint, burn). The TWAP Oracle periodically samples these accumulators into a ring buffer of observations:
Ring Buffer (24 observations):
+---+---+---+---+---+---+---+---+---+---+
| 0 | 1 | 2 | 3 | 4 | 5 | ... | 23|
+---+---+---+---+---+---+---+---+---+---+
^ ^
Oldest Latest
TWAP = (latestCumulative - olderCumulative) / timeElapsed
Manipulation Resistance
The TWAP Oracle includes multiple layers of protection:
| Protection | Parameter | Description |
|---|---|---|
| Update interval | 15 minutes minimum | Prevents rapid overwriting of observations |
| TWAP period | 30 minutes minimum | Prevents short-window price manipulation |
| Per-observation deviation | 50% max | Rejects individual observations that deviate too far |
| Cumulative deviation | 100% max | Prevents slow-drift compounding attacks |
| Deviation decay | 6-hour half-life | Exponential decay of cumulative deviation |
| Warm-up period | 3 observations + 2 hours | Oracle is not trusted until warmed up |
| Minimum reserves | 0.1 LYTH | Prevents dust manipulation |
| Post-gap cooldown | 25% deviation limit for 3 updates | Reduced allowance after long inactivity |
Consulting the Oracle
// Get TWAP over a specified period (minimum 30 minutes)
function consult(address pair, uint256 period)
external view
returns (uint256 price0Average, uint256 price1Average);
// Get TWAP over the minimum period (30 minutes)
function consult(address pair)
external view
returns (uint256 price0Average, uint256 price1Average);
The oracle reverts if it is not yet warmed up or if there is insufficient history for the requested period.
Oracle Status
// Check if oracle can be updated
function canUpdate(address pair) external view returns (bool);
// Check warm-up progress
function getWarmUpStatus(address pair) external view
returns (bool warmedUp, uint256 observationsNeeded, uint256 timeSpanNeeded);
// Check cumulative deviation health
function getCumulativeDeviationStatus(address pair) external view
returns (uint256 currentDeviation, uint256 remainingCapacity, uint256 timeSinceUpdate);
Pair Management
Pair Creation
Pairs are created via MonoFactory.createPair() using CREATE2 for deterministic addresses:
function createPair(address tokenA, address tokenB)
external returns (address pair);
Each unique token pair can only have one MonoPair contract. The factory maintains a bidirectional mapping (getPair[A][B] and getPair[B][A]).
Pair Utilities
Each MonoPair exposes utility functions:
| Function | Description |
|---|---|
getReserves() | Returns current reserves and last update timestamp |
skim(address to) | Sends excess token balances (above reserves) to an address |
sync() | Forces reserves to match actual token balances |
WLYTH (Wrapped LYTH)
WLYTH is a standard WETH9-pattern wrapper that converts native LYTH to an ERC-20 token:
| Function | Description |
|---|---|
deposit() | Wrap native LYTH to WLYTH (send LYTH as msg.value) |
withdraw(uint256) | Unwrap WLYTH back to native LYTH |
totalSupply() | Returns the contract's native LYTH balance |
Users typically do not interact with WLYTH directly. The MonoRouter handles wrapping and unwrapping automatically when using the LYTH-specific swap and liquidity functions.
How to Trade on MonoHub
Step 1: Connect Wallet
Connect an EVM wallet and switch to a supported chain (e.g., Sprintnet).
Step 2: Navigate to Trading
Go to the Trading page at /trading or click on a specific token from the Token Markets page.
Step 3: Select Tokens
Choose the token pair you want to trade. For example, swap LYTH for a graduated MonoPump token.
Step 4: Enter Amount
Enter the amount to sell or the amount to receive. The interface will calculate the estimated output, price impact, and minimum received (based on your slippage tolerance).
Step 5: Approve and Swap
If swapping an ERC-20 token (not native LYTH), approve the router to spend your tokens first. Then confirm the swap transaction.
Related
- MonoPump -- Token launcher (tokens graduate to MonoSwap)
- LP Farming -- Earn rewards on your LP positions
- DeFi Overview -- Full ecosystem overview