Documentation Index
Fetch the complete documentation index at: https://anypay-trails-api-docs.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Swap mode provides a flexible interface for cross-chain token exchanges. Users control both input and output selections, with the widget handling optimal routing across bridges and DEXs.
Trade Types: Supports both EXACT_INPUT and EXACT_OUTPUT trade types.
Configuration
Required Props
| Prop | Type | Description |
|---|
mode | "swap" | Sets the widget to swap mode |
Optional Props
| Prop | Type | Description |
|---|
fromChainId | number | Pre-select source chain |
fromToken | string | Pre-select source token |
toChainId | number | Pre-select destination chain |
toToken | string | Pre-select destination token |
slippageTolerance | string | number | Slippage tolerance (default: 0.5%) |
swapProvider | RouteProvider | Preferred same-chain swap provider: "AUTO" (default), "RELAY", "SUSHI", "ZEROX", "CCTP", "LZ_OFT", "LIFI", "WETH" |
swapProviderFallback | boolean | When true, fall back to another provider if the preferred swap provider is unavailable |
bridgeProvider | RouteProvider | Preferred bridge provider: "AUTO" (default), "RELAY", "CCTP", "LZ_OFT", "LIFI", "WETH" |
bridgeProviderFallback | boolean | When true, fall back to another provider if the preferred bridge provider is unavailable |
Minimal configuration - users select everything:
import { TrailsWidget } from '0xtrails/widget'
<TrailsWidget
apiKey="YOUR_API_KEY"
mode="swap"
onCheckoutComplete={({ sessionId }) => {
console.log('Swap completed:', sessionId)
}}
>
<button>Swap Tokens</button>
</TrailsWidget>
Pre-select chains and tokens:
<TrailsWidget
apiKey="YOUR_API_KEY"
mode="swap"
fromChainId={1} // Ethereum
fromToken="USDC"
toChainId={8453} // Base
toToken="ETH"
slippageTolerance="0.01" // 1%
onCheckoutComplete={({ sessionId }) => {
console.log('Swap completed:', sessionId)
}}
>
<button>Swap USDC to ETH</button>
</TrailsWidget>
Custom Route Provider
Specify routing preference:
<TrailsWidget
apiKey="YOUR_API_KEY"
mode="swap"
bridgeProvider="CCTP" // Use Circle's CCTP for USDC cross-chain
swapProvider="SUSHI" // Use SushiSwap for on-chain swaps
onCheckoutComplete={({ sessionId }) => {
console.log('Swap completed:', sessionId)
}}
>
<button>Swap via CCTP</button>
</TrailsWidget>
Headless Implementation with useQuote
For custom UI implementations, use the useQuote hook directly:
Basic useQuote Implementation
import { useQuote, TradeType } from '0xtrails'
import { useWalletClient, useAccount } from 'wagmi'
import { useState, useEffect } from 'react'
export const CustomSwap = () => {
const { data: walletClient } = useWalletClient()
const { address } = useAccount()
const { quote, swap, isLoadingQuote, quoteError, refetchQuote } = useQuote({
walletClient,
fromTokenAddress: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC on Ethereum
fromChainId: 1,
toTokenAddress: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', // USDC on Base
toChainId: 8453,
swapAmount: '1000000', // 1 USDC (6 decimals)
tradeType: TradeType.EXACT_INPUT,
toRecipient: address,
slippageTolerance: '0.005', // 0.5%
onStatusUpdate: (states) => {
console.log('Transaction status:', states)
},
})
// Refresh quotes every 30 seconds
useEffect(() => {
const interval = setInterval(() => {
refetchQuote?.()
}, 30000)
return () => clearInterval(interval)
}, [refetchQuote])
const handleSwap = async () => {
if (!swap) return
try {
const result = await swap()
console.log('Swap result:', result)
} catch (error) {
console.error('Swap failed:', error)
}
}
if (isLoadingQuote) return <div>Loading quote...</div>
if (quoteError) return <div>Error: {String(quoteError)}</div>
if (!quote) return <div>No quote available</div>
return (
<div>
<h3>Quote</h3>
<p>From: {quote.originAmountFormatted} {quote.originToken.symbol}</p>
<p>To: {quote.destinationAmountFormatted} {quote.destinationToken.symbol}</p>
<p>Fee: {quote.totalFeeAmountUsdDisplay}</p>
<p>Rate: 1 {quote.originToken.symbol} = {quote.destinationTokenRate} {quote.destinationToken.symbol}</p>
<p>Est. Time: {quote.completionEstimateSeconds}s</p>
<button onClick={handleSwap}>Execute Swap</button>
</div>
)
}
Trade Types
User specifies exact input amount; output amount varies:
const { quote } = useQuote({
// ... other props
swapAmount: '100000000', // 100 USDC exactly
tradeType: TradeType.EXACT_INPUT,
})
// User sends exactly 100 USDC, receives ~0.039 ETH (varies with price)
EXACT_OUTPUT
User specifies exact output amount; input amount varies:
const { quote } = useQuote({
// ... other props
swapAmount: '100000000000000000', // 0.1 ETH exactly
tradeType: TradeType.EXACT_OUTPUT,
})
// User receives exactly 0.1 ETH, sends ~256 USDC (varies with price)
Quote Types
type UseQuoteProps = {
walletClient?: WalletClient
fromTokenAddress?: string | null
fromChainId?: number | null
toTokenAddress?: string | null
toChainId?: number | null
swapAmount?: string | bigint
toRecipient?: string | null
tradeType?: TradeType | null
slippageTolerance?: string | number | null
onStatusUpdate?: (txs: TransactionState[]) => void | null
}
type UseQuoteReturn = {
quote: Quote | null
swap: (() => Promise<SwapReturn | null>) | null
isLoadingQuote: boolean
quoteError: unknown
refetchQuote: (() => Promise<void>) | null
}
Quote Refresh Strategy
Quotes can become stale. Implement refresh logic:
// Refresh every 30 seconds
useEffect(() => {
const interval = setInterval(() => {
refetchQuote?.()
}, 30000)
return () => clearInterval(interval)
}, [refetchQuote])
// Refresh on window focus
useEffect(() => {
const handleFocus = () => refetchQuote?.()
window.addEventListener('focus', handleFocus)
return () => window.removeEventListener('focus', handleFocus)
}, [refetchQuote])
Event Handling
<TrailsWidget
apiKey="YOUR_API_KEY"
mode="swap"
onCheckoutStart={({ sessionId }) => {
console.log('Swap started:', sessionId)
}}
onCheckoutComplete={({ sessionId }) => {
console.log('Swap completed:', sessionId)
}}
onCheckoutError={({ sessionId, error }) => {
console.error('Swap failed:', error)
}}
/>
useQuote Transaction Status
const { quote, swap } = useQuote({
// ... other props
onStatusUpdate: (states) => {
states.forEach(state => {
console.log(`${state.chainId}: ${state.status}`)
// States: 'pending', 'executing', 'completed', 'failed'
})
},
})
Error Handling
import { getIsUserRejectionError, InsufficientBalanceError } from '0xtrails'
const handleSwap = async () => {
try {
await swap()
} catch (error) {
if (getIsUserRejectionError(error)) {
console.log('User rejected transaction')
} else if (error instanceof InsufficientBalanceError) {
console.error('Insufficient balance')
} else {
console.error('Swap failed:', error)
}
}
}
Use Cases
- Cross-chain Token Exchange: Swap any token to any other token across chains
- Portfolio Rebalancing: Shift holdings between chains and tokens
- Yield Optimization: Move assets to chains with better yields
- Gas Token Acquisition: Get native tokens for new chains
- Arbitrage: Take advantage of price differences across chains
Technical Notes
- Quotes automatically factor in gas costs and fees
- Multiple liquidity sources are queried for optimal routing
- Supports both same-chain and cross-chain swaps
- Native token wrapping/unwrapping is handled automatically
- Slippage protection is built-in
See Also