From 18e85e192c37a7413a2abb4e893948a7cdc32717 Mon Sep 17 00:00:00 2001 From: Sid Vishnoi <8426945+sidvishnoi@users.noreply.github.com> Date: Thu, 15 May 2025 19:09:48 +0530 Subject: [PATCH] perf(bg/PaymentSession): fewer probing requests using exchange rate --- src/background/services/paymentSession.ts | 21 ++++++++++++++++++++- src/background/utils.ts | 9 +++++++-- src/shared/helpers/misc.ts | 20 ++++++++++++++++++++ 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/background/services/paymentSession.ts b/src/background/services/paymentSession.ts index 52451c582..f811ee187 100644 --- a/src/background/services/paymentSession.ts +++ b/src/background/services/paymentSession.ts @@ -15,7 +15,13 @@ import { isOutOfBalanceError, isTokenExpiredError, } from '@/background/services/openPayments'; -import { bigIntMax, convert, getNextSendableAmount } from '@/background/utils'; +import { + bigIntMax, + convert, + convertWithExchangeRate, + getExchangeRatesMemoized, + getNextSendableAmount, +} from '@/background/utils'; import type { MonetizationEventDetails, MonetizationEventPayload, @@ -144,6 +150,19 @@ export class PaymentSession { } } + if (isCrossCurrency) { + try { + const exchangeRates = await getExchangeRatesMemoized(); + amountToSend = convertWithExchangeRate( + amountToSend, + this.receiver, + this.sender, + exchangeRates, + ); + this.logger.debug('minSendAmount: via exchangeRate', amountToSend); + } catch {} + } + // This all will eventually get replaced by OpenPayments response update // that includes a min rate that we can directly use. await this.setIncomingPaymentUrl(); diff --git a/src/background/utils.ts b/src/background/utils.ts index a636af8a3..00dcab76b 100644 --- a/src/background/utils.ts +++ b/src/background/utils.ts @@ -10,7 +10,7 @@ import type { Browser, Runtime } from 'webextension-polyfill'; import { BACKGROUND_TO_POPUP_CONNECTION_NAME } from '@/shared/messages'; import { EXCHANGE_RATES_URL } from './config'; import { INTERNAL_PAGE_URL_PROTOCOLS, NEW_TAB_PAGES } from './constants'; -import { notNullOrUndef } from '@/shared/helpers'; +import { memoize, notNullOrUndef } from '@/shared/helpers'; import type { WalletAddress } from '@interledger/open-payments'; type OnConnectCallback = Parameters< @@ -90,6 +90,11 @@ export const getExchangeRates = async (): Promise => { return rates; }; +export const getExchangeRatesMemoized = memoize(getExchangeRates, { + maxAge: 15 * 60 * 1000, + mechanism: 'stale-while-revalidate', +}); + export const getExchangeRate = ( rates: ExchangeRates, forAssetCode: string, @@ -121,7 +126,7 @@ export const convertWithExchangeRate = ( const scaleDiff = from.assetScale - to.assetScale; const scaledExchangeRate = exchangeRate * 10 ** scaleDiff; - const converted = BigInt(Math.ceil(Number(amount) / scaledExchangeRate)); + const converted = BigInt(Math.round(Number(amount) / scaledExchangeRate)); return typeof amount === 'string' ? (converted.toString() as T) diff --git a/src/shared/helpers/misc.ts b/src/shared/helpers/misc.ts index 08e036808..aeefe8bba 100644 --- a/src/shared/helpers/misc.ts +++ b/src/shared/helpers/misc.ts @@ -55,6 +55,26 @@ export function withResolvers() { return { resolve, reject, promise }; } +export function memoize Promise>( + fn: T, + options: { maxAge: number; mechanism: 'stale-while-revalidate' | 'max-age' }, +): () => ReturnType { + const { maxAge, mechanism = 'max-age' } = options; + let result: ReturnType; + let lastCall = 0; + return () => { + const lastResult = result; + if (Date.now() - lastCall > maxAge) { + lastCall = Date.now(); + result = fn() as ReturnType; + } + if (mechanism === 'stale-while-revalidate') { + return lastResult; + } + return result; + }; +} + export const isOkState = (state: Storage['state']) => { return Object.values(state).every((value) => value === false); };