<!--
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.
-->

# Cross-Chain: Advanced Usage

## Aggregator

For complex scenarios, use the Aggregator to manage multiple providers with sorting, timeout handling, and built-in order tracking.

### Minimal Setup

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

const acrossProvider = createCrossChainProvider(PROTOCOLS.ACROSS, { isTestnet: true });

const aggregator = createAggregator({
    providers: [acrossProvider],
});
```

### Full Configuration

```typescript
import type { QuoteRequest } from "@wonderland/interop-cross-chain";
import {
    createAggregator,
    createCrossChainProvider,
    OrderTrackerFactory,
    PROTOCOLS,
    SortingStrategyFactory,
} from "@wonderland/interop-cross-chain";

const acrossProvider = createCrossChainProvider(PROTOCOLS.ACROSS, { isTestnet: true });

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

// Get quotes from all providers
const response = await aggregator.getQuotes({
    user: "0xYourAddress",
    input: {
        chainId: 11155111,
        assetAddress: "0xInputToken",
        amount: "1000000000000000000",
    },
    output: {
        chainId: 84532,
        assetAddress: "0xOutputToken",
        recipient: "0xRecipient",
    },
    swapType: "exact-input",
});

// Handle results
if (response.quotes.length > 0) {
    const bestQuote = response.quotes[0];
    console.log(`Best quote from ${bestQuote.provider}`);
}

response.errors.forEach((error) => {
    console.error(`Error: ${error.errorMsg}`);
});
```

For more details on the Aggregator configuration, see the [API Reference](/cross-chain/api#aggregator).

## Automatic ERC-20 Approvals

When ERC-20 inputs need an `approve` before the transfer, the aggregator can handle it for you. Pass an `approvalService` to `createAggregator` and every quote it returns already has the necessary `approve` transactions (`TransactionStep`s with `category: "approval"`) prepended to `order.steps`. If the user already holds sufficient allowance, nothing is prepended.

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

const relayProvider = createCrossChainProvider(PROTOCOLS.RELAY);

const approvalService = createApprovalService({
    rpcUrls: {
        1: "https://mainnet.infura.io/v3/YOUR_API_KEY",
        8453: "https://base-mainnet.g.alchemy.com/v2/YOUR_API_KEY",
    },
});

const aggregator = createAggregator({
    providers: [relayProvider],
    approvalService,
});

// Every quote returned now has approval steps prepended when needed.
const response = await aggregator.getQuotes({
    /* ... */
});
```

### Picking a client source

`createApprovalService` has three ways to reach the chain:

* **No config** (`createApprovalService()`) — the service falls back to viem's default public transport for each chain. Fine for quick experiments; public endpoints are rate-limited and should not be used in production.
* **`rpcUrls`** — a `Record<number, string>` keyed by chain ID. The right choice for multichain apps: the service reads allowances on the input chain of each quote, so every origin chain you intend to bridge from needs an entry.
* **`publicClient`** — a pre-configured viem `PublicClient`. Takes precedence over `rpcUrls` and is used for **every** chain the service is asked about, so it only makes sense when every quote originates on the same chain (e.g. a checkout that only accepts USDC on mainnet). Passing a single-chain client into a multichain service will send `multicall` traffic for the wrong network.

Because approval steps live inside the normal `order.steps` array, your execution loop does not need a separate approval code path — iterate the steps in order and each `approve` fires before the transfer step that needs it.

Approval steps live in `order.steps` as regular `TransactionStep`s tagged with `category: "approval"`. `getTransactionSteps(order)` returns every user-submittable on-chain step (transfers and approvals, in emission order) so your execution loop stays a single `for` and the approval fires before the transfer that needs it. Use `isApprovalStep(step)` only when you want to surface a different UI state:

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

for (const step of getTransactionSteps(quote.order)) {
    console.log(isApprovalStep(step) ? "Approving token…" : "Sending bridge tx…");

    const hash = await walletClient.sendTransaction({
        to: step.transaction.to,
        data: step.transaction.data,
        value: step.transaction.value ? BigInt(step.transaction.value) : undefined,
    });
    await publicClient.waitForTransactionReceipt({ hash });
}
```

### Choosing an amount strategy

`ApprovalAmountStrategy` decides the amount encoded in each generated `approve`. Two implementations ship with the SDK:

* **`ExactAmountStrategy`** (default): approves exactly `required`. Smallest allowance footprint — one `approve` per order against the same `(token, spender)` pair.
* **`InfiniteAmountStrategy`**: approves `type(uint256).max`. The first order grants an unbounded allowance and later orders for the same pair skip the approval step entirely, at the cost of an unbounded allowance to the spender.

If your app accepts Permit2-based gasless flows (OIF `oif-escrow-v0`, Bungee gasless), prefer `InfiniteAmountStrategy`: Permit2 pulls tokens via the ERC-20 allowance every transfer, so `ExactAmountStrategy` would require re-approving Permit2 on every order and defeat the point of the gasless path.

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

const approvalService = createApprovalService({
    rpcUrls,
    amountStrategy: new InfiniteAmountStrategy(),
});
```

You can also supply your own strategy by implementing the `ApprovalAmountStrategy` interface — for example, a capped-allowance strategy that approves `min(required * 10, cap)`.

### Failure handling

The service is best-effort. Allowance reads run through `MulticallAllowanceReader`, which batches one `multicall` per chain so a failure on one chain never affects reads on another. When a whole batch fails (RPC down, chain unknown to viem), the affected quotes pass through unmodified. Without an `approvalService`, aggregator output is unchanged.

Pass a `failureHandler` to observe batch failures. Defaults to `console.warn`; pass `{ handle: () => {} }` to silence, or your own implementation to route failures into telemetry.

### Provider coverage

The approval service can only enrich a quote whose provider declares its allowance requirements in `order.checks.allowances`. Coverage across shipped providers:

* **Across, LiFi Intents, OIF `oif-user-open-v0`** — always populated for ERC-20 inputs.
* **Relay, Bungee** — populated whenever the API flags an approve as needed (including the one-time Permit2 approval for Bungee's gasless path). Omitted when the API considers no approve required.
* **OIF `oif-escrow-v0`** (Permit2-based gasless) — populated: the adapter parses the EIP-712 payload and surfaces a Permit2 allowance entry so the first-time `approve(PERMIT2, ...)` is prepended automatically. Pair with `InfiniteAmountStrategy` (see above) to avoid re-approving on every order, since Permit2 consumes the ERC-20 allowance on each pull.
* **OIF `oif-3009-v0`, `oif-resource-lock-v0`** — not populated, and correctly so: EIP-3009 and resource-lock flows don't use ERC-20 `approve`.

For the full API reference see [Approval Service](/cross-chain/api#approval-service).

## Order Tracking

The aggregator includes built-in order tracking when configured with a `trackerFactory`. After executing a transaction, use `aggregator.track()` to monitor the cross-chain transfer:

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

// Execute the transaction
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,
});

// Track with real-time events
const tracker = aggregator.track({
    txHash: hash,
    providerId: quote.provider, // e.g., "across"
    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));
tracker.on(OrderStatus.Failed, (update) => console.log("Failed:", update.failureReason));
tracker.on(OrderTrackerEvent.Timeout, (payload) => console.log("Timeout:", payload.message));
tracker.on(OrderTrackerEvent.Error, (error) => console.error("Tracking error:", error));
```

### One-Time Status Check

For a simple status check without event-based tracking:

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

console.log(status.status); // OrderStatus
if (status.fillEvent) {
    console.log(`Filled by: ${status.fillEvent.relayer}`);
}
```

For more details, see [Order Tracking](/cross-chain/order-tracking).

## Exact-input vs. exact-output swaps

`QuoteRequest.swapType` decides which side of the transfer is fixed:

* **`"exact-input"`** (default): you fix `input.amount`. The provider quotes the output amount the user will receive after fees and slippage. Right when the user starts from a known balance — "send 100 USDC across, whatever lands is fine as long as it clears slippage".
* **`"exact-output"`**: you fix `output.amount`. The provider quotes the input amount the user must supply. Right when the destination amount is what matters — paying an invoice, topping up to an exact balance, funding a contract that expects a specific amount.

Exact-output example:

```typescript
// Deliver exactly 50 USDC on Base. The provider tells us how much USDC
// we need to send from Optimism to cover fees and slippage.
const response = await aggregator.getQuotes({
    user: "0xYourAddress",
    input: {
        chainId: 10, // Optimism
        assetAddress: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85", // USDC
        // no `amount` — provider fills this in
    },
    output: {
        chainId: 8453, // Base
        assetAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC
        recipient: "0xRecipient",
        amount: "50000000", // 50 USDC (6 decimals)
    },
    swapType: "exact-output",
});

const quote = response.quotes[0];
// Read the quoted input back from the preview
console.log(`Needs ${quote.preview.inputs[0].amount} input wei`);
```

Provider coverage:

* **Across, Relay, OIF** — support both modes.
* **Bungee, LiFi Intents** — `exact-input` only. Requesting `exact-output` from these providers surfaces an error in `response.errors` (the original message is preserved via `errorMsg`); the rest of the aggregator's providers continue normally.

## Error Handling

The package includes specific error types for better error handling:

```typescript
import {
    ProviderGetQuoteFailure,
    ProviderNotFound,
    ProviderTimeout,
    UnsupportedAction,
    UnsupportedChainId,
    UnsupportedProtocol,
} from "@wonderland/interop-cross-chain";

try {
    const response = await aggregator.getQuotes({
        /* ... */
    });
} catch (error) {
    if (error instanceof UnsupportedProtocol) {
        console.error("Protocol not supported");
    } else if (error instanceof UnsupportedChainId) {
        console.error("Chain ID not supported");
    } else if (error instanceof ProviderNotFound) {
        console.error("Provider not found");
    } else if (error instanceof ProviderGetQuoteFailure) {
        console.error("Failed to get quote:", error.message);
    } else if (error instanceof ProviderTimeout) {
        console.error("Request timed out");
    }
}
```

## Best Practices

1. Always check both `quotes` and `errors` in the aggregator response
2. Quotes are sorted by best output amount by default
3. Use `OrderTracker` to monitor cross-chain transfers
4. Handle errors appropriately using the provided error types
5. Set appropriate timeouts for quote requests
6. Test your implementation on testnet before moving to production
7. Provide custom RPC URLs for better reliability

## Next steps

* [API Reference](/cross-chain/api) — complete function signatures and types
* [Concepts](/cross-chain/concepts) — understand the architecture and provider trade-offs
* [Order Tracking](/cross-chain/order-tracking) — detailed tracking patterns and provider notes
