Lunarys Protocol Overview
Executive Summary
Lunarys is the world's first fully encrypted decentralized exchange (eDEX) built with Fully Homomorphic Encryption (FHE). Every aspect of the protocol -- from trading volumes to liquidity depths -- remains encrypted on-chain, providing unprecedented privacy while maintaining complete decentralization.
Core Architecture
The Lunarys protocol consists of the following components:
1. EPOOL (Encrypted Pool)
The core AMM contract implementing a Uniswap V2-style constant product formula with fully encrypted reserves.
- Reserves stored as encrypted
euint64values - Obfuscated reserves publicly visible for price discovery (multiplied by random factor 1M-3M)
- Atomic swaps -- single-transaction execution, no oracle settlement phase
- 4-term Taylor approximation for efficient FHE pricing
- CERC20 LP token with encrypted shares
2. EPoolFactory
Deploys new EPOOL instances with deterministic pool keys based on (token0, token1, swapFee). Canonical token ordering ensures each pair has exactly one pool per fee tier.
3. UniversalRouter
Pool discovery and route validation helper. Due to FHE encrypted input verification, the router cannot execute swaps on behalf of users. Users must call pool functions directly.
4. Token System (CERC20 / ERC7984)
Confidential ERC20 tokens with encrypted balances. All tokens use 6 decimals. Operator approvals via setOperator allow pools to transfer tokens.
How Swaps Work (Atomic)
Unlike traditional DEXes where everything is public, Lunarys uses obfuscated reserves and encrypted inputs for privacy-preserving execution in a single transaction.
flowchart LR
A[User] -->|1. Fetch obfuscated reserves| B[EPOOL]
B -->|2. Decrypt via gateway| C[FHE Gateway]
C -->|clear values + proof| A
A -->|3. Encrypt swap amount| D[FHE SDK]
D -->|encrypted handles| A
A -->|4. atomicSwapAForB| B
B -->|5. Validate proof & compute| B
B -->|6a. Success: transfer output| A
B -->|6b. Fail: refund input| A
B -->|7. Rotate factor| B
style A fill:#805ad5,color:#fff
style B fill:#2d3748,color:#fff
style C fill:#d69e2e,color:#000
style D fill:#4a5568,color:#fff
Step-by-Step Swap Flow
1. User fetches obfuscated reserves from pool
-> obfuscatedStates() returns (obfReserveA, obfReserveB, obfLPSupply)
2. User decrypts obfuscated reserves via the FHE gateway
-> Gets clear values + cryptographic proof
3. User encrypts swap amount client-side
-> Creates encrypted (amountIn, minAmountOut) via FHE SDK
4. User calls atomicSwapAForB(
amountInExt, minAmountOutExt, proofIn,
recipient,
decryptedORA, decryptedORB, reserveProof
)
5. Pool validates:
- reserveProof matches current obfuscated ciphertexts
- Pulls amountIn from user
6. Pool computes (all encrypted):
- effectiveIn = amountIn * (1 - swapFeeBps / 1,000,000)
- amountOut via 4-term Taylor approximation
- Checks: effectiveIn <= maxEffectiveIn (Taylor bound)
- Checks: amountOut >= minAmountOut (slippage)
7. If both checks pass:
- Transfer amountOut to recipient
- Add amountIn to reserves
If either fails:
- Refund amountIn to caller
8. Pool rotates obfuscation factor (fresh RNG)
-> New obfuscated reserves publicly available
Privacy Guarantees
| What remains encrypted | What is public |
|---|---|
| True reserve amounts | Pool exists (pair address) |
| Trade input/output amounts | Swap occurred (event emitted) |
| LP positions | Direction (A-to-B or B-to-A) |
| Price impact | Participant addresses |
Obfuscation Mechanism
True reserves are never revealed. Instead, the pool exposes obfuscated reserves:
obfuscatedReserveA = reserveA * factor
obfuscatedReserveB = reserveB * factor
obfuscatedLPSupply = totalSupply * factor
Where factor is a random value in range [1,000,000 to ~2,966,050] (~3x uncertainty). The same factor is used for all three values, so the price ratio is preserved but absolute magnitudes are hidden.
The factor is rotated on every state-changing operation (swap, liquidity add/remove) to prevent correlation attacks.
How Liquidity Works
Bootstrap (One-Time Initialization)
The pool owner calls bootstrap() once to seed initial liquidity:
bootstrap(amountAExt, amountBExt, inputProof)
-> Pulls encrypted amounts of both tokens
-> LP tokens minted = (amountA >> 1) + (amountB >> 1)
-> Sets reservesInitialized = true
Contribute (Any User, After Bootstrap)
contributeLiquidity(
amountAExt, amountBExt, amountProof,
decryptedORA, decryptedORB, decryptedOL, OProof
)
-> Validates obfuscated state proof
-> Pulls both token amounts
-> Mints LP = min(amountA * obfTotalSupply / obfReserveA,
amountB * obfTotalSupply / obfReserveB)
(factor cancels in the ratio)
Remove Liquidity
removeLiquidity(
sharesToRemoveExt, sharesProof,
decryptedORA, decryptedORB, decryptedOL, OProof
)
-> Proportional withdrawal: grossX = shares * obfReserveX / obfTotalSupply
-> 0.05% withdrawal fee (prevents JIT attacks)
-> Net amounts transferred to user
-> LP tokens burned
Fee Model (Uniswap V2 Style)
| Fee Type | Amount | Description |
|---|---|---|
| Swap fee | Configurable (e.g. 0.3% = 3,000 BPS) | Deducted from input amount |
| Withdrawal fee | 0.05% (500 BPS) | Applied on liquidity removal |
| BPS denominator | 1,000,000 | All fees expressed in this scale |
- Fees stay implicitly in reserves (no fee accumulator or growth tracking)
- LPs realize accumulated fees when removing liquidity
- No public fee tracking that could leak trading activity
Deployed Contracts (Sepolia)
See Deployed Addresses for complete details.
| Contract | Address |
|---|---|
| EPoolFactory | 0xa830205E7b5e1b75a6e6EED34207C7FEc7aBEEAE |
| UniversalRouter | 0x6ef96225151e207cA92cF923C4edD0aC3235842c |
| Airdrop | 0x5509c8b9995EfEB21A9B6a25995040fF75b960b9 |
Tokens (All 6 Decimals)
| Symbol | Address |
|---|---|
| eBTC | 0xF7De724CC4ef8c881a5aEbae274A3c05dEF7aa40 |
| eETH | 0x5112d35acc479EA1a9B406d1c7B36b37C2A196d7 |
| eUSD | 0x531385Bf03cF41CbfAfc424C67e6A6e9f5B6cad7 |
| LUN | 0x7F1117204141c6044579C965Ce1fcca1e51E74f2 |
| eUSDT | 0x8EFe087A2895D942574DB6764A884612fCe4EBE1 |
| eBNB | 0x25aE630fBdB2EDc6D0053cf93a5e1D9739cBb528 |