跳到主要内容

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:

ProtectionParameterDescription
Update interval15 minutes minimumPrevents rapid overwriting of observations
TWAP period30 minutes minimumPrevents short-window price manipulation
Per-observation deviation50% maxRejects individual observations that deviate too far
Cumulative deviation100% maxPrevents slow-drift compounding attacks
Deviation decay6-hour half-lifeExponential decay of cumulative deviation
Warm-up period3 observations + 2 hoursOracle is not trusted until warmed up
Minimum reserves0.1 LYTHPrevents dust manipulation
Post-gap cooldown25% deviation limit for 3 updatesReduced 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:

FunctionDescription
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:

FunctionDescription
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.