Handling Failures and Recovery

View as Markdown

Cross-chain swaps are non-atomic as they span two chains and a bridge provider in between. If the origin transaction succeeds but the bridge cannot complete, the user’s funds have left their wallet and need to be recovered.

This page explains what happens when a bridge fails, how recovery works, and what your integration should do for each scenario. For the happy-path status lifecycle, see Tracking Transaction Status.

Origin refund vs dest retry

The recovery path depends on where the transfer fails.

Origin refund. If the bridge fails, the bridge provider automatically returns the tokens on the origin chain. The status changes from refund_pending to refund_succeeded when the refund is complete.

Dest retry. If the bridge provider’s call on the destination chain fails, /status returns manualTransaction: null and 0x retries that call. A successful retry changes the status to bridge_filled.

CCIP is the exception. 0x does not yet submit dest retries for CCIP.

Arbitrum Bridge is the only bridge with a recovery deadline. 0x retries the recovery transaction a few times, but does not keep retrying until the deadline. After deadline, status can become failed.

How Failures Surface

When you poll GET /cross-chain/status and the response has status: "bridge_failed", it includes a failure object with everything needed to understand what went wrong and what to do next.

Do not stop polling on bridge_failed while failure.status is refund_pending or manual_action_required. When a destination retry succeeds, the top-level status changes to bridge_filled and the failure object is removed.

Failure Object Structure

{
"status": "bridge_failed",
"bridge": "across_v4",
"failure": {
"reason": "expired",
"status": "refund_pending",
"transactions": [
{ "chainId": 8453, "txHash": "0xabc...", "timestamp": 1712345678 }
],
"recovery": {
"chainId": 8453,
"token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"amount": "2500000000",
"estimatedTimeSeconds": 1200,
"deadline": null,
"manualTransaction": null,
"settledAmount": null
}
}
}

Failure Reasons

ReasonMeaning
expiredThe bridge provider is unable to execute the cross-chain transaction before its expiry.
cancelledThe bridge has been cancelled by the user or a third-party entity. This can occur only if someone bypasses 0x’s recommended flow and interacts directly with the bridge provider’s system, or if the bridge provider experiences a technical failure.
out_of_gasThe destination call ran out of gas before it could complete. See Recovery Behavior by Bridge for the next step.
internalThe bridge provider encountered an internal error. See Recovery Behavior by Bridge for the next step.
unknownThe reason of the bridge failure cannot be determined by 0x.

Failure Statuses

StatusDescription
refund_pendingThe bridge provider has started an automatic refund. Continue polling /status until the refund is complete.
refund_succeededThe bridge provider has issued the refund. The user receives the funds in originAddress or destinationAddress, depending on where the refund occurs. Check failure.recovery for the chain, token, and settledAmount.
manual_action_requiredThe destination call did not complete and requires recovery. manualTransaction remains null because the user does not need to sign anything. 0x submits the recovery transaction, except for CCIP. Continue polling /status.
no_actions_requiredThe bridge failed before transferring the tokens from the origin chain, so the user still has their funds and no action is required.
failedThe recovery process failed and the status is terminal. The user cannot recover the funds through /status. Contact 0x support with the zid from the response.

Recovery Step Fields

FieldTypeDescription
chainIdnumberChain where recovery occurs. Destination retries use the destination chain. Refunds usually use the origin chain, but Mayan MCTP and FastMCTP refunds can use the origin or destination chain.
tokenstringToken recovered or delivered. A destination retry delivers the bridge step’s buyToken. A refund returns the bridge step’s sellToken on the origin chain or buyToken on the destination chain. The token may differ from the quote’s sellToken.
amountstringExpected recovery amount (bigint).
estimatedTimeSecondsnumber or nullSeconds until automatic refund. Present when refund_pending.
deadlinenumber or nullUnix timestamp after which an Arbitrum Bridge recovery can change to failed. Only Arbitrum Bridge uses this field.
manualTransactionobject or nullCurrently null. The user does not need to sign or submit a destination retry transaction.
settledAmountstring or nullActual refunded amount. Populated after a refund confirms. May differ from amount.

What your integration should do

  1. Poll /status.
  2. If failure.status is refund_pending or refund_succeeded, treat it as a refund. Do not ask the user to sign a transaction.
  3. If failure.status is manual_action_required, continue polling. 0x submits the destination retry unless the bridge is CCIP. Do not ask the user to sign a transaction.
  4. If failure.status is failed, contact 0x support with the zid from /status.

Refund Token and Chain

The refund token and chain depend on where the failure happened and what the route looked like. The refund is not always in the user’s original sellToken on originChain. What you get back depends on the bridge (see table below) and the route composition.

The main thing to understand: the refund token is based on the bridge step’s sell or buy token and not the route’s sellToken. If the route included a swap or wrap before the bridge, the bridge step’s tokens differ from what the user started with:

RouteSell tokenBridge tokenToken refunded (origin-side bridges)
USDC → bridgeUSDCUSDCUSDC
ETH → wrap → WETH → bridgeETHWETHWETH (not ETH)
ETH → swap → USDC → bridgeETHUSDCUSDC (not ETH)

The recovery.token, recovery.chainId, and recovery.amount fields contain the exact details. For destination retries, these fields describe delivery on the destination chain, not an origin refund.

Tracking Refund Progress

For automatic refunds, keep polling /status:

  1. bridge_failed + refund_pending - refund initiated
  2. bridge_failed + refund_succeeded - refund confirmed, failure.transactions has refund tx hash and recovery.settledAmount has actual refunded amount

Tracking Dest Retry Progress

For destination retries submitted by 0x, continue polling /status:

  1. When the top-level status is bridge_failed and failure.status is manual_action_required, the destination retry is in progress. manualTransaction is null. Retry transaction hashes may appear in failure.transactions and the top-level transactions array before they are mined. A transaction hash does not mean that recovery is complete.
  2. When the retry succeeds, the top-level status changes to bridge_filled and the failure object is removed.
  3. If failure.status remains manual_action_required, the destination retry has not completed. Only Arbitrum Bridge can move to failed after deadline.

Recovery Behavior by Bridge

In this table, “bridge step’s sell/buy token” refers to the tokens on the bridge step specifically - not the user’s original sellToken/buyToken from the quote request. If the route included a swap or wrap before the bridge, these are different. See Refund Token and Chain above.

BridgeRecoveryChainToken
Across V4Automatic origin refundOriginBridge step’s sellToken
Arbitrum Bridge0x dest retry (has deadline)DestinationBridge step’s buyToken
Circle Forwarder0x dest retryDestinationBridge step’s buyToken
CCIPDest retry not submitted by 0xDestinationBridge step’s buyToken
Gas.zipAutomatic origin refundOriginBridge step’s sellToken
HyperCore BridgeNot tracked--
Linea BridgeNot tracked--
Mayan SwiftAutomatic origin refundOriginBridge step’s sellToken
Mayan MCTP / FastMCTPAutomatic refundOrigin or destinationBridge step’s sellToken on origin or buyToken on destination
NEAR IntentsAutomatic origin refundOriginBridge step’s sellToken
OFT (LayerZero)0x dest retryDestinationBridge step’s buyToken
PAXG0x dest retryDestinationBridge step’s buyToken
RelayAutomatic origin refundOriginBridge step’s sellToken
SquidAutomatic origin refundOriginBridge step’s sellToken
Stargate V20x dest retryDestinationBridge step’s buyToken
Superchain0x dest retryDestinationBridge step’s buyToken

For bridges marked “Not tracked”, the 0x status service does not track the recovery process. The bridge provider may still offer recovery through their own tools.

Example: Automatic Refund (Across)

{
"failure": {
"reason": "expired", "status": "refund_pending",
"recovery": { "chainId": 8453, "token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "amount": "2500000000", "estimatedTimeSeconds": 1200 }
}
}

Example: Dest Retry in Progress (Stargate)

A Stargate transfer to Arbitrum (chain 42161) ran out of gas while executing on the destination chain. 0x submits a recovery transaction to retry the destination call, so manualTransaction is null. Recovery transaction hashes can appear in transactions while status remains bridge_failed. Keep polling until the status changes to bridge_filled.

{
"failure": {
"reason": "out_of_gas", "status": "manual_action_required",
"recovery": { "chainId": 42161, "token": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "amount": "2500000000",
"manualTransaction": null }
}
}

Example: Arbitrum Bridge Deadline

0x retries the recovery transaction a few times, but does not keep retrying until deadline. The user does not need to sign or submit the recovery transaction.

{
"failure": {
"reason": "out_of_gas", "status": "manual_action_required",
"recovery": { "chainId": 42161, "deadline": 1713000000, "manualTransaction": null }
}
}