Skip to main content
GET
Get bridge quote
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.
Median latency for this API is tracked live on OpenChainBench, the open benchmark suite we built and open-sourced.
Mobula on OpenChainBench: 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

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

  • 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. You must echo this exact intentId back via the intentId query param when submitting the signature — 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 for the full schema).
  • signatureRequired: true for evm:* and hl:mainnet origins. false for solana:solana (the depositor-signed memo replaces the signature).
  • prediction.persisted indicates whether the server has committed your signed intent. false on the unsigned preview call; 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).
  • 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). 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).
  • destinationType / originType are echoed back only for HyperLiquid routes (spot or perps), reflecting the venue the funds land in / are pulled from.

Integrator fee

Pass feeBps + feeWallet to take a cut on top of the Mobula protocol fee.
  • feeBps is your fee in basis points — integer 0500 (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.

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.

sponsorGate

Returned on every gasless EVM quote. Everything the batch has to be signed against, stated by the server:
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 or wrapped-native origin: that deposit must carry native value", or "Amount does not cover the sponsored origin gas ($…)".
  • You can skip the signed-quote confirm call entirely on this path: sign the returned typedData and send that signature in the /execute body instead — the server persists the intent from the values this quote already computed.
The full batch schema, delegation rules, and a runnable example live on the 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:
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:
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: 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)

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 tokensapprove 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-20approve 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: 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.
Both shapes carry an extra prioritySend block when the quote asked for one — see Priority send.

HyperLiquid origin (deposit.hl)

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.
EVM and HyperLiquid origins still use the normal preview, sign, and commit flow: the preview supplies the concrete minAmountOut that must be signed. Solana origins do not require that signature step. auto also works on 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

For evm:* and hl:mainnet origins the destination token (and, for HL, the destination address) is not committed on chain — the server holds it against the depositor’s address until the deposit lands. Without binding that to the depositor’s key, anyone who knows the address could overwrite it and redirect funds to a different token. To close that hole, /quote enforces an EIP-712 signature flow. The flow is two API calls plus one wallet signature:
  1. Preview callGET /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 callGET /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.evm (or deposit.hl) on chain.

EIP-712 schema

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 signed intent — bound to (sender, originChainId, destinationChainId)only when called with a valid signature (Solana origin: committed on every call, since the memo carries the binding). The server uses it to resolve destinationToken, recipient, and slippage when the deposit lands. For EVM/HL origins a deposit with no committed signature is rejected (and refunded) — so always complete the commit call before broadcasting.

Example

EVM origin (signed-quote flow, Base → BSC, 100 USDC → USDT):
For a full sign-and-broadcast walkthrough across EVM, Solana, and HyperLiquid origins, see the Bridge Implementation guide. After broadcasting the deposit, poll /status/:id/wait with quote.intentId.

Query Parameters

originChainId
string
required

Origin chain ID (e.g., "evm:8453", "solana:solana", "hl:mainnet")

destinationChainId
string
required

Destination chain ID (e.g., "evm:8453", "solana:solana", "hl:mainnet")

amount
string
required

Human-readable amount of origin token to bridge (e.g., "0.1")

walletAddress
string
required

Recipient wallet address on the destination chain (EVM hex, Solana base58, or HL hex).

originToken
string

Origin token contract/mint address. Omit or pass the zero address for the native token.

destinationToken
string

Destination token contract/mint address. Omit or pass the zero address for the native token.

slippage
string

Slippage tolerance in percent (0-50, default: 1).

senderAddress
string

Origin-chain sender address. Required when bridging an SPL token from Solana (the wallet signing the swap+deposit TX).

gasless
enum<string>

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.

Available options:
true,
false
prioritySend
enum<string>

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.

Available options:
helius,
jito

Response

200 - application/json

Bridge quote response. Either data is populated with quote + deposit instructions, or error describes why the quote could not be built.

data
object
error
string

Error message when the quote could not be built (missing params, unsupported route, price unavailable, amount over cap, swap failed, …).