> ## Documentation Index
> Fetch the complete documentation index at: https://docs.houdiniswap.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Private Routes with an On-chain Hop

> Reach more tokens privately. Collect a second refund address before you turn it on.

## Overview

Some tokens have no direct private route. For these tokens we can still offer a private swap: the swap runs privately to a hub token (for example USDC), then an on-chain hop takes the hub token to the token your user wants.

> [!NOTE]
>
> **Coverage**: With this feature, almost any token on Solana and on high-volume EVM chains, such as Ethereum, Base, Robinhood, Arbitrum, BNB Chain, Polygon, Optimism, Avalanche and more, can be the output of a private swap.

### How the route works

The on-chain hop is an **extra leg added after a normal private swap**. The private part is unchanged:

```mermaid theme={null}
graph LR
    A[User Deposit] --> B[Private swap]
    B --> C[Hub token, e.g. USDC]
    C --> D[On-chain hop]
    D --> E[Destination Wallet]
    style A fill:#e1f5ff,color:#000
    style E fill:#e1f5ff,color:#000
    style B fill:#fff3cd,color:#000
    style C fill:#fff3cd,color:#000
    style D fill:#d4edda,color:#000
```

1. **Private leg**: the deposit goes through the same multi-hop private routing as any [private swap](/developer-hub/swap-flows/private-swap.md), and comes out as the hub token. This is what breaks the link between your user's deposit and the output.
2. **On-chain hop**: the hub token is swapped on-chain into the destination token and sent to `addressTo`.

The swap stays private. The on-chain hop starts only after the private leg has finished, so the user's deposit is never linked to it.

This route needs one extra field when you create the exchange: `intermediateRefundAddress`.

> [!WARNING]
>
> **Add support for `intermediateRefundAddress` before you turn the feature on.** If you turn it on first, your users will get quotes that you cannot turn into orders.

## How it fits in

This is an **option on the [Private Swap](/developer-hub/swap-flows/private-swap.md) flow**, not a separate flow. Fetching tokens, sending the deposit and polling status are unchanged. Only these things differ:

| Stage | What changes |
| - | - |
| Partner settings | The feature must be turned on for your account. See [Turn it on](#turn-it-on) |
| `GET /quotes` | Some private quotes now reach tokens with no direct private route. They carry three extra fields. See [Spot the route in a quote](#spot-the-route-in-a-quote) |
| `POST /exchanges` | Send `intermediateRefundAddress` for those quotes |

## When you get this route

All of these must be true:

* The feature is turned on for your account.
* The quote is private (`types=private`).
* The quote is floating rate, not [fixed](/developer-hub/swap-flows/fixed-rate-swap.md).
* The amount is the send amount (`amountType=send`), not the receive amount.
* No direct private route exists to the destination token.

When a token has a direct route, you get the normal private quote, as before.

## Turn it on

To turn on **Private routes with on-chain hop** for your account, contact your Houdini representative and we will turn it on for you.

To check the setting, call [Get partner profile](/api-reference/partner-account/get-partner-profile.md) (`GET /me`) and read `privateOnchainLegEnabled`.

## Limits

This route has its own amount limits, and they are lower than for a normal private swap.

When the destination token can only be reached through this route, an amount outside the limits returns an error instead of an empty `quotes` array:

* `422 QUOTE_OVER_LIMIT` when the amount is too high
* `422 AMOUNT_TOO_LOW` when the amount is too low

The error message gives the allowed range. The same limits are checked again when you create the exchange.

## Spot the route in a quote

A quote for this route has these extra fields:

| Field | Type | Meaning |
| - | - | - |
| `requiresIntermediateRefundAddress` | boolean | `true`. You must send `intermediateRefundAddress` when you create the exchange |
| `intermediateRefundChain` | string | Chain the address must be on, for example `base`, `ethereum` or `solana` |
| `intermediateRefundToken` | string | Token the address would get back, for example `USDC` |

Check `requiresIntermediateRefundAddress` on every private quote. When it is `true`, ask your user for a wallet on `intermediateRefundChain` before they confirm the swap.

## Create the exchange

Send the quote's `quoteId` as usual, plus `intermediateRefundAddress`:

```json theme={null}
{
  "quoteId": "6689b73ec90e45f3b3e51566",
  "addressTo": "<destination address>",
  "refundAddress": "<refund address on the source chain>",
  "intermediateRefundAddress": "0x1111111111111111111111111111111111111111"
}
```

Rules for `intermediateRefundAddress`:

* It must be a valid address on `intermediateRefundChain`.
* It must be able to receive `intermediateRefundToken`.
* **Use a wallet your user controls, not an exchange deposit address.**

### Two refund addresses

| Field | Covers | Chain |
| - | - | - |
| `refundAddress` | The deposit leg | Source chain |
| `intermediateRefundAddress` | The on-chain hop. If the hop cannot finish, the hub token is sent here | `intermediateRefundChain` |

## Error Codes

| Code | HTTP | Why | What to do |
| - | - | - | - |
| `INTERMEDIATE_REFUND_ADDRESS_REQUIRED` | 422 | The quote needs `intermediateRefundAddress` and you did not send it | Collect the address and send it with the exchange |
| `ADDRESS_REFUND_INVALID_FOR_CHAIN` | 422 | `intermediateRefundAddress` is not a valid address for `intermediateRefundChain` | Validate the address against `intermediateRefundChain`, not the source chain |
| `ONCHAIN_LEG_ROUTE_NOT_AVAILABLE` | 422 | The route is no longer available, for example because the feature was turned off after you got the quote | Get a new quote |
| `VALIDATION_ERROR` | 422 | The quote has expired. `fields.quoteId` says so | Get a new quote |
| `QUOTE_OVER_LIMIT` / `AMOUNT_TOO_LOW` | 422 | The amount is outside this route's limits | See [Limits](#limits) |

> [!NOTE]
>
> These errors are not retried on another route. Fix the request or get a new quote.

***

## Next Steps

> **[Private Swap](/developer-hub/swap-flows/private-swap.md)**
>
> The base private swap flow

> **[Order Lifecycle](/developer-hub/core-concepts/order-lifecycle.md)**
>
> Understand all order statuses

> **[Error Codes](/developer-hub/troubleshooting/error-codes-and-troubleshooting.md)**
>
> Full list of API errors

> **[Fixed Rate & Reverse Quote](/developer-hub/swap-flows/fixed-rate-swap.md)**
>
> Lock the rate or set the receive amount


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.