Liquidity Provision
Overview
Lunarys uses a Uniswap V2-style liquidity model with encrypted reserves. Liquidity providers deposit both tokens symmetrically and receive encrypted LP shares (CERC20 tokens). The pool uses a two-step process: bootstrap (one-time initialization) followed by contribute (ongoing deposits).
Liquidity Lifecycle
flowchart LR
subgraph Bootstrap["Bootstrap (Owner, One-Time)"]
B1[Deposit Token A + Token B] --> B2["Mint LP = (amtA >> 1) + (amtB >> 1)"]
end
subgraph Contribute["Contribute (Any User)"]
C1[Fetch & decrypt obfuscated state] --> C2[Encrypt deposit amounts]
C2 --> C3[Call contributeLiquidity]
C3 --> C4["Mint LP = min(ratioA, ratioB)"]
end
subgraph Remove["Remove Liquidity"]
R1[Encrypt LP shares to burn] --> R2[Call removeLiquidity]
R2 --> R3["Proportional withdrawal − 0.05% fee"]
end
Bootstrap --> Contribute
Contribute --> Remove
style Bootstrap fill:#2d3748,color:#fff
style Contribute fill:#2d3748,color:#fff
style Remove fill:#2d3748,color:#fff
Bootstrap (One-Time Initialization)
Function Signature
function bootstrap(
externalEuint64 amountAExt,
externalEuint64 amountBExt,
bytes calldata inputProof
) external
Flow
- Only callable by pool owner
- Can only be called once (
reservesInitializedflag) - Pulls encrypted amounts of both tokens from caller
- Mints LP tokens:
liquidityAmount = (amountA >> 1) + (amountB >> 1) - Sets
reservesInitialized = true - Generates initial obfuscated state
Events
event LiquiditySeeded(address indexed provider);
Contribute Liquidity (After Bootstrap)
Function Signature
function contributeLiquidity(
externalEuint64 amountAExt, // Encrypted amount of token A
externalEuint64 amountBExt, // Encrypted amount of token B
bytes calldata amountProof, // FHE proof for amounts
uint128 decryptedORA, // Decrypted obfuscated reserve A
uint128 decryptedORB, // Decrypted obfuscated reserve B
uint128 decryptedOL, // Decrypted obfuscated LP supply
bytes calldata OProof // FHE proof for obfuscated state
) external
Flow
- Validate obfuscated state proof against current ciphertexts
- Pull both token amounts from user
- Compute LP tokens to mint (proportional, factor cancels):
mintA = amountA * obfTotalSupply / obfReserveA
mintB = amountB * obfTotalSupply / obfReserveB
mintAmount = min(mintA, mintB) - Mint LP tokens to caller
- Update reserves and rotate obfuscation factor
Why Factor Cancels
The obfuscation factor f appears in both numerator and denominator:
amountA * (totalSupply * f) / (reserveA * f) = amountA * totalSupply / reserveA
So proportional minting works correctly with obfuscated values.
Excess Deposits
If a user provides non-proportional amounts, only the minimum ratio is minted. The excess stays in the pool as an implicit donation to all LPs.
Remove Liquidity
Function Signature
function removeLiquidity(
externalEuint64 sharesToRemoveExt, // Encrypted LP shares to burn
bytes calldata sharesProof, // FHE proof
uint128 decryptedORA, // Decrypted obfuscated reserve A
uint128 decryptedORB, // Decrypted obfuscated reserve B
uint128 decryptedOL, // Decrypted obfuscated LP supply
bytes calldata OProof // FHE proof for obfuscated state
) external
Flow
- Validate proof
- Transfer LP tokens into pool (burns source)
- Compute proportional withdrawal:
grossA = sharesToRemove * obfReserveA / obfTotalSupply
grossB = sharesToRemove * obfReserveB / obfTotalSupply - Apply 0.05% withdrawal fee:
feeA = grossA * 500 / 1,000,000
feeB = grossB * 500 / 1,000,000
netA = grossA - feeA
netB = grossB - feeB - Transfer net amounts to user
- Burn LP tokens
- Update reserves and rotate obfuscation factor
Withdrawal Fee
A 0.05% (500 BPS) fee is applied on liquidity removal to prevent Just-In-Time (JIT) liquidity attacks. The fee stays in the pool, benefiting remaining LPs.
Events
event LiquidityAdjusted(address indexed caller, bool add);
LP Token Model
CERC20 LP Shares
The EPOOL contract itself is a CERC20 token -- LP shares are encrypted and tracked on-chain:
- Encrypted balances: Only the LP can see their own share count
- 6 decimals: Consistent with underlying tokens
- No public tracking: No fee accumulators or public growth metrics
Fee Realization
LPs realize accumulated swap fees when they remove liquidity. Since swap fees are added to reserves (Uniswap V2 style), the pool's reserve-to-supply ratio grows over time. When an LP removes their shares, they receive proportionally more tokens than they deposited.
Prerequisites
Before adding liquidity, users must:
- Set operator on both tokens:
token.setOperator(poolAddress, expiryTimestamp) - Fetch and decrypt current obfuscated state via the FHE gateway
- Encrypt deposit amounts via FHE SDK
Security Considerations
- Proof validation: Obfuscated state proof prevents manipulation
- Proportional minting:
min(ratioA, ratioB)prevents dilution attacks - Withdrawal fee: Prevents JIT liquidity exploitation
- Reentrancy protection: All functions use ReentrancyGuard
- One-time bootstrap: Prevents reinitialization