fynd 0.43.0

High-performance DeFi route-finding engine — embeddable library and CLI
docs.rs failed to build fynd-0.43.0
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Visit the last successful build: fynd-0.0.1

Fynd

A high-performance DeFi route-finding engine built on Tycho. Finds optimal swap routes across multiple DeFi protocols in real-time.

[!CAUTION] Alpha Software — Unaudited Contracts

Fynd's smart contracts (TychoRouter V3, Vault, Executors) are still undergoing a security audit. Funds stored in the router (including vault deposits) may be lost. Use at your own discretion.

Features

  • Multi-protocol routing - Routes through your favorite on-chain liquidity protocol, like Uniswap, Balancer, Curve, RFQ protocols, or any other protocol supported by Tycho.
  • Real-time market data - Tycho Stream keeps all liquidity states synchronized every block
  • Multi-algorithm competition - Multiple solver pools run different algorithm configurations in parallel; the best result wins
  • Gas-aware ranking - Solutions are ranked by net output after gas costs, not just raw output
  • Sub-100ms solves - Dedicated OS threads for CPU-bound route finding, separate from the async I/O runtime
  • Production-ready - Prometheus metrics, structured logging, health endpoints, graceful shutdown
  • Extensible - Implement the Algorithm trait to add new routing strategies with zero framework changes
  • Modular - Use just the core solving logic, or build a custom HTTP server with your own middleware

Architecture

Fynd is organized into three crates:

  • fynd-core - Pure solving logic with no HTTP dependencies. Use this if you want to integrate Fynd's routing algorithms into your own application.
  • fynd-rpc - HTTP RPC server builder with customizable middleware. Use this to build a custom HTTP server with your own configuration.
  • fynd - Complete CLI application that runs an HTTP RPC server. Use this to run Fynd as a standalone service.

Prerequisites

Run the Solver

# Clone and build
git clone https://github.com/propeller-heads/fynd.git
cd fynd
cargo build --release

# Set required environment variables
export TYCHO_API_KEY=your-api-key
export RUST_LOG=fynd=info

# Run
cargo run --release serve

Note: --rpc-url defaults to https://eth.llamarpc.com. For production, provide a dedicated endpoint:

cargo run --release serve -- \
  --tycho-url tycho-fynd-ethereum.propellerheads.xyz \
  --rpc-url https://your-rpc-provider.com/v1/your_key \
  --protocols uniswap_v2,uniswap_v3

The solver starts on http://localhost:3000 by default.

Including RFQ Protocols

You can include RFQ (Request-for-Quote) protocols alongside on-chain protocols:

cargo run --release serve \
  --protocols all_onchain,rfq:bebop

Limitations:

  • RFQ protocols cannot run alone — at least one on-chain protocol is required.

Environment variables:

  • RFQ protocols typically require API keys, which are passed via environment variables. Check the RFQ protocol docs for the specific variables each protocol needs.

Run on a specific chain

You can run on any chain supported by Tycho (see Tycho Hosted endpoint)

export RPC_URL=<RPC_FOR_TARGET_CHAIN>
cargo run --release serve --chain unichain

Request a Quote

curl -X POST http://localhost:3000/v1/quote \
  -H "Content-Type: application/json" \
  -d '{
    "orders": [
      {
        "token_in": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
        "token_out": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
        "amount": "1000000000000000000",
        "side": "sell",
        "sender": "0x0000000000000000000000000000000000000001"
      }
    ],
    "options": {
      "timeout_ms": 5000
    }
  }'

Check Health

curl http://localhost:3000/v1/health

API Reference

POST /v1/quote

Submit one or more swap orders and receive optimal routes.

Request:

Field Type Required Description
orders[].token_in address Yes Token to sell
orders[].token_out address Yes Token to buy
orders[].amount string Yes Amount in token units (integer string)
orders[].side string Yes "sell" (exact input)
orders[].sender address Yes Sender address
orders[].receiver address No Receiver (defaults to sender)
options.timeout_ms integer No Solve timeout in ms (default: 100)
options.min_responses integer No Early return threshold (default: 0, wait for all)
options.max_gas string No Max gas filter (no limit if omitted)
options.encoding_options.slippage float No Slippage tolerance for encoded transactions (e.g., 0.01 for 1%). No encoding if encoding_options is omitted
options.encoding_options.transfer_type string No Input token transfer method: transfer_from (default) or transfer_from_permit2
options.encoding_options.permit object No Permit2 single-token authorization. Required when using transfer_from_permit2
options.encoding_options.permit2_signature string No Permit2 signature (hex-encoded). Required when permit is set
options.encoding_options.client_fee_params object No Client fee configuration. See Client Fees

Response:

{
  "orders": [
    {
      "order_id": "uuid",
      "status": "success",
      "route": {
        "swaps": [
          {
            "component_id": "0x...",
            "protocol": "uniswap_v3",
            "token_in": "0x...",
            "token_out": "0x...",
            "amount_in": "1000000000000000000",
            "amount_out": "3200000000",
            "gas_estimate": "150000",
            "split": "0.5"
          }
        ]
      },
      "amount_in": "1000000000000000000",
      "amount_out": "3200000000",
      "gas_estimate": "150000",
      "amount_out_net_gas": "3199500000",
      "gas_price": "25000000000",
      "block": {
        "number": 19000000,
        "hash": "0x...",
        "timestamp": 1700000000
      },
      "transaction": {
        "to": "0x...",
        "value": "0",
        "data": "0x..."
      },
      "fee_breakdown": {
        "router_fee": "320000",
        "client_fee": "0",
        "max_slippage": "31996800",
        "min_amount_received": "3167683200"
      }
    }
  ],
  "total_gas_estimate": "150000",
  "solve_time_ms": 45
}

GET /v1/health

Returns service health status. HTTP 200 if healthy, 503 if unhealthy.

The service is healthy when market data is fresh (< 60s old), derived data has been computed at least once, and gas price is not stale (when --gas-price-stale-threshold-secs is configured). The derived_data_ready field indicates overall readiness, not per-block freshness — algorithms that require fresh derived data will wait for recomputation before solving.

Configuration

CLI / Environment Variables

Flag Env Var Default Description
--rpc-url RPC_URL https://eth.llamarpc.com Ethereum RPC endpoint for the target chain
--tycho-url TYCHO_URL (chain-specific) Tycho URL (e.g. tycho-fynd-ethereum.propellerheads.xyz)
--tycho-api-key TYCHO_API_KEY - Tycho API key
--chain - Ethereum Target chain
-p, --protocols - (all available) Protocols to index (comma-separated). Auto-fetched from Tycho RPC if omitted.
--http-host HTTP_HOST 0.0.0.0 HTTP bind address
--http-port HTTP_PORT 3000 API port
--min-tvl - 10.0 Minimum pool TVL in native token
--worker-router-timeout-ms - 100 Default solve timeout
-w, --worker-pools-config WORKER_POOLS_CONFIG worker_pools.toml Worker pools config
--blacklist-config BLACKLIST_CONFIG blacklist.toml Blacklist config
--gas-price-stale-threshold-secs - (disabled) Health returns 503 when gas price exceeds this age

See --help for the full list.

Find the list of all available protocols on Tycho here

Worker Pools (worker_pools.toml)

Configure solver pools with different algorithms and parameters:

[pools.fast_2hop]
algorithm = "most_liquid"
num_workers = 5
max_hops = 2
timeout_ms = 100

[pools.deep_3hop]
algorithm = "most_liquid"
num_workers = 3
min_hops = 2
max_hops = 3
timeout_ms = 5000

A Worker Pool runs a configurable number of Worker threads, all using the same algorithm and pulling tasks from a shared queue. Each worker handles one order at a time — so a pool with 5 workers can solve up to 5 orders concurrently.

Multiple pools run in parallel, each producing its own solution per order. The system then picks the best result across pools within the timeout.

Example: Given the config above and 3 incoming orders:

  • fast_2hop assigns 1 worker per order (3/5 workers busy)
  • deep_3hop assigns 1 worker per order (3/3 workers busy)

Each order gets 2 candidate solutions — one from each pool — and the best is selected.

Blacklist (blacklist.toml)

Exclude specific pools from routing:

[blacklist]
components = [
    "0x86d257cdb7bc9c0df10e84c8709697f92770b335",
]

Observability

  • Metrics: Prometheus endpoint at http://localhost:9898/metrics
  • Logging: Structured logging via RUST_LOG (e.g., RUST_LOG=info,fynd=debug)
  • Health: GET /v1/health returns data freshness and pool count

Extensibility

Using a Custom Algorithm

Implement the Algorithm trait and plug it into a WorkerPoolBuilder via with_algorithm() — no changes to fynd-core required:

let (pool, task_handle) = WorkerPoolBuilder::new()
    .name("my-solver")
    .with_algorithm("my_algo", |config| MyAlgorithm::new(config))
    .algorithm_config(algorithm_config)
    .num_workers(4)
    .build(market_data, derived_data, event_rx, derived_event_rx)?;

See the custom_algorithm example for a full walkthrough.