0xtrails
Version:
SDK for Trails
1,180 lines (1,067 loc) • 31 kB
text/typescript
import { QueryClient, useQuery } from "@tanstack/react-query"
import { zeroAddress, erc20Abi, createPublicClient, http } from "viem"
import { getChainInfo, getSupportedChains } from "./chains.js"
import {
arbitrum,
base,
optimism,
polygon,
mainnet,
avalanche,
etherlink,
linea,
unichain,
worldchain,
} from "viem/chains"
import { getRelaySupportedTokens } from "./relaySdk.js"
import { useReadContracts } from "wagmi"
import { useMemo } from "react"
import { logger } from "./logger.js"
export type SupportedToken = {
id: string
symbol: string
name: string
contractAddress: string
decimals: number
chainId: number
chainName: string
imageUrl: string
}
export const commonTokenImages: Record<string, string> = {
ETH: "https://assets.sequence.info/images/tokens/large/1/0x0000000000000000000000000000000000000000.webp",
WETH: "https://assets.sequence.info/images/tokens/large/1/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2.webp",
POL: "https://assets.sequence.info/images/tokens/large/137/0x0000000000000000000000000000000000000000.webp",
USDC: "https://assets.sequence.info/images/tokens/large/1/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48.webp",
USDT: "https://assets.sequence.info/images/tokens/large/1/0xdac17f958d2ee523a2206206994597c13d831ec7.webp",
DAI: "https://assets.sequence.info/images/tokens/large/1/0x6b175474e89094c44da98b954eedeac495271d0f.webp",
WBTC: "https://assets.sequence.info/images/tokens/large/1/0x2260fac5e5542a773aa44fbcfedf7c193bc2c599.webp",
BAT: "https://assets.sequence.info/images/tokens/large/1/0x0d8775f648430679a709e98d2b0cb6250d2887ef.webp",
ARB: "https://assets.sequence.info/images/tokens/large/42161/0x912ce59144191c1204e64559fe8253a0e49e6548.webp",
LINK: "https://assets.sequence.info/images/tokens/large/1/0x514910771af9ca656af840dff83e8264ecf986ca.webp",
XTZ: "https://assets.sequence.info/images/tokens/large/42793/0x0000000000000000000000000000000000000000.webp",
WXTZ: "https://assets.coingecko.com/coins/images/976/standard/Tezos-logo.png?1696502091",
}
const cacheVersion = "01"
// LocalStorage cache utilities for token images
const TOKEN_IMAGE_CACHE_KEY = `trails-sdk:token-image-cache:${cacheVersion}`
const TOKEN_IMAGE_CACHE_EXPIRY = 7 * 24 * 60 * 60 * 1000 // 7 days
interface TokenImageCacheEntry {
imageUrl: string
timestamp: number
found: boolean
}
// Token info cache utilities
const TOKEN_INFO_CACHE_KEY = `trails-sdk:token-info-cache:${cacheVersion}`
const TOKEN_INFO_CACHE_EXPIRY = 24 * 60 * 60 * 1000 // 24 hours
interface TokenInfoCacheEntry {
id: string
symbol: string
name: string
contractAddress: string
decimals: number
chainId: number
chainName: string
imageUrl: string
timestamp: number
}
// In-memory token info cache
const tokenInfoCache = new Map<string, TokenInfoCacheEntry>()
function getTokenInfoCache(): Record<string, TokenInfoCacheEntry> {
if (typeof window === "undefined") return {}
try {
const cached = localStorage.getItem(TOKEN_INFO_CACHE_KEY)
if (!cached) return {}
const parsed = JSON.parse(cached) as Record<string, TokenInfoCacheEntry>
const now = Date.now()
// Clean up expired entries
const validEntries = Object.entries(parsed).filter(([_, entry]) => {
return now - entry.timestamp < TOKEN_INFO_CACHE_EXPIRY
})
return Object.fromEntries(validEntries) as Record<
string,
TokenInfoCacheEntry
>
} catch {
return {}
}
}
function setTokenInfoCache(
key: string,
tokenInfo: {
id: string
symbol: string
name: string
contractAddress: string
decimals: number
chainId: number
chainName: string
imageUrl: string
},
): void {
if (typeof window === "undefined") return
try {
const cache = getTokenInfoCache()
cache[key] = {
...tokenInfo,
timestamp: Date.now(),
}
localStorage.setItem(TOKEN_INFO_CACHE_KEY, JSON.stringify(cache))
// Also update in-memory cache
tokenInfoCache.set(key, cache[key])
} catch (error) {
logger.console.warn("[trails-sdk] Failed to cache token info:", error)
}
}
function getCachedTokenInfo(
chainId: number,
contractAddress: string,
): TokenInfoCacheEntry | null {
const key = `${chainId}:${contractAddress.toLowerCase()}`
// Check in-memory cache first
const memoryEntry = tokenInfoCache.get(key)
if (memoryEntry) {
return memoryEntry
}
// Check localStorage cache
const cache = getTokenInfoCache()
const cachedEntry = cache[key]
if (cachedEntry) {
// Update in-memory cache
tokenInfoCache.set(key, cachedEntry)
return cachedEntry
}
return null
}
function getTokenImageCache(): Record<string, TokenImageCacheEntry> {
if (typeof window === "undefined") return {}
try {
const cached = localStorage.getItem(TOKEN_IMAGE_CACHE_KEY)
if (!cached) return {}
const parsed = JSON.parse(cached) as Record<string, TokenImageCacheEntry>
const now = Date.now()
// Clean up expired entries
const validEntries = Object.entries(parsed).filter(([_, entry]) => {
return now - entry.timestamp < TOKEN_IMAGE_CACHE_EXPIRY
})
return Object.fromEntries(validEntries) as Record<
string,
TokenImageCacheEntry
>
} catch {
return {}
}
}
function setTokenImageCache(
key: string,
imageUrl: string,
found: boolean,
): void {
if (typeof window === "undefined") return
try {
const cache = getTokenImageCache()
cache[key] = {
imageUrl,
timestamp: Date.now(),
found,
}
localStorage.setItem(TOKEN_IMAGE_CACHE_KEY, JSON.stringify(cache))
} catch (error) {
logger.console.warn("[trails-sdk] Failed to cache token image:", error)
}
}
// Dedicated QueryClient for token image fetching
const tokenImageQueryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 24 * 60 * 60 * 1000, // 24 hours
gcTime: 7 * 24 * 60 * 60 * 1000, // 7 days
retry: 2,
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
refetchOnWindowFocus: false,
refetchOnReconnect: false,
},
},
})
// Priority tokens that should appear first in the list
const PRIORITY_TOKENS = [
"ETH",
"WETH",
"AVAX",
"WAVAX",
"xDAI",
"POL",
"USDC",
"USDT",
"DAI",
"MATIC",
"ARB",
"OP",
"BAT",
"WBTC",
"cbBTC",
"XAI",
]
// Sort function for tokens with the specified priority order
function sortTokens(tokens: SupportedToken[]): SupportedToken[] {
return tokens.sort((a, b) => {
// 1. Priority tokens first (ETH, USDC, USDT, DAI)
const aPriority = PRIORITY_TOKENS.indexOf(a.symbol)
const bPriority = PRIORITY_TOKENS.indexOf(b.symbol)
// If both are priority tokens, sort by priority order
if (aPriority !== -1 && bPriority !== -1) {
return aPriority - bPriority
}
// If only one is a priority token, prioritize it
if (aPriority !== -1) return -1
if (bPriority !== -1) return 1
// 2. Tokens with imageUrl before those without
const aHasImage = !!a.imageUrl
const bHasImage = !!b.imageUrl
if (aHasImage && !bHasImage) return -1
if (!aHasImage && bHasImage) return 1
// 3. Alphabetical by symbol
const symbolComparison = a.symbol.localeCompare(b.symbol)
if (symbolComparison !== 0) {
return symbolComparison
}
// 4. If symbols are the same, sort by chainName
return a.chainName.localeCompare(b.chainName)
})
}
export function getCachedTokenImageUrl(
chainId: number,
contractAddress: string,
symbol: string,
) {
const cacheKey = `${chainId}:${contractAddress}:${symbol}`
const cache = getTokenImageCache()
const cachedEntry = cache[cacheKey]
return cachedEntry?.imageUrl || ""
}
export async function getTokenImageUrlOrFallback({
chainId,
contractAddress,
symbol,
}: {
chainId?: number
contractAddress?: string
symbol?: string
}): Promise<string> {
const cacheKey = `${chainId}:${contractAddress}:${symbol}`
const imageUrl = getTokenImageUrl({ chainId, contractAddress, symbol })
// Check localStorage cache first
const cache = getTokenImageCache()
const cachedEntry = cache[cacheKey]
if (cachedEntry) {
if (cachedEntry.found) {
return cachedEntry.imageUrl
} else {
return "" // Return empty string if we previously found no image
}
}
// Use QueryClient to fetch and cache the result
try {
const result = await tokenImageQueryClient.fetchQuery({
queryKey: ["tokenImage", chainId, contractAddress],
queryFn: async () => {
const response = await fetch(imageUrl, {
method: "HEAD", // Only fetch headers to check if image exists
cache: "no-cache", // Don't cache the fetch request itself
})
const found = response.ok
const resultUrl = found ? imageUrl : ""
// Cache the result in localStorage
setTokenImageCache(cacheKey, resultUrl, found)
return resultUrl
},
staleTime: 24 * 60 * 60 * 1000, // 24 hours
gcTime: 7 * 24 * 60 * 60 * 1000, // 7 days
})
return result
} catch (error) {
logger.console.error("[trails-sdk] Error fetching token image:", error)
// Cache the failure to avoid repeated requests
setTokenImageCache(cacheKey, "", false)
return ""
}
}
export async function getAllTokens(): Promise<SupportedToken[]> {
const relayTokens = (await getRelaySupportedTokens()) as SupportedToken[]
return relayTokens
}
export async function getSupportedTokens(): Promise<SupportedToken[]> {
const tokens = await getAllTokens()
for (const token of tokens) {
if (!token.imageUrl) {
token.imageUrl = getTokenImageUrl({
chainId: token.chainId,
contractAddress: token.contractAddress,
symbol: token.symbol,
})
}
getTokenImageUrlOrFallback({
chainId: token.chainId,
contractAddress: token.contractAddress,
symbol: token.symbol,
})
.then((imageUrl) => {
token.imageUrl = imageUrl
})
.catch((error) => {
logger.console.error(
"[trails-sdk] Error getting token image url:",
error,
)
})
}
const additionalTokens: SupportedToken[] = []
for (const commonToken of commonTokens) {
const contractAddress = commonToken.contractAddress
const exists = tokens.some(
(t) =>
t.chainId === commonToken.chainId &&
t.contractAddress.toLowerCase() === contractAddress.toLowerCase(),
)
if (!exists) {
additionalTokens.push(commonToken)
}
}
const supportedChains = await getSupportedChains()
const allTokens = [...tokens, ...additionalTokens]
const supportedChainTokens = allTokens.filter((token) => {
// Always include Etherlink tokens
if (token.chainId === etherlink.id) {
return true
}
return supportedChains.some((chain) => chain.id === token.chainId)
})
const uniqueTokens = supportedChainTokens.filter(
(token, index, self) =>
index ===
self.findIndex(
(t) =>
t.chainId === token.chainId &&
t.contractAddress.toLowerCase() ===
token.contractAddress.toLowerCase(),
),
)
// Sort tokens according to priority order
const sortedTokens = sortTokens(uniqueTokens as SupportedToken[])
return sortedTokens
}
export function useSupportedTokens({ chainId }: { chainId?: number } = {}): {
supportedTokens: SupportedToken[]
isLoadingTokens: boolean
} {
const { data: supportedTokens = [], isLoading: isLoadingTokens } = useQuery({
queryKey: ["supportedTokens"],
queryFn: getSupportedTokens,
staleTime: 60 * 60 * 1000, // 1 hour - tokens rarely change
gcTime: 24 * 60 * 60 * 1000, // 24 hours - keep in cache for a full day
refetchOnWindowFocus: false, // Don't refetch when window regains focus
refetchOnReconnect: false, // Don't refetch on network reconnect
})
const filteredTokens = useMemo(() => {
if (!chainId) {
return supportedTokens
}
return supportedTokens.filter((token) => token.chainId === chainId)
}, [supportedTokens, chainId])
return {
supportedTokens: filteredTokens || [],
isLoadingTokens,
}
}
export async function getSourceTokenList(): Promise<string[]> {
const tokens = await getSupportedTokens()
return tokens.map((token) => token.symbol)
}
export function useSourceTokenList(): string[] {
const { data: list = [] } = useQuery({
queryKey: ["sourceTokenList"],
queryFn: getSourceTokenList,
staleTime: 1000 * 60 * 60, // 1 hour - token list rarely changes
gcTime: 1000 * 60 * 60 * 24, // 24 hours cache time
refetchOnWindowFocus: false,
refetchOnReconnect: false,
})
return list
}
const tokenNames: Record<string, string> = {
ETH: "Ethereum",
WETH: "Wrapped ETH",
USDC: "USDC",
USDT: "Tether",
DAI: "Dai Stablecoin",
OP: "Optimism",
ARB: "Arbitrum",
POL: "POL",
MATIC: "Matic Token",
BAT: "Basic Attention Token",
}
export const tokensToPrefix: Record<string, string> = {
USDC: "USDC",
ETH: "ETH",
POL: "POL",
}
export const tokenNamePrefixes: Record<string, string> = {
[optimism.id]: "Optimistic",
[arbitrum.id]: "Arbitrum",
[polygon.id]: "Polygon",
[etherlink.id]: "Etherlink",
}
export function getFormatttedTokenName(
currentName: string,
tokenSymbol: string,
chainId?: number,
): string {
let name = tokenNames[tokenSymbol] || currentName || tokenSymbol
if (chainId) {
try {
const chainInfo = getChainInfo(chainId)
if (chainInfo) {
if (tokensToPrefix[tokenSymbol]) {
if (chainId !== mainnet.id) {
name = `${chainInfo?.name} ${tokenSymbol}`
}
const prefix = tokenNamePrefixes[chainId]
if (prefix) {
name = `${prefix} ${tokenSymbol}`
}
}
}
} catch (e) {
logger.console.error("[trails-sdk] Error getting chain info:", e)
}
}
return name
}
export async function getTokenInfo(
chainId: number,
contractAddress: string,
): Promise<SupportedToken | null> {
const normalizedAddress = contractAddress.toLowerCase()
// Check cache first
const cachedInfo = getCachedTokenInfo(chainId, normalizedAddress)
if (cachedInfo) {
return {
id: cachedInfo.id,
symbol: cachedInfo.symbol,
name: cachedInfo.name,
contractAddress: cachedInfo.contractAddress,
decimals: cachedInfo.decimals,
chainId: cachedInfo.chainId,
chainName: cachedInfo.chainName,
imageUrl: cachedInfo.imageUrl,
}
}
// Check if it's a native token
const chainInfo = getChainInfo(chainId)
if (normalizedAddress === zeroAddress.toLowerCase()) {
const nativeInfo: SupportedToken = {
id: `${chainInfo?.nativeCurrency.symbol || "ETH"}-${chainInfo?.name || "ethereum"}`,
symbol: chainInfo?.nativeCurrency.symbol || "ETH",
name: chainInfo?.nativeCurrency.name || "Ethereum",
contractAddress: zeroAddress,
decimals: chainInfo?.nativeCurrency.decimals || 18,
chainId,
chainName: chainInfo?.name || "Ethereum",
imageUrl: getTokenImageUrl({
chainId,
contractAddress: zeroAddress,
symbol: chainInfo?.nativeCurrency.symbol || "ETH",
}),
}
// Cache the native token info
setTokenInfoCache(`${chainId}:${normalizedAddress}`, nativeInfo)
return nativeInfo
}
// Look in supported tokens
const tokens = await getSupportedTokens()
const token = tokens.find(
(t) =>
t.chainId === chainId &&
t.contractAddress.toLowerCase() === normalizedAddress,
)
if (token) {
// Cache the token info
setTokenInfoCache(`${chainId}:${normalizedAddress}`, token)
return token
}
// If not found in supported tokens, try to fetch on-chain data
try {
const chainInfo = getChainInfo(chainId)
if (!chainInfo) {
logger.console.warn(
`[trails-sdk] Chain info not found for chainId: ${chainId}`,
)
return null
}
// Create a public client for the specific chain
const publicClient = createPublicClient({
chain: chainInfo,
transport: http(),
})
// Read token data on-chain
const [name, symbol, decimals] = await Promise.all([
publicClient.readContract({
address: normalizedAddress as `0x${string}`,
abi: erc20Abi,
functionName: "name",
}),
publicClient.readContract({
address: normalizedAddress as `0x${string}`,
abi: erc20Abi,
functionName: "symbol",
}),
publicClient.readContract({
address: normalizedAddress as `0x${string}`,
abi: erc20Abi,
functionName: "decimals",
}),
])
// Create token info from on-chain data
const onChainTokenInfo: SupportedToken = {
id: `${symbol}-${chainInfo.name}`,
name: name,
symbol: symbol,
decimals: decimals,
chainId: chainId,
contractAddress: normalizedAddress,
chainName: chainInfo.name,
imageUrl: getTokenImageUrl({
chainId,
contractAddress: normalizedAddress,
symbol: symbol,
}),
}
// Cache the on-chain token info
setTokenInfoCache(`${chainId}:${normalizedAddress}`, onChainTokenInfo)
logger.console.log(
"[trails-sdk] Fetched token info on-chain:",
onChainTokenInfo,
)
return onChainTokenInfo
} catch (error) {
logger.console.error(
"[trails-sdk] Error fetching on-chain token info:",
error,
)
return null
}
}
export async function getTokenAddress(chainId: number, tokenSymbol: string) {
const chainInfo = getChainInfo(chainId)
if (tokenSymbol === chainInfo?.nativeCurrency.symbol) {
return zeroAddress
}
const tokens = await getSupportedTokens()
const token = tokens.find(
(t) => t.symbol === tokenSymbol && t.chainId === chainId,
)
if (token?.contractAddress) {
return token.contractAddress
}
throw new Error(
`Unsupported token symbol: ${tokenSymbol} for chainId: ${chainId}`,
)
}
type UseTokenAddressProps = {
chainId?: number | null
tokenSymbol?: string | null
}
export function useTokenAddress({
chainId,
tokenSymbol,
}: UseTokenAddressProps) {
const { data: tokenAddress } = useQuery({
queryKey: ["tokenAddress", chainId, tokenSymbol],
queryFn: () =>
chainId && tokenSymbol ? getTokenAddress(chainId, tokenSymbol) : null,
enabled: !!chainId && !!tokenSymbol,
staleTime: 60 * 60 * 1000, // 1 hour - token addresses rarely change
gcTime: 24 * 60 * 60 * 1000, // 24 hours - keep in cache for a full day
refetchOnWindowFocus: false, // Don't refetch when window regains focus
refetchOnReconnect: false, // Don't refetch on network reconnect
})
return tokenAddress || null
}
export function getCommonTokenImageUrl({
symbol,
contractAddress,
chainId,
}: {
symbol?: string | null
contractAddress?: string | null
chainId?: number | null
}) {
if (!symbol || !contractAddress || !chainId) {
return ""
}
if (!symbol) {
const token = commonTokens.find(
(t) =>
t.chainId === chainId &&
t.contractAddress?.toLowerCase() === contractAddress.toLowerCase(),
)
if (token) {
symbol = token.symbol
}
}
if (!symbol) {
return ""
}
const symbolKey = tokenImageSymbolMap[symbol] ?? symbol
if (commonTokenImages[symbolKey]) {
return commonTokenImages[symbolKey]
}
return ""
}
export function getTokenImageUrl({
chainId,
contractAddress,
symbol,
}: {
chainId?: number
contractAddress?: string
symbol?: string
}) {
if (!chainId || !contractAddress || !symbol) {
return ""
}
const cachedImageUrl = getCachedTokenImageUrl(
chainId,
contractAddress,
symbol,
)
if (cachedImageUrl) {
return cachedImageUrl
}
if (symbol) {
const commonImageUrl = getCommonTokenImageUrl({
symbol,
contractAddress,
chainId,
})
if (commonImageUrl) {
return commonImageUrl
}
}
const imageUrl = `https://assets.sequence.info/images/tokens/large/${chainId}/${contractAddress.toLowerCase()}.webp`
return imageUrl
}
// React hook for token image fetching with caching
export function useTokenImageUrl({
chainId,
contractAddress,
symbol,
}: {
chainId?: number
contractAddress?: string
symbol?: string
}): {
imageUrl: string
isLoading: boolean
error: Error | null
hasImage: boolean
} {
const {
data: imageUrl = "",
isLoading,
error,
} = useQuery({
queryKey: ["tokenImage", chainId, contractAddress, symbol],
queryFn: () =>
getTokenImageUrlOrFallback({ chainId, contractAddress, symbol }),
enabled: !!chainId && !!contractAddress,
staleTime: 24 * 60 * 60 * 1000, // 24 hours
gcTime: 7 * 24 * 60 * 60 * 1000, // 7 days
retry: 2,
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
refetchOnWindowFocus: false,
refetchOnReconnect: false,
})
return {
imageUrl,
isLoading,
error,
hasImage: !!imageUrl,
}
}
export function useTokenList() {
const { supportedTokens: tokens, isLoadingTokens } = useSupportedTokens()
return {
tokens,
isLoadingTokens: isLoadingTokens,
}
}
export function useTokenInfo({
address,
chainId,
}: {
address: string
chainId?: number
}): {
tokenInfo: Partial<SupportedToken> | null
isLoading: boolean
error: Error | null
} {
// Always call hooks unconditionally
const isAddress = address?.startsWith("0x") ?? false
// Check cache first
const cachedInfo = useMemo(() => {
if (!isAddress || !chainId) return null
return getCachedTokenInfo(chainId, address)
}, [isAddress, chainId, address])
const contract = {
address: address as `0x${string}`,
abi: erc20Abi,
chainId,
} as const
const result = useReadContracts({
contracts:
!!isAddress && !!chainId && !cachedInfo
? [
{ ...contract, functionName: "name" },
{ ...contract, functionName: "symbol" },
{ ...contract, functionName: "decimals" },
]
: [],
})
const error =
result?.error ?? result?.data?.find((r) => r.error)?.error ?? null
const [name, symbol, decimals] = result.data ?? []
const chainInfo = getChainInfo(chainId!)
const tokenInfo = useMemo(() => {
// If we have cached info, use it
if (cachedInfo) {
return {
id: cachedInfo.id,
name: cachedInfo.name,
symbol: cachedInfo.symbol,
decimals: cachedInfo.decimals,
chainId: cachedInfo.chainId,
contractAddress: cachedInfo.contractAddress,
chainName: cachedInfo.chainName,
imageUrl: cachedInfo.imageUrl,
}
}
// If we have blockchain data, use it and cache it
if (symbol?.result && name?.result && decimals?.result != null) {
const tokenInfo: SupportedToken = {
id: symbol.result,
name: name.result,
symbol: symbol.result,
decimals: decimals.result,
chainId: chainId!,
contractAddress: address,
chainName: chainInfo?.name ?? "",
imageUrl: getTokenImageUrl({
chainId,
contractAddress: address,
symbol: symbol.result,
}),
}
// Cache the result
setTokenInfoCache(`${chainId}:${address.toLowerCase()}`, tokenInfo)
return tokenInfo
}
return null
}, [
cachedInfo,
address,
chainId,
chainInfo?.name,
name?.result,
symbol?.result,
decimals?.result,
])
// Early return if not a valid address or chainId
if (!isAddress || !chainId) {
return {
tokenInfo: null,
isLoading: false,
error: null,
}
}
return {
tokenInfo,
isLoading: !cachedInfo && result.isLoading,
error: error,
}
}
export const tokenImageSymbolMap: Record<string, string> = {
ETH: "ETH",
cbETH: "ETH",
POL: "POL",
WPOL: "POL",
USDC: "USDC",
"USDC.e": "USDC",
USDT: "USDT",
DAI: "DAI",
xDAI: "DAI",
WBTC: "WBTC",
cbBTC: "WBTC",
BAT: "BAT",
ARB: "ARB",
LINK: "LINK",
}
export const commonTokens: SupportedToken[] = [
// Native tokens
{
id: "ETH-ethereum",
symbol: "ETH",
name: "Ethereum",
contractAddress: "0x0000000000000000000000000000000000000000",
decimals: 18,
chainId: mainnet.id,
chainName: mainnet.name,
imageUrl: commonTokenImages.ETH!,
},
{
id: "ETH-arbitrum",
symbol: "ETH",
name: "ETH Arbitrum",
contractAddress: "0x0000000000000000000000000000000000000000",
decimals: 18,
chainId: arbitrum.id,
chainName: arbitrum.name,
imageUrl: commonTokenImages.ETH!,
},
{
id: "ETH-optimism",
symbol: "ETH",
name: "ETH Optimism",
contractAddress: "0x0000000000000000000000000000000000000000",
decimals: 18,
chainId: optimism.id,
chainName: optimism.name,
imageUrl: commonTokenImages.ETH!,
},
{
id: "ETH-base",
symbol: "ETH",
name: "ETH Base",
contractAddress: "0x0000000000000000000000000000000000000000",
decimals: 18,
chainId: base.id,
chainName: base.name,
imageUrl: commonTokenImages.ETH!,
},
{
id: "POL-polygon",
symbol: "POL",
name: "Polygon",
contractAddress: "0x0000000000000000000000000000000000000000",
decimals: 18,
chainId: polygon.id,
chainName: polygon.name,
imageUrl: commonTokenImages.POL!,
},
// USDC
{
id: "USDC-ethereum",
symbol: "USDC",
name: "USDC",
contractAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
decimals: 6,
chainId: mainnet.id,
chainName: mainnet.name,
imageUrl: commonTokenImages.USDC!,
},
{
id: "USDC-arbitrum",
symbol: "USDC",
name: "USDC Arbitrum",
contractAddress: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
decimals: 6,
chainId: arbitrum.id,
chainName: arbitrum.name,
imageUrl: commonTokenImages.USDC!,
},
{
id: "USDC-avalanche",
symbol: "USDC",
name: "USDC Avalanche",
contractAddress: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
decimals: 6,
chainId: avalanche.id,
chainName: avalanche.name,
imageUrl: commonTokenImages.USDC!,
},
{
id: "USDC-base",
symbol: "USDC",
name: "USDC Base",
contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
decimals: 6,
chainId: base.id,
chainName: base.name,
imageUrl: commonTokenImages.USDC!,
},
{
id: "USDC-linea",
symbol: "USDC",
name: "USDC Linea",
contractAddress: "0x176211869cA2b568f2A7D4EE941E073a821EE1ff",
decimals: 6,
chainId: linea.id,
chainName: linea.name,
imageUrl: commonTokenImages.USDC!,
},
{
id: "USDC-optimism",
symbol: "USDC",
name: "USDC Optimism",
contractAddress: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
decimals: 6,
chainId: optimism.id,
chainName: optimism.name,
imageUrl: commonTokenImages.USDC!,
},
{
id: "USDC-polygon",
symbol: "USDC",
name: "USDC Polygon",
contractAddress: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
decimals: 6,
chainId: polygon.id,
chainName: polygon.name,
imageUrl: commonTokenImages.USDC!,
},
{
id: "USDC-unichain",
symbol: "USDC",
name: "USDC Unichain",
contractAddress: "0x078D782b760474a361dDA0AF3839290b0EF57AD6",
decimals: 6,
chainId: unichain.id,
chainName: unichain.name,
imageUrl: commonTokenImages.USDC!,
},
{
id: "USDC-worldchain",
symbol: "USDC",
name: "USDC WorldChain",
contractAddress: "0x79A02482A880bCe3F13E09da970dC34dB4cD24D1",
decimals: 6,
chainId: worldchain.id,
chainName: worldchain.name,
imageUrl: commonTokenImages.USDC!,
},
// Basic Attention Token
{
id: "BAT-ethereum",
symbol: "BAT",
name: "Basic Attention Token",
contractAddress: "0x0D8775F648430679A709E98d2b0Cb6250d2887EF",
decimals: 18,
chainId: mainnet.id,
chainName: mainnet.name,
imageUrl: commonTokenImages.BAT!,
},
{
id: "BAT-polygon",
symbol: "BAT",
name: "Basic Attention Token",
contractAddress: "0x3Cef98bb43d732E2F285eE605a8158cDE967D219",
decimals: 18,
chainId: polygon.id,
chainName: polygon.name,
imageUrl: commonTokenImages.BAT!,
},
{
id: "BAT-avalanche",
symbol: "BAT",
name: "Basic Attention Token",
contractAddress: "0x98443B96EA4b0858FDF3219Cd13e98C7A4690588",
decimals: 18,
chainId: avalanche.id,
chainName: avalanche.name,
imageUrl: commonTokenImages.BAT!,
},
// ARB
{
id: "ARB-arbitrum",
symbol: "ARB",
name: "Arbitrum",
contractAddress: "0x912CE59144191C1204E64559FE8253a0e49E6548",
decimals: 18,
chainId: arbitrum.id,
chainName: arbitrum.name,
imageUrl: commonTokenImages.ARB!,
},
// Chainlink
{
id: "LINK-ethereum",
symbol: "LINK",
name: "Chainlink",
contractAddress: "0x514910771AF9Ca656af840dff83E8264EcF986CA",
decimals: 18,
chainId: mainnet.id,
chainName: mainnet.name,
imageUrl: commonTokenImages.LINK!,
},
// Etherlink tokens
{
id: "XTZ-etherlink",
symbol: "XTZ",
name: "Tezos",
contractAddress: "0x0000000000000000000000000000000000000000",
decimals: 18,
chainId: etherlink.id,
chainName: etherlink.name,
imageUrl: commonTokenImages.XTZ as string,
},
{
id: "WXTZ-etherlink",
symbol: "WXTZ",
name: "Wrapped XTZ",
contractAddress: "0xc9B53AB2679f573e480d01e0f49e2B5CFB7a3EAb",
decimals: 18,
chainId: etherlink.id,
chainName: etherlink.name,
imageUrl: commonTokenImages.WXTZ as string,
},
{
id: "USDC-etherlink",
symbol: "USDC",
name: "USD Coin",
contractAddress: "0x796Ea11Fa2dD751eD01b53C372fFDB4AAa8f00F9",
decimals: 6,
chainId: etherlink.id,
chainName: etherlink.name,
imageUrl: commonTokenImages.USDC as string,
},
{
id: "WETH-etherlink",
symbol: "WETH",
name: "Wrapped ETH",
contractAddress: "0xfc24f770F94edBca6D6f885E12d4317320BcB401",
decimals: 18,
chainId: etherlink.id,
chainName: etherlink.name,
imageUrl: commonTokenImages.WETH as string,
},
{
id: "USDT-etherlink",
symbol: "USDT",
name: "Bridged USDT",
contractAddress: "0x2C03058C8AFC06713be23e58D2febC8337dbfE6A",
decimals: 6,
chainId: etherlink.id,
chainName: etherlink.name,
imageUrl: commonTokenImages.USDT as string,
},
]
export const wethAddresses: Record<string, string> = {
1: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
10: "0x4200000000000000000000000000000000000006",
8453: "0x4200000000000000000000000000000000000006",
42161: "0x82af49447d8a07e3bd95bd0d56f35241523fbab1",
137: "0x7ceb23fd6bc0add59e62ac25578270cff1b9f619",
100: "0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1",
}
export function getWethAddress(chainId: number): string | null {
return wethAddresses[chainId] ?? null
}