jupiter-client.ts 1.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
  1. import { HTTPError } from 'ky'
  2. import { config } from '../config'
  3. /**
  4. * Returns Jupiter API authentication headers.
  5. * Shared across all Jupiter API callers (swap, price, etc.).
  6. * Ref: https://dev.jup.ag/portal/setup.md
  7. */
  8. export function getJupiterHeaders(): Record<string, string> {
  9. const headers: Record<string, string> = {
  10. Accept: 'application/json',
  11. }
  12. if (config.jupiterApiKey) {
  13. headers['x-api-key'] = config.jupiterApiKey
  14. }
  15. return headers
  16. }
  17. /**
  18. * Retry wrapper with exponential backoff + jitter for Jupiter API calls.
  19. * Only retries on HTTP 429 (rate limited); all other errors propagate immediately.
  20. * Formula: delay = min(baseDelayMs * 2^attempt + jitter(0..500ms), maxDelayMs)
  21. * Ref: https://dev.jup.ag/portal/rate-limit.md
  22. */
  23. export async function withRetry<T>(
  24. fn: () => Promise<T>,
  25. maxAttempts = 3,
  26. baseDelayMs = 1000,
  27. maxDelayMs = 10000,
  28. ): Promise<T> {
  29. let lastError: unknown
  30. for (let attempt = 0; attempt < maxAttempts; attempt++) {
  31. try {
  32. return await fn()
  33. } catch (error) {
  34. lastError = error
  35. const status = error instanceof HTTPError ? error.response.status : null
  36. const isRateLimited = status === 429
  37. if (!isRateLimited || attempt >= maxAttempts - 1) throw error
  38. const jitter = Math.random() * 500
  39. const delay = Math.min(baseDelayMs * Math.pow(2, attempt) + jitter, maxDelayMs)
  40. console.warn(
  41. `[Jupiter] Rate limited (HTTP 429), retrying in ${Math.round(delay)}ms (attempt ${attempt + 1}/${maxAttempts})`,
  42. )
  43. await new Promise((resolve) => setTimeout(resolve, delay))
  44. }
  45. }
  46. throw lastError
  47. }