> ## Documentation Index
> Fetch the complete documentation index at: https://docs.range.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Cross-Chain Lookup

> Find both legs of a bridge or cross-chain swap from one transaction hash, and check which protocols and networks the lookup covers.

A cross-chain transfer has two transactions on two networks: one sends the funds, the other delivers them. The cross-chain lookup takes a hash from either one and returns both legs, with what was sent and what was actually delivered. The coverage endpoint tells you which protocols and networks the lookup can answer for.

Typical uses:

* **Tax and accounting:** link the two legs of a cross-chain swap so cost basis carries across. `destination.time` is when the new asset was acquired.
* **Exchanges and custodians:** find the origin network and sender of a deposit.
* **Compliance:** follow funds across a bridge.
* **Wallets and support:** check the status of a user's transfer.

> [!NOTE]
>
> **You'll need an API key.** Pass it as `Authorization: Bearer YOUR_API_KEY` to `https://api.range.org`, as in the [Quickstart](https://docs.range.org/data-api/quickstart.md). Both endpoints are part of the Data API.

***

## 1. Look Up a Transfer by Either Hash

[Get cross-chain transfers by hash](https://docs.range.org/api-reference/crosschain-transfers/get-crosschain-transfers-by-hash) takes one path parameter, `tx_hash`. No network, protocol or direction is needed. This example uses the delivery hash of an Across transfer from Base to Ink:

```bash cURL theme={null}
curl https://api.range.org/v2/cross-chain/transfers/by-hash/0xcd367755d9d6cb6a3dac0220db8dff590dc04d0ed7d21f05e102dfdded1c66a7 \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```javascript JavaScript theme={null}
const txHash =
  "0xcd367755d9d6cb6a3dac0220db8dff590dc04d0ed7d21f05e102dfdded1c66a7";

const response = await fetch(
  `https://api.range.org/v2/cross-chain/transfers/by-hash/${txHash}`,
  { headers: { Authorization: "Bearer YOUR_API_KEY" } }
);
const data = await response.json();
```

```python Python theme={null}
import requests

tx_hash = "0xcd367755d9d6cb6a3dac0220db8dff590dc04d0ed7d21f05e102dfdded1c66a7"

response = requests.get(
    f"https://api.range.org/v2/cross-chain/transfers/by-hash/{tx_hash}",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
)
data = response.json()
```

> **Example Response**
>
> ```json theme={null}
> {
>   "items": [
>     {
>       "id": "YmFzZS9pbmsvNjIzMzkzNg",
>       "protocol": "across",
>       "status": "SUCCEEDED",
>       "time": "2026-09-22T11:02:37.000Z",
>       "matched_leg": "destination",
>       "source": {
>         "network": "base",
>         "tx_hash": "0x9539f73f932974f9c1cc50658324a3007850ca8b944570f21038d07937e36175",
>         "address": "0x4e0fdbb660635e76ed66799cd6505372a960cfd3",
>         "time": "2026-09-22T11:02:37.000Z",
>         "asset": { "symbol": "WETH", "amount": 0.0002, "usd": 0.543358 }
>       },
>       "destination": {
>         "network": "ink",
>         "tx_hash": "0xcd367755d9d6cb6a3dac0220db8dff590dc04d0ed7d21f05e102dfdded1c66a7",
>         "address": "0x4e0FdBb660635e76ED66799Cd6505372A960cfD3",
>         "time": "2026-09-22T11:02:41.000Z",
>         "asset": { "symbol": "WETH", "amount": 0.000199803952048095, "usd": 0.542825 }
>       }
>     }
>   ],
>   "meta": { "next_cursor": null, "page_number": 1 }
> }
> ```

The send hash, `0x9539f73f932974f9c1cc50658324a3007850ca8b944570f21038d07937e36175`, returns the same transfer with `matched_leg` set to `source`.

### Key Response Fields

| Field | What It Tells You |
| - | - |
| `items` | Matching transfers, newest first, 100 per page. Usually one. Empty when no cross-chain transfer for this hash is indexed. |
| `meta.next_cursor` | Cursor for the next page, or `null` when this page holds the rest. Pass it back as `cursor` with the same hash. |
| `id` | Stable transfer ID. [Get token transfer by ID](https://docs.range.org/api-reference/token-transfers/get-token-transfer-by-id.md) accepts it, except on a `DEPOSITED` item, which is not a transfer. |
| `protocol` | The protocol that carried the transfer, for example `across`. |
| `status` | Settlement status. See [Statuses](#statuses). |
| `time` | When the transfer started, in UTC. |
| `matched_leg` | Which leg your hash is: `source`, `destination` or `both`. The other leg is the counterpart. `both` means the record uses one transaction for both legs. A Circle Gateway deposit hash matches as `source`. |
| `source` / `destination` | Each leg's `network`, `tx_hash`, `address`, `time` and `asset` (`symbol`, `amount`, `usd`). |
| `source.time` / `destination.time` | Each leg's own onchain time. `destination.time` is when the funds arrived and is `null` until the delivery is observed. |
| `destination.asset` | What was actually delivered, read from the protocol's record and never copied from the sent side. A cross-chain swap shows the new asset. Present only for a `SUCCEEDED` transfer whose record carries a delivered amount, otherwise `null`. |

Any `destination` field is `null` while it is not known. `destination` itself is `null` when nothing is known about where the funds land, for example an `UNPAIRED` deposit.

### Circle Gateway Deposits

For Circle Gateway, the hash of the deposit into Gateway also works. It returns the transfers that deposit funded, matched as `source` and listed in `funding_deposit_tx_hashes`; `source.tx_hash` is still the transfer's own burn. While a deposit is not wholly linked to transfers, the answer also holds a `DEPOSITED` item for the deposit itself: its `source` leg is the deposit and its `destination` is `null`. Its `gateway_deposit.state` is `unspent`, `partly_linked`, `withdrawn` or `unlinked`, and `gateway_deposit.reason` says why an `unlinked` deposit cannot be tied to a transfer. `DEPOSITED` items appear only in this lookup, never in `GET /v2/transfers`, and coverage does not count them.

EVM hashes match in any letter case. Hashes on other networks match exactly as the chain shows them.

Nearly every hash belongs to one transfer. An IBC relayer transaction can deliver many transfers under one hash; follow `meta.next_cursor` until it is `null` to read them all:

```bash theme={null}
curl "https://api.range.org/v2/cross-chain/transfers/by-hash/$TX_HASH?cursor=$NEXT_CURSOR" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

***

## 2. Check Coverage

[Get cross-chain coverage](https://docs.range.org/api-reference/crosschain-transfers/get-crosschain-coverage) takes no parameters. It lists every protocol the lookup can return and the networks it is indexed on, as a source or a destination.

```bash cURL theme={null}
curl https://api.range.org/v2/cross-chain/coverage \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```javascript JavaScript theme={null}
const response = await fetch("https://api.range.org/v2/cross-chain/coverage", {
  headers: { Authorization: "Bearer YOUR_API_KEY" },
});
const data = await response.json();
```

```python Python theme={null}
import requests

response = requests.get(
    "https://api.range.org/v2/cross-chain/coverage",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
)
data = response.json()
```

> **Example Response (Abbreviated, Illustrative Dates)**
>
> ```json theme={null}
> {
>   "items": [
>     {
>       "protocol": "across",
>       "networks": [
>         {
>           "network": "base",
>           "covered_from": "2023-06-01T00:00:00.000Z",
>           "indexed_through": "2026-10-21T09:30:00.000Z"
>         }
>       ]
>     }
>   ]
> }
> ```

| Field | What It Tells You |
| - | - |
| `covered_from` | The oldest transfer held for this protocol on this network. Older transfers are not in the index. |
| `indexed_through` | The newest transfer held. A transfer after it may not be indexed yet, so look it up again later. A value far in the past means the pair is no longer indexed. |

Coverage is read from the same index as the lookup, so the two always agree. It refreshes every 10 minutes. Protocol and network pairs with only a handful of records are left out.

Coverage lists networks, not contract addresses. On solver-based protocols such as Relay, the delivery is paid from a solver's own wallet and does not touch the protocol's contract, so a filter on contract addresses skips delivery hashes. Send every unlinked hash on a covered network: a miss costs one call and returns an empty `items`.

***

## Statuses

| Status | Meaning |
| - | - |
| `SUCCEEDED` | Both legs observed. |
| `PENDING` | Sent, delivery not observed yet. Look it up again later. |
| `UNPAIRED` | The send is indexed but its delivery has not been matched. |
| `REFUNDED` | Funds returned to the sender. There is no delivered asset. |
| `TIMEOUT` | Delivery did not complete on the destination network. |
| `ERROR_ON_DESTINATION` | Delivery did not complete on the destination network. |
| `DEPOSITED` | A Circle Gateway deposit not wholly linked to a transfer. See [Circle Gateway Deposits](#circle-gateway-deposits). |

Treat any status not listed here as unsettled.

***

## Errors

| HTTP Code | Meaning | What to Do |
| -: | - | - |
| **400** | `tx_hash` is empty, longer than 128 characters or contains characters other than letters and digits. | Send the hash as the chain shows it. |
| **401** | Missing or invalid API key. | Check your `Authorization: Bearer` header. See [Authentication](https://docs.range.org/data-api/authentication.md). |
| **429** | Too many requests. | Back off and retry. See [Rate Limits](https://docs.range.org/data-api/authentication.md#rate-limits). |
| **503** | Cross-chain data is temporarily unavailable. | Retry with exponential backoff. |

The 503 body:

```json theme={null}
{
  "message": "Cross-chain data is temporarily unavailable",
  "error": "Service Unavailable",
  "statusCode": 503
}
```

***

## When to Use GET /v2/transfers Instead

[Get token transfers](https://docs.range.org/api-reference/token-transfers/get-token-transfers.md) (`GET /v2/transfers?tx_hash=`) is the general transfer search: it filters by address, network, date and more, paginates and returns any transfer. Use the cross-chain lookup when you hold one hash of a bridge or cross-chain swap and need the other leg and what was actually delivered.

***

## Recommended Integration

**1. Load coverage**

Call `GET /v2/cross-chain/coverage` and keep the result. It refreshes every 10 minutes, so calling it more often returns the same answer.

**2. Look up every unlinked hash on a covered network**

Call `GET /v2/cross-chain/transfers/by-hash/{tx_hash}`. Do not pre-filter by contract address.

**3. Read the counterpart**

Use `matched_leg` to find the other leg. Read `destination.asset` for what was delivered and `destination.time` for when. If `meta.next_cursor` is set, fetch the next page before matching: the transfer you need may be on it.

**4. Retry unsettled transfers**

For `PENDING` or `UNPAIRED`, look the hash up again later. Lookups may be served from cache for up to 60 seconds.

**5. Explain an empty result**

Compare the transaction with coverage. If its network is not listed, or it is older than `covered_from`, it is outside coverage. If it is newer than `indexed_through`, look it up again later. Otherwise, the index holds no cross-chain transfer for it.

***

## Reference

> **[Get cross-chain transfers by hash](https://docs.range.org/api-reference/crosschain-transfers/get-crosschain-transfers-by-hash)**
>
> Full parameter and response schema for the lookup.

> **[Get cross-chain coverage](https://docs.range.org/api-reference/crosschain-transfers/get-crosschain-coverage)**
>
> Full response schema for coverage.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.