Getting Started
This guide uses privateKeyToAccount and a plain Node.js script to keep the flow focused on the SDK. If you're wiring the SDK into a React / Next.js app with an injected wallet, jump to Frontend Integration for the wagmi-based pattern.
In this tutorial, you'll execute a cross-chain token transfer using the Interop SDK. By the end, you'll know how to create a provider, fetch a quote, send a transaction, and discover which chains and tokens are supported.
Prerequisites
- Node.js v20.x or higher
- A private key with testnet funds
- An RPC URL for your origin chain (e.g., Sepolia)
Install the package
viem is a peer dependency (^2.28.0) — install it alongside the package:
npm install @wonderland/interop-cross-chain viem
# or
yarn add @wonderland/interop-cross-chain viem
# or
pnpm add @wonderland/interop-cross-chain viem
Create a provider
The SDK uses a factory pattern. Let's start with Relay on testnet:
import { createCrossChainProvider, PROTOCOLS } from "@wonderland/interop-cross-chain";
const provider = createCrossChainProvider(PROTOCOLS.RELAY, { isTestnet: true });
Examples throughout the docs use the PROTOCOLS constant (PROTOCOLS.RELAY, PROTOCOLS.ACROSS, PROTOCOLS.OIF, PROTOCOLS.BUNGEE, PROTOCOLS.LIFI_INTENTS) rather than the literal strings it maps to — it keeps the protocol name typo-safe and discoverable in your IDE.
Other available providers: Across, OIF. See Supported Providers for the full list.
Set up your wallet
You'll need viem clients to interact with the blockchain:
import { createPublicClient, createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { sepolia } from "viem/chains";
const account = privateKeyToAccount("0xYOUR_PRIVATE_KEY");
const RPC_URL = "https://sepolia.infura.io/v3/YOUR_API_KEY";
const publicClient = createPublicClient({
chain: sepolia,
transport: http(RPC_URL),
});
const walletClient = createWalletClient({
chain: sepolia,
transport: http(RPC_URL),
account,
});
Fetch a quote
Request a quote for a cross-chain transfer:
const quotes = await provider.getQuotes({
user: account.address,
input: {
chainId: 11155111, // Sepolia
assetAddress: "0xInputTokenAddress",
amount: "100000000000000000", // 0.1 tokens in wei
},
output: {
chainId: 84532, // Base Sepolia
assetAddress: "0xOutputTokenAddress",
recipient: account.address,
},
swapType: "exact-input",
});
const quote = quotes[0];
console.log(`Quote from ${quote.provider}`);
Native tokens (ETH, MATIC, etc.)
To bridge a native asset, use NATIVE_ASSET_ADDRESS as the assetAddress:
import { createCrossChainProvider, NATIVE_ASSET_ADDRESS } from "@wonderland/interop-cross-chain";
const quotes = await provider.getQuotes({
user: account.address,
input: {
chainId: 11155111, // Sepolia
assetAddress: NATIVE_ASSET_ADDRESS,
amount: "100000000000000000", // 0.1 ETH in wei
},
output: {
chainId: 84532, // Base Sepolia
assetAddress: NATIVE_ASSET_ADDRESS,
recipient: account.address,
},
swapType: "exact-input",
});
Both 0x0000000000000000000000000000000000000000 (zero address) and 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee (EIP-7528) are accepted by the SDK as native asset sentinels. NATIVE_ASSET_ADDRESS exports the EIP-7528 form (0xeeee...eeee) and is the canonical constant used in all SDK examples.
Handle ERC-20 approvals
ERC-20 inputs need an approve before the transfer step. The SDK can do this for you: wire an approvalService into the aggregator and every returned quote already has the necessary approve TransactionSteps prepended to order.steps. Iterate the steps in order and each approve fires before the step that needs it.
See Automatic ERC-20 Approvals for the setup, or the Execute Intent guide for a full runnable example.
Execute the transaction
Quotes contain either signature steps (gasless) or transaction steps (user pays gas). Handle both:
import {
getSignatureSteps,
getTransactionSteps,
isSignatureOnlyOrder,
} from "@wonderland/interop-cross-chain";
if (isSignatureOnlyOrder(quote.order)) {
// Gasless: sign EIP-712 typed data, solver executes on your behalf
const step = getSignatureSteps(quote.order)[0];
const { signatureType, ...typedData } = step.signaturePayload;
const signature = await walletClient.signTypedData(typedData);
await provider.submitOrder(quote, signature);
console.log("Order submitted via signature");
} else {
// User pays gas: send every transaction step in order.
// A quote may contain one or more transaction steps depending on the provider.
// Quotes from an `Aggregator` configured with `approvalService` may also have
// `approve` steps prepended before the transfer.
for (const step of getTransactionSteps(quote.order)) {
const hash = await walletClient.sendTransaction({
to: step.transaction.to,
data: step.transaction.data,
value: step.transaction.value ? BigInt(step.transaction.value) : undefined,
});
console.log("Transaction sent:", hash);
const receipt = await publicClient.waitForTransactionReceipt({ hash });
if (receipt.status !== "success") {
throw new Error(`Step failed: ${step.description ?? "transaction"}`);
}
console.log("Confirmed: Success");
}
}
Which chains and tokens are supported?
Rather than hard-coding a supported-tokens list, ask the aggregator at runtime. discoverAssets() queries every configured provider in parallel and returns a pre-processed map keyed by numeric chain ID:
const discovered = await aggregator.discoverAssets({ chainIds: [1, 8453] });
// Token addresses available on each chain
console.log(discovered.tokensByChain[1]); // Ethereum
console.log(discovered.tokensByChain[8453]); // Base
// Token metadata (nested by chain ID then lowercase address)
const usdc = discovered.tokenMetadata[1]?.["0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"];
console.log(usdc?.symbol, usdc?.decimals); // "USDC", 6
Omit chainIds to discover across every chain each provider supports. For a single-provider variant and the full DiscoveredAssets shape, see the API Reference.
Compare quotes from multiple providers
Use the Aggregator to fetch and sort quotes from multiple providers at once:
import {
createAggregator,
createCrossChainProvider,
PROTOCOLS,
} from "@wonderland/interop-cross-chain";
const acrossProvider = createCrossChainProvider(PROTOCOLS.ACROSS, { isTestnet: true });
const aggregator = createAggregator({
providers: [provider, acrossProvider],
});
const response = await aggregator.getQuotes({
/* same QuoteRequest as above */
});
console.log(`Got ${response.quotes.length} quotes`);
response.errors.forEach((err) => console.warn(`Provider error: ${err.errorMsg}`));
Quick reference
Execution flow
- Create provider →
createCrossChainProvider(PROTOCOLS.ACROSS)(or usecreateAggregatorfor multiple — wire anapprovalServiceto enrich quotes with ERC-20approvesteps automatically) - Get quotes →
provider.getQuotes(request)oraggregator.getQuotes(request) - Check order type →
isSignatureOnlyOrder(quote.order)- Signature (gasless):
signTypedData()→provider.submitOrder(quote, signature) - Transaction (user pays gas): iterate
getTransactionSteps(quote.order)and send each — approval steps (when present) come first (see example above — convert stringvaluetoBigInt)
- Signature (gasless):
- Track →
createOrderTracker(provider)for single-provider oraggregator.track({ txHash, providerId, originChainId, destinationChainId })for aggregator
Which function should I use?
| I want to... | Use |
|---|---|
| Get quotes from one provider | provider.getQuotes(request) |
| Get quotes from multiple providers | aggregator.getQuotes(request) |
| Build a quote locally (no provider API) | aggregator.buildQuote(providerId, request) |
| Submit a signed order | provider.submitOrder(quote, signature) |
| Check if order is gasless | isSignatureOnlyOrder(quote.order) |
| Get signature steps from an order | getSignatureSteps(quote.order) |
| Get transaction steps from an order | getTransactionSteps(quote.order) |
| Track an order (single provider) | createOrderTracker(provider) → tracker.watchOrder({ txHash, ... }) |
| Track an order (aggregator) | aggregator.track({ txHash, providerId, originChainId, ... }) |
| Discover supported tokens | aggregator.discoverAssets() |
Next steps
- Order Tracking — monitor your transfer from initiation to completion
- Supported Providers — all providers and their configuration options
- Concepts — understand intent-based architecture and EIP-7683
- Advanced Usage — sorting strategies, timeouts, error handling
- API Reference — complete function signatures and types