import {z} from 'zod' export type GetApiSearchArgs = {query?: string | undefined, symbol?: string | undefined, page?: number | undefined, limit?: number | undefined, format?: 'full' | undefined, cursor?: string | undefined, sortBy?: ('liquidityUsd' | 'marketCapUsd' | 'priceUsd' | 'volume' | 'volume_5m' | 'volume_15m' | 'volume_30m' | 'volume_1h' | 'volume_6h' | 'volume_12h' | 'volume_24h' | 'top10' | 'dev' | 'insiders' | 'snipers' | 'holders' | 'buys' | 'sells' | 'totalTransactions' | 'fees.total' | 'fees.totalTrading' | 'fees.totalTips' | 'createdAt' | 'lpBurn' | 'curvePercentage' | 'communityMessages') | undefined, sortOrder?: ('asc' | 'desc') | undefined, hasImage?: boolean | undefined, hasSocials?: boolean | undefined, image?: string | undefined, launchpad?: string | undefined, showAllPools?: boolean | undefined, showPriceChanges?: boolean | undefined, minCreatedAt?: number | undefined, maxCreatedAt?: number | undefined, minGraduatedAt?: number | undefined, maxGraduatedAt?: number | undefined, minLiquidity?: number | undefined, maxLiquidity?: number | undefined, minMarketCap?: number | undefined, maxMarketCap?: number | undefined, minVolume?: number | undefined, maxVolume?: number | undefined, volumeTimeframe?: ('5m' | '15m' | '30m' | '1h' | '6h' | '12h' | '24h') | undefined, minVolume_5m?: number | undefined, maxVolume_5m?: number | undefined, minVolume_15m?: number | undefined, maxVolume_15m?: number | undefined, minVolume_30m?: number | undefined, maxVolume_30m?: number | undefined, minVolume_1h?: number | undefined, maxVolume_1h?: number | undefined, minVolume_6h?: number | undefined, maxVolume_6h?: number | undefined, minVolume_12h?: number | undefined, maxVolume_12h?: number | undefined, minVolume_24h?: number | undefined, maxVolume_24h?: number | undefined, minBuys?: number | undefined, maxBuys?: number | undefined, minSells?: number | undefined, maxSells?: number | undefined, minTotalTransactions?: number | undefined, maxTotalTransactions?: number | undefined, minHolders?: number | undefined, maxHolders?: number | undefined, minTop10?: number | undefined, maxTop10?: number | undefined, minDev?: number | undefined, maxDev?: number | undefined, minInsiders?: number | undefined, maxInsiders?: number | undefined, minSnipers?: number | undefined, maxSnipers?: number | undefined, minBundlers?: number | undefined, maxBundlers?: number | undefined, minBundlerPercentage?: number | undefined, maxBundlerPercentage?: number | undefined, minRiskScore?: number | undefined, maxRiskScore?: number | undefined, lpBurn?: number | undefined, market?: string | undefined, freezeAuthority?: string | undefined, mintAuthority?: string | undefined, deployer?: string | undefined, creator?: string | undefined, status?: ('graduating' | 'graduated' | 'default') | undefined, minCurvePercentage?: number | undefined, maxCurvePercentage?: number | undefined, twitter?: string | undefined, telegram?: string | undefined, discord?: string | undefined, website?: string | undefined, facebook?: string | undefined, instagram?: string | undefined, youtube?: string | undefined, reddit?: string | undefined, tiktok?: string | undefined, github?: string | undefined, minFeesTotal?: number | undefined, maxFeesTotal?: number | undefined, minFeesTrading?: number | undefined, maxFeesTrading?: number | undefined, minFeesTips?: number | undefined, maxFeesTips?: number | undefined, hasCoinCommunity?: boolean | undefined, minCommunityMessages?: number | undefined, maxCommunityMessages?: number | undefined}; export type GetApiSearchOptions = {baseUrl?: string | undefined, apiKey?: string | undefined, fetch?: typeof fetch | undefined}; export const events = z.record(z.string(), z.object({priceChangePercentage: z.number().optional()})); export const searchToken = z.object({id: z.string().optional(), name: z.string().optional(), symbol: z.string().optional(), mint: z.string().optional(), decimals: z.number().int().optional(), image: z.string().optional(), hasSocials: z.boolean().optional(), hasCoinCommunity: z.boolean().optional(), communityMessages: z.number().int().optional(), holders: z.number().int().optional(), launchpad: z.object({name: z.string().optional(), url: z.string().optional(), logo: z.string().optional(), curvePercentage: z.number().optional(), fee: z.number().optional()}).optional(), jupiter: z.boolean().optional(), verified: z.boolean().optional(), liquidityUsd: z.number().optional(), marketCapUsd: z.number().optional(), priceUsd: z.number().optional(), lpBurn: z.number().int().optional(), market: z.string().optional(), quoteToken: z.string().optional(), freezeAuthority: z.string().nullable().optional(), mintAuthority: z.string().nullable().optional(), poolAddress: z.string().optional(), deployer: z.string().nullable().optional(), status: z.string().optional(), createdAt: z.number().int().optional(), graduatedAt: z.number().int().nullable().optional(), lastUpdated: z.number().int().optional(), events: events.optional(), buys: z.number().int().optional(), sells: z.number().int().optional(), totalTransactions: z.number().int().optional(), volume: z.number().optional(), volume_5m: z.number().optional(), volume_15m: z.number().optional(), volume_30m: z.number().optional(), volume_1h: z.number().optional(), volume_6h: z.number().optional(), volume_12h: z.number().optional(), volume_24h: z.number().optional(), top10: z.number().optional(), dev: z.number().optional(), insiders: z.number().optional(), snipers: z.number().optional(), bundlers: z.object({count: z.number().int().optional(), balance: z.number().optional(), percentage: z.number().optional()}).optional(), riskScore: z.number().int().optional(), socials: z.object({twitter: z.string().optional(), website: z.string().optional(), telegram: z.string().optional(), discord: z.string().optional()}).optional(), fees: z.object({total: z.number().optional(), totalTrading: z.number().optional(), totalTips: z.number().optional()}).optional(), tokenDetails: z.object({creator: z.string().optional(), tx: z.string().optional(), time: z.number().int().optional()}).optional()}); export const searchResponse = z.object({status: z.string().optional(), data: z.array(searchToken).optional(), total: z.number().int().optional(), pages: z.number().int().optional(), page: z.number().int().optional(), cursor: z.string().optional(), nextCursor: z.string().optional(), hasMore: z.boolean().optional()}); export const validateResponse = async ( schema: { readonly '~standard': { readonly validate: (value: unknown) => ({ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }) | Promise<{ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }> } }, value: unknown ): Promise => { const result = await schema['~standard'].validate(value) if (result.issues !== undefined) { const detail = result.issues .map(({ message, path }) => path?.length ? `${path.map((segment) => String(typeof segment === 'object' ? segment.key : segment)).join('.')}: ${message}` : message ) .join('; ') throw new Error(`Response does not match the schema: ${detail}`, { cause: { issues: result.issues } }) } return result.value }; /** * Token Search * * GET /search */ export const getApiSearch = async (args: GetApiSearchArgs, options?: GetApiSearchOptions) => { const {baseUrl = "https://data.solanatracker.io", fetch: fetchFn = fetch, apiKey} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('accept', "application/json") if (args.query != null) query.append("query", String(args.query)) if (args.symbol != null) query.append("symbol", String(args.symbol)) if (args.page != null) query.append("page", String(args.page)) if (args.limit != null) query.append("limit", String(args.limit)) if (args.format != null) query.append("format", String(args.format)) if (args.cursor != null) query.append("cursor", String(args.cursor)) if (args.sortBy != null) query.append("sortBy", String(args.sortBy)) if (args.sortOrder != null) query.append("sortOrder", String(args.sortOrder)) if (args.hasImage != null) query.append("hasImage", String(args.hasImage)) if (args.hasSocials != null) query.append("hasSocials", String(args.hasSocials)) if (args.image != null) query.append("image", String(args.image)) if (args.launchpad != null) query.append("launchpad", String(args.launchpad)) if (args.showAllPools != null) query.append("showAllPools", String(args.showAllPools)) if (args.showPriceChanges != null) query.append("showPriceChanges", String(args.showPriceChanges)) if (args.minCreatedAt != null) query.append("minCreatedAt", String(args.minCreatedAt)) if (args.maxCreatedAt != null) query.append("maxCreatedAt", String(args.maxCreatedAt)) if (args.minGraduatedAt != null) query.append("minGraduatedAt", String(args.minGraduatedAt)) if (args.maxGraduatedAt != null) query.append("maxGraduatedAt", String(args.maxGraduatedAt)) if (args.minLiquidity != null) query.append("minLiquidity", String(args.minLiquidity)) if (args.maxLiquidity != null) query.append("maxLiquidity", String(args.maxLiquidity)) if (args.minMarketCap != null) query.append("minMarketCap", String(args.minMarketCap)) if (args.maxMarketCap != null) query.append("maxMarketCap", String(args.maxMarketCap)) if (args.minVolume != null) query.append("minVolume", String(args.minVolume)) if (args.maxVolume != null) query.append("maxVolume", String(args.maxVolume)) if (args.volumeTimeframe != null) query.append("volumeTimeframe", String(args.volumeTimeframe)) if (args.minVolume_5m != null) query.append("minVolume_5m", String(args.minVolume_5m)) if (args.maxVolume_5m != null) query.append("maxVolume_5m", String(args.maxVolume_5m)) if (args.minVolume_15m != null) query.append("minVolume_15m", String(args.minVolume_15m)) if (args.maxVolume_15m != null) query.append("maxVolume_15m", String(args.maxVolume_15m)) if (args.minVolume_30m != null) query.append("minVolume_30m", String(args.minVolume_30m)) if (args.maxVolume_30m != null) query.append("maxVolume_30m", String(args.maxVolume_30m)) if (args.minVolume_1h != null) query.append("minVolume_1h", String(args.minVolume_1h)) if (args.maxVolume_1h != null) query.append("maxVolume_1h", String(args.maxVolume_1h)) if (args.minVolume_6h != null) query.append("minVolume_6h", String(args.minVolume_6h)) if (args.maxVolume_6h != null) query.append("maxVolume_6h", String(args.maxVolume_6h)) if (args.minVolume_12h != null) query.append("minVolume_12h", String(args.minVolume_12h)) if (args.maxVolume_12h != null) query.append("maxVolume_12h", String(args.maxVolume_12h)) if (args.minVolume_24h != null) query.append("minVolume_24h", String(args.minVolume_24h)) if (args.maxVolume_24h != null) query.append("maxVolume_24h", String(args.maxVolume_24h)) if (args.minBuys != null) query.append("minBuys", String(args.minBuys)) if (args.maxBuys != null) query.append("maxBuys", String(args.maxBuys)) if (args.minSells != null) query.append("minSells", String(args.minSells)) if (args.maxSells != null) query.append("maxSells", String(args.maxSells)) if (args.minTotalTransactions != null) query.append("minTotalTransactions", String(args.minTotalTransactions)) if (args.maxTotalTransactions != null) query.append("maxTotalTransactions", String(args.maxTotalTransactions)) if (args.minHolders != null) query.append("minHolders", String(args.minHolders)) if (args.maxHolders != null) query.append("maxHolders", String(args.maxHolders)) if (args.minTop10 != null) query.append("minTop10", String(args.minTop10)) if (args.maxTop10 != null) query.append("maxTop10", String(args.maxTop10)) if (args.minDev != null) query.append("minDev", String(args.minDev)) if (args.maxDev != null) query.append("maxDev", String(args.maxDev)) if (args.minInsiders != null) query.append("minInsiders", String(args.minInsiders)) if (args.maxInsiders != null) query.append("maxInsiders", String(args.maxInsiders)) if (args.minSnipers != null) query.append("minSnipers", String(args.minSnipers)) if (args.maxSnipers != null) query.append("maxSnipers", String(args.maxSnipers)) if (args.minBundlers != null) query.append("minBundlers", String(args.minBundlers)) if (args.maxBundlers != null) query.append("maxBundlers", String(args.maxBundlers)) if (args.minBundlerPercentage != null) query.append("minBundlerPercentage", String(args.minBundlerPercentage)) if (args.maxBundlerPercentage != null) query.append("maxBundlerPercentage", String(args.maxBundlerPercentage)) if (args.minRiskScore != null) query.append("minRiskScore", String(args.minRiskScore)) if (args.maxRiskScore != null) query.append("maxRiskScore", String(args.maxRiskScore)) if (args.lpBurn != null) query.append("lpBurn", String(args.lpBurn)) if (args.market != null) query.append("market", String(args.market)) if (args.freezeAuthority != null) query.append("freezeAuthority", String(args.freezeAuthority)) if (args.mintAuthority != null) query.append("mintAuthority", String(args.mintAuthority)) if (args.deployer != null) query.append("deployer", String(args.deployer)) if (args.creator != null) query.append("creator", String(args.creator)) if (args.status != null) query.append("status", String(args.status)) if (args.minCurvePercentage != null) query.append("minCurvePercentage", String(args.minCurvePercentage)) if (args.maxCurvePercentage != null) query.append("maxCurvePercentage", String(args.maxCurvePercentage)) if (args.twitter != null) query.append("twitter", String(args.twitter)) if (args.telegram != null) query.append("telegram", String(args.telegram)) if (args.discord != null) query.append("discord", String(args.discord)) if (args.website != null) query.append("website", String(args.website)) if (args.facebook != null) query.append("facebook", String(args.facebook)) if (args.instagram != null) query.append("instagram", String(args.instagram)) if (args.youtube != null) query.append("youtube", String(args.youtube)) if (args.reddit != null) query.append("reddit", String(args.reddit)) if (args.tiktok != null) query.append("tiktok", String(args.tiktok)) if (args.github != null) query.append("github", String(args.github)) if (args.minFeesTotal != null) query.append("minFeesTotal", String(args.minFeesTotal)) if (args.maxFeesTotal != null) query.append("maxFeesTotal", String(args.maxFeesTotal)) if (args.minFeesTrading != null) query.append("minFeesTrading", String(args.minFeesTrading)) if (args.maxFeesTrading != null) query.append("maxFeesTrading", String(args.maxFeesTrading)) if (args.minFeesTips != null) query.append("minFeesTips", String(args.minFeesTips)) if (args.maxFeesTips != null) query.append("maxFeesTips", String(args.maxFeesTips)) if (args.hasCoinCommunity != null) query.append("hasCoinCommunity", String(args.hasCoinCommunity)) if (args.minCommunityMessages != null) query.append("minCommunityMessages", String(args.minCommunityMessages)) if (args.maxCommunityMessages != null) query.append("maxCommunityMessages", String(args.maxCommunityMessages)) if (apiKey !== undefined) headers.set("x-api-key", apiKey) const url = `${baseUrl.replace(/\/+$/, '')}/search${query.size > 0 ? `?${query}` : ''}` const res = await fetchFn(url, {method: "GET", headers}) if (!res.ok) throw new Error(`Request failed with status ${res.status}`, {cause: {status: res.status, body: await res.text()}}) return validateResponse(searchResponse, await res.json()) };