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.
  • 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.
  • 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.

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 the MobulaRouter.executeRoute selector (0xa564dfa4); 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.

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.

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).

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, …).