Bridge Execute (Gasless)
[Alpha Preview] Submit a user-signed EIP-7702 batch and let Mobula broadcast it and pay the origin gas — bridge from an EVM wallet holding zero native token.
POST /api/2/bridge/execute broadcasts an EVM origin deposit on the user’s
behalf and pays its gas. The user signs the batch off-chain; Mobula’s solver
sends it. A wallet with 0 ETH/BNB/POL can approve and bridge in a single
signature.
The origin gas is not free — it’s priced into the quote and deducted from
the amount delivered, which is why /execute only accepts intents quoted with
gasless=true.
How it works
Under the hood this is an EIP-7702 sponsored batch:- The user’s EOA is delegated to
MobulaBatchExecutor. The delegation rides along as an authorization tuple on the first sponsored transaction — the wallet never sends a delegation transaction of its own. - The user signs an EIP-712
Batch— the quote’sapprove+bridgeToken/swapAndBridgesteps, exactly the transactions they would otherwise broadcast themselves. - Mobula submits a type-4 transaction carrying that batch and pays the gas.
The calls execute from the user’s own EOA, so
msg.senderat the bridge is still the user, and the deposit is indistinguishable from a self-sent one (same refund path, same/statuslifecycle).
Authentication
Same as every bridge endpoint, and required — the call is refused with401
without it. Pass your API key as a query parameter on the POST URL
(POST /api/2/bridge/execute?apiKey=YOUR_API_KEY) or send it as
Authorization: Bearer <apiKey-or-short-lived-JWT>, exactly like /quote.
The request costs one credit on the resolved key.
Availability
sponsorGate.batchExecutor, and the authorization tuple must name whatever that
says. A client holding its own copy is a second source of truth for the address
its batch is signed against: when the executor is redeployed, the batch is built
for one delegate and submitted against an account still delegated to another,
which does not necessarily fail loudly. Since no intent can reach /execute
without a gasless=true quote first, reading it from the quote costs nothing.
It is currently the same address on every supported chain, but that is a fact
about today’s deployment, not a guarantee:
The flow
signature in the /execute body to skip the signed-quote
confirm call — /execute persists the intent from the quote’s own stash, so
the whole trade is two HTTP calls.
Request body
Batch EIP-712 schema
gasToken and minBalance are a balance gate the executor checks before any
call runs: if the account does not hold minBalance of gasToken, the batch
reverts immediately. It exists because Mobula pays the gas whether a batch
succeeds or fails, so a deposit that was never going to move any tokens has to
fail in ~35k gas rather than burning the whole limit.
Set them to the origin token and the exact amount the quote priced —
/execute refuses a batch whose gasToken is not the quoted origin token, or
whose minBalance is below the quoted deposit.
verifyingContract is the user’s own account — that’s what stops a batch
signed for one delegated EOA from being replayed against another.
Authorization tuple
addressmust be the chain’sMobulaBatchExecutor; anything else is refused.chainIdmust be the origin chain — the0“any chain” wildcard is refused.nonceis the authority’s current account nonce, notnonce + 1: the solver submits the transaction, not the user. With viem’ssignAuthorization, that means not passingexecutor: 'self'. Get this wrong and the chain silently drops the authorization, leaving the batch to callexecuteon an account with no code.
eth_getCode(authority): an empty result is a
plain EOA, and a delegated one is exactly 0xef0100 || <20-byte delegate>. Send
a tuple when that delegate isn’t MobulaBatchExecutor.
Nonce lanes
MobulaBatchExecutor uses a two-dimensional nonce — key << 64 | seq,
sequential within a lane, independent across lanes — and every intent gets its
own lane derived from its intentId. Two deposits signed before either mines
can’t collide, and a fresh lane always starts at sequence 0, so no client ever
reads the chain to pick a nonce. Re-signing the same intent reuses its lane,
which is what makes a retry mutually exclusive with the attempt it replaces.
Response
depositTxHashis the sponsored transaction carrying the batch.sponsoredGasLimitis the gas units the send was capped at — exactly the units the quote charged for.statusis alwayspending: the deposit has been broadcast, not yet filled. Poll/status/{intentId}/waitas with any other deposit.
What the server checks
A sponsored batch spends Mobula’s gas, so/execute validates rather than
rebuilds it (rebuilding would change the calldata and void the user’s
signature):
- The intent’s prediction must say it was quoted gasless, and quoted for
this
authority. - Every
calls[].tomust be one of three addresses: the origin token the quote priced (to approve), the chain’sSwapBridgeHelper, orMobulaBridge. Anything else is out of scope. - No call may carry native
value. batchNoncemust be this intent’s lane;deadlinemust be in the future; at most 4 calls.batchSignaturemust recover toauthority, and every authorization tuple must be signed byauthorityand delegate toMobulaBatchExecutoron this chain.- The gas limit of the broadcast is the quote’s own units — never a client- or solver-supplied number.
Idempotency and retries
intentId is the idempotency key: the first /execute for an intent claims it
for 15 minutes, and a second returns 409. On a 504 the claim is
deliberately retained — the broadcast may still land, so poll
/status/{intentId} before doing anything else. Every other failure releases
the claim, so you can fix the request and retry the same intent.
When the batch reverts on-chain
A200 means the transaction was broadcast, not that it succeeded. A batch
can still revert once mined — a stale approval, a swap that moves past its own
limit, an out-of-gas. When it does, no tokens moved, so no deposit exists and no
bridge intent is ever created.
/status/{intentId} reports that case as failed rather than leaving it
pending:
Errors
{ "error": "...", "intentId": "..." }.
Example
Base → BSC, 100 USDC → USDT, from a wallet with no ETH. Two HTTP calls, three signatures (one of which disappears after the first ever gasless deposit).Cost model
The user pays no origin gas, but the trade does. On agasless=true quote:
fees.originSponsorGasUsdis what the sponsored send costs, and it’s already deducted fromestimatedAmountOutand included intotalFeeUsd.- The signed
minAmountOutis derived from that same netted amount, so the floor the solver enforces matches the number the user was shown. - The units charged are the exact gas limit the broadcast gets — the batch can never burn more than the quote reserved.
"Amount does not cover the sponsored origin gas ($…)" rather than quoting a
payout it can’t honour.
See also
- Bridge Quote —
gasless=true, the typed data, and the deposit steps this endpoint batches. - Bridge Status — the lifecycle after the deposit is broadcast.
- Bridge Implementation guide — the full non-gasless flow across EVM, Solana, and HyperLiquid.
Body
Origin chain ID of the quote (e.g., "evm:8453"). Must be an EVM chain with a MobulaBatchExecutor.
The intentId returned by the gasless=true quote.
The user's EOA — signer of the batch, and the account the batched calls execute from. Must match the address the intent was quoted for.
The quote's steps, in order, as batch calls (approve → bridgeToken/swapAndBridge). Max 4, each with value "0".
MobulaBatchExecutor nonce, decimal string. Must equal this intent's lane: (keccak256(utf8Bytes(intentId)) & (2**192 - 1)) << 64.
Unix seconds after which the batch signature is refused. Must be in the future.
Token whose balance gates the batch on-chain, taken verbatim from the quote's sponsorGate.gasToken. Must be the token this intent was quoted for; anything else is refused.
Minimum gasToken balance the account must hold for the batch to run, from the quote's sponsorGate.minBalance. Must be at least the quoted deposit amount. The executor checks it before any call runs, so a batch that was never going to move tokens fails in ~35k gas instead of burning its whole limit.
65-byte EIP-712 signature by authority over the Batch(calls, nonce, deadline, gasToken, minBalance) payload (domain MobulaBatchExecutor v1, verifyingContract = authority).
Which bridge entrypoint the batch ends on, matching the quote's last step type.
bridgeToken, swapAndBridge Execution mechanism. Only sponsored7702 exists today; the field is optional and reserved.
sponsored7702 EIP-7702 authorization tuples. Exactly one on the account's first sponsored send (it delegates the EOA to MobulaBatchExecutor in the same transaction); empty afterwards.
Optional EIP-712 bridge-intent signature over the quote's typedData. Send it here to skip the signed-quote confirm call — /execute persists the intent from the quote's stash. Omit if you already committed the signature via GET /2/bridge/quote.
Response
The sponsored deposit was broadcast.