System Architecture
This document describes the technical architecture of Lunarys Protocol.
Contract Architecture
┌─────────────────┐
│ PoolFactory │
│ │
│ - Creates pools│
│ - Registry │
└────────┬────────┘
│ deploys
▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ FHERC20 A │◄───│ PrivacyPool │───►│ FHERC20 B │
│ │ │ │ │ │
│ - encBalances │ │ - reserveA │ │ - encBalances │
│ - encTransfer │ │ - reserveB │ │ - encTransfer │
└─────────────────┘ │ - swapFee │ └─────────────────┘
│ - feeVaults │
└────────┬────────┘
│ mints
▼
┌─────────────────┐
│ PositionNFT │
│ │
│ - positions │
│ - liquidity │
└─────────────────┘
Core Components
PrivacyPool
The main AMM contract that handles:
- Liquidity Management: Bootstrap, contribute, and remove liquidity
- Swaps: Execute encrypted token swaps
- Fee Accounting: Track and distribute fees to LPs and protocol
- Reserve Tracking: Maintain encrypted reserve balances with checkpoints
Key state variables:
euint64 private reserveA; // Encrypted reserve of token A
euint64 private reserveB; // Encrypted reserve of token B
euint64 private feeVaultA; // LP fees accumulated in token A
euint64 private feeVaultB; // LP fees accumulated in token B
euint64 private totalLiquidity; // Total liquidity shares
euint128 private feeGrowthA_RAY; // Cumulative fee growth for A
euint128 private feeGrowthB_RAY; // Cumulative fee growth for B
PoolFactory
Factory contract for deploying pools:
- Creates PrivacyPool instances for token pairs
- Deploys and configures PositionNFT for each pool
- Maintains registry of all pools
- Enforces unique pool per token pair
Key functions:
function createPool(
IFHERC20 tokenA,
IFHERC20 tokenB,
uint24 swapFee
) external returns (address pool);
function getPool(
address tokenA,
address tokenB
) external view returns (address pool);
PositionNFT
ERC721 token representing liquidity positions:
- Minted when liquidity is added
- Burned when liquidity is removed
- Stores encrypted position data
- Tracks fee growth entry points
Position structure:
struct Position {
address token0;
address token1;
int24 tickLower;
int24 tickUpper;
euint64 liquidity;
euint64 token0Amount;
euint64 token1Amount;
bool isConfidential;
uint256 createdAt;
uint256 lastUpdated;
}
FHERC20
Encrypted ERC20 implementation:
- Standard ERC20 for plaintext balances
- FHERC20 interface for encrypted balances
- Wrap/unwrap between plaintext and encrypted
- Permission-based decryption
Data Flow
Swap Flow
1. User submits encrypted amount (InEuint64)
2. Pool pulls tokens from user (encTransferFrom)
3. Fee is calculated and split (LP + Protocol)
4. AMM formula computes output amount
5. Reserves are updated
6. Output tokens sent to recipient (encTransfer)
7. Events emitted with encrypted handles
Add Liquidity Flow
1. User submits encrypted amounts for both tokens
2. Pool pulls tokens from user
3. Reserves are updated
4. PositionNFT is minted with encrypted amounts
5. Fee growth entry point is recorded
6. Total liquidity is incremented
Remove Liquidity Flow
1. User submits position NFT ID
2. Pool verifies ownership
3. Position amounts are read
4. Tokens are transferred back to user
5. PositionNFT is burned
6. Reserves and liquidity are decremented
FHE Operations
Encrypted Types
| Type | Description | Use Case |
|---|---|---|
euint32 | Encrypted uint32 | Fee percentages |
euint64 | Encrypted uint64 | Token amounts, reserves |
euint128 | Encrypted uint128 | Fee growth accumulators |
ebool | Encrypted boolean | Conditional logic |
Key FHE Functions
FHE.asEuint64(value) // Encrypt plaintext value
FHE.add(a, b) // Encrypted addition
FHE.sub(a, b) // Encrypted subtraction
FHE.mul(a, b) // Encrypted multiplication
FHE.div(a, b) // Encrypted division
FHE.select(cond, a, b) // Conditional select
FHE.allow(value, address) // Grant decryption permission
FHE.allowThis(value) // Allow contract to use value
FHE.decrypt(value) // Request async decryption
Permission System
FHE values require explicit permissions for access:
- Contract Permission:
FHE.allowThis(value)- Allows the contract to use the value in computations - User Permission:
FHE.allow(value, address)- Allows a specific address to decrypt the value - Transfer Permission: When transferring encrypted values, both sender and recipient need permissions
Hook System
PrivacyPool supports optional hooks for extensibility:
interface IPrivacyPoolSwapHook {
function beforeSwap(
address caller,
bool aForB,
uint256 amountInHandle,
bytes calldata hookData,
bytes calldata context
) external;
function afterSwap(
address caller,
address recipient,
bool aForB,
uint256 amountInHandle,
uint256 amountOutHandle,
bytes calldata hookData,
bytes calldata context
) external;
}
Hooks can be used for:
- Custom fee logic
- Access control
- Analytics
- External integrations
Gas Considerations
FHE operations are computationally expensive:
| Operation | Approximate Gas Cost |
|---|---|
| Encrypt value | ~50,000 |
| Add/Sub | ~30,000 |
| Multiply | ~100,000 |
| Division | ~150,000 |
| Decrypt request | ~100,000 |
Swaps typically cost 500,000 - 1,000,000 gas depending on complexity.