Request for Quote Protocols

Request for Quote (RFQ) protocols work differently from on-chain protocols. Instead of reading pool data from the chain, they fetch prices from off-chain market makers via WebSocket or API.

You ask for a quote for a specific trade size, and they return a price. Quotes can be:

  • Indicative — estimated prices used for simulation.

  • Binding — firm prices, valid for a short time, used at execution.

Tycho supports streaming, simulating, and executing RFQ quotes as part of multi-protocol swaps.

Quickstart

The RFQ quickstart is similar to the other protocols quickstart.

See the code here. As of now, Bebop is the only supported provider.

You need to set up the API credentials of the desired RFQs to access live pricing data and quoting:

export BEBOP_USER=<your-bebop-ws-username>
export BEBOP_KEY=<your-bebop-ws-key>

Then run the example:

cargo run --release --example rfq_quickstart

You’ll need to request credentials directly from RFQ providers.

What it does

The quickstart:

  • Connects to the RFQ stream and fetches live price updates.

  • Simulates the best available amount out for a given pair (default: 10 USDC → WETH on mainnet).

  • Encodes the swap and prepares calldata to execute it via the Tycho Router.

If you want to see results for a different token, amount, or chain, you can set additional flags:

cargo run --release --example rfq_quickstart -- --sell-token "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" --buy-token "0x4200000000000000000000000000000000000006" --sell-amount 10 --chain "base" --swapper-pk $PK

This example would seek the best swap for 10 USDC -> WETH on Base.

Set up

You’ll need to configure:

  • Tycho URL (by default "tycho-beta.propellerheads.xyz")

  • Tycho API key (by default, the test key is sampletoken)

  • RFQ API keys (currenlty only the Bebop user and key are needed. Have a look at src/rfq/constants.rs to see the authentication variables that are expected)

To get token information from Tycho Indexer RPC please use load_all_tokens.

RFQClient

Each RFQ protocol will have its own client. The client can stream live prices updates and request binding quotes.

Example setup for Bebop:

let bebop_client = BebopClientBuilder::new(chain, bebop_ws_user, bebop_ws_key)
    .tokens(rfq_tokens)
    .tvl_threshold(cli.tvl_threshold)
    .build()
    .expect("Failed to create RFQ clients");

TVL threshold is specified in USD, as most RFQ quotes are USD-denominated. This setting filters out token pairs with low liquidity on the RFQ side, helping avoid thin or illiquid quotes.

Note: Some RFQ providers may support tokens that Tycho does not. Because execution happens through the Tycho Router, it’s important to ensure that all tokens used in RFQ quotes are also supported by Tycho.

Stream: Real-Time Price Updates

The RFQStreamBuilder handles registration of multiple RFQ clients and merges their message streams. It merges updates from one or more RFQ clients and decodes them into Update messages:

let rfq_stream_builder = RFQStreamBuilder::new()
    .add_client::<BebopState>("bebop", Box::new(bebop_client))
    .set_tokens(all_tokens.clone())
    .await;
  • Use add_client() for each RFQ provider.

  • Streams that return errors are removed automatically.

RFQ streams are timestamped, not block-based. Each update provides the full known state from the provider at that moment (not just deltas). The removed_pairs field indicates any pairs that disappeared since the last update. The new_pairs field contains all the currently available pairs.

Simulation

You can simulate a swap against an RFQ state using:

state.get_amount_out(amount_in, &sell_token, &buy_token)

This returns an indicative output amount, which you can use to decide if this swap is worth including.

Encoding

After choosing the best swap, you can use Tycho Execution to encode it. This is very similar to the encoding done in the general quickstart.

Create a solution object

The key parameter is minimum amount out, which protects against slippage and MEV. The quickstart applies 0.25% slippage tolerance.

Build the Swap and Solution:

let swap =
    SwapBuilder::new(component, sell_token.address.clone(), buy_token.address.clone())
        .protocol_state(state)
        .estimated_amount_in(sell_amount.clone())
        .build();

let solution = Solution {
    sender: user_address.clone(),
    receiver: user_address,
    given_token: sell_token.address,
    given_amount: sell_amount,
    checked_token: buy_token.address,
    exact_out: false,
    checked_amount: min_amount_out,
    swaps: vec![simple_swap],
    ..Default::default()
}

When working with RFQs, two fields are required in Swap:

  • protocol_state: This is needed to enable the runtime generation of a binding quote at encoding time—for example:

    state.request_binding_quote(&GetAmountOutParams { ... }).await
  • estimated_amount_in : This represents the estimaed input amount for the quote request. It’s especially important when the swap path is complex (e.g., involving multiple hops), where the actual input amount may differ slightly because of slippage. We recommend setting estimated_amount_in a bit higher than your expected value. Many RFQs enforce that execution can only occur for amounts less than or equal to the quoted base amount—so setting it conservatively helps avoid dropping funds. If the actual required input exceeds your estimate, any leftover tokens will remain in the Tycho Router.

This mechanism also makes RFQs composable with other on-chain swaps. That enables hybrid routing strategies, such as a path like Uniswap → RFQ → Curve, seamlessly combining RFQ-based and traditional on-chain routes.

Encode solution

let encoder = TychoRouterEncoderBuilder::new()
    .chain(chain)
    .user_transfer_type(UserTransferType::TransferFromPermit2)
    .build()
    .expect("Failed to build encoder");

let encoded_solution = encoder
    .encode_solutions(vec![solution.clone()])
    .expect("Failed to encode router calldata")[0]

Encode full method calldata

You need to build the full calldata for the router. Tycho handles the swap encoding, but you control the full input to the router method. This quickstart provides helper functions (encode_tycho_router_call and sign_permit)

Use it as follows:

let tx = encode_tycho_router_call(
    named_chain.into(),
    encoded_solution.clone(),
    &solution,
    chain.native_token().address,
    signer.clone(),
)
.expect("Failed to encode router call");

Execution

This step allows you to test or perform real transactions based on the best available swap options. For this step, you need to pass your wallet's private key in the run command. Handle it securely and never expose it publicly.

cargo run --release --example quickstart -- --swapper-pk $PK

Once the best swap is found you can:

  1. Simulate the swap: Tests the swap without executing it on-chain. It simulates an approval (for permit2) and a swap transaction on the node. If the status is false, the simulation has failed. You can print the full simulation output for detailed failure information.

  2. Execute the swap: Performs the swap on-chain using your real funds. The process performs an approval (for permit2) and a swap transaction. You'll receive transaction hashes and statuses. After a successful execution, the program will exit. If the transaction fails, the program continues to stream new price updates.

  3. Skip this swap: Ignores this swap. Then the program resumes listening for price updates.

Last updated