Documentation Index
Fetch the complete documentation index at: https://anypay-docs-sdk-0-15-0-updates.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
All hooks and utilities are available from the 0xtrails package unless stated otherwise.
TrailsProvider
Required for Hooks: The TrailsProvider must wrap your application when using any Trails hooks. This provider configures the necessary context for all hook functionality.
import { TrailsProvider } from '0xtrails'
<TrailsProvider
config={{
trailsApiKey: "...",
trailsApiUrl: "...", // optional
sequenceIndexerUrl: "...", // optional
sequenceNodeGatewayUrl: "...", // optional
intentProtocol: "v1.5", // optional — defaults to v1.5
}}
>
<App />
</TrailsProvider>
Apps without wagmi and react-query
The Trails SDK uses wagmi and @tanstack/react-query internally. If your app already sets up those providers, TrailsProvider will use them automatically.
If your app does not already include these providers, you need to add them above TrailsProvider:
'use client'
import { TrailsProvider } from '0xtrails'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { createConfig, http, WagmiProvider } from 'wagmi'
import { arbitrum, base, polygon } from 'wagmi/chains'
const wagmiConfig = createConfig({
chains: [base, polygon, arbitrum],
transports: {
[base.id]: http(),
[polygon.id]: http(),
[arbitrum.id]: http(),
},
})
const queryClient = new QueryClient()
export const Providers = ({ children }: { children: React.ReactNode }) => {
return (
<WagmiProvider config={wagmiConfig}>
<QueryClientProvider client={queryClient}>
<TrailsProvider config={{ trailsApiKey: 'YOUR_API_KEY' }}>
{children}
</TrailsProvider>
</QueryClientProvider>
</WagmiProvider>
)
}
This requirement is temporary. A future SDK release will bundle these dependencies so only TrailsProvider is needed.
Configuration Options
| Option | Type | Required | Description |
|---|
trailsApiKey | string | Yes | Your Trails API key. Get your API key from the Trails Dashboard |
| | | |
trailsApiUrl | string | No | Custom API endpoint URL if using a self-hosted instance. |
sequenceIndexerUrl | string | No | Custom Sequence indexer URL. |
sequenceNodeGatewayUrl | string | No | Custom Sequence node gateway URL. |
sequenceMetadataUrl | string | No | Custom Sequence metadata URL. |
sequenceApiUrl | string | No | Custom Sequence API URL. |
walletConnectProjectId | string | No | WalletConnect project ID for wallet connections. |
slippageTolerance | string | number | No | Default slippage tolerance for swaps (e.g., 0.005 for 0.5%). |
debug | boolean | No | Enable debug logging (default: false). |
intentProtocol | "v1" | "v1.5" | No | Override intent protocol version (default: "v1.5"). See Protocol v1.5. |
Chains
import { getSupportedChains, useSupportedChains } from '0xtrails'
getSupportedChains(): Promise<Chain[]>
useSupportedChains(): { supportedChains: Chain[], isLoadingChains: boolean }
Utility Functions
import { getChainInfo, getAllChains } from '0xtrails'
// Get info for specific chain
const chain = getChainInfo(8453) // Base chain info
// Get all chains (including unsupported)
const allChains = getAllChains()
Chain Type
type Chain = {
id: number
name: string
chainId: number
rpcUrls: string[]
nativeCurrency: {
name: string
symbol: string
decimals: number
}
blockExplorerUrls?: string[]
imageUrl?: string
}
Tokens
import { getSupportedTokens, useSupportedTokens, useTokenList } from '0xtrails'
useTokenList(): { tokens: Token[] | undefined, isLoadingTokens: boolean }
useSupportedTokens({ chainId?: number }): { supportedTokens: Token[], isLoadingTokens: boolean }
getSupportedTokens(): Promise<Token[]>
Additional Helpers
import { useTokenInfo, useTokenAddress } from '0xtrails'
// Returns basic info for an ERC-20 by address and chainId
const { tokenInfo, isLoading, error } = useTokenInfo({
address: '0x...',
chainId: 8453
})
// Get token address by symbol and chain
const tokenAddress = useTokenAddress({
chainId: 8453,
tokenSymbol: 'USDC'
})
Token Type
The unified Token type is used throughout the Trails SDK:
type Token = {
// Required properties
symbol: string
name: string
decimals: number
contractAddress: string // Use zeroAddress for native tokens
// Optional properties
tokenId?: string
chainId?: number
chainName?: string
imageUrl?: string
isCustomToken?: boolean
isSufficientBalance?: boolean
isNativeToken?: boolean
// Balance fields (raw → formatted → display)
balance?: string // Raw big number string (e.g., "1000000000000000000")
balanceFormatted?: string // Formatted number without commas (e.g., "1.0")
balanceDisplay?: string // Formatted with commas (e.g., "1,000.00")
// USD Balance fields
balanceUsd?: number // Raw number (e.g., 1000.50)
balanceUsdFormatted?: string // Without commas/symbol (e.g., "1000.50")
balanceUsdDisplay?: string // With commas/symbol (e.g., "$1,000.50")
// Price fields (raw → formatted → display)
priceUsd?: number // Raw number (e.g., 1.50)
priceUsdFormatted?: string // Without commas/symbol (e.g., "1.50")
priceUsdDisplay?: string // With commas/symbol (e.g., "$1.50")
}
Balances
import {
useTokenBalances,
getAccountTotalBalanceUsd,
useAccountTotalBalanceUsd,
getHasSufficientBalanceToken,
useHasSufficientBalanceToken,
getHasSufficientBalanceUsd,
useHasSufficientBalanceUsd,
} from '0xtrails'
Balance hooks and utilities:
useTokenBalances(address, options?): Returns sorted token balances enriched with USD price
useTokenBalancesForMultipleAccounts(addresses, options?): Returns token balances for multiple accounts
useAccountTotalBalanceUsd(address): Returns total USD balance across tokens
useHasSufficientBalanceUsd(address, targetUsd): Check if account has sufficient USD balance
useHasSufficientBalanceToken(address, tokenAddress, tokenAmount, chainId): Check token balance
Example Usage
import { useTokenBalances, useAccountTotalBalanceUsd } from '0xtrails'
import type { Token } from '0xtrails'
const WalletBalance = ({ address }: { address: string }) => {
const [currentPage, setCurrentPage] = useState(1)
const [pageSize, setPageSize] = useState(10)
const { sortedTokens, isLoadingBalances, page } = useTokenBalances(address, {
page: currentPage,
pageSize: pageSize,
})
const { totalBalanceUsd, totalBalanceUsdFormatted } = useAccountTotalBalanceUsd(address)
if (isLoadingBalances) return <div>Loading balances...</div>
return (
<div>
<h3>Total Balance: {totalBalanceUsdFormatted}</h3>
{sortedTokens.map((token: Token) => (
<div key={`${token.chainId}-${token.contractAddress}`}>
{token.symbol}: {token.balanceDisplay} ({token.balanceUsdDisplay})
</div>
))}
{/* Pagination controls */}
<div>
<button
onClick={() => setCurrentPage(prev => Math.max(1, prev - 1))}
disabled={currentPage === 1}
>
Previous
</button>
<span>Page {currentPage}</span>
<button
onClick={() => setCurrentPage(prev => prev + 1)}
disabled={!page?.more}
>
Next
</button>
</div>
</div>
)
}
useTokenBalances
Signature:
function useTokenBalances(
address: Address | null,
options?: {
page?: number
pageSize?: number
}
): UseTokenBalancesReturn
Return Type:
type UseTokenBalancesReturn = {
sortedTokens: Token[] // Tokens sorted by USD balance (highest first)
isLoadingBalances: boolean
isLoadingPrices: boolean
isLoadingSortedTokens: boolean
balanceError: Error | null
page: Page | undefined // Pagination info
}
type Page = {
column: string
pageSize: number
more: boolean // Whether there are more results
}
Token balances are returned as Token[] with balance fields populated. See the Token Type above for all available fields including balance, balanceFormatted, balanceDisplay, balanceUsd, etc.
Fetches token balances for multiple wallet addresses in a single API call. More efficient than calling useTokenBalances multiple times.
Signature:
function useTokenBalancesForMultipleAccounts(
addresses: (Address | null)[],
options?: {
page?: number
pageSize?: number
}
): UseTokenBalancesForMultipleAccountsReturn
Return Type:
type UseTokenBalancesForMultipleAccountsReturn = {
/** Per-account token balances data, keyed by lowercase address */
balancesByAccount: {
[account: string]: {
tokenBalancesData: {
tokens: Token[]
} | undefined
isLoading: boolean
error: Error | null
}
}
/** Combined loading state - true if any account is loading */
isLoading: boolean
/** Combined error - first error encountered */
error: Error | null
}
Example:
import { useTokenBalancesForMultipleAccounts } from '0xtrails'
const MultiWalletBalances = ({ addresses }: { addresses: string[] }) => {
const { balancesByAccount, isLoading, error } = useTokenBalancesForMultipleAccounts(addresses)
if (isLoading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return (
<div>
{addresses.map(address => {
const accountData = balancesByAccount[address.toLowerCase()]
const tokens = accountData?.tokenBalancesData?.tokens || []
return (
<div key={address}>
<h4>{address.slice(0, 6)}...{address.slice(-4)}</h4>
{tokens.map(token => (
<div key={`${token.chainId}-${token.contractAddress}`}>
{token.symbol}: {token.balanceDisplay}
</div>
))}
</div>
)
})}
</div>
)
}
Quotes and Swapping
import {
useQuote,
useTrailsSendTransaction,
type UseQuoteReturn,
type UseQuoteProps,
type UseTrailsSendTransactionParameters,
type UseTrailsSendTransactionReturnType,
type ActionItem,
type SwapReturn,
type Quote
} from '0xtrails'
useQuote Hook
The useQuote hook provides real-time quotes for token swaps, cross-chain transfers and ability to pass in calldata for executions - enabling you to use Trails headlessly.
from.amount and to.amount are human-readable decimal strings such as "1" or "0.25". Do not pass raw wei strings; the SDK scales amounts using the token decimals.
Pass walletAddress to preview quotes before a wallet is connected. If a connected walletClient.account.address is available, it becomes the quote owner and takes precedence over walletAddress. Executing with send still requires a real wallet client.
Usage Example
import { useEffect } from 'react'
import { useQuote } from '0xtrails'
import { useWalletClient, useAccount } from 'wagmi'
export const SwapComponent = () => {
const { data: walletClient } = useWalletClient()
const { address } = useAccount()
const { quote, send, isLoadingQuote, quoteError, refetchQuote } = useQuote({
walletClient,
from: {
token: 'USDC',
chain: 'ethereum',
amount: '1',
},
to: {
token: 'USDC',
chain: 'base',
recipient: address,
},
slippageTolerance: '0.005', // 0.5%
onStatusUpdate: (states) => {
console.log('Transaction states:', states)
},
})
// Quotes can become stale; refetch every ~30 seconds
useEffect(() => {
const id = setInterval(() => {
refetchQuote?.()
}, 30000)
return () => clearInterval(id)
}, [refetchQuote])
const handleSwap = async () => {
if (!send) return
try {
const result = await send()
console.log('Swap completed:', result)
} catch (error) {
console.error('Swap failed:', error)
}
}
if (isLoadingQuote) return <div>Getting quote...</div>
if (quoteError) return <div>Error: {quoteError.message}</div>
if (!quote) return <div>No quote available</div>
return (
<div>
<div>
From: {quote.fromAmount} {quote.originToken.symbol}
</div>
<div>
To: {quote.toAmount} {quote.destinationToken.symbol}
</div>
<div>
Fee: {quote.fees?.totalFeeAmountUsdDisplay}
</div>
<button onClick={handleSwap}>
Execute Swap
</button>
</div>
)
}
Types
type UseQuoteProps = {
walletClient?: any
walletAddress?: string | null
from: {
token: string
chain: ChainIdentifier
amount?: string // human-readable; EXACT_INPUT when set
decimals?: number // optional source token decimals override
}
to: {
token: string
chain: ChainIdentifier
recipient?: string | null
approve?: string | null
amount?: string // human-readable; EXACT_OUTPUT when set
decimals?: number // optional destination token decimals override
calls?: (Call | Call[])[] | null
calldata?: string | null // deprecated; prefer calls or actions
}
slippageTolerance?: string | number | null
onStatusUpdate?: ((transactionStates: TransactionState[]) => void) | null
swapProvider?: RouteProvider | null
bridgeProvider?: RouteProvider | null
swapProviderFallback?: boolean
bridgeProviderFallback?: boolean
checkoutOnHandlers?: Partial<CheckoutOnHandlers>
selectedFeeOption?: FeeOption | null
abortSignal?: AbortSignal
apiKey?: string | null
nodeGatewayEnv?: 'prod' | 'dev' | 'local' | 'cors-anywhere'
isSmartWallet?: boolean | null
mode?: 'pay' | 'fund' | 'earn' | 'swap' | 'withdraw' | null
intentProtocolVersion?: 'v1' | 'v1.5' | null
actions?: ActionItem[]
}
type UseQuoteReturn = {
quote: Quote | null
send: ((options?: SendOptions) => Promise<SwapReturn | null>) | null
isLoadingQuote: boolean
quoteError: QuoteError | null
quoteErrorPrettified: string
refetchQuote: () => Promise<void>
abort: () => void
feeOptions: FeeOption[]
isRecipientContract: boolean | null
isSenderContractOnOrigin: boolean | null
isSenderContractOnDestination: boolean | null
}
type ActionItem =
| LendActionItem
| SwapActionItem
| DepositActionItem
| CustomActionItem
| AssertActionItem
useTrailsSendTransaction Hook
useTrailsSendTransaction creates a button-driven transaction flow with the Trails modal. It accepts most useQuote options except from, to, walletClient, and selectedFeeOption; pass destination requirements when calling sendTransaction.
tokenAmount and fromAmount are human-readable decimal strings. Native value remains a raw wei bigint, matching wagmi.
import { useTrailsSendTransaction } from '0xtrails'
const { sendTransaction, isPending, error } = useTrailsSendTransaction()
sendTransaction({
to: '0x1234567890123456789012345678901234567890',
value: 1000000000000000n, // 0.001 native token
})
Types
type TrailsSendTransactionVariables = {
to?: `0x${string}`
value?: bigint
data?: `0x${string}`
tokenAddress?: `0x${string}`
tokenAmount?: string // human-readable destination amount
tokenDecimals?: number
fromTokenAddress?: `0x${string}`
fromChainId?: number
fromAmount?: string // human-readable origin amount
fromDecimals?: number
}
type UseTrailsSendTransactionParameters =
Omit<UseQuoteProps, 'from' | 'to' | 'walletClient' | 'selectedFeeOption'> & {
calls?: DestinationCallsInput
onSuccess?: (data: SwapReturn, variables: TrailsSendTransactionVariables) => void
onError?: (error: Error, variables: TrailsSendTransactionVariables) => void
onSettled?: (
data: SwapReturn | undefined,
error: Error | null,
variables: TrailsSendTransactionVariables,
) => void
receiptActionButtonText?: string
onReceiptAction?: () => void
}
type UseTrailsSendTransactionReturnType = {
sendTransaction: (variables: TrailsSendTransactionVariables) => void
sendTransactionAsync: (variables: TrailsSendTransactionVariables) => Promise<SwapReturn>
retry: () => void
data: SwapReturn | undefined
error: Error | null
isPending: boolean
isSuccess: boolean
isError: boolean
isIdle: boolean
status: 'idle' | 'pending' | 'error' | 'success'
reset: () => void
variables: TrailsSendTransactionVariables | undefined
}
Earn Markets and Providers
import {
useEarnMarkets,
useEarnProviders,
useEarnBalances,
type UseEarnMarketsParams,
type UseEarnMarketsReturn,
type UseEarnProvidersParams,
type UseEarnProvidersReturn,
type UseEarnBalancesParams,
type UseEarnBalancesReturn,
} from '0xtrails'
useEarnMarkets Hook
Fetch earn markets for lend and deposit actions. Use the returned market id as a marketId.
import { useEarnMarkets } from '0xtrails'
const { data, total, isLoading, error, refetch } = useEarnMarkets({
chain: 'polygon',
type: 'vault',
provider: 'morpho',
sortBy: 'rewardRateDesc',
search: 'USDT'
})
Types
type UseEarnMarketsParams = {
chain?: ChainIdentifier
provider?: ProviderId
type?: 'lending' | 'vault'
sortBy?: 'statusEnterAsc' | 'statusEnterDesc' | 'statusExitAsc' | 'statusExitDesc' | 'rewardRateAsc' | 'rewardRateDesc'
limit?: number
offset?: number
search?: string
}
type UseEarnMarketsReturn = {
data: EarnMarket[] | undefined
total: number
isLoading: boolean
error: Error | null
refetch: () => void
}
useEarnProviders Hook
Fetch supported earn providers.
import { useEarnProviders } from '0xtrails'
const { data, total, isLoading, error, refetch } = useEarnProviders({
limit: 50,
})
Types
type UseEarnProvidersParams = {
limit?: number
offset?: number
}
type UseEarnProvidersReturn = {
data: Provider[] | undefined
total: number
isLoading: boolean
error: Error | null
refetch: () => void
}
useEarnBalances Hook
Fetch earn positions for a wallet. Use chain for one network or chains to batch multiple networks in one backend request.
import { useEarnBalances } from '0xtrails'
const { data, errors, isLoading, error, refetch } = useEarnBalances({
walletAddress: '0x9ec762D784B653E168d93eA3078B023d8958dBc6',
chain: 'polygon',
})
const { data: multiChainBalances } = useEarnBalances({
walletAddress: '0x9ec762D784B653E168d93eA3078B023d8958dBc6',
chains: ['polygon', 'ethereum', 'base'],
})
Restrict the lookup to one market:
const { data } = useEarnBalances({
walletAddress,
chain: 'polygon',
marketId: 'polygon-usdc-aave-v3-lending',
})
Types
type UseEarnBalancesParams =
| {
walletAddress: string
chain: ChainIdentifier
chains?: never
marketId?: string
arguments?: EarnBalanceQuery['arguments']
enabled?: boolean
}
| {
walletAddress: string
chains: ChainIdentifier[]
chain?: never
marketId?: string
arguments?: EarnBalanceQuery['arguments']
enabled?: boolean
}
type UseEarnBalancesReturn = {
data: EarnBalances[] | undefined
errors: EarnBalanceError[]
isLoading: boolean
error: Error | null
refetch: () => void
}
Constants
import { TRAILS_ROUTER_PLACEHOLDER_AMOUNT } from '0xtrails'
TRAILS_ROUTER_PLACEHOLDER_AMOUNT
This constant provides a placeholder value for amounts when encoding calldata in fund mode. It’s used to replace dynamic output amounts during contract execution.
Use Case: When creating calldata for fund mode transactions where the final amount isn’t known until execution time, use this placeholder in your encoded function calls. The Trails system will replace it with the actual dynamic amount during contract execution.
Example:
import { Fund } from '0xtrails/widget'
import { encodeFunctionData } from 'viem'
import { TRAILS_ROUTER_PLACEHOLDER_AMOUNT } from '0xtrails'
export const Example = () => {
const calldata = encodeFunctionData({
abi: stakingABI,
functionName: 'stake',
args: [TRAILS_ROUTER_PLACEHOLDER_AMOUNT] // Will be replaced with actual amount
})
return (
<Fund
apiKey="YOUR_API_KEY"
to={{
recipient: "0x...", // Staking contract
currency: "ETH",
chain: "ethereum",
calldata,
}}
>
<button>Stake ETH</button>
</Fund>
)
}
This pattern is essential for fund mode transactions where users choose their input amount, but the contract needs to receive the exact output amount after any swaps or bridging operations.
Transaction History
import {
useIntentTransactionHistory,
getAccountTransactionHistory,
useAccountTransactionHistory,
useIntentTransactionHistory,
getTxTimeDiff,
} from '0xtrails'
getAccountTransactionHistory
Gets transaction history for a wallet address interacting with Trails.
Signature:
function getAccountTransactionHistory(params: {
chainId: number
accountAddress: string
pageSize?: number
includeMetadata?: boolean
page?: number
}): Promise<TransactionHistoryResponse>
Usage:
const history = await getAccountTransactionHistory({
chainId: 1,
accountAddress: '0x...',
pageSize: 10,
})
useAccountTransactionHistory
Hook for fetching transaction history from a user’s wallet address for a specific chain.
import { useAccountTransactionHistory } from '0xtrails'
Usage
import { useAccountTransactionHistory } from '0xtrails'
export const TransactionList = () => {
const { data, isLoading, error } = useAccountTransactionHistory({
chainId: 1,
accountAddress: '0x123...',
// Optional:
// pageSize: 10,
// includeMetadata: true,
// page: 0,
// abortSignal: new AbortController().signal,
})
if (isLoading) return <div>Loading...</div>
if (error) return <div>Error: {String(error)}</div>
return (
<div>
{data?.transactions?.map(tx => (
<div key={tx.txnHash}>
Transaction: {tx.txnHash} | Block: {tx.blockNumber} | Time: {tx.timestamp}
</div>
))}
</div>
)
}
Types
type TransactionHistoryItemFromAPI = {
txnHash: string
blockNumber: number
blockHash: string
chainId: number
metaTxnID: string | null
transfers: Transfer[]
timestamp: string
}
type TransactionHistoryItem = TransactionHistoryItemFromAPI & {
explorerUrl?: string
chainName?: string
}
type TransactionHistoryResponse = {
page: {
column: string
pageSize: number
more: boolean
}
transactions: TransactionHistoryItem[]
}
type GetAccountTransactionHistoryParams = {
chainId: number
accountAddress: string
pageSize?: number
includeMetadata?: boolean
page?: number
abortSignal?: AbortSignal
}
useIntentTransactionHistory
React hook for fetching transaction history for a specific intent address.
Usage:
const {
transactions: transactionHistory,
loading: isLoadingHistory,
error: historyError,
refetch: refetchIntentHistory,
} = useIntentTransactionHistory({
accountAddress: '0x123...',
pageSize: 10,
enabled: true,
})
getTxTimeDiff
Gets the time difference for a transaction in human-readable format.
Signature: (timestamp: string | number) => string
Usage:
const timeDiff = getTxTimeDiff(transaction.timestamp)
console.log(timeDiff) // "5 minutes ago"
SDK Utilities
import { SDK_VERSION, getVersion } from '0xtrails'
SDK_VERSION: Current SDK version string
getVersion(): Returns the current SDK version at runtime
FundMethod Type
import type { FundMethod } from '0xtrails'
FundMethod is exported as a strict union: "wallet" | "direct-transfer" | "onramp-mesh" | "onramp-meld".
Encoders
import { getERC20TransferData } from '0xtrails'
getERC20TransferData
Encodes ERC20 transfer calldata for token transfers.
Signature: (params: { recipient: string, amount: bigint }) => string
Usage:
const transferData = getERC20TransferData(
{
recipient: '0x1234567890123456789012345678901234567890',
amount: 1000000n,
}
)
Error Handling
import {
InsufficientBalanceError,
UserRejectionError,
getIsUserRejectionError,
getIsBalanceTooLowError,
getIsApiError,
getIsRateLimitedError,
getIsRequiredAmountNotMetError,
getIsNoAvailableQuoteError,
getIsQuoteFailedError,
getIsQuoteTokenError,
getIsQuoteInputError,
getIsInsufficientLiquidityError,
getIsWalletAlreadyConnectedError,
getPrettifiedErrorMessage,
} from '0xtrails'
Error Classes
InsufficientBalanceError
Error thrown when a user has insufficient balance to complete a transaction.
try {
await send?.()
} catch (error) {
if (error instanceof InsufficientBalanceError) {
console.error('Insufficient balance for this transaction')
}
}
UserRejectionError
Error thrown when a user rejects a transaction in their wallet.
try {
await send?.()
} catch (error) {
if (error instanceof UserRejectionError) {
console.log('User rejected the transaction')
}
}
Error Detection Utilities
All error detection functions have the same signature: (error: unknown) => boolean
| Function | Description |
|---|
getIsUserRejectionError | User rejected the transaction in their wallet |
getIsBalanceTooLowError | Insufficient balance for the transaction |
getIsApiError | API returned an error |
getIsRateLimitedError | Request was rate limited |
getIsRequiredAmountNotMetError | Required minimum amount not met |
getIsNoAvailableQuoteError | No quote available for the requested swap |
getIsQuoteFailedError | Quote request failed |
getIsQuoteTokenError | Invalid token in quote request |
getIsQuoteInputError | Invalid input parameters for quote |
getIsInsufficientLiquidityError | Insufficient liquidity for the swap |
getIsWalletAlreadyConnectedError | Wallet is already connected |
getPrettifiedErrorMessage
Converts any error into a user-friendly message string.
Signature: (error: unknown) => string
try {
await send?.()
} catch (error) {
const userMessage = getPrettifiedErrorMessage(error)
showNotification(userMessage)
// e.g., "Insufficient balance" instead of technical error details
}
Example: Comprehensive Error Handling
import {
getIsUserRejectionError,
getIsBalanceTooLowError,
getIsNoAvailableQuoteError,
getIsRateLimitedError,
getPrettifiedErrorMessage,
} from '0xtrails'
async function handleSwap() {
try {
await send?.()
} catch (error) {
if (getIsUserRejectionError(error)) {
// User cancelled - don't show error notification
return
}
if (getIsBalanceTooLowError(error)) {
showNotification('Not enough balance. Please add funds.')
return
}
if (getIsNoAvailableQuoteError(error)) {
showNotification('No route available for this swap. Try a different token pair.')
return
}
if (getIsRateLimitedError(error)) {
showNotification('Too many requests. Please wait a moment.')
return
}
// Fallback to prettified message
showNotification(getPrettifiedErrorMessage(error))
}
}
useGetIntent
Hook to fetch intent data from the Trails API.
import { useGetIntent, type UseGetIntentParams, type UseGetIntentReturn } from '0xtrails'
Usage
import { useGetIntent } from '0xtrails'
export const IntentDetails = ({ intentId }: { intentId: string }) => {
const { intent, isLoading, isError, error, refetch } = useGetIntent({
intentId,
enabled: true,
})
if (isLoading) return <div>Loading intent...</div>
if (isError) return <div>Error: {error?.message}</div>
if (!intent) return <div>Intent not found</div>
return (
<div>
<p>Intent ID: {intent.intentId}</p>
<p>Status: {intent.status}</p>
<p>Origin Chain: {intent.quoteRequest.originChainId}</p>
<p>Destination Chain: {intent.quoteRequest.destinationChainId}</p>
<button onClick={refetch}>Refresh</button>
</div>
)
}
Types
type UseGetIntentParams = {
intentId: string | undefined
enabled?: boolean
}
type UseGetIntentReturn = {
intent: Intent | null
isLoading: boolean
isError: boolean
error: Error | null
refetch: () => void
}
useIntentRecover
Hook for recovering funds from stuck or failed intent transactions. Trails automatically refunds upon reverting, but this hook handles situations where funds are stuck without an explicit revert. It automatically selects the intent address (origin or destination) with the highest balance for recovery.
import {
useIntentRecover,
type UseIntentRecoverParams,
type UseIntentRecoverReturn,
} from '0xtrails'
Usage
import { useIntentRecover } from '0xtrails'
import { useWalletClient, useAccount } from 'wagmi'
export const RecoverComponent = ({ intentId }: { intentId: string }) => {
const { data: walletClient } = useWalletClient()
const { address } = useAccount()
const {
intent,
isLoadingIntent,
hasIntentBalance,
recoverToken,
selectedRecoveryTarget,
signPayload,
getRecoverTx,
recover,
getRecoverStatus,
intentError,
} = useIntentRecover({
intentId,
walletClient,
refundToAddress: address, // Optional: defaults to connected wallet
})
// Option 1: Automatic flow (recommended)
const handleAutoRecover = async () => {
try {
const { txHash, receipt, chainId } = await recover()
console.log('Recovery successful:', txHash)
// Optionally check status
const status = await getRecoverStatus({ txHash, chainId })
console.log('Recovery status:', status)
} catch (error) {
console.error('Recovery failed:', error)
}
}
// Option 2: Manual flow for more control
const handleManualRecover = async () => {
try {
// Step 1: Sign the recovery payload
const { signature, payload } = await signPayload()
// Step 2: Get the recovery transaction
const recoverTx = await getRecoverTx({
signedHash: signature,
payload,
})
// Step 3: Execute the recovery transaction
const txHash = await walletClient.sendTransaction({
to: recoverTx.to,
data: recoverTx.data,
chain: recoverTx.chain,
})
console.log('Recovery transaction sent:', txHash)
} catch (error) {
console.error('Recovery failed:', error)
}
}
if (isLoadingIntent) return <div>Loading intent...</div>
if (intentError) return <div>Error: {intentError.message}</div>
if (!hasIntentBalance) return <div>No balance to recover</div>
return (
<div>
{recoverToken && (
<p>
Recovering {recoverToken.balanceFormatted} {recoverToken.symbol}
({recoverToken.balanceUsdDisplay}) from {selectedRecoveryTarget?.chainName}
</p>
)}
<button onClick={handleAutoRecover}>
Recover Funds
</button>
</div>
)
}
Types
type UseIntentRecoverParams = {
intentId: string | undefined
walletClient?: WalletClient
refundToAddress?: `0x${string}` // Optional: address to receive recovered funds (defaults to wallet address)
}
type UseIntentRecoverReturn = {
intent: Intent | null
isLoadingIntent: boolean
isLoadingBalances: boolean
intentError: Error | null
balancesError: Error | null
hasIntentBalance: boolean
/** Token that will be recovered (the one with highest balance) */
recoverToken: Token | null
/** Information about which intent address will be recovered from */
selectedRecoveryTarget: {
address: string
chainId: number
chainName: string
isOrigin: boolean
} | null
refetchIntent: () => void
signPayload: () => Promise<{
signature: string
payload: Payload
refundCall: PayloadCall
}>
getRecoverTx: (params: {
signedHash: string
payload: Payload
}) => Promise<{
to: `0x${string}`
data: `0x${string}`
chainId: number
chain: Chain
}>
/** One-call recovery: signs, builds, and sends the recovery transaction */
recover: () => Promise<{
txHash: `0x${string}`
receipt: TransactionReceipt
chainId: number
}>
/** Check recovery transaction status */
getRecoverStatus: (params: {
txHash: `0x${string}`
chainId: number
}) => Promise<{
status: 'success' | 'fail' | 'pending'
}>
}
useIntentRecoverWithAddress
Recovery hook designed for apps that handle signing externally (e.g., embedded wallets, or custodial solutions). Unlike useIntentRecover which requires a walletClient, this hook accepts a plain walletAddress and exposes getDataToSign so you can sign the EIP-712 typed data with any signing method.
Like useIntentRecover, it automatically selects the intent address (origin or destination) with the highest balance for recovery.
import {
useIntentRecoverWithAddress,
type UseIntentRecoverWithSignatureParams,
type UseIntentRecoverWithSignatureReturn,
} from '0xtrails'
Usage
import { useIntentRecoverWithAddress } from '0xtrails'
export const RecoverComponent = ({ intentId }: { intentId: string }) => {
const walletAddress = '0x...' // Your externally managed wallet address
const {
intent,
isLoadingIntent,
hasIntentBalance,
recoverToken,
selectedRecoveryTarget,
getDataToSign,
getRecoverTx,
getRecoverStatus,
intentError,
} = useIntentRecoverWithAddress({
intentId,
walletAddress,
refundToAddress: '0x...', // Optional: defaults to walletAddress
})
const handleRecover = async () => {
try {
// Step 1: Get EIP-712 typed data to sign
const { payload, typedData, chainId } = await getDataToSign()
// Step 2: Sign with your external wallet/signer
const signature = await myCustomWallet.signTypedData({
account: walletAddress,
...typedData,
})
// Step 3: Build the recovery transaction
const recoverTx = await getRecoverTx({ signature, payload })
// Step 4: Send the transaction with your external wallet
const hash = await myCustomWallet.sendTransaction({
to: recoverTx.to,
data: recoverTx.data,
chain: recoverTx.chain,
})
console.log('Recovery transaction sent:', hash)
// Optionally check status
const status = await getRecoverStatus({ txHash: hash, chainId })
console.log('Recovery status:', status)
} catch (error) {
console.error('Recovery failed:', error)
}
}
if (isLoadingIntent) return <div>Loading intent...</div>
if (intentError) return <div>Error: {intentError.message}</div>
if (!hasIntentBalance) return <div>No balance to recover</div>
return (
<div>
{recoverToken && (
<p>
Recovering {recoverToken.balanceFormatted} {recoverToken.symbol}
({recoverToken.balanceUsdDisplay}) from {selectedRecoveryTarget?.chainName}
</p>
)}
<button onClick={handleRecover}>
Recover Funds
</button>
</div>
)
}
Types
type UseIntentRecoverWithSignatureParams = {
intentId: string | undefined
walletAddress?: `0x${string}` // The address that will sign the recovery payload
refundToAddress?: `0x${string}` // Optional: address to receive recovered funds (defaults to walletAddress)
}
type UseIntentRecoverWithSignatureReturn = {
intent: Intent | null
isLoadingIntent: boolean
isLoadingBalances: boolean
intentError: Error | null
balancesError: Error | null
hasIntentBalance: boolean
/** Token that will be recovered (the one with highest balance) */
recoverToken: Token | null
/** Information about which intent address will be recovered from */
selectedRecoveryTarget: {
address: string
chainId: number
chainName: string
isOrigin: boolean
} | null
refetchIntent: () => void
/** Returns EIP-712 typed data for external signing */
getDataToSign: () => Promise<{
payload: Payload
refundCall: PayloadCall
chainId: number
typedData: TypedData // EIP-712 typed data to pass to your signer
}>
getRecoverTx: (params: {
signature: string
payload: Payload
}) => Promise<{
to: `0x${string}`
data: `0x${string}`
chainId: number
chain: Chain
}>
/** Check recovery transaction status */
getRecoverStatus: (params: {
txHash: `0x${string}`
chainId: number
}) => Promise<{
status: 'success' | 'fail' | 'pending'
}>
}
Comparison with useIntentRecover
| Feature | useIntentRecover | useIntentRecoverWithAddress |
|---|
| Signing | Uses walletClient internally | You sign externally via getDataToSign |
| One-call recovery | recover() handles everything | Manual: getDataToSign → sign → getRecoverTx → send |
| Best for | Standard wallets | Embedded wallets, custodial signers |
signPayload | Yes | No (replaced by getDataToSign) |
recover | Yes | No (you control the full flow) |
useCommitIntent / useExecuteIntent
Mutation hooks for committing and executing intents. These are lower-level hooks for advanced users who need direct control over the intent lifecycle.
import { useCommitIntent, useExecuteIntent } from '0xtrails'
useCommitIntent
const { mutate: commitIntent, isPending, isError, error, data } = useCommitIntent()
// Commit an intent
commitIntent(intent)
useExecuteIntent
const { mutate: executeIntent, isPending, isError, error, data } = useExecuteIntent()
// Execute an intent with deposit signature
executeIntent({
intent,
depositSignature,
})
TrailsClient
For advanced use cases, you can access the underlying Trails API client directly.
import { getTrailsClient, useTrailsClient, TrailsClient } from '0xtrails'
useTrailsClient Hook
import { useTrailsClient } from '0xtrails'
function MyComponent() {
const trailsClient = useTrailsClient()
const fetchIntent = async (intentId: string) => {
const response = await trailsClient.getIntent({ intentId })
return response.intent
}
// Use client for direct API calls
}
getTrailsClient Function
For non-React contexts:
import { getTrailsClient } from '0xtrails'
const client = getTrailsClient({
apiKey: 'YOUR_API_KEY',
hostname: 'https://api.trails.xyz', // optional
})
// Direct API calls
const quote = await client.quoteIntent({
originChainId: 1,
destinationChainId: 8453,
// ... other params
})
Advanced: Intent Functions
These are advanced functions for direct intent management. Most developers should use the focused components or useQuote hook instead.
import {
calculateOriginAndDestinationIntentAddresses,
commitIntent,
sendOriginTransaction,
} from '0xtrails'
Types
type OriginCallParams = {
to: string
value: bigint
data: string
chainId: number
}
type RouteProvider = 'AUTO' | 'CCTP' | 'RELAY' | 'SUSHI' | 'ZEROX'
type TrailsFee = {
amount: bigint
amountUsd: number
token: Token
}
calculateOriginAndDestinationIntentAddresses
Calculates both origin and destination intent addresses.
Signature:
function calculateOriginAndDestinationIntentAddresses(
params: IntentParams
): Promise<{ originAddress: string; destinationAddress: string }>
Usage:
const { originAddress, destinationAddress } =
await calculateOriginAndDestinationIntentAddresses(params)
commitIntent
Commits an intent to the blockchain.
Signature: (params: CommitIntentParams) => Promise<CommitIntentResult>
Usage:
const result = await commitIntent({
walletClient,
fromChainId: 1,
toChainId: 8453,
// ... other params
})
sendOriginTransaction
Sends the origin transaction for an intent.
Signature: (params: SendOriginTxParams) => Promise<string>
Usage:
const txHash = await sendOriginTransaction({
walletClient,
intentAddress: '0x...',
// ... other params
})