import { HTTPError } from 'ky' import { config } from '../config' /** * Returns Jupiter API authentication headers. * Shared across all Jupiter API callers (swap, price, etc.). * Ref: https://dev.jup.ag/portal/setup.md */ export function getJupiterHeaders(): Record { const headers: Record = { Accept: 'application/json', } if (config.jupiterApiKey) { headers['x-api-key'] = config.jupiterApiKey } return headers } /** * Retry wrapper with exponential backoff + jitter for Jupiter API calls. * Only retries on HTTP 429 (rate limited); all other errors propagate immediately. * Formula: delay = min(baseDelayMs * 2^attempt + jitter(0..500ms), maxDelayMs) * Ref: https://dev.jup.ag/portal/rate-limit.md */ export async function withRetry( fn: () => Promise, maxAttempts = 3, baseDelayMs = 1000, maxDelayMs = 10000, ): Promise { let lastError: unknown for (let attempt = 0; attempt < maxAttempts; attempt++) { try { return await fn() } catch (error) { lastError = error const status = error instanceof HTTPError ? error.response.status : null const isRateLimited = status === 429 if (!isRateLimited || attempt >= maxAttempts - 1) throw error const jitter = Math.random() * 500 const delay = Math.min(baseDelayMs * Math.pow(2, attempt) + jitter, maxDelayMs) console.warn( `[Jupiter] Rate limited (HTTP 429), retrying in ${Math.round(delay)}ms (attempt ${attempt + 1}/${maxAttempts})`, ) await new Promise((resolve) => setTimeout(resolve, delay)) } } throw lastError }