# Recover stuck withdrawals

> Find Arbitrum and OP Stack withdrawals to Ethereum that were never claimed, and build the prove, finalize or claim transaction for the user's wallet to sign.

Source: https://docs.layerhop.com/guides/recover-withdrawals

Withdrawing from a rollup back to Ethereum through its native bridge takes **two transactions**: one on the L2, then, after a waiting period, one on Ethereum. If nobody sends the second one, the funds stay in the bridge. They aren't lost, but nobody gets them either. **Recover** finds those withdrawals and builds the missing transaction. The user's own wallet signs it; LayerHop never signs or sends anything.

The same tool runs at [app.layerhop.com/recover](https://app.layerhop.com/recover).

## Which bridges are covered?

| Chain | Steps on Ethereum | Wait | Address scan |
| --- | --- | --- | --- |
| Arbitrum One, Arbitrum Nova | claim (`Outbox.executeTransaction`) | about 6.4 days | full history since Nitro (Aug 2022) |
| OP Mainnet, Unichain, Ink, Soneium | prove, then finalize | 7 days after proving | full history (OP Mainnet since Bedrock, June 2023) |
| World Chain, Lisk, Mode | prove, then finalize | 7 days after proving | full history |
| Base | prove, then finalize | 1 day after proving, plus the time Base's dispute game takes to settle | about the last 2 days; older ones by transaction hash |
| Zora | prove, then finalize | 7 days after proving | about the last 2 days; older ones by transaction hash |

On **OP Mainnet, Unichain, Ink and Soneium** the dispute games prove *super roots*, and we don't build that proof yet: those withdrawals come back as `prove_elsewhere`. Prove them on [Superbridge](https://superbridge.app), then come back to finalize.

Arbitrum token withdrawals are found when they go through the standard, custom, WETH or DAI gateway. **Not covered:** Polygon PoS, zkSync, Linea, Scroll, Arbitrum Classic withdrawals (before Aug 2022) and OP Mainnet withdrawals from before Bedrock. Use those chains' own bridges.

## Scan an address

```bash
curl "https://api.layerhop.com/v1/recover/withdrawals?address=0xa5BA6e62e9a2CBDeac73F675C26257C75A3FE401"
```

[`GET /v1/recover/withdrawals`](/api-reference#scan-an-address-for-stuck-withdrawals) checks every chain above (or one, with `chain=`) and returns each withdrawal with its `state`:

| `state` | Meaning | What to do |
| --- | --- | --- |
| `ready` | The final step can be sent now (`step`: `execute` or `finalize`). | Build it with `GET /v1/recover/claim`. |
| `ready_to_prove` | OP Stack step 1 of 2. | Build the `prove`, send it, then finalize after the proof window. |
| `waiting` | Inside the challenge or proof window. | Come back at `readyAt` (`readyAtEstimated` when it's our estimate). |
| `prove_elsewhere` | Needs a proof we don't build for this chain. | Prove on `bridgeUrl`, finalize here. |
| `claimed` | Already done. | Nothing. |
| `unknown` | We couldn't read the state right now. | Try again later. |

`coverage` tells you, per chain, whether the whole history was scanned (`complete`) and where it started (`fromBlock`). A chain that couldn't be scanned is reported with `error: true` instead of failing the request. Scans look at up to 40 withdrawal transactions per chain, newest first, and are cached for 5 minutes.

## Look up one withdrawal

When you have the L2 transaction (or the scan can't reach back far enough), look it up directly; this works for any age:

```bash
curl "https://api.layerhop.com/v1/recover/withdrawals/base/0xf71673a8269db98a30c358ece7d4b55dc8786fa0622772731920631806737560"
```

## Build the next transaction

```bash
curl "https://api.layerhop.com/v1/recover/claim?chain=arbitrum&txHash=0x397ea01fc089ed8b89bbdf6eb588755996dc6b90ce1eb641369cc05f3659c1c6&index=0&sender=0xYourWallet"
```

The answer carries the withdrawal's fresh state and, when it is actionable, `step`, `tx` (an [`EvmTx`](/api-reference#evmtx) on Ethereum, chain id 1) and `simulation`, the result of an `eth_call` of `tx` from `sender`. Send `tx` from the user's wallet only when `simulation.ok` is `true`. `index` is the last part of the withdrawal's `id`.

- **Arbitrum**: `Outbox.executeTransaction` with a Merkle proof built against the latest send root the rollup confirmed on Ethereum. We check the proof's root against the Outbox before returning it.
- **OP Stack prove**: `OptimismPortal.proveWithdrawalTransaction` against the latest dispute game. We rebuild the game's output root from the L2 block and storage proof and only return the transaction when it matches the game's root claim **and** the simulation passes.
- **OP Stack finalize**: `finalizeWithdrawalTransactionExternalProof` with whoever proved it. A proof whose game lost, was blacklisted or was retired needs a new proof: the withdrawal comes back as `ready_to_prove`.

Anyone can send these transactions, and the funds always go to the withdrawal's recipient (`to`), never to the sender. That makes it safe to help someone else claim.

## Limits and keys

At most 2 scans and 30 lookups / claim builds per IP per minute (cached answers don't count), within per-minute budgets shared by all clients (a busy server answers `RATE_LIMITED` with `retry-after`). A claim whose simulation on Ethereum fails comes back without `tx` (with `simulation.error`), so nothing that would revert is offered for signing. Token withdrawals show a token and amount only from Arbitrum's known gateway contracts. The operator can make scans faster and fuller with `ETHERSCAN_API_KEY` (Etherscan v2; Base needs a paid plan) and `BLOCKSCOUT_API_KEY` (higher limits on the public Blockscout explorers). Without them the scan uses public explorers and RPCs.

## Related

- [Track any transfer](/guides/track-transfers): a withdrawal's L2 transaction there points straight to Recover.
- [Concepts: non-custodial](/concepts/non-custodial)
