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

# Bridge Quote

> [Alpha Preview] Get a cross-chain bridge quote with a ready-to-sign deposit transaction. EVM, Solana, and HyperLiquid.

<Warning>
  **Alpha Preview** — Endpoints, response shape, contract addresses, and supported
  routes may change without notice. Don't depend on it for production-critical
  flows until it leaves alpha.
</Warning>

<Note>Median latency for this API is tracked live on [OpenChainBench](https://openchainbench.com/benchmarks/bridge-quote-latency), the open benchmark suite we built and open-sourced.</Note>

[![Mobula on OpenChainBench: bridge quote latency](https://openchainbench.com/api/badge/bridge-quote-latency/mobula)](https://openchainbench.com/benchmarks/bridge-quote-latency)

`GET /api/2/bridge/quote` returns a ready-to-sign deposit transaction plus a
`intentId` you'll use to poll status. The Mobula solver detects the deposit
(flash blocks on Base, gRPC on Solana) and fills on the destination chain.

Typical end-to-end latency: \~500 ms for Base ↔ Solana, \~1–3 s elsewhere.

## Query parameters

| Name                 | Required    | Notes                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| -------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `apiKey`             | yes         | Your Mobula API key, passed in the query string (`?apiKey=…`) or as an `Authorization: Bearer <key>` header (the query param wins when both are present). Omitting both returns `{ "error": "Missing required parameter: apiKey (or Authorization: Bearer <token>)" }`.                                                                                                                                                                                                                                                                                                                                   |
| `originChainId`      | yes         | One of the supported chain IDs (see [Bridge Routes](/rest-api-reference/endpoint/bridge-routes)).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `destinationChainId` | yes         | Same set. When equal to `originChainId`, the endpoint short-circuits to the Swap API — see [Same-chain quotes](#same-chain-quotes).                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `walletAddress`      | yes         | **Destination recipient.** Format-validated against the destination chain: EVM regex for `evm:*` and `hl:mainnet`, Base58 for `solana:solana`.                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `amount`             | conditional | Decimal human units (`"0.05"`, not wei). Provide **either** `amount` **or** `amountRaw`, not both. Must be finite, positive, ≤ `1e15`. Not required when source and destination are the same chain.                                                                                                                                                                                                                                                                                                                                                                                                       |
| `amountRaw`          | conditional | Raw amount in the **origin** token's base units — a positive integer string, e.g. `"1500000"` for 1.5 USDC (6 decimals). Mirrors the `/api/2/swap/quoting` convention; use it when you already hold the base-unit amount to avoid human→raw precision loss. Mutually exclusive with `amount`; passing both returns `"Provide either amount (human units) or amountRaw (base units), not both"`.                                                                                                                                                                                                           |
| `originToken`        | no          | Omit, or pass `0x0000…0000` / `0xeeee…eeee` for the native token. EVM addresses are checksummed server-side.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `destinationToken`   | no          | Same rules. When omitted on a Solana destination, the API substitutes wSOL (`So11111111111111111111111111111111111111112`).                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `senderAddress`      | conditional | **Required** for (1) Solana SPL bridges (the server needs it to build the ATA + SPL transfer + memo) and (2) EVM/HL origins whose destination is **not** EVM (e.g. EVM→Solana) — there `walletAddress` is a base58 recipient and can't double as the EVM signer. For HL origin, this is the HL spot wallet that will sign — it must equal the EIP-712 signer (see [Signed-quote flow](#signed-quote-flow)). Optional for EVM→EVM (defaults to `walletAddress`); must be a `0x…` address on EVM origins.                                                                                                   |
| `feePayerAddress`    | no          | Solana origin only: the Solana address that pays the deposit transaction's fee (set as the built transaction's fee payer). Defaults to `senderAddress`. Must be a valid Solana address or the quote returns `"feePayerAddress must be a Solana address"`. Cannot be combined with `gasless=true`, which sets our own fee payer.                                                                                                                                                                                                                                                                           |
| `gasless`            | no          | `true` prices the origin deposit as **sponsored**: Mobula pays the origin gas, charged to the quote instead of the user's wallet. Cross-chain only, non-native origin token only. On an **EVM** origin we broadcast a user-signed EIP-7702 batch — a quote priced this way is the only one [`POST /execute`](/rest-api-reference/endpoint/bridge-execute) accepts. On a **Solana** origin the returned transaction comes back already co-signed by our fee payer; there is no `/execute` call. See [Gasless EVM deposits](#gasless-evm-deposits) and [Gasless Solana deposits](#gasless-solana-deposits). |
| `prioritySend`       | no          | Solana origin, cross-chain only: `helius` or `jito`. The built deposit carries that rail's tip transfer and a compute-unit price, so you can broadcast it through that provider — to whichever of its regional endpoints is closest to you. See [Priority send](#priority-send).                                                                                                                                                                                                                                                                                                                          |
| `slippage`           | no          | Percent. Default `1`, valid range `0` to `50`. Pass `auto` to use the server recommendation when building the quote's absolute `minAmountOut`. See [Auto slippage](#auto-slippage).                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `destinationType`    | no          | HyperLiquid destinations only: which venue the funds land in — `spot` (default) or `perps`. Ignored on non-HL destinations. Invalid values return `"Invalid destinationType: must be 'spot' or 'perps'"`.                                                                                                                                                                                                                                                                                                                                                                                                 |
| `originType`         | no          | HyperLiquid origins only: which venue the deposit is pulled from — `spot` (default) or `perps`. Ignored on non-HL origins. Invalid values return `"Invalid originType: must be 'spot' or 'perps'"`.                                                                                                                                                                                                                                                                                                                                                                                                       |
| `feeBps`             | no          | Optional **integrator fee** in basis points (integer `0`–`500`, i.e. up to 5%). Only applied when `feeWallet` is also set. `> 500` returns `"feeBps exceeds the maximum integrator fee (500 bps)"`; negative/non-integer returns `"feeBps must be a non-negative integer"`. See [Integrator fee](#integrator-fee).                                                                                                                                                                                                                                                                                        |
| `feeWallet`          | no          | **Integrator payout address.** Paid in USDC on the **destination** chain at fill, so it must be a valid destination-ecosystem address — `0x…` for `evm:*`/`hl:mainnet` destinations, base58 for `solana:solana`. A mismatch returns `"feeWallet must be a … address"`. Required for `feeBps` to take effect.                                                                                                                                                                                                                                                                                              |
| `refundTo`           | no          | **Where a failed bridge refunds to**, in *origin*-ecosystem format — `0x…` for `evm:*`/`hl:mainnet`, base58 for `solana:solana`. Defaults to the address the deposit came from. On an EVM origin it is written into the deposit calldata, so the bridge records it on the deposit and the refund can only ever pay that address. An address in the wrong ecosystem's format is rejected — no quote — with `"refundTo must use the … address format"`. See [Refund address](#refund-address).                                                                                                              |
| `signature`          | conditional | EIP-712 signature over the typed-data payload returned by the unsigned call. **Required only for `hl:mainnet` origins** to commit the prediction — an HL transfer carries nothing on chain, so the signature is what binds it. `evm:*` and `solana:solana` origins commit on the first call and need no signature; one sent anyway is still verified. See [Signed-quote flow](#signed-quote-flow).                                                                                                                                                                                                        |
| `intentId`           | conditional | Echo back the `intentId` returned by the unsigned call when submitting `signature`. Format `xxxxxxx-xxxxxxx-xxx` (lowercase hex).                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `deadline`           | conditional | Echo back the `deadline` returned by the unsigned call. Unix seconds. Server rejects expired deadlines.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `minAmountOut`       | conditional | Echo back the `minAmountOut` (raw destination-token units) from the typedData. The signed value is authoritative — the server reconstructs the typedData from this exact value before verifying the signature.                                                                                                                                                                                                                                                                                                                                                                                            |

Validation errors come back as `{ "error": "..." }` with HTTP 200 — always check
the `error` key before reading `data`. The exceptions: a signature that fails to
recover on the commit call is HTTP 400, an unknown API key is 404, and a
prediction-persist failure is 503.

## Response

```json theme={null}
{
  "data": {
    "intentId": "a3b4ba1-e34523c-324",
    "deadline": 1764032100,
    "typedData": { /* EIP-712 payload to sign — see Signed-quote flow */ },
    "signatureRequired": true,
    "prediction": { "persisted": false },
    "estimatedAmountOut": "0.68421052",
    "estimatedAmountOutUsd": "1.99",
    "recommendedSlippage": 1.5,
    "appliedSlippage": 1.5,
    "fees": {
      "bridgeFeeBps": 5,
      "bridgeFeeUsd": "0.0010",
      "destFillGasUsd": "0.0800",
      "gasFeeUsd": "0.0800",
      "totalFeeUsd": "0.0810",
      "integratorFeeBps": 30,
      "integratorFeeUsd": "0.0060",
      "destActivationCostUsd": "0.0020"
    },
    "estimatedTimeMs": 1000,
    "destinationType": "spot",
    "originType": "spot",
    "steps": [ /* optional, see below */ ],
    "deposit": { /* one of evm | solana | hl */ }
  }
}
```

* `intentId` is the user-facing handle, format `xxxxxxx-xxxxxxx-xxx` (lowercase
  hex). Pass it to `GET /status/:id` or `/status/:id/wait`. There is also an
  on-chain `bytes32` intent ID emitted by `MobulaBridge` on EVM deposits — both
  resolve in `/status/:id`, so use whichever you have. When you do submit a
  signature, **echo this exact `intentId` back via the `intentId` query param**
  — the signed payload binds to it.
* `deadline` is Unix seconds. The server rejects signed calls past this point.
* `typedData` is the EIP-712 structured-data payload your wallet should sign
  (see [Signed-quote flow](#signed-quote-flow) for the full schema).
* `signatureRequired: true` for `hl:mainnet` origins only. `false` for `evm:*`
  and `solana:solana`, whose deposit — the transaction the depositor signs — is
  itself the authorization.
* `prediction.persisted` indicates whether the server has committed your intent.
  `true` on the first call for `evm:*` and `solana:solana`; for `hl:mainnet`,
  `false` on the unsigned preview and `true` after a successful signed call.
* `steps` lists the on-chain transactions for **EVM origins**: `[{type:'bridge'}]`
  for native, `[{type:'approve'},{type:'bridgeToken'}]` for direct-bridge tokens,
  `[{type:'approve'},{type:'swapAndBridge'}]` for other ERC-20s. Solana and HL
  deposits omit `steps`.
* `recommendedSlippage` is the slippage % we suggest signing with — the measured
  **price impact of the quote's swap leg(s)** (max across the origin and
  destination swaps; `0` for a direct stable transfer) plus any fee-on-transfer
  tax, plus a `1%` drift buffer, rounded up to `0.01` and capped at `50`.
  Native-token destinations are floored at `2%` (the fill is served by a market
  swap). Bridge fee and destination gas are **not** part of it — they're already
  deducted from the quoted output. The solver refunds any fill below the signed
  `minAmountOut` (failure code `slippage`), so a tolerance under this value is a
  near-guaranteed refund. If your `slippage` is below it, re-quote at the
  recommendation before signing, or pass `slippage=auto` to apply it while the
  quote is built (see [Auto slippage](#auto-slippage)).
* `appliedSlippage` appears **only** when you request `slippage=auto`: the
  recommendation used to calculate the quote's signed `minAmountOut`. It is
  absent on numeric-slippage calls, where the value you supplied is applied.
* `fees` are **real, deducted** amounts — there is no placeholder. `bridgeFeeUsd`
  is the Mobula protocol fee (`bridgeFeeBps`, currently `5`); `destFillGasUsd` is
  what the solver pays to fill on the destination chain; `gasFeeUsd` equals
  `destFillGasUsd`. `destActivationCostUsd` appears **only** when the destination
  needs a one-off account-creation cost (e.g. Solana ATA rent for a first-time
  recipient) — it's omitted otherwise. `integratorFeeBps` / `integratorFeeUsd`
  appear **only** when you pass `feeBps` + `feeWallet` (see
  [Integrator fee](#integrator-fee)). `totalFeeUsd` is
  `bridgeFeeUsd + gasFeeUsd` plus the integrator cut when present — it does
  **not** include `destActivationCostUsd`. `estimatedAmountOut` /
  `estimatedAmountOutUsd` are already net of every fee (activation cost
  included); the user additionally pays only origin-chain gas to broadcast the
  deposit. On a `gasless=true` quote, `originSponsorGasUsd` appears in `fees`
  and that origin gas is deducted too — the user pays nothing on the origin
  chain (see [Gasless EVM deposits](#gasless-evm-deposits)).
* `destinationType` / `originType` are echoed back only for HyperLiquid routes
  (`spot` or `perps`), reflecting the venue the funds land in / are pulled from.

## Refund address

When a bridge cannot be filled the deposit is returned, by default to the address
it came from — `msg.sender` on EVM (pinned on chain in the deposit record), the
transfer sender on Solana and HyperLiquid.

That address is not always where the money came from. An ERC-4337 or EIP-7702
deposit is sent by an EOA that a smart wallet funded a hop earlier in the same
transaction, so the refund lands on the EOA rather than the wallet the user
watches. We do not guess that hop — a swap-funded deposit's previous sender is a
pool, and a sponsored one's is our own infrastructure — so pass `refundTo` when
the refund belongs somewhere other than the depositing address:

```
GET /api/2/bridge/quote?...&refundTo=0xYourSmartWallet
```

On EVM the address travels in the deposit calldata you are handed back, so it is
part of what the user signs and the bridge stores it next to the deposit: the
refund destination is fixed at deposit time and cannot be changed afterwards. On
Solana and HyperLiquid the deposit is a bare transfer, so we hold it with the
intent and the refund send uses it.

The quote echoes `data.refundTo` when one was accepted.

## Integrator fee

Pass `feeBps` + `feeWallet` to take a cut on top of the Mobula protocol fee.

* `feeBps` is your fee in basis points — integer `0`–`500` (up to 5%). It only
  applies when `feeWallet` is also present.
* The fee is paid **in USDC on the destination chain, at fill time** (not on the
  origin). `feeWallet` must therefore be a valid *destination-ecosystem* address
  (`0x…` for `evm:*`/`hl:mainnet`, base58 for `solana:solana`); a mismatch is
  rejected before the fee is charged. On HyperLiquid destinations a payout to an
  unactivated account is skipped (first receipt on an unactivated HL account
  costs the sender \~\$1).
* The cut is folded into the signed `minAmountOut`, so it comes out of the
  bridged amount — the recipient still receives exactly `estimatedAmountOut`.
* When applied, the quote's `fees` object echoes `integratorFeeBps` and
  `integratorFeeUsd`, and `totalFeeUsd` includes it.

```
GET /api/2/bridge/quote?...&feeBps=30&feeWallet=0xYourFeeWallet
```

## Gasless EVM deposits

Pass `gasless=true` to have **Mobula broadcast the origin deposit and pay its
gas**, so a wallet holding zero ETH/BNB/POL can still bridge. The user signs the
deposit as an EIP-7702 batch instead of sending it; you submit that batch to
[`POST /execute`](/rest-api-reference/endpoint/bridge-execute).

```
GET /api/2/bridge/quote?...&gasless=true
→ { data: { intentId, typedData, steps, sponsorGate: { … },
            fees: { originSponsorGasUsd: "0.0123", … }, … } }
```

### `sponsorGate`

Returned on every gasless EVM quote. Everything the batch has to be signed
against, stated by the server:

```json theme={null}
"sponsorGate": {
  "batchExecutor": "0x29440460fbdda286fe259b0b1cbfbc018d47dfda",
  "gasToken": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
  "minBalance": "1000000"
}
```

| Field           | Use it for                                                                                                                                                                                                                                         |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `batchExecutor` | The delegate your EIP-7702 authorization tuple must name. **Do not hardcode this** — it changes whenever the executor is redeployed, and a batch signed for one delegate against an account delegated to another does not necessarily fail loudly. |
| `gasToken`      | Goes into the signed `Batch` as `gasToken`. `/execute` refuses anything that isn't the token this intent was quoted for.                                                                                                                           |
| `minBalance`    | Goes into the signed `Batch` as `minBalance`. `/execute` refuses anything below the quoted deposit amount.                                                                                                                                         |

`gasToken` and `minBalance` are an on-chain gate the executor checks before any
call runs, so a deposit that was never going to move tokens fails in \~35k gas
instead of burning the whole limit. Pass all three through untouched: they are
covered by the batch signature, so a value you compute yourself only produces a
digest `/execute` cannot verify.

* The origin gas is **not free** — it's priced into this quote:
  `fees.originSponsorGasUsd` is deducted from `estimatedAmountOut`, folded into
  `totalFeeUsd`, and baked into the signed `minAmountOut`. It's the only way
  `/execute` can recover what it spends, which is why an intent quoted without
  `gasless=true` is refused there.
* `steps` **are** the batch the user signs (approve + `bridgeToken` /
  `swapAndBridge`) — carry them verbatim, in order, into `/execute`.
* The quote is refused when the route can't be sponsored:
  `"Gasless deposits are not available on <chain>"` (no executor deployed
  there), `"Gasless is not available for same-chain swaps"`, `"Gasless is not
  available for a native origin: that deposit must carry native value"`, or
  `"Amount does not cover the sponsored origin gas ($…)"`. A **wrapped-native**
  origin (WETH/WBNB/WPOL) *is* sponsorable: it batches as `approve` +
  `bridgeWrappedNative`, carrying no native value.
* It is also refused with `"Gasless is temporarily unavailable on <chain>:
  origin gas cannot be priced right now"` when the origin's live gas price or
  its native token price can't be read — we never sponsor a send we cannot
  charge for.
* There is no signing step before `/execute` on this path: this quote already
  committed the intent, so the batch signature is the only one the deposit
  needs (plus the one-off 7702 authorization).

The full batch schema, delegation rules, and a runnable example live on the
[Bridge Execute](/rest-api-reference/endpoint/bridge-execute) page.

## Gasless Solana deposits

Pass `gasless=true` on a `solana:solana` origin and the deposit transaction
comes back **already signed by Mobula's fee payer**, so a wallet holding zero
SOL can bridge and you never run a payer key of your own:

```
GET /api/2/bridge/quote?...&originChainId=solana:solana&gasless=true
→ deposit.solana = {
    type: "spl-transfer" | "swap-transfer",
    serializedTx: "<base64, fee-payer signature already attached>",
    coSigned: true,
    feePayer: "<Mobula fee payer>",
    blockhash: "<the blockhash that signature covers>"
  }
```

`VersionedTransaction.deserialize` it, have the user's wallet sign, and submit
straight to any RPC — no `/execute` hop, one wallet prompt, nothing else in the
path.

* **Do not touch `recentBlockhash`.** It is part of what our signature covers;
  replacing it voids the co-signature. The transaction is good for as long as
  that blockhash lives (\~60s), which is also when you should re-quote.
* The fee is priced into the quote as `fees.originSponsorGasUsd`, deducted from
  `estimatedAmountOut` and baked into the memo's `minAmountOut` — the same
  accounting as the EVM path.
* Native SOL origins are refused: that deposit is the gas token itself. So are
  same-chain swaps, and a request that also sets `feePayerAddress`.

## Priority send

Solana deposits can be broadcast through a staked-connection rail instead of a
plain RPC, so they land under congestion. Pass `prioritySend=helius` or
`prioritySend=jito` on a `solana:solana` origin and the deposit transaction is
built with that rail's tip transfer and a compute-unit price already in it —
there is nothing for you to assemble:

```
GET /api/2/bridge/quote?...&originChainId=solana:solana&prioritySend=helius
→ deposit.solana.prioritySend = {
    provider: "helius",
    tipAccount: "<the account the built tip pays>",
    tipLamports: 5000
  }
```

Sign the transaction as usual, then POST it as a normal `sendTransaction`
JSON-RPC call — with `skipPreflight: true` and `maxRetries: 0`, retrying
yourself — to **whichever endpoint of that provider is closest to you**:

| Provider | Endpoints                                                                                                                                                                                                                                                                                             |
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `helius` | `https://sender.helius-rpc.com/fast?swqos_only=true` (global, HTTPS), or a regional host over HTTP: `slc-`, `ewr-`, `lon-`, `fra-`, `ams-`, `sg-`, `tyo-` + `sender.helius-rpc.com/fast?swqos_only=true`. Regional hosts are plain HTTP — fine from a backend, blocked as mixed content in a browser. |
| `jito`   | `https://mainnet.block-engine.jito.wtf/api/v1/transactions`, or `amsterdam.`, `frankfurt.`, `london.`, `dublin.`, `ny.`, `slc.`, `singapore.`, `tokyo.` + `mainnet.block-engine.jito.wtf/api/v1/transactions`.                                                                                        |

Both rails are open — no API key, no plan, no credits — so the tip is the
entire cost: **5,000 lamports** for Helius Sender (SWQOS-only routing) and
**10,000 lamports** for Jito.

* Who pays the tip follows who pays the transaction. On a self-paid deposit it
  comes out of the sender's SOL like the fee does. On a `gasless=true` deposit
  **we** pay it, so it is priced into `fees.originSponsorGasUsd` and deducted
  from `estimatedAmountOut` — the quote you get already accounts for it.
* Native SOL origins build their own transaction client-side, so there the
  `prioritySend` block also carries `computeUnitPriceMicroLamports` and you add
  the tip transfer + compute-budget instructions yourself.
* Omitting the parameter changes nothing: no extra call is made and the deposit
  is built exactly as before.

## `deposit` shapes

The shape depends on `originChainId`. Sign and broadcast whichever one is
present.

### EVM origin (`deposit.evm`)

```json theme={null}
{
  "to": "0xa834E70303322D86E5DaE95ee47E9c6a073d9812",
  "data": "0x...",
  "value": "50000000000000000",
  "chainId": 8453,
  "approvalAddress": "0x...",      // present for ERC-20 origins
  "approvalToken": "0x...",        // present for ERC-20 origins
  "approvalAmount": "115792...255" // MAX_UINT256
}
```

Three code paths:

* **Native ETH/BNB/POL** — single `bridge()` call on `MobulaBridge`. `value`
  is the raw amount in wei. `steps` has one `bridge` entry.
* **Direct-bridge tokens** — `approve` step to `MobulaBridge`, then
  `bridgeToken()`. `value` is `"0"`. The set is exactly the chain's canonical
  USDC (Base, BSC, Arbitrum, Polygon) — every other ERC-20, including USDbC and
  USDT, takes the swap-and-bridge path. Robinhood Chain has no USDC, so it's
  native + swap only.
* **Any other ERC-20** — `approve` step to `SwapBridgeHelper`, then
  `swapAndBridge()` — atomic swap to native + bridge in one TX. The embedded
  swap calldata is validated server-side to start with a recognised
  `MobulaRouter.executeRoute` selector (`0x1688b026` for v2.19.0, or
  `0xa564dfa4` for v2.17.0); if it doesn't, the quote returns
  `"Swap quote failed: invalid calldata selector"`.

`MobulaBridge` (MobulaBridgeV2, deployed 2026-06-04) is the **same proxy address
on every EVM chain**:

| Chain                        | Bridge contract                              |
| ---------------------------- | -------------------------------------------- |
| `evm:8453` (Base)            | `0xa834E70303322D86E5DaE95ee47E9c6a073d9812` |
| `evm:56` (BSC)               | `0xa834E70303322D86E5DaE95ee47E9c6a073d9812` |
| `evm:42161` (Arbitrum)       | `0xa834E70303322D86E5DaE95ee47E9c6a073d9812` |
| `evm:137` (Polygon)          | `0xa834E70303322D86E5DaE95ee47E9c6a073d9812` |
| `evm:4663` (Robinhood Chain) | `0xa834E70303322D86E5DaE95ee47E9c6a073d9812` |

The swap-and-bridge path approves a separate per-chain `SwapBridgeHelper` (the
spender named in the `approve` step) — always approve the spender the step
specifies, not a hardcoded address.

Approval handling: `approvalAmount` is always `MAX_UINT256`, so a single
approve per (token, spender) is enough forever. Skip the `approve` step only
if the current on-chain allowance already covers `amount`.

### Solana origin (`deposit.solana`)

Two shapes depending on token:

* **Native SOL** — `{ to, amount, memo }`. Build a `SystemProgram.transfer`
  for `amount` lamports to `to` (the solver address), then add a memo
  instruction (program `MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr`) whose
  data is the `memo` string. The memo is a JSON blob the solver
  parses to recover `intentId`, `destinationChainId`, `recipient`,
  `destinationToken`, and `minAmountOut`.
* **SPL** — `{ type: "spl-transfer" | "swap-transfer", serializedTx }`. The
  server has already built the full versioned transaction. Canonical USDC is a
  direct `spl-transfer` (SPL transfer + ATA creation if missing + memo); any
  other SPL token is `swap-transfer` — an atomic swap to USDC + transfer + memo
  in one transaction. Either way, just `VersionedTransaction.deserialize`,
  sign, and send. On a `gasless=true` quote the same shape carries
  `coSigned: true`, `feePayer` and `blockhash`, and the transaction is already
  signed by that payer — see [Gasless Solana deposits](#gasless-solana-deposits).

Both shapes carry an extra `prioritySend` block when the quote asked for one —
see [Priority send](#priority-send).

### HyperLiquid origin (`deposit.hl`)

```json theme={null}
{
  "type": "spotSend",
  "to": "0x74CfC17edF89aD6134c04c446c3Be6dD288F0B8d",
  "token": "USDH:0x54e00a5988577cb0b0c9ab0cb6ef7f4b",
  "amount": "1.0"
}
```

`type` follows `originType`: `spotSend` (the default, `originType=spot`)
carries a `token` field; `usdSend` (`originType=perps`, always USDC) has no
`token`. Submit the matching action to the solver L1 address (`to` from the
response — don't hardcode it) using your HL signer.

## Same-chain quotes

When `originChainId === destinationChainId`, the server short-circuits
into a swap-wrapper. The response is the **raw Swap API response**, not the
bridge shape above (no `intentId`, no `deposit.evm/solana/hl`). Approval
amounts in the response are overridden to `MAX_UINT256` server-side.

Branch on the response: `data.deposit` present → bridge flow; otherwise →
swap flow.

## Auto slippage

With numeric `slippage`, clients may need to quote again when the returned
`recommendedSlippage` is higher than the requested value. `slippage=auto`
applies the recommendation while building the quote. The resulting absolute
`minAmountOut` is included consistently in the typed data, deposit transaction,
or Solana memo and is enforced by the solver.

```
GET /api/2/bridge/quote?...&slippage=auto
→ { data: { typedData, appliedSlippage: 4.2, deposit, ... } }
```

HyperLiquid origins still use the preview, sign, and commit flow: the preview
supplies the concrete `minAmountOut` that must be signed. EVM and Solana origins
take the `minAmountOut` straight from the returned deposit instructions — one
call, no signature step.

`auto` also works on [same-chain quotes](#same-chain-quotes): the returned swap
transaction's `minOut` is set from the recommendation in a single call (the swap
executes atomically, so its floor stays in the transaction itself).

## Signed-quote flow

**`hl:mainnet` origins only.** An HL deposit is a bare USDC transfer: it names no
destination chain, no destination address and no floor, so nothing on chain says
what the transfer was for. The EIP-712 intent is what binds it, and `/quote`
enforces the signature before it commits anything.

`evm:*` and `solana:solana` origins do not use this flow. Their deposit is a
transaction the depositor signs anyway, and it names the intent it was quoted
for — an `IntentRef` log next to the EVM deposit, the memo on Solana — alongside
the destination chain, the recipient and the minimum out. The quote commits on
the first call and the deposit itself says which quote it belongs to. You may
still sign and submit the `typedData` on an EVM origin (the server verifies it),
but nothing requires it.

The settlement carries the same id: an EVM fill or refund emits `IntentRef`
beside its `IntentFilled` / `DepositRefunded` log, and a Solana fill or refund
carries the intent id in a memo. Both ends of a bridge can therefore be read
back to the intent from the chain alone. Hyperliquid is the exception in both
directions — its transfers carry nothing, which is why its intents are
signed.

The HL flow is two API calls plus one wallet signature:

1. **Preview call** — `GET /quote?...` with no `signature`. Server returns the
   quote, the deposit calldata, and a `typedData` payload. **Nothing is
   committed yet.**
2. **Sign** — your wallet signs `typedData` (e.g. `eth_signTypedData_v4` for
   injected wallets, `walletClient.signTypedData` with viem). The signer's
   address must equal `typedData.message.sender`.
3. **Commit call** — `GET /quote?...&signature=<sig>&intentId=<id>&deadline=<ts>&minAmountOut=<raw>`
   with the same input params as call 1. Server reconstructs the typedData
   from the query params, recovers the signer, and commits your signed intent
   if everything lines up. Returns the same response shape with
   `prediction.persisted: true`.
4. **Broadcast** — submit `deposit.hl` (`spotSend`, or `usdSend` when
   `originType=perps`).

### EIP-712 schema

```
Domain:
  name:    "Mobula Bridge"
  version: "1"
  chainId: <numeric origin chain id>   // 8453, 56, 42161, 137, 4663 — and 1 (NOT 999) for hl:mainnet

BridgeIntent:
  intentId           string    — same as `data.intentId` (dashed hex, signed as a string)
  sender             address   — depositor EVM key (HL origin: HL spot wallet; EVM: walletAddress)
  originChainId      string    — e.g. "evm:8453"
  originToken        string    — "0x000…0" for native, or token address / HL token id
  amountIn           uint256   — raw origin-token units
  destinationChainId string
  destinationToken   string
  recipient          bytes32   — EVM dest: address left-padded; Solana dest: bs58-decoded
  minAmountOut       uint256   — raw destination-token units, echoed back as the `minAmountOut` query param
  deadline           uint64    — Unix seconds, echoed back as the `deadline` query param
```

viem omits the `EIP712Domain` type from `types` — inject it client-side
(`name:string, version:string, chainId:uint256`) before signing if your signer
needs it.

**HyperLiquid specifics.** HL signs all actions under Ethereum mainnet, so for
`hl:mainnet` origins `typedData.domain.chainId` is `1` (not `999`) — switch the
wallet to chain `1` before signing. An HL bridge then needs **two** signatures:
(1) this EIP-712 `BridgeIntent` confirm, then (2) the HL transfer to the solver
(`spotSend`, or `usdSend` when `originType=perps`).

### Failure modes on the commit call

* **HTTP 400** `Signature recovers to 0x…; expected sender 0x…` — the signature
  recovered to a different address than the expected signer. Re-check
  `typedData.message.sender` and the signer.
* **HTTP 400** `Invalid bridge intent signature: …` — the signature is
  malformed (recovery itself threw).
* **HTTP 200** `{ "error": "Signature deadline has expired or is invalid" }` —
  `deadline` is past `now`. Re-quote.
* **HTTP 200** `{ "error": "Signed-mode quote requires intentId, deadline,
  minAmountOut, and signature query params (echo the values returned by the
  unsigned call)" }` — one of the four signed-mode params is missing.

## Side effects

`/quote` commits your intent on every call for `evm:*` and `solana:solana`
origins, and **only when called with a valid signature** for `hl:mainnet`. The
server uses it to resolve `destinationToken`, `recipient`, and `slippage` when
the deposit lands. `evm:*` and `solana:solana` intents are found by the id the
deposit carries, so a later quote does not disturb one you already broadcast;
`hl:mainnet` is bound to `(sender, originChainId, destinationChainId)` and the
last signed quote for that route wins. An HL transfer with no committed
signature is rejected (and refunded) — so always complete the commit call before
sending it.

## Example

EVM origin (Base → BSC, 100 USDC → USDT) — one call, then broadcast:

```typescript theme={null}
import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { base } from "viem/chains";

const account = privateKeyToAccount("0xYourPrivKey");
const client = createWalletClient({ account, chain: base, transport: http() });

const params = new URLSearchParams({
  originChainId: "evm:8453",
  destinationChainId: "evm:56",
  originToken: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", // USDC on Base
  destinationToken: "0x55d398326f99059fF775485246999027B3197955", // USDT on BSC
  amount: "100",
  walletAddress: account.address,
  senderAddress: account.address,
  apiKey: "YOUR_API_KEY",
});

// 1. Quote. The intent is committed by this call — no signature step.
const quote = await fetch(`https://api.mobula.io/api/2/bridge/quote?${params}`)
  .then((r) => r.json());
if (quote.error) throw new Error(quote.error);
const { intentId, estimatedAmountOut, prediction } = quote.data;
if (!prediction?.persisted) throw new Error("intent was not committed; retry");

// 2. Broadcast the on-chain deposit (run quote.data.steps in order when the
//    token needs an approve first).
const tx = quote.data.deposit.evm;
const hash = await client.sendTransaction({
  to: tx.to,
  data: tx.data,
  value: BigInt(tx.value),
});
console.log("Deposit tx:", hash);
console.log("Poll status with:", intentId);
```

For a full sign-and-broadcast walkthrough across EVM, Solana, and HyperLiquid
origins, see the [Bridge Implementation guide](/guides/bridge-implementation).
After broadcasting the deposit, poll
[`/status/:id/wait`](/rest-api-reference/endpoint/bridge-status) with
`quote.intentId`.


## OpenAPI

````yaml get /2/bridge/quote
openapi: 3.0.0
info:
  version: 1.0.0
  title: Mobula API
  description: >-
    Documentation of the Mobula API


    **Demo API**: The default server (demo-api.mobula.io) is a demo API with
    rate limits.

    For production use, please use api.mobula.io with an API key from
    https://admin.mobula.io
servers:
  - url: https://demo-api.mobula.io/api/
    description: Demo API (rate limited, for testing only)
  - url: https://api.mobula.io/api/
    description: Production API (requires API key)
security: []
tags:
  - name: V2 - Token
    description: Token details, price, security, ATH, and holder data
  - name: V2 - Market Data
    description: Market details, OHLCV history, and lighthouse metrics
  - name: V2 - Trades
    description: Token trades, enriched trades, and trade filters
  - name: V2 - Wallet
    description: Wallet positions, activity, trades, analysis, and labels
  - name: V2 - Assets
    description: Cross-chain asset details and price history
  - name: V2 - Swap
    description: Swap quoting and execution
  - name: V2 - Perps
    description: Perpetual futures quoting, execution, and positions
  - name: V2 - Bridge
    description: Cross-chain bridge quoting and intent status (Alpha Preview)
  - name: V2 - DeFi
    description: Bonding pools and pulse data
  - name: V2 - Search
    description: Universal fast search
  - name: V2 - Usage
    description: Per-key API and WebSocket usage history
  - name: V2 - Blockchains
    description: System metadata and chain listings
  - name: V2 - Prediction Markets
    description: >-
      Polymarket markets/events, wallet positions, and the full execution stack
      (auth, order build/submit/cancel, approvals, pUSD wrap/unwrap, deploy,
      deposit/withdraw, redeem). Alpha — see /api/2/pm/*.
  - name: V1 - Market Data
    description: Market prices, history, sparklines, pairs, and multi-data
  - name: V1 - Wallet
    description: Wallet portfolio, transactions, history, and NFTs
  - name: V1 - Token
    description: First buyers
  - name: V1 - Trades
    description: Market trades by pair
  - name: V1 - Metadata
    description: Token metadata, categories, and news
  - name: V1 - Assets
    description: List all assets
  - name: V1 - Search
    description: Search for assets, tokens, and pairs
  - name: V1 - DeFi
    description: Bonding pool pulse data
  - name: V1 - Blockchains
    description: Blockchain listings, pairs, and stats
  - name: V1 - Webhooks
    description: Webhook management
  - name: V1 - Feed
    description: Custom feed creation
paths:
  /2/bridge/quote:
    get:
      tags:
        - V2 - Bridge
      summary: Get bridge quote
      description: >-
        [Alpha Preview] Get a cross-chain bridge quote with a ready-to-sign
        deposit transaction. Supported chains: Base (evm:8453), BSC (evm:56),
        Arbitrum (evm:42161), Polygon (evm:137), Solana (solana:solana),
        HyperLiquid (hl:mainnet). For native tokens, omit
        originToken/destinationToken or pass the zero address. ERC-20 origins on
        EVM go through SwapBridgeHelper (atomic swap+bridge); SPL origins on
        Solana require senderAddress. Max trade is $10,000 USD per transaction.
      parameters:
        - schema:
            type: string
            description: Origin chain ID (e.g., "evm:8453", "solana:solana", "hl:mainnet")
          required: true
          description: Origin chain ID (e.g., "evm:8453", "solana:solana", "hl:mainnet")
          name: originChainId
          in: query
        - schema:
            type: string
            description: >-
              Destination chain ID (e.g., "evm:8453", "solana:solana",
              "hl:mainnet")
          required: true
          description: >-
            Destination chain ID (e.g., "evm:8453", "solana:solana",
            "hl:mainnet")
          name: destinationChainId
          in: query
        - schema:
            type: string
            description: Human-readable amount of origin token to bridge (e.g., "0.1")
          required: true
          description: Human-readable amount of origin token to bridge (e.g., "0.1")
          name: amount
          in: query
        - schema:
            type: string
            description: >-
              Recipient wallet address on the destination chain (EVM hex, Solana
              base58, or HL hex).
          required: true
          description: >-
            Recipient wallet address on the destination chain (EVM hex, Solana
            base58, or HL hex).
          name: walletAddress
          in: query
        - schema:
            type: string
            description: >-
              Origin token contract/mint address. Omit or pass the zero address
              for the native token.
          required: false
          description: >-
            Origin token contract/mint address. Omit or pass the zero address
            for the native token.
          name: originToken
          in: query
        - schema:
            type: string
            description: >-
              Destination token contract/mint address. Omit or pass the zero
              address for the native token.
          required: false
          description: >-
            Destination token contract/mint address. Omit or pass the zero
            address for the native token.
          name: destinationToken
          in: query
        - schema:
            type: string
            description: 'Slippage tolerance in percent (0-50, default: 1).'
          required: false
          description: 'Slippage tolerance in percent (0-50, default: 1).'
          name: slippage
          in: query
        - schema:
            type: string
            description: >-
              Origin-chain sender address. Required when bridging an SPL token
              from Solana (the wallet signing the swap+deposit TX).
          required: false
          description: >-
            Origin-chain sender address. Required when bridging an SPL token
            from Solana (the wallet signing the swap+deposit TX).
          name: senderAddress
          in: query
        - schema:
            type: string
            enum:
              - 'true'
              - 'false'
            description: >-
              Set to "true" to price the origin deposit as gasless: Mobula pays
              the origin gas, which is charged to the quote instead. Cross-chain
              only, non-native origin tokens only. On an EVM origin the deposit
              is an EIP-7702 sponsored batch and this is the only quote POST
              /2/bridge/execute accepts; on a Solana origin
              deposit.solana.serializedTx comes back already co-signed by Mobula
              fee payer (coSigned true) and is submitted directly to an RPC.
          required: false
          description: >-
            Set to "true" to price the origin deposit as gasless: Mobula pays
            the origin gas, which is charged to the quote instead. Cross-chain
            only, non-native origin tokens only. On an EVM origin the deposit is
            an EIP-7702 sponsored batch and this is the only quote POST
            /2/bridge/execute accepts; on a Solana origin
            deposit.solana.serializedTx comes back already co-signed by Mobula
            fee payer (coSigned true) and is submitted directly to an RPC.
          name: gasless
          in: query
        - schema:
            type: string
            enum:
              - helius
              - jito
            description: >-
              Solana origin, cross-chain only. Builds that rail's tip transfer
              and a compute-unit price into the deposit transaction so it can be
              broadcast through a staked connection instead of a plain RPC;
              deposit.solana.prioritySend echoes the tip account and amount.
              Send the signed transaction to whichever of that provider's
              regional endpoints is closest to you (Helius Sender -
              sender.helius-rpc.com/fast?swqos_only=true plus
              slc/ewr/lon/fra/ams/sg/tyo regional hosts over HTTP; Jito -
              mainnet.block-engine.jito.wtf/api/v1/transactions plus
              amsterdam/frankfurt/london/dublin/ny/slc/singapore/tokyo). Both
              rails are open (no API key, no plan, no credits); the tip is the
              whole cost - 5000 lamports on Helius Sender (SWQOS-only), 10000 on
              Jito. On a gasless=true quote the tip is ours to pay and is
              included in fees.originSponsorGasUsd.
          required: false
          description: >-
            Solana origin, cross-chain only. Builds that rail's tip transfer and
            a compute-unit price into the deposit transaction so it can be
            broadcast through a staked connection instead of a plain RPC;
            deposit.solana.prioritySend echoes the tip account and amount. Send
            the signed transaction to whichever of that provider's regional
            endpoints is closest to you (Helius Sender -
            sender.helius-rpc.com/fast?swqos_only=true plus
            slc/ewr/lon/fra/ams/sg/tyo regional hosts over HTTP; Jito -
            mainnet.block-engine.jito.wtf/api/v1/transactions plus
            amsterdam/frankfurt/london/dublin/ny/slc/singapore/tokyo). Both
            rails are open (no API key, no plan, no credits); the tip is the
            whole cost - 5000 lamports on Helius Sender (SWQOS-only), 10000 on
            Jito. On a gasless=true quote the tip is ours to pay and is included
            in fees.originSponsorGasUsd.
          name: prioritySend
          in: query
        - schema:
            type: string
            description: >-
              Where a failed bridge refunds to, in ORIGIN-ecosystem format
              (`0x…` on `evm:*`/`hl:mainnet`, base58 on `solana:solana`).
              Defaults to the address the deposit came from, which is not always
              where the funds originated - an ERC-4337 / EIP-7702 deposit is
              sent by an EOA a smart wallet funded a hop earlier, and the refund
              lands on that EOA. On EVM the address is written into the deposit
              calldata, so the bridge stores it on the deposit itself and the
              refund can only ever pay it. An address in the wrong ecosystem's
              format is rejected with "refundTo must use the … address format"
              and no quote is built.
          required: false
          description: >-
            Where a failed bridge refunds to, in ORIGIN-ecosystem format (`0x…`
            on `evm:*`/`hl:mainnet`, base58 on `solana:solana`). Defaults to the
            address the deposit came from, which is not always where the funds
            originated - an ERC-4337 / EIP-7702 deposit is sent by an EOA a
            smart wallet funded a hop earlier, and the refund lands on that EOA.
            On EVM the address is written into the deposit calldata, so the
            bridge stores it on the deposit itself and the refund can only ever
            pay it. An address in the wrong ecosystem's format is rejected with
            "refundTo must use the … address format" and no quote is built.
          name: refundTo
          in: query
      responses:
        '200':
          description: >-
            Bridge quote response. Either `data` is populated with quote +
            deposit instructions, or `error` describes why the quote could not
            be built.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      estimatedAmountOut:
                        type: string
                        description: >-
                          Estimated amount of destination token the recipient
                          will receive (8 decimals).
                      estimatedAmountOutUsd:
                        type: string
                        description: USD value of the estimated output (2 decimals).
                      fees:
                        type: object
                        properties:
                          bridgeFeeBps:
                            type: number
                            description: Mobula bridge fee in basis points.
                          gasFeeUsd:
                            type: string
                            description: Estimated gas fee in USD.
                          totalFeeUsd:
                            type: string
                            description: Total fee in USD.
                          originSponsorGasUsd:
                            type: string
                            description: >-
                              Present only on `gasless=true` quotes — the origin
                              gas Mobula pays to broadcast the sponsored
                              deposit, deducted from the output and included in
                              `totalFeeUsd`.
                        required:
                          - bridgeFeeBps
                          - gasFeeUsd
                          - totalFeeUsd
                      refundTo:
                        type: string
                        description: >-
                          Echo of the `refundTo` this quote was built with — on
                          EVM it is baked into the deposit calldata below.
                          Absent when the refund goes to the depositing address.
                      estimatedTimeMs:
                        type: number
                        description: Estimated end-to-end fill latency in milliseconds.
                      sponsorGate:
                        type: object
                        description: >-
                          Present only on a gasless EVM quote. Everything the
                          sponsored batch must be signed against, stated by the
                          server — pass all three through to POST
                          /2/bridge/execute untouched. They are covered by the
                          batch signature, so a value the client computes itself
                          only yields a digest /execute cannot verify.
                        properties:
                          batchExecutor:
                            type: string
                            description: >-
                              The MobulaBatchExecutor your EIP-7702
                              authorization tuple must name. Do not hardcode it:
                              it changes whenever the executor is redeployed,
                              and a batch signed for one delegate submitted
                              against an account delegated to another does not
                              necessarily fail loudly.
                          gasToken:
                            type: string
                            description: >-
                              The origin token being bridged; goes into the
                              signed Batch as `gasToken`.
                          minBalance:
                            type: string
                            description: >-
                              Raw units of `gasToken` the account must hold for
                              the batch to run; goes into the signed Batch as
                              `minBalance`.
                        required:
                          - batchExecutor
                          - gasToken
                          - minBalance
                      steps:
                        type: array
                        items:
                          type: object
                          properties:
                            type:
                              type: string
                              description: >-
                                Step type: "approve", "bridge", "bridgeToken",
                                or "swapAndBridge".
                            description:
                              type: string
                            tx:
                              type: object
                              properties:
                                to:
                                  type: string
                                data:
                                  type: string
                                value:
                                  type: string
                                chainId:
                                  type: number
                                approvalAddress:
                                  type: string
                                approvalToken:
                                  type: string
                                approvalAmount:
                                  type: string
                              required:
                                - to
                                - data
                                - value
                                - chainId
                              description: EVM transaction payload to sign.
                          required:
                            - type
                            - tx
                        description: >-
                          Ordered steps the user must execute (e.g., approve →
                          bridge). Present when the deposit requires multiple
                          transactions.
                      deposit:
                        type: object
                        properties:
                          evm:
                            type: object
                            properties:
                              to:
                                type: string
                              data:
                                type: string
                              value:
                                type: string
                              chainId:
                                type: number
                              approvalAddress:
                                type: string
                              approvalToken:
                                type: string
                              approvalAmount:
                                type: string
                            required:
                              - to
                              - data
                              - value
                              - chainId
                            description: >-
                              EVM transaction payload (native, direct
                              bridgeToken, or swapAndBridge).
                          solana:
                            type: object
                            properties:
                              type:
                                type: string
                                enum:
                                  - swap-and-bridge
                              to:
                                type: string
                              amount:
                                type: string
                                description: Amount in lamports.
                              memo:
                                type: string
                                description: >-
                                  JSON-encoded memo carrying destination chain,
                                  recipient, destination token, and
                                  minAmountOut.
                              serializedTx:
                                type: string
                                nullable: true
                                description: >-
                                  Base64-encoded VersionedTransaction (for
                                  swap-and-bridge flow).
                            description: >-
                              Solana deposit. Native SOL: memo-based transfer
                              (to/amount/memo). SPL: type=swap-and-bridge with
                              serializedTx.
                          hl:
                            type: object
                            properties:
                              type:
                                type: string
                                enum:
                                  - spotSend
                              to:
                                type: string
                              token:
                                type: string
                              amount:
                                type: string
                            required:
                              - type
                              - to
                              - token
                              - amount
                            description: HyperLiquid Core spotSend payload.
                        description: >-
                          Ready-to-sign deposit payload. Exactly one of `evm`,
                          `solana`, or `hl` is populated, matching
                          `originChainId`.
                    required:
                      - estimatedAmountOut
                      - estimatedAmountOutUsd
                      - fees
                      - estimatedTimeMs
                      - deposit
                  error:
                    type: string
                    description: >-
                      Error message when the quote could not be built (missing
                      params, unsupported route, price unavailable, amount over
                      cap, swap failed, …).

````