Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/priceClient/adapters/acrossApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,17 @@ export class PriceFeed extends BaseHTTPAdapter implements PriceFeedAdapter {
}

// todo: Support bundled prices in the API endpoint.
getPricesByAddress(addresses: string[], currency = "usd"): Promise<TokenPrice[]> {
return Promise.all(addresses.map((address) => this.getPriceByAddress(address, currency)));
// Unlike other adapters, this adapter returns undefined prices when the Across API fails to resolve a price.
// This might need to change if the API allows bundles price requests.
async getPricesByAddress(addresses: string[], currency = "usd"): Promise<(TokenPrice | undefined)[]> {
const promises = await Promise.allSettled(addresses.map((address) => this.getPriceByAddress(address, currency)));
return promises.map((result, index) => {
const address = addresses[index];
if (result.status === "rejected") {
return undefined;
}
return { address, price: result.value.price, timestamp: result.value.timestamp };
});
}

private validateResponse(response: unknown): response is AcrossPrice {
Expand Down
14 changes: 8 additions & 6 deletions src/priceClient/priceClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export type TokenPrice = CoinGeckoPrice; // Temporary inversion; CoinGecko shoul
export interface PriceFeedAdapter {
readonly name: string;
getPriceByAddress(address: string, currency: string): Promise<TokenPrice>;
getPricesByAddress(addresses: string[], currency: string): Promise<TokenPrice[]>;
getPricesByAddress(addresses: string[], currency: string): Promise<(TokenPrice | undefined)[]>;
}

// It's convenient to map TokenPrice objects by their address, but consumers typically want an array
Expand Down Expand Up @@ -129,20 +129,22 @@ export class PriceClient implements PriceFeedAdapter {

const addrsToRequest = Object.entries(priceCache).map((entry: [string, TokenPrice]) => entry[1].address);
for (const priceFeed of this.priceFeeds) {
// Only request prices for addresses that are not already in the price cache.
const _addrsToRequest = addrsToRequest.filter((address) => !priceCache[address.toLowerCase()]);
this.logger.debug({
at: "PriceClient#updatePrices",
message: `Looking up prices via ${priceFeed.name}.`,
tokens: addrsToRequest,
tokens: _addrsToRequest,
});
try {
const feedPricesResponse = await priceFeed.getPricesByAddress(addrsToRequest, currency);
skipped = this.updateCache(priceCache, feedPricesResponse, addrsToRequest);
const feedPricesResponse = await priceFeed.getPricesByAddress(_addrsToRequest, currency);
skipped = this.updateCache(priceCache, feedPricesResponse, _addrsToRequest);
if (skipped.length === 0) break; // All done
} catch (err) {
this.logger.debug({
at: "PriceClient#updatePrices",
message: `Price lookup against ${priceFeed.name} failed (${err}).`,
tokens: addrsToRequest,
tokens: _addrsToRequest,
});
// Failover to the next price feed...
}
Expand All @@ -159,7 +161,7 @@ export class PriceClient implements PriceFeedAdapter {
}
}

private updateCache(priceCache: PriceCache, prices: TokenPrice[], expected: string[]): string[] {
private updateCache(priceCache: PriceCache, prices: (TokenPrice | undefined)[], expected: string[]): string[] {
const updated: TokenPrice[] = [];
const skipped: { [token: string]: string } = {}; // Includes reason for skipping
const now = msToS(Date.now());
Expand Down