Skip to content

Commit 7120c4e

Browse files
authored
Fix bugs in token price fetching which cause excessive API calls (#3258)
* fix race condition where we make duplicate reqs * make tokenPricesToFetch a state variable
1 parent 1d3d5cd commit 7120c4e

File tree

2 files changed

+46
-8
lines changed

2 files changed

+46
-8
lines changed

wormhole-connect/src/config/tokens.ts

+23
Original file line numberDiff line numberDiff line change
@@ -156,9 +156,12 @@ export class TokenMapping<T> {
156156
// Mapping of Chain -> token address -> T
157157
_mapping: Map<Chain, Map<string, T>>;
158158

159+
size: number;
160+
159161
constructor() {
160162
this.lastUpdate = new Date();
161163
this._mapping = new Map();
164+
this.size = 0;
162165
}
163166

164167
add(token: TokenId, value: T) {
@@ -168,6 +171,7 @@ export class TokenMapping<T> {
168171

169172
this._mapping.get(token.chain)!.set(token.address.toString(), value);
170173
this.lastUpdate = new Date();
174+
this.size += 1;
171175
}
172176

173177
// You can get a token either using its string key, TokenId, or with (chain, address)
@@ -233,6 +237,14 @@ export class TokenMapping<T> {
233237
);
234238
}
235239

240+
getAllTokenIds(): TokenId[] {
241+
return Array.from(this._mapping.keys()).flatMap((chain) =>
242+
Array.from(this._mapping.get(chain)!.keys()).map((address) =>
243+
Wormhole.tokenId(chain, address),
244+
),
245+
);
246+
}
247+
236248
get chains(): Chain[] {
237249
return Array.from(this._mapping.keys());
238250
}
@@ -242,6 +254,13 @@ export class TokenMapping<T> {
242254
other.forEach(this.add);
243255
}
244256

257+
// Removes all records from the TokenMapping
258+
clear() {
259+
this.lastUpdate = new Date();
260+
this._mapping = new Map();
261+
this.size = 0;
262+
}
263+
245264
forEach(callback: (tokenId: TokenId, val: T) => void) {
246265
this._mapping.forEach((nextLevel, chain) => {
247266
nextLevel.forEach((val, addr) => {
@@ -250,6 +269,10 @@ export class TokenMapping<T> {
250269
});
251270
});
252271
}
272+
273+
get empty(): boolean {
274+
return this.size === 0;
275+
}
253276
}
254277

255278
export class TokenCache extends TokenMapping<Token> {

wormhole-connect/src/contexts/TokensContext.tsx

+23-8
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ interface TokensProviderProps {
4747
export interface TokenPrice {
4848
price: number | undefined; // USD price
4949
timestamp: Date;
50+
isFetching?: boolean;
5051
}
5152

5253
export const TokensProvider: React.FC<TokensProviderProps> = ({ children }) => {
@@ -58,6 +59,10 @@ export const TokensProvider: React.FC<TokensProviderProps> = ({ children }) => {
5859
const [tokenPrices, _setTokenPrices] = useState<TokenMapping<TokenPrice>>(
5960
new TokenMapping(),
6061
);
62+
const [tokenPricesToFetch, _setTokenPricesToFetch] = useState<
63+
TokenMapping<boolean>
64+
>(new TokenMapping());
65+
6166
const [isFetchingTokenPrices, setIsFetchingPrices] = useState(false);
6267
const [lastTokenPriceUpdate, setLastPriceUpdate] = useState(new Date());
6368

@@ -104,21 +109,32 @@ export const TokensProvider: React.FC<TokensProviderProps> = ({ children }) => {
104109
[],
105110
);
106111

107-
const tokenPricesToFetch: Set<TokenId> = new Set();
108-
109112
const updateTokenPrices = useDebouncedCallback(async () => {
110-
if (tokenPricesToFetch.size === 0) return;
113+
if (tokenPricesToFetch.empty) return;
111114

112-
const tokens = Array.from(tokenPricesToFetch);
115+
const tokens = tokenPricesToFetch.getAllTokenIds();
113116
console.info('Fetching token prices', tokens);
114117

115118
try {
116119
setIsFetchingPrices(true);
120+
const timestamp = new Date();
121+
122+
// Flag that this price is being fetched, so that we don't start another concurrent request for it in getTokenPrice
123+
for (const token of tokens) {
124+
tokenPrices.add(token, {
125+
timestamp,
126+
price: undefined,
127+
isFetching: true,
128+
});
129+
}
130+
131+
// Clear list for future invocations of getTokenPrice
132+
tokenPricesToFetch.clear();
133+
117134
const prices = await fetchTokenPrices(tokens);
118135

119-
for (const token of tokenPricesToFetch.values()) {
136+
for (const token of tokens) {
120137
const price = prices.get(token);
121-
const timestamp = new Date();
122138
if (price) {
123139
tokenPrices.add(token, {
124140
timestamp,
@@ -134,7 +150,6 @@ export const TokensProvider: React.FC<TokensProviderProps> = ({ children }) => {
134150
} catch (e) {
135151
console.error(e);
136152
} finally {
137-
tokenPricesToFetch.clear();
138153
setIsFetchingPrices(false);
139154
setLastPriceUpdate(new Date());
140155
}
@@ -148,7 +163,7 @@ export const TokensProvider: React.FC<TokensProviderProps> = ({ children }) => {
148163
if (cachedPrice) {
149164
return cachedPrice.price;
150165
} else {
151-
tokenPricesToFetch.add(tokenId);
166+
tokenPricesToFetch.add(tokenId, true);
152167
updateTokenPrices();
153168
return undefined;
154169
}

0 commit comments

Comments
 (0)