import {z} from 'zod' export type GetApiCoinsTopGainersLosersArgs = {vs_currency: string, duration?: ('1h' | '24h' | '7d' | '14d' | '30d' | '60d' | '1y') | undefined, price_change_percentage?: string | undefined, top_coins?: ('300' | '500' | '1000' | 'all') | undefined}; export type GetApiCoinsTopGainersLosersOptions = {baseUrl?: string | undefined, headerAuth?: string | undefined, fetch?: typeof fetch | undefined}; export const topGainersLosersItem = z.object({id: z.string(), symbol: z.string(), name: z.string(), image: z.string(), market_cap_rank: z.number().int().nullable(), usd: z.number(), usd_24h_vol: z.number(), usd_24h_change: z.number().nullable(), usd_1h_change: z.number().nullable().optional(), usd_7d_change: z.number().nullable().optional(), usd_14d_change: z.number().nullable().optional(), usd_30d_change: z.number().nullable().optional(), usd_60d_change: z.number().nullable().optional(), usd_200d_change: z.number().nullable().optional(), usd_1y_change: z.number().nullable().optional()}); export const topGainersLosers = z.object({top_gainers: z.array(topGainersLosersItem), top_losers: z.array(topGainersLosersItem)}); 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 }; /** * Top Gainers & Losers * * GET /coins/top_gainers_losers * * Also accepts auth: queryAuth */ export const getApiCoinsTopGainersLosers = async (args: GetApiCoinsTopGainersLosersArgs, options?: GetApiCoinsTopGainersLosersOptions) => { const {baseUrl = "https://pro-api.coingecko.com/api/v3", fetch: fetchFn = fetch, headerAuth} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('accept', "application/json") if (args.vs_currency != null) query.append("vs_currency", String(args.vs_currency)) if (args.duration != null) query.append("duration", String(args.duration)) if (args.price_change_percentage != null) query.append("price_change_percentage", String(args.price_change_percentage)) if (args.top_coins != null) query.append("top_coins", String(args.top_coins)) if (headerAuth !== undefined) headers.set("x-cg-pro-api-key", headerAuth) const url = `${baseUrl.replace(/\/+$/, '')}/coins/top_gainers_losers${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(topGainersLosers, await res.json()) };