Get bridge intent status
Bridge (Alpha Preview)
Bridge Status
[Alpha Preview] Look up the status of a bridge intent, or long-poll until it terminates.
GET
Get bridge intent status
Two endpoints share this page:
This is the expected state right after the deposit is broadcast but before
the chain listener has indexed it. Keep polling.
On deposit detection the solver writes
Long-poll variant. Blocks server-side until the intent reaches
Pass the key as the
Every other destination returns a real
See the Bridge Implementation guide for the
full no-sleep
GET /api/2/bridge/status/{id}— returns the current row immediately.GET /api/2/bridge/status/{id}/wait— long-polls server-side until the intent reaches a terminal state (or the timeout elapses).
Path parameter
id accepts any of these — pass whichever you have:
intentIdfrom/quote(formatxxxxxxx-xxxxxxx-xxx).- The on-chain
bytes32intent ID (EVM only, emitted byMobulaBridge). - The deposit TX hash.
- The fill TX hash.
Response
latencyMs is the deposit-detected → fill-confirmed delta. settleTxHash /
timestamps.settled populate only once the solver has been reimbursed on
the origin chain — that step is async and can lag the user-visible fill. On a
slippage refund, failureReason.code is "slippage" and a human-readable
message field is added telling the user to raise their slippage.
fillTxHashPending is true when the fill is already complete
(status: "filled") but its canonical destination tx hash hasn’t been indexed
yet — the case for Hyperliquid destinations, whose L1 hash is assigned a
moment after the funds move. While it’s true, fillTxHash is null. Treat the
bridge as done as soon as you see filled; fetch the hash separately (see the
best practice below).
Status lifecycle
The statuses the solver actually writes:| Status | Meaning | Terminal? |
|---|---|---|
pending | Row not yet created, or deposit not yet detected. | No |
filling | Deposit detected; fill in progress on the destination chain. | No |
filled | Fill confirmed — user has received funds. | Yes (happy path) |
settled | Solver reimbursed on origin chain. | Yes |
refunded | Fill couldn’t complete; user refunded on the origin chain. | Yes |
failed | Refund also failed — manual intervention. | Yes |
filling directly — there is no
intermediate deposited status. (deposited and retrying exist in the
underlying type enum but are not currently emitted; retries are tracked in a
separate queue and the intent stays in filling.) Treat any non-terminal status
as “keep waiting,” and any unknown status defensively.
The terminal set is filled, settled, failed, refunded. Stop polling once
you see one of them.
GET /status/{id}/wait
Long-poll variant. Blocks server-side until the intent reaches filled,
settled, failed, or refunded, then returns the same shape as
/status/{id}. If the window elapses first, you get the current
(non-terminal) row and should call again.
Query parameter
| Name | Notes |
|---|---|
timeout | Milliseconds. Default 30000, capped at 60000. |
waitForFillTxHash | true to hold the long-poll until the canonical fillTxHash is present, not just until the intent is terminal. Use it to fetch the real Hyperliquid hash after you’ve already shown the bridge complete. Every other destination returns immediately (its hash is set up front). |
How it actually waits
The controller hands the intent to a centralized batched poller (IntentWaiterService) that, while any waiter is registered, runs one pass
every 10 ms across all active waiters: a Redis MGET of the solver’s
mirrored intent-state keys (no replication lag — the fast path), with a Postgres
primary read for anything Redis didn’t resolve. The first pass that sees your
row terminal (status IN ('filled','settled','failed','refunded')) resolves the
request. There is no Postgres LISTEN/NOTIFY and no per-request fallback timer
— that earlier implementation was replaced because it exhausted PG backend
connections.
In practice, fills are delivered with the latency of the destination-chain
listener — typically a few hundred ms. The 30 s default is just an upper
bound; you almost always return earlier.
Recommended client loop
?apiKey= query param (not an Authorization header).
Don’t add a client-side sleep — the server already blocks until something
happens; firing again with no delay keeps one open long-poll waiting for the
next state change.
Stale-response handling
If you start a new bridge while a previous/wait is still in flight, the
previous response will arrive after your new intentId is active. Track
the active intent ID client-side and discard any /wait result that doesn’t
match it — otherwise you’ll attach an old fill TX to the new attempt.
Best practice: don’t block completion on the fill hash (Hyperliquid)
A Hyperliquid-destination fill is final the instant the funds leave HL, but HL only assigns the canonical L1 transaction hash a moment later. The solver therefore marks the intentfilled immediately and patches fillTxHash in
afterward — so a filled response can briefly carry fillTxHash: null with
fillTxHashPending: true.
Show the user “bridge complete” as soon as status is filled — do not wait
on the hash. Then, only if you want an explorer link, make a second long-poll
with ?waitForFillTxHash=true and update your UI when the hash lands:
fillTxHash up front (fillTxHashPending: false), so the second call returns immediately and this branch is a no-op.
Example
while(true) polling loop and how to handle stale responses
when running multiple bridges in parallel.