<!--
Sitemap:
- [Interop SDK Documentation](/index): Build multichain TypeScript apps with interoperable addresses, cross-chain quotes, transfer execution, and order tracking across providers.
- [Install Interop SDK](/installation): Install @wonderland/interop-addresses and @wonderland/interop-cross-chain, then choose the package that fits your multichain app.
- [Addresses Module](/addresses/): Parse, encode, and resolve chain-aware addresses that combine an account, chain, and optional ENS name into one unambiguous identifier.
- [Addresses: Getting Started](/addresses/getting-started): Parse an interoperable address and extract its components — go from a human-readable name like vitalik.eth@eip155:1 to address and chain ID.
- [Addresses: Concepts](/addresses/concepts): How EIP-7930, ERC-7828, and CAIP-350 combine address and chain into a single, unambiguous, validated identifier for multichain apps.
- [Parsing an Interoperable Name](/addresses/example): Walkthrough for wallet developers — extract the raw address and chain ID from an interop address so legacy contracts keep working.
- [Addresses: Advanced Usage](/addresses/advanced-usage): Advanced patterns for interoperable addresses — checksums, validation, error handling, and round-trip conversions between formats.
- [Addresses: API Reference](/addresses/api): Function signatures and types for the interop-addresses package — high-level helpers, name-layer methods, and binary-layer primitives.
- [Cross-Chain Module](/cross-chain/): One open source integration for cross-chain actions — compare quotes across providers, execute orders, and track completion.
- [Cross-Chain: Getting Started](/cross-chain/getting-started): Execute a cross-chain token transfer end-to-end — create a provider, fetch a quote, send the transaction, and track the order to completion.
- [Cross-Chain: Concepts](/cross-chain/concepts): The intent-based architecture behind the cross-chain package and how the SDK unifies heterogeneous bridge providers behind a common interface.
- [Supported Providers](/cross-chain/providers): Supported cross-chain providers (Across, Relay, OIF, Bungee, LiFi Intents) and how to configure each via createCrossChainProvider.
- [Across Provider](/cross-chain/across-provider): Configure and use the Across Protocol provider for cross-chain token transfers via the Across bridge infrastructure.
- [Relay Provider](/cross-chain/relay-provider): Configure and use the Relay Protocol provider for cross-chain transfers, including opt-in gasless execution via EIP-3009 signatures.
- [OIF Provider](/cross-chain/oif-provider): Direct integration with any OIF-compliant solver — submission modes, resource locks, and configuration for Open Intents Framework backends.
- [Bungee Provider](/cross-chain/bungee-provider): Cross-chain token transfers via Bungee — onchain transactions, gasless permit2 flow, and tier-based API access for sandbox or production.
- [LiFi Intents Provider](/cross-chain/lifi-intents-provider): Cross-chain transfers through the LI.FI intent solver marketplace, where competing solvers fulfill deposits at the best available rate.
- [Cross-Chain: Full Example](/cross-chain/example): End-to-end example of executing a cross-chain transfer — setup, quote, approval handling, transaction submission, and order tracking.
- [Cross-Chain: Frontend Integration](/cross-chain/frontend-integration): Wire the cross-chain SDK into a React/Next.js app with wagmi v2 — a useCrossChainSwap hook covering quotes, approvals, and submission.
- [Order Tracking](/cross-chain/order-tracking): Track cross-chain orders from initiation to completion via OIF OrderStatus events, with both onchain and offchain observation strategies.
- [Cross-Chain: Advanced Usage](/cross-chain/advanced-usage): Aggregator patterns for cross-chain transfers — multi-provider quote fetching, sorting strategies, timeouts, and built-in order tracking.
- [Cross-Chain: API Reference](/cross-chain/api): Function signatures and types for the interop-cross-chain package — providers, aggregator, approval service, and order tracking.
-->

# Order Tracking

The SDK includes **order tracking** to monitor cross-chain transfers from initiation to completion.

Tracking supports **two ways of observing the same lifecycle**, depending on the provider and how the order is created:

* **Onchain tracking**: derive tracking data from the origin transaction (e.g. ERC-7683 open event), then watch the fill on the destination chain.
* **Offchain tracking**: query a provider API for order state transitions (e.g. polling an "order status / deposit status" endpoint).

## Overview

The `OrderTracker` streams updates using the full OIF `OrderStatus` set (from `@openintentsframework/oif-specs`). Not every provider emits every status — the table below shows which statuses each provider actually produces:

| Status      | Description                    | Across | Relay | OIF | Bungee | LiFi Intents |
| ----------- | ------------------------------ | ------ | ----- | --- | ------ | ------------ |
| `Created`   | Order created on-chain         | —      | —     | ✓   | —      | —            |
| `Pending`   | Awaiting execution             | ✓      | ✓     | ✓   | ✓      | ✓            |
| `Executing` | Filler is processing the order | —      | ✓     | ✓   | ✓      | —            |
| `Executed`  | Fill transaction submitted     | —      | —     | ✓   | —      | —            |
| `Settling`  | Settlement in progress         | —      | ✓     | —   | —      | ✓            |
| `Settled`   | Settlement complete (reserved) | —      | —     | —   | —      | —            |
| `Finalized` | Order fully complete           | ✓      | ✓     | ✓   | ✓      | ✓            |
| `Failed`    | Order failed                   | ✓      | ✓     | ✓   | ✓      | ✓            |
| `Refunded`  | Funds returned to sender       | ✓      | ✓     | ✓   | ✓      | —            |

You can subscribe to **any** `OrderStatus` via `tracker.on(OrderStatus.<status>, ...)` — the examples below show the most common ones.

In addition, the tracker can emit:

* `Timeout` - the SDK stopped watching (the order may still finalize before its onchain deadline)
* `Error` - an unexpected error occurred

## Which Tracker API to Use?

The SDK exposes three ways to track an order. Choose based on your use case:

| API                           | How to call      | Input                                                                                                                    | Use when                                                                                                                                              |
| ----------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `aggregator.track()`          | Event-emitter    | `txHash` + `providerId` + `originChainId` + `destinationChainId`                                                         | You have a tx hash and want real-time events                                                                                                          |
| `tracker.watchOrder()`        | Async generator  | (`txHash` + `originChainId`) or (`orderId` + `originChainId` + `destinationChainId`) or (both, with optional `tracking`) | You need `orderId`-based tracking (required for signature-based/gasless orders), want to choose tracking method explicitly, or prefer async iteration |
| `aggregator.getOrderStatus()` | One-shot promise | `txHash` + `providerId` + `originChainId`                                                                                | You only need the current status without streaming                                                                                                    |

:::warning
`watchOrder` supports three param shapes:

* `WatchOrderByTxHash` — pass `txHash` only. Uses on-chain event tracking.
* `WatchOrderByOrderId` — pass `orderId` only. Uses API-based tracking. Required for signature-based/gasless orders that don't have a `txHash` at open time.
* `WatchOrderExplicit` — pass both `txHash` and `orderId`, plus an optional `tracking: 'on-chain' | 'api'` field. Defaults to `'api'` when not specified.

:::

:::info\[Mixed-step orders]
Some providers may return mixed-step orders (containing both transaction steps and signature steps) in a single order. When an `approvalService` is wired in, the SDK may also prepend approval `TransactionStep`s to signature-based orders, so consumers should iterate `order.steps` in order and handle each step by its `kind`.
:::

## Basic Usage

The recommended way to track orders is through the `Aggregator`, which handles tracker creation and caching automatically:

```ts twoslash
import {
    createAggregator,
    createCrossChainProvider,
    OrderTrackerFactory,
} from "@wonderland/interop-cross-chain";

const acrossProvider = createCrossChainProvider("across", { isTestnet: true });

const aggregator = createAggregator({
    providers: [acrossProvider],
    trackerFactory: new OrderTrackerFactory({
        rpcUrls: {
            11155111: "https://sepolia.infura.io/v3/YOUR_API_KEY",
            84532: "https://base-sepolia.g.alchemy.com/v2/YOUR_API_KEY",
        },
    }),
});
```

### Tracking an Order

After sending the transaction, use `aggregator.track()` for real-time updates:

```typescript
import {
    getTransactionSteps,
    OrderStatus,
    OrderTrackerEvent,
} from "@wonderland/interop-cross-chain";

const quote = response.quotes[0];
const step = getTransactionSteps(quote.order)[0];
const hash = await walletClient.sendTransaction({
    to: step.transaction.to,
    data: step.transaction.data,
    value: step.transaction.value ? BigInt(step.transaction.value) : undefined,
});

const tracker = aggregator.track({
    txHash: hash,
    providerId: quote.provider,
    originChainId: 11155111,
    destinationChainId: 84532,
    timeout: 300000, // 5 minutes
});

tracker.on(OrderStatus.Pending, (update) => console.log("Pending:", update.message));
tracker.on(OrderStatus.Finalized, (update) => {
    console.log("Finalized!", update.fillTxHash);
    if (update.warnings?.length) {
        // e.g. destination swap failed, user received fallback token
        console.warn("Warnings:", update.warnings);
    }
});
tracker.on(OrderStatus.Failed, (update) => console.log("Failed:", update.failureReason));
tracker.on(OrderStatus.Refunded, () => console.log("Refunded"));
tracker.on(OrderTrackerEvent.Timeout, (payload) => console.log("Timeout:", payload.message));
tracker.on(OrderTrackerEvent.Error, (error) => console.error("Error:", error));
```

### Getting Current Status

Check the current status of an order without watching:

```typescript
const status = await aggregator.getOrderStatus({
    txHash: "0xabc...",
    providerId: "across",
    originChainId: 11155111,
});

console.log(status.status); // OrderStatus
console.log(status.orderId); // Order ID

if (status.fillEvent) {
    console.log(`Filled by: ${status.fillEvent.relayer}`);
    console.log(`Fill tx: ${status.fillEvent.fillTxHash}`);
    if (status.warnings?.length) {
        // e.g. destination swap failed, user received fallback token
        console.warn("Warnings:", status.warnings);
    }
}
```

## Provider Notes (Across)

* **Mainnet**: Across uses **API-based fill tracking** by default (polls `GET /deposit/status?depositTxnRef=...`). This reduces reliance on destination-chain RPCs.
* **Testnet**: Across uses **event-based fill tracking** by default (Across testnet API is not reliable), so you should provide RPC URLs for both origin and destination chains.

## Provider Notes (Relay)

* Relay uses **API-based tracking** for both mainnet and testnet. Both opened intent parsing and fill watching use the `/intents/status/v3` endpoint.
* **No RPC URLs required** — all tracking is done through the Relay API.
* Transaction notification is **automatic** — when tracking starts, the pre-tracker calls `POST /transactions/index` to accelerate solver indexing. No manual step required.

```typescript
const originChainId = 11155111;
const destinationChainId = 84532;

const hash = await walletClient.sendTransaction({ ... });

// Start tracking — Relay is automatically notified via the pre-tracker
const tracker = aggregator.track({
    txHash: hash,
    providerId: quote.provider,
    originChainId,
    destinationChainId,
});
```

## Advanced: Standalone Tracker

For advanced use cases, you can create a tracker directly without using the aggregator:

```typescript
import { createCrossChainProvider, createOrderTracker } from "@wonderland/interop-cross-chain";

const acrossProvider = createCrossChainProvider("across", { isTestnet: true });

const tracker = createOrderTracker(acrossProvider, {
    rpcUrls: {
        11155111: "https://sepolia.infura.io/v3/YOUR_API_KEY",
        84532: "https://base-sepolia.g.alchemy.com/v2/YOUR_API_KEY",
    },
});
```

### Watching an Order

Watch an order with real-time updates using an async generator:

```typescript
import { OrderStatus, OrderTrackerYieldType } from "@wonderland/interop-cross-chain";

for await (const item of tracker.watchOrder({
    txHash: "0xabc...",
    originChainId: 11155111,
    destinationChainId: 84532,
    timeout: 300000, // 5 minutes
})) {
    if (item.type === OrderTrackerYieldType.Timeout) {
        console.log(`Timeout: ${item.payload.message}`);
        break;
    }

    console.log(`Status: ${item.update.status}`);
    console.log(`Message: ${item.update.message}`);

    if (item.update.status === OrderStatus.Finalized) {
        console.log(`Filled in tx: ${item.update.fillTxHash}`);
        const url = item.update.explorers?.tracker ?? item.update.explorers?.origin;
        if (url) console.log(`Track at: ${url}`);
        break;
    } else if (item.update.status === OrderStatus.Failed) {
        console.log("Order failed");
        break;
    }
}
```

### Custom Public Client

You can also provide a custom viem PublicClient:

```typescript
import { createOrderTracker } from "@wonderland/interop-cross-chain";
import { createPublicClient, http } from "viem";
import { sepolia } from "viem/chains";

const publicClient = createPublicClient({
    chain: sepolia,
    transport: http("https://sepolia.infura.io/v3/YOUR_API_KEY"),
});

const tracker = createOrderTracker(acrossProvider, {
    publicClient,
});
```

## Error Handling

The tracker handles errors gracefully:

```typescript
import { OrderTrackerYieldType } from "@wonderland/interop-cross-chain";

try {
    for await (const item of tracker.watchOrder({
        txHash: "0x...",
        originChainId: 11155111,
        destinationChainId: 84532,
    })) {
        if (item.type === OrderTrackerYieldType.Timeout) {
            // SDK stopped watching; order may still finalize before onchain deadline
            break;
        }

        // Handle item.update
    }
} catch (error) {
    if (error instanceof Error) {
        console.error("Tracking error:", error.message);
    }
}
```

## Best Practices

1. Always set an appropriate timeout for watching
2. Handle all `OrderStatus` updates appropriately in your UI
3. Use `getOrderStatus()` for one-time checks instead of watching
4. Provide custom RPC URLs for better reliability (origin chain always; destination chain for event-based fill tracking)
5. Treat `timeout` as non-terminal (the order can still finalize onchain)

## Next Step

Explore more complex scenarios: [Advanced Usage](/cross-chain/advanced-usage)

## References

* [ERC-7683: Cross-Chain Intents](https://www.erc7683.org/)
* [Open Intents Framework](https://github.com/openintentsframework)
* [Order Tracking Types](/cross-chain/api#order-tracker)
* [Concepts](/cross-chain/concepts) — how intent-based transfers and tracking work
