Intent Tracking
The Intent Tracking system allows you to monitor cross-chain transfers from initiation to completion, following the EIP-7683 standard. This feature provides real-time status updates as intents progress through their lifecycle.
Overview
The Intent Tracker monitors cross-chain transfers through their complete lifecycle:
- Opening - Transaction submitted, waiting for confirmation
- Opened - Open event detected on origin chain
- Filling - Waiting for fill on destination chain
- Filled - Transfer completed successfully
- Expired - Transfer deadline exceeded
Basic Usage
The recommended way to track intents is through the ProviderExecutor, which handles tracker creation and caching automatically:
import {
createCrossChainProvider,
createProviderExecutor,
IntentTrackerFactory,
} from "@wonderland/interop-cross-chain";
const acrossProvider = createCrossChainProvider(
"across",
{ apiUrl: "https://testnet.across.to/api" },
{},
);
const executor = createProviderExecutor({
providers: [acrossProvider],
trackerFactory: new IntentTrackerFactory({
rpcUrls: {
11155111: "https://sepolia.infura.io/v3/YOUR_API_KEY",
84532: "https://base-sepolia.g.alchemy.com/v2/YOUR_API_KEY",
},
}),
});
Tracking an Intent
After executing a transaction, use executor.track() for real-time updates:
const quote = response.quotes[0];
const hash = await walletClient.sendTransaction(quote.preparedTransaction);
const tracker = executor.track({
txHash: hash,
providerId: quote.provider,
originChainId: 11155111,
destinationChainId: 84532,
timeout: 300000, // 5 minutes
});
tracker.on("opening", (update) => console.log("Opening..."));
tracker.on("opened", (update) => console.log("Opened:", update.orderId));
tracker.on("filling", (update) => console.log("Waiting for fill..."));
tracker.on("filled", (update) => console.log("Filled!", update.fillTxHash));
tracker.on("expired", (update) => console.log("Transfer expired"));
tracker.on("error", (error) => console.error("Error:", error));
Getting Current Status
Check the current status of an intent without watching:
const status = await executor.getIntentStatus({
txHash: "0xabc...",
providerId: "across",
originChainId: 11155111,
});
console.log(status.status); // 'opening' | 'opened' | 'filling' | 'filled' | 'expired'
console.log(status.orderId); // Order ID
console.log(status.inputAmount); // Input amount (bigint)
console.log(status.outputAmount); // Output amount (bigint)
if (status.fillEvent) {
console.log(`Filled by: ${status.fillEvent.relayer}`);
console.log(`Fill tx: ${status.fillEvent.fillTxHash}`);
}
Intent Status Types
Opening
The transaction has been submitted but the Open event hasn't been detected yet.
{
status: 'opening',
message: 'Transaction submitted, waiting for confirmation...'
}
Opened
The Open event has been detected on the origin chain. The intent is now waiting to be filled.
{
status: 'opened',
orderId: '0x...',
openTxHash: '0x...',
timestamp: 1234567890,
message: 'Intent opened with orderId 0x...'
}
Filling
The intent is actively being filled on the destination chain.
{
status: 'filling',
orderId: '0x...',
openTxHash: '0x...',
timestamp: 1234567890,
message: 'Waiting for relayer to fill intent...'
}
Filled
The transfer has been completed successfully.
{
status: 'filled',
orderId: '0x...',
openTxHash: '0x...',
fillTxHash: '0xdef...',
timestamp: 1234567890,
message: 'Intent filled in block 12345'
}
Expired
The transfer deadline has been exceeded.
{
status: 'expired',
orderId: '0x...',
openTxHash: '0x...',
timestamp: 1234567890,
message: 'Intent expired before fill'
}
Advanced: Standalone Tracker
For advanced use cases, you can create a tracker directly without using the executor:
import { createCrossChainProvider, createIntentTracker } from "@wonderland/interop-cross-chain";
const acrossProvider = createCrossChainProvider(
"across",
{ apiUrl: "https://testnet.across.to/api" },
{},
);
const tracker = createIntentTracker(acrossProvider, {
rpcUrls: {
11155111: "https://sepolia.infura.io/v3/YOUR_API_KEY",
84532: "https://base-sepolia.g.alchemy.com/v2/YOUR_API_KEY",
},
});
Watching an Intent
Watch an intent with real-time updates using an async generator:
for await (const update of tracker.watchIntent({
txHash: "0xabc...",
originChainId: 11155111,
destinationChainId: 84532,
timeout: 300000, // 5 minutes
})) {
console.log(`Status: ${update.status}`);
console.log(`Message: ${update.message}`);
if (update.status === "filled") {
console.log(`Filled in tx: ${update.fillTxHash}`);
break;
} else if (update.status === "expired") {
console.log("Transfer expired");
break;
}
}
Custom Public Client
You can also provide a custom viem PublicClient:
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 = createIntentTracker(acrossProvider, {
publicClient,
});
Error Handling
The Intent Tracker handles errors gracefully:
try {
for await (const update of tracker.watchIntent({
txHash: "0x...",
originChainId: 11155111,
destinationChainId: 84532,
})) {
// Handle updates
}
} catch (error) {
if (error instanceof Error) {
console.error("Tracking error:", error.message);
}
}
Best Practices
- Always set an appropriate timeout for intent watching
- Handle all status types appropriately in your UI
- Use
getIntentStatus()for one-time checks instead of watching - Provide custom RPC URLs for better reliability
- Monitor for expired intents and handle them appropriately
Next Step
Explore more complex scenarios: Advanced Usage