Architecture
Overview
Fynd is a solver built on Tycho that finds optimal swap routes across DeFi protocols. It is organized as a multi-crate Rust workspace:
fynd-core- Pure solving logic with no HTTP dependenciesfynd-rpc- HTTP RPC server libraryfynd- CLI binary that runs the complete routing service
This modular architecture allows users to:
Use just the routing algorithms (
fynd-core) in their own applicationsBuild custom HTTP servers with their own middleware (
fynd-rpc)Run the complete solver as a standalone service (
fyndbinary)
Design Decisions
Concurrency Model: Hybrid async/threaded -- I/O on tokio, route finding on dedicated OS threads
Data Sharing:
Arc<RwLock<>>with write-preferring lock for MarketState (single writer, many readers)Path-Finding: Pluggable
Algorithmtrait with associated graph types, allowing each algorithm to use its preferred graph representationGraph Management:
GraphManagertrait with incremental updates from market events; built-in implementation usespetgraph::StableDiGraphMulti-Solver Competition: Multiple worker pools with different configurations compete per request; WorkerPoolRouter selects the best result
Output Format: Structured
Quoteobjects (routes, amounts, gas estimates) with optional encoded transactionDerived Data Pipeline: Pre-computed spot prices, component depths, and token gas prices fed to algorithms via a separate computation framework
Observability: Prometheus metrics on port 9898, structured tracing, health endpoint
Architecture Diagram
Components
1. API Layer (RouterApi)
Crate: fynd-rpc Location: fynd-rpc/src/api/
Actix Web HTTP handlers. Validates requests, delegates to WorkerPoolRouter, returns JSON responses.
Endpoints:
POST /v1/quote-- Submit quote requestsGET /v1/health-- Health check (data freshness, derived data readiness, gas-price staleness, solver pool count)GET /v1/info-- Instance info (chain ID, router address, Permit2 address)GET /metrics-- Prometheus metrics (separate server, port 9898 by default)
2. WorkerPoolRouter
Crate: fynd-core Location: fynd-core/src/worker_pool_router/
Orchestrates quote requests across multiple worker pools:
Allocates the worker pools that serve each order, before dispatch. Each order is classified (
OrderClass) and matched against each worker pool's configuration (SolverPoolHandle::serves) — today on the caller's access to exclusive liquidity, so a request without access is never dispatched to an exclusive-access worker poolFans out each order to its allocated worker pools in parallel
Manages per-request timeouts with optional early return
Refines gas estimates using
estimate_gas_usage(tycho-execution) before cross-pool ranking — algorithms use a fast naive estimate internally; this step applies a more accurate one that accounts for token transfers and protocol overheadSelects the best solution by refined
amount_out_net_gasOptionally encodes winning solutions into on-chain transactions (when
EncodingOptionsare provided)Reports failures with error types and metrics
3. Worker Pool
Crate: fynd-core Location: fynd-core/src/worker_pool/
Manages dedicated OS threads for CPU-bound route finding. Each pool has:
A name and algorithm assignment
A bounded
TaskQueue(viaasync_channel)N
SolverWorkerinstances on separate threads
Pools can use either a built-in algorithm by name (e.g., "most_liquid") or a custom Algorithm implementation via WorkerPoolBuilder::with_algorithm. Pools are configured via worker_pools.toml for built-in algorithms, or programmatically via the builder for custom algorithms. Multiple pools can use the same algorithm with different parameters (e.g., fast 2-hop vs deep 3-hop).
4. SolverWorker
Crate: fynd-core Location: fynd-core/src/worker_pool/worker.rs
Each worker:
Initializes a graph from market topology
Runs a prioritized
select!loop: shutdown > market events > derived events > solve tasksMaintains a
ReadinessTrackerfor derived data requirementsCalls the algorithm's
find_best_routewith aSolveRequest: the local graph, the shared market data, the order, and the pools and tokens the request excluded
5. Algorithm Trait
Crate: fynd-core Location: fynd-core/src/algorithm/
Pluggable interface for route-finding algorithms:
Specifies preferred graph type and graph manager via associated types
Stateless: receives graph as parameter
Declares derived data requirements (fresh vs stale)
Built-in algorithms:
MostLiquidAlgorithm-- BFS path enumeration, depth-weighted scoring, ProtocolSim simulation, gas-adjusted ranking.BellmanFordAlgorithm-- Bellman-Ford relaxation with gas-aware edge weights, configurable viaAlgorithmConfig.gas_aware.PathFrankWolfeAlgorithm-- Frank-Wolfe path-based optimization for multi-hop routing.WaterFillAlgorithm-- portfolio split router: exhaustive plus bounded amount-aware candidate discovery, then the best net of a single path, a coarse disjoint floor, a refined 256-chunk disjoint split, and a shared-component fill-and-spill; gas-aware net ranking when derived token gas prices are available.
6. Encoding
Crate: fynd-core Location: fynd-core/src/encoding/
Encodes solved routes into on-chain transactions. When EncodingOptions are provided, delegates to TychoEncoder to produce ABI-encoded calldata for the appropriate router function (singleSwap, sequentialSwap, splitSwap, and their Permit2/Vault variants). Supports optional ClientFeeParams for client fee configuration.
7. Graph Module
Crate: fynd-core Location: fynd-core/src/graph/
Graph management infrastructure:
GraphManagertrait: initialize + incremental updates from eventsPetgraphStableDiGraphManager: Implementation usingpetgraph::StableDiGraphEdgeWeightUpdaterWithDerived: Updates edge weights from derived data (component depths)Pathtype: Sequence of edges for route representation
8. MarketState / MarketData
Crate: fynd-core Location: fynd-core/src/feed/market_data.rs
MarketState is the single source of truth for all market state. Contains components, simulation states, tokens, gas prices, sync status, and block info. Protected by Arc<RwLock<>> (write-preferring). MarketData is the cheap-to-clone shared handle used to access it — call read() for a base view or read_labeled(label) for an overlay-aware view.
Provides extract_subset_with_overlay() for creating filtered snapshots that algorithms can use without holding the main lock.
9. TychoFeed
Crate: fynd-core Location: fynd-core/src/feed/tycho_feed.rs
Background task that connects to Tycho's WebSocket API, processes component/state updates, updates MarketState (via MarketData), and broadcasts MarketEvents. Applies TVL filtering with hysteresis (components are added at min_tvl and removed at min_tvl / tvl_buffer_ratio), token recency filtering (traded_n_days_ago), blocklisting, and token quality filtering.
10. Derived Data System
Crate: fynd-core Location: fynd-core/src/derived/
Pre-computes analytics from raw market data:
SpotPriceComputation: Spot prices for all component pairsComponentDepthComputation: Liquidity depth at configured slippageTokenGasPriceComputation: Token prices relative to gas token
Computations run in dependency order. Workers use ReadinessTracker to wait for required data before solving.
11. Gas Price Fetcher
Crate: fynd-core Location: fynd-core/src/feed/gas.rs
Background worker that fetches gas prices from the RPC node. Signaled by TychoFeed after each block update.
12. Builder
Crate: fynd-rpc Location: fynd-rpc/src/builder.rs
FyndRPCBuilder assembles the entire system: creates feed, worker pools, computation manager, worker pool router, and HTTP server. FyndRPC runs the system and handles graceful shutdown.
13. CLI Binary
Crate: fynd Location: src/main.rs, src/cli.rs and src/serve.rs
Command-line application that parses CLI arguments, sets up observability (tracing, metrics), and uses FyndRPCBuilder to run the complete routing service.
main.rs only dispatches; the work lives in serve.rs as a library, so a binary embedding Fynd can reuse this command line with its own algorithms registered rather than reimplementing it. run_solver owns the runtime, the tracing subscriber and the metrics recorder; serve is the entry point for a caller that already owns those.
Data Flow
Quote Request Flow
Market Update Flow
Threading Model
Communication channels:
HTTP -> WorkerPoolRouter: direct call (same async runtime)
WorkerPoolRouter -> Workers:
async_channelper pool (bounded, backpressure)Workers -> WorkerPoolRouter:
oneshotchannel (single response)TychoFeed -> Workers:
broadcastchannel (MarketEvent)ComputationManager -> Workers:
broadcastchannel (DerivedDataEvent)All -> MarketState (via MarketData):
Arc<RwLock<>>(read-heavy)
Last updated