diff --git a/src/services/marketRate/coingeckoFetcher.ts b/src/services/marketRate/coingeckoFetcher.ts index 6320a69..c039ae1 100644 --- a/src/services/marketRate/coingeckoFetcher.ts +++ b/src/services/marketRate/coingeckoFetcher.ts @@ -1,4 +1,5 @@ import axios from "axios"; +import { OUTGOING_HTTP_TIMEOUT_MS } from "../../utils/httpTimeout.js"; import { withRetry } from "../../utils/retryUtil.js"; export class CoinGeckoFetcher { @@ -11,7 +12,10 @@ export class CoinGeckoFetcher { */ static async fetchXlmUsdPrice(): Promise { const response = await withRetry( - () => axios.get(CoinGeckoFetcher.API_URL), + () => + axios.get(CoinGeckoFetcher.API_URL, { + timeout: OUTGOING_HTTP_TIMEOUT_MS, + }), { maxRetries: 3, retryDelay: 1000, diff --git a/src/services/marketRate/ghsFetcher.ts b/src/services/marketRate/ghsFetcher.ts index 1252710..e656935 100644 --- a/src/services/marketRate/ghsFetcher.ts +++ b/src/services/marketRate/ghsFetcher.ts @@ -1,6 +1,7 @@ import axios from 'axios'; import { MarketRateFetcher, MarketRate } from './types'; import { validatePrice } from './validation'; +import { OUTGOING_HTTP_TIMEOUT_MS } from '../../utils/httpTimeout.js'; type CoinGeckoPriceResponse = { stellar?: { @@ -42,7 +43,7 @@ export class GHSRateFetcher implements MarketRateFetcher { () => axios.get( this.coinGeckoUrl, { - timeout: 10000, + timeout: OUTGOING_HTTP_TIMEOUT_MS, headers: { "User-Agent": "StellarFlow-Oracle/1.0", }, @@ -81,7 +82,7 @@ export class GHSRateFetcher implements MarketRateFetcher { () => axios.get( this.coinGeckoUrl, { - timeout: 10000, + timeout: OUTGOING_HTTP_TIMEOUT_MS, headers: { "User-Agent": "StellarFlow-Oracle/1.0", }, @@ -100,7 +101,7 @@ export class GHSRateFetcher implements MarketRateFetcher { () => axios.get( this.usdToGhsUrl, { - timeout: 10000, + timeout: OUTGOING_HTTP_TIMEOUT_MS, headers: { "User-Agent": "StellarFlow-Oracle/1.0", }, @@ -119,7 +120,7 @@ export class GHSRateFetcher implements MarketRateFetcher { const usdPrice = validatePrice(stellarPrice.usd); const exchangeRateResponse = await axios.get(this.usdToGhsUrl, { - timeout: 10000, + timeout: OUTGOING_HTTP_TIMEOUT_MS, headers: { "User-Agent": "StellarFlow-Oracle/1.0", }, diff --git a/src/services/marketRate/kesFetcher.ts b/src/services/marketRate/kesFetcher.ts index 59b35bb..7a91126 100644 --- a/src/services/marketRate/kesFetcher.ts +++ b/src/services/marketRate/kesFetcher.ts @@ -1,6 +1,7 @@ import axios from 'axios'; import { MarketRateFetcher, MarketRate, RateSource } from './types'; import { validatePrice } from './validation'; +import { OUTGOING_HTTP_TIMEOUT_MS } from '../../utils/httpTimeout.js'; /** * Binance Ticker Response Interface @@ -170,7 +171,7 @@ const BINANCE_P2P_URL = /** * Default timeout for API requests (ms) */ -const DEFAULT_TIMEOUT_MS = 8000; +const DEFAULT_TIMEOUT_MS = OUTGOING_HTTP_TIMEOUT_MS; /** * Approximate KES/USD rate for calculation fallback @@ -505,7 +506,7 @@ export class KESRateFetcher implements MarketRateFetcher { try { const response = await withRetry( () => axios.get(cbkSource.url, { - timeout: 10000, + timeout: OUTGOING_HTTP_TIMEOUT_MS, headers: { "User-Agent": "StellarFlow-Oracle/1.0", Accept: "application/json", @@ -545,7 +546,7 @@ export class KESRateFetcher implements MarketRateFetcher { try { const response = await withRetry( () => axios.get(source.url, { - timeout: 10000, + timeout: OUTGOING_HTTP_TIMEOUT_MS, headers: { "User-Agent": "StellarFlow-Oracle/1.0", Accept: "application/json", diff --git a/src/services/marketRate/ngnFetcher.ts b/src/services/marketRate/ngnFetcher.ts index 92fd0eb..912f1de 100644 --- a/src/services/marketRate/ngnFetcher.ts +++ b/src/services/marketRate/ngnFetcher.ts @@ -1,4 +1,5 @@ import axios from "axios"; +import { OUTGOING_HTTP_TIMEOUT_MS } from "../../utils/httpTimeout.js"; import { MarketRateFetcher, MarketRate, @@ -112,7 +113,7 @@ export class NGNRateFetcher implements MarketRateFetcher { `${this.vtpassBase()}/service-variations`, { params: { serviceID: serviceId }, - timeout: 15000, + timeout: OUTGOING_HTTP_TIMEOUT_MS, headers: { ...headers, "User-Agent": "StellarFlow-Oracle/1.0", @@ -147,7 +148,7 @@ export class NGNRateFetcher implements MarketRateFetcher { const coinGeckoResponse = await withRetry( () => axios.get(this.coinGeckoUrl, { - timeout: 10000, + timeout: OUTGOING_HTTP_TIMEOUT_MS, headers: { "User-Agent": "StellarFlow-Oracle/1.0", }, @@ -178,7 +179,7 @@ export class NGNRateFetcher implements MarketRateFetcher { const coinGeckoResponse = await withRetry( () => axios.get(this.coinGeckoUrl, { - timeout: 10000, + timeout: OUTGOING_HTTP_TIMEOUT_MS, headers: { "User-Agent": "StellarFlow-Oracle/1.0", }, @@ -211,7 +212,7 @@ export class NGNRateFetcher implements MarketRateFetcher { const coinGeckoResponse = await withRetry( () => axios.get(this.coinGeckoUrl, { - timeout: 10000, + timeout: OUTGOING_HTTP_TIMEOUT_MS, headers: { "User-Agent": "StellarFlow-Oracle/1.0", }, @@ -228,7 +229,7 @@ export class NGNRateFetcher implements MarketRateFetcher { const fxResponse = await withRetry( () => axios.get(this.usdToNgnUrl, { - timeout: 10000, + timeout: OUTGOING_HTTP_TIMEOUT_MS, headers: { "User-Agent": "StellarFlow-Oracle/1.0", }, diff --git a/src/services/multiSigService.ts b/src/services/multiSigService.ts index 203a5c5..dfd0744 100644 --- a/src/services/multiSigService.ts +++ b/src/services/multiSigService.ts @@ -1,6 +1,7 @@ import prisma from "../lib/prisma"; import { Keypair } from "@stellar/stellar-sdk"; import dotenv from "dotenv"; +import { createTimeoutSignal } from "../utils/httpTimeout.js"; dotenv.config(); @@ -205,6 +206,7 @@ export class MultiSigService { Authorization: `Bearer ${process.env.MULTI_SIG_AUTH_TOKEN || ""}`, }, body: JSON.stringify(payload), + signal: createTimeoutSignal(), }); if (!response.ok) { diff --git a/src/services/webhook.ts b/src/services/webhook.ts index 7537926..265e919 100644 --- a/src/services/webhook.ts +++ b/src/services/webhook.ts @@ -1,4 +1,5 @@ import axios from "axios"; +import { OUTGOING_HTTP_TIMEOUT_MS } from "../utils/httpTimeout.js"; import { withRetry } from "../utils/retryUtil.js"; type MarkdownText = { @@ -107,7 +108,7 @@ export class WebhookService { () => axios.post(webhookUrl, message, { headers: { "Content-Type": "application/json" }, - timeout: 5000, + timeout: OUTGOING_HTTP_TIMEOUT_MS, }), { maxRetries: 3, diff --git a/src/utils/httpTimeout.ts b/src/utils/httpTimeout.ts new file mode 100644 index 0000000..d4dd9ce --- /dev/null +++ b/src/utils/httpTimeout.ts @@ -0,0 +1,5 @@ +export const OUTGOING_HTTP_TIMEOUT_MS = 5000; + +export function createTimeoutSignal(timeoutMs = OUTGOING_HTTP_TIMEOUT_MS): AbortSignal { + return AbortSignal.timeout(timeoutMs); +} diff --git a/src/utils/retryUtil.ts b/src/utils/retryUtil.ts index 50b4086..6c72a9e 100644 --- a/src/utils/retryUtil.ts +++ b/src/utils/retryUtil.ts @@ -1,4 +1,5 @@ import axios, { AxiosError, AxiosRequestConfig } from "axios"; +import { OUTGOING_HTTP_TIMEOUT_MS } from "./httpTimeout.js"; /** * Retry configuration options @@ -190,7 +191,10 @@ export function createRetryableAxiosInstance( axiosConfig: AxiosRequestConfig = {}, retryConfig: RetryConfig = {} ) { - const instance = axios.create(axiosConfig); + const instance = axios.create({ + timeout: OUTGOING_HTTP_TIMEOUT_MS, + ...axiosConfig, + }); // Add response interceptor for retry logic instance.interceptors.response.use(