RaumFi v3 Architecture
Architecture guide for RaumFi v3 concentrated liquidity on Stellar Soroban, including factory, pool, router, tick storage, NFT positions, quoter, governance, and TWAP oracle design.
Contract workspace
| Contract | Role |
|---|---|
factory | Deploys pool instances at a deterministic address per (token0, token1, fee), owns the fee-tier registry, and gates admin actions behind a 24-hour timelocked transfer. |
pool | Core CLMM logic for one token pair and fee tier - tick-indexed liquidity, swap execution, fee accounting, and a TWAP oracle. The largest contract in the workspace. |
router | User-facing entry point: swap_exact_input, swap_exact_output, and an add_liquidity convenience wrapper around the position manager. |
position_manager | Mints an NFT per LP position (via stellar_tokens::non_fungible, OpenZeppelin-style enumerable), and is the only address authorized to call a pool's add_liquidity. |
governance | Token-weighted proposal and voting flow - propose, vote, execute. |
quoter | Simulates a swap's output against live pool/tick state without executing a trade, for frontend price previews. |
tick_lens | Read-only pool and tick-data access, including paginated tick scans, for indexers and UIs. |
token | A local SEP-41 token contract used for tests and, on testnet, standing in for XLM/USDC. |
Pool state and fee tiers
Each pool's core state (PoolState, defined in pool/src/types.rs) tracks its current_tick, aggregate liquidity, and sqrt_price_x96 - stored as a (u128, u128) high/low pair, since Soroban's SDK has no native u256. Three fee tiers are registered by default at factory.initialize: 0.05% (10-tick spacing), 0.3% (60-tick spacing), and 1% (200-tick spacing), each independently enable-able by the admin via enable_fee_tier. Per-tick data (TickData: gross/net liquidity, fee growth outside the tick, TWAP accumulators) is stored under individual TickData(tick_index) persistent-storage keys rather than a bitmap, trading some storage density for simpler Soroban key-value access.
Positions are NFTs, not pool-internal structs
Unlike the pool-internal position mapping in Uniswap V3, position state here (PositionData: pool, tick range, liquidity, accrued fees, fee-growth checkpoints) is owned entirely by the position_manager contract and represented as an NFT - mint() sequentially assigns a token_id, calls the target pool's add_liquidity (auth-gated so only the position manager can call it), and stores the resulting position under that token ID. Transferring the NFT transfers the position.
Swap execution and price limits
The router's swap_exact_input / swap_exact_output currently support single-hop paths only (path.len() != 1 panics) - multi-hop routing is present in the PathElement type but not yet wired through to chained pool calls. Each swap resolves a sqrt_price_limit_x96 bound: if the caller passes (0, 0), the router defaults to the protocol's global min/max sqrt-price constants; otherwise it validates the caller's limit sits within those bounds and on the correct side of the pool's current price for the trade direction.
TWAP oracle
Pools maintain a circular buffer of Observation entries (timestamp, cumulative tick, cumulative liquidity) up to MAX_ORACLE_CARDINALITY (1024), written on every swap. observe(seconds_ago) interpolates between the two bracketing observations to reconstruct a tick/liquidity cumulative at an arbitrary past timestamp - the same mechanism Uniswap V3 uses for its geometric-mean TWAP.
Frontend
A Next.js app with routes for /dashboard, /swap, /pools, /governance, and /terminal (plus /about, /contact, and /privacy). Each contract has a matching generated TypeScript client under frontend/src/contracts/, regenerated by generate_bindings.sh whenever a contract is redeployed. Wallet connectivity goes through StellarContext, which wraps Freighter, Albedo, Lobstr, Ledger (@ledgerhq/hw-app-str), and WalletConnect.