Bridge Quote
[Alpha Preview] Get a cross-chain bridge quote with a ready-to-sign deposit transaction. EVM, Solana, and HyperLiquid.
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
{ "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
intentIdis the user-facing handle, formatxxxxxxx-xxxxxxx-xxx(lowercase hex). Pass it toGET /status/:idor/status/:id/wait. There is also an on-chainbytes32intent ID emitted byMobulaBridgeon EVM deposits — both resolve in/status/:id, so use whichever you have. You must echo this exactintentIdback via theintentIdquery param when submitting the signature — the signed payload binds to it.deadlineis Unix seconds. The server rejects signed calls past this point.typedDatais the EIP-712 structured-data payload your wallet should sign (see Signed-quote flow for the full schema).signatureRequired: trueforevm:*andhl:mainnetorigins.falseforsolana:solana(the depositor-signed memo replaces the signature).prediction.persistedindicates whether the server has committed your signed intent.falseon the unsigned preview call;trueafter a successful signed call.stepslists 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 omitsteps.recommendedSlippageis the slippage % we suggest signing with — the measured price impact of the quote’s swap leg(s) (max across the origin and destination swaps;0for a direct stable transfer) plus any fee-on-transfer tax, plus a1%drift buffer, rounded up to0.01and capped at50. Native-token destinations are floored at2%(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 signedminAmountOut(failure codeslippage), so a tolerance under this value is a near-guaranteed refund. If yourslippageis below it, re-quote at the recommendation before signing, or passslippage=autoto apply it while the quote is built (see Auto slippage).appliedSlippageappears only when you requestslippage=auto: the recommendation used to calculate the quote’s signedminAmountOut. It is absent on numeric-slippage calls, where the value you supplied is applied.feesare real, deducted amounts — there is no placeholder.bridgeFeeUsdis the Mobula protocol fee (bridgeFeeBps, currently5);destFillGasUsdis what the solver pays to fill on the destination chain;gasFeeUsdequalsdestFillGasUsd.destActivationCostUsdappears 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/integratorFeeUsdappear only when you passfeeBps+feeWallet(see Integrator fee).totalFeeUsdisbridgeFeeUsd + gasFeeUsdplus the integrator cut when present — it does not includedestActivationCostUsd.estimatedAmountOut/estimatedAmountOutUsdare already net of every fee (activation cost included); the user additionally pays only origin-chain gas to broadcast the deposit. On agasless=truequote,originSponsorGasUsdappears infeesand that origin gas is deducted too — the user pays nothing on the origin chain (see Gasless EVM deposits).destinationType/originTypeare echoed back only for HyperLiquid routes (spotorperps), reflecting the venue the funds land in / are pulled from.
Integrator fee
PassfeeBps + feeWallet to take a cut on top of the Mobula protocol fee.
feeBpsis your fee in basis points — integer0–500(up to 5%). It only applies whenfeeWalletis also present.- The fee is paid in USDC on the destination chain, at fill time (not on the
origin).
feeWalletmust therefore be a valid destination-ecosystem address (0x…forevm:*/hl:mainnet, base58 forsolana: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 exactlyestimatedAmountOut. - When applied, the quote’s
feesobject echoesintegratorFeeBpsandintegratorFeeUsd, andtotalFeeUsdincludes it.
Gasless EVM deposits
Passgasless=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.originSponsorGasUsdis deducted fromestimatedAmountOut, folded intototalFeeUsd, and baked into the signedminAmountOut. It’s the only way/executecan recover what it spends, which is why an intent quoted withoutgasless=trueis refused there. stepsare 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
typedDataand send that signature in the/executebody instead — the server persists the intent from the values this quote already computed.
Gasless Solana deposits
Passgasless=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 fromestimatedAmountOutand baked into the memo’sminAmountOut— 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. PassprioritySend=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:
sendTransaction
JSON-RPC call — with skipPreflight: true and maxRetries: 0, retrying
yourself — to whichever endpoint of that provider is closest to you:
- 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=truedeposit we pay it, so it is priced intofees.originSponsorGasUsdand deducted fromestimatedAmountOut— the quote you get already accounts for it. - Native SOL origins build their own transaction client-side, so there the
prioritySendblock also carriescomputeUnitPriceMicroLamportsand 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)
- Native ETH/BNB/POL — single
bridge()call onMobulaBridge.valueis the raw amount in wei.stepshas onebridgeentry. - Direct-bridge tokens —
approvestep toMobulaBridge, thenbridgeToken().valueis"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 —
approvestep toSwapBridgeHelper, thenswapAndBridge()— atomic swap to native + bridge in one TX. The embedded swap calldata is validated server-side to start with a recognisedMobulaRouter.executeRouteselector (0x1688b026for v2.19.0, or0xa564dfa4for 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:
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 aSystemProgram.transferforamountlamports toto(the solver address), then add a memo instruction (programMemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr) whose data is thememostring. The memo is a JSON blob the solver parses to recoverintentId,destinationChainId,recipient,destinationToken, andminAmountOut. - SPL —
{ type: "spl-transfer" | "swap-transfer", serializedTx }. The server has already built the full versioned transaction. Canonical USDC is a directspl-transfer(SPL transfer + ATA creation if missing + memo); any other SPL token isswap-transfer— an atomic swap to USDC + transfer + memo in one transaction. Either way, justVersionedTransaction.deserialize, sign, and send. On agasless=truequote the same shape carriescoSigned: true,feePayerandblockhash, and the transaction is already signed by that payer — see Gasless Solana deposits.
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
WhenoriginChainId === 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 numericslippage, 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.
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
Forevm:* 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:
- Preview call —
GET /quote?...with nosignature. Server returns the quote, the deposit calldata, and atypedDatapayload. Nothing is committed yet. - Sign — your wallet signs
typedData(e.g.eth_signTypedData_v4for injected wallets,walletClient.signTypedDatawith viem). The signer’s address must equaltypedData.message.sender. - 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 withprediction.persisted: true. - Broadcast — submit
deposit.evm(ordeposit.hl) on chain.
EIP-712 schema
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-checktypedData.message.senderand 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" }—deadlineis pastnow. 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):/status/:id/wait with
quote.intentId.Query Parameters
Origin chain ID (e.g., "evm:8453", "solana:solana", "hl:mainnet")
Destination chain ID (e.g., "evm:8453", "solana:solana", "hl:mainnet")
Human-readable amount of origin token to bridge (e.g., "0.1")
Recipient wallet address on the destination chain (EVM hex, Solana base58, or HL hex).
Origin token contract/mint address. Omit or pass the zero address for the native token.
Destination token contract/mint address. Omit or pass the zero address for the native token.
Slippage tolerance in percent (0-50, default: 1).
Origin-chain sender address. Required when bridging an SPL token from Solana (the wallet signing the swap+deposit TX).
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.
true, false 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.
helius, jito Response
Bridge quote response. Either data is populated with quote + deposit instructions, or error describes why the quote could not be built.