Skip to content

Commit 034249e

Browse files
authored
perf(bg/PaymentSession): fewer quote requests using exchange rate (#1056)
1 parent ea5ea1d commit 034249e

3 files changed

Lines changed: 47 additions & 3 deletions

File tree

src/background/services/paymentSession.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,13 @@ import {
1515
isOutOfBalanceError,
1616
isTokenExpiredError,
1717
} from '@/background/services/openPayments';
18-
import { bigIntMax, convert, getNextSendableAmount } from '@/background/utils';
18+
import {
19+
bigIntMax,
20+
convert,
21+
convertWithExchangeRate,
22+
getExchangeRatesMemoized,
23+
getNextSendableAmount,
24+
} from '@/background/utils';
1925
import type {
2026
MonetizationEventDetails,
2127
MonetizationEventPayload,
@@ -144,6 +150,19 @@ export class PaymentSession {
144150
}
145151
}
146152

153+
if (isCrossCurrency) {
154+
try {
155+
const exchangeRates = await getExchangeRatesMemoized();
156+
amountToSend = convertWithExchangeRate(
157+
amountToSend,
158+
this.receiver,
159+
this.sender,
160+
exchangeRates,
161+
);
162+
this.logger.debug('minSendAmount: via exchangeRate', amountToSend);
163+
} catch {}
164+
}
165+
147166
// This all will eventually get replaced by OpenPayments response update
148167
// that includes a min rate that we can directly use.
149168
await this.setIncomingPaymentUrl();

src/background/utils.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import type { Browser, Runtime } from 'webextension-polyfill';
1010
import { BACKGROUND_TO_POPUP_CONNECTION_NAME } from '@/shared/messages';
1111
import { EXCHANGE_RATES_URL } from './config';
1212
import { INTERNAL_PAGE_URL_PROTOCOLS, NEW_TAB_PAGES } from './constants';
13-
import { notNullOrUndef } from '@/shared/helpers';
13+
import { memoize, notNullOrUndef } from '@/shared/helpers';
1414
import type { WalletAddress } from '@interledger/open-payments';
1515

1616
type OnConnectCallback = Parameters<
@@ -90,6 +90,11 @@ export const getExchangeRates = async (): Promise<ExchangeRates> => {
9090
return rates;
9191
};
9292

93+
export const getExchangeRatesMemoized = memoize(getExchangeRates, {
94+
maxAge: 15 * 60 * 1000,
95+
mechanism: 'stale-while-revalidate',
96+
});
97+
9398
export const getExchangeRate = (
9499
rates: ExchangeRates,
95100
forAssetCode: string,
@@ -121,7 +126,7 @@ export const convertWithExchangeRate = <T extends AmountValue | bigint>(
121126
const scaleDiff = from.assetScale - to.assetScale;
122127
const scaledExchangeRate = exchangeRate * 10 ** scaleDiff;
123128

124-
const converted = BigInt(Math.ceil(Number(amount) / scaledExchangeRate));
129+
const converted = BigInt(Math.round(Number(amount) / scaledExchangeRate));
125130

126131
return typeof amount === 'string'
127132
? (converted.toString() as T)

src/shared/helpers/misc.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,26 @@ export function withResolvers<T>() {
5555
return { resolve, reject, promise };
5656
}
5757

58+
export function memoize<T extends () => Promise<unknown>>(
59+
fn: T,
60+
options: { maxAge: number; mechanism: 'stale-while-revalidate' | 'max-age' },
61+
): () => ReturnType<T> {
62+
const { maxAge, mechanism = 'max-age' } = options;
63+
let result: ReturnType<T>;
64+
let lastCall = 0;
65+
return () => {
66+
const lastResult = result;
67+
if (Date.now() - lastCall > maxAge) {
68+
lastCall = Date.now();
69+
result = fn() as ReturnType<T>;
70+
}
71+
if (mechanism === 'stale-while-revalidate') {
72+
return lastResult;
73+
}
74+
return result;
75+
};
76+
}
77+
5878
export const isOkState = (state: Storage['state']) => {
5979
return Object.values(state).every((value) => value === false);
6080
};

0 commit comments

Comments
 (0)