Skip to content
This repository was archived by the owner on Apr 10, 2025. It is now read-only.

explore page: use common numeraire #223

Merged
merged 9 commits into from
Jan 13, 2025
3 changes: 2 additions & 1 deletion src/pages/explore/api/use-summaries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { SummaryData } from '@/shared/api/server/summary/types';
import { DurationWindow } from '@/shared/utils/duration';
import { apiFetch } from '@/shared/utils/api-fetch';

const BASE_LIMIT = 15;
/// The base limit will need to be increased as more trading pairs are added to the explore page.
const BASE_LIMIT = 20;
const BASE_PAGE = 0;
const BASE_WINDOW: DurationWindow = '1d';

Expand Down
4 changes: 2 additions & 2 deletions src/pages/explore/ui/pair-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export const PairCard = ({ loading, summary }: PairCardProps) => {
{shortify(Number(getFormattedAmtFromValueView(summary.liquidity)))}
</Text>
<Text detail color='text.secondary'>
{summary.quoteAsset.symbol}
{'USDC'}
</Text>
</>
)}
Expand All @@ -134,7 +134,7 @@ export const PairCard = ({ loading, summary }: PairCardProps) => {
{shortify(Number(getFormattedAmtFromValueView(summary.directVolume)))}
</Text>
<Text detail color='text.secondary'>
{summary.quoteAsset.symbol}
{'USDC'}
</Text>
</>
)}
Expand Down
46 changes: 45 additions & 1 deletion src/pages/explore/ui/pairs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
import { PairCard } from '@/pages/explore/ui/pair-card';
import { useSummaries } from '@/pages/explore/api/use-summaries';
import SpinnerIcon from '@/shared/assets/spinner-icon.svg';
import { useMultipleSummaries } from '@/pages/trade/model/useSummary';
import { SummaryData } from '@/shared/api/server/summary/types';
import { calculateScaledAmount, collectQuoteAssets } from '@/shared/utils/price-conversion';

/** A hook that fires the callback when observed element (on the bottom of the page) is in the view */
const useObserver = (disabled: boolean, cb: VoidFunction) => {
Expand Down Expand Up @@ -51,8 +54,49 @@

const [search, setSearch] = useState('');

// Fetch trading pair summaries.
const { data, isLoading, isRefetching, isFetchingNextPage, fetchNextPage } = useSummaries(search);

// Create temporary translator to normalize volumes/liquidity to USDC values.
// Deep clone is neccesary since objects are passed by reference in typeScript.
let translator = data?.pages ? structuredClone(data.pages) : undefined;

Check failure on line 62 in src/pages/explore/ui/pairs.tsx

View workflow job for this annotation

GitHub Actions / Lint

'translator' is never reassigned. Use 'const' instead

// Extract quote assets from all trading pairs.
let quoteAssetsList: string[] = [];
if (translator) {
quoteAssetsList = collectQuoteAssets(translator);
}

// Get USDC exchange rates for all quote assets.
const usdcPriceQueries = useMultipleSummaries(quoteAssetsList);

// Calculate USDC-denominated volumes and liquidity for each trading pair.
// For each pair, scale the values using USDC price of the quote asset.
let volumePairs: bigint[] = [];

Check failure on line 75 in src/pages/explore/ui/pairs.tsx

View workflow job for this annotation

GitHub Actions / Lint

'volumePairs' is never reassigned. Use 'const' instead
let liquidityPairs: bigint[] = [];

Check failure on line 76 in src/pages/explore/ui/pairs.tsx

View workflow job for this annotation

GitHub Actions / Lint

'liquidityPairs' is never reassigned. Use 'const' instead

data?.pages[0]?.forEach((item, index) => {
let usdcPrice = usdcPriceQueries[index]?.data as SummaryData;

Check failure on line 79 in src/pages/explore/ui/pairs.tsx

View workflow job for this annotation

GitHub Actions / Lint

'usdcPrice' is never reassigned. Use 'const' instead

if (usdcPrice && 'directVolume' in usdcPrice && 'liquidity' in usdcPrice) {

Check failure on line 81 in src/pages/explore/ui/pairs.tsx

View workflow job for this annotation

GitHub Actions / Lint

Unnecessary conditional, value is always truthy
calculateScaledAmount(item, usdcPrice, 'directVolume', volumePairs, index);
calculateScaledAmount(item, usdcPrice, 'liquidity', liquidityPairs, index);
}
});

// Update volume and liquidity amounts with their respective USDC-normalized values,
// skipping USDC pairs.
if (translator) {
translator.forEach(page => {
page.forEach((item, itemIndex) => {
if (quoteAssetsList[itemIndex] !== 'USDC') {
item.directVolume.valueView.value!.amount!.lo = volumePairs[itemIndex]!;

Check failure on line 93 in src/pages/explore/ui/pairs.tsx

View workflow job for this annotation

GitHub Actions / Lint

Forbidden non-null assertion

Check failure on line 93 in src/pages/explore/ui/pairs.tsx

View workflow job for this annotation

GitHub Actions / Lint

Forbidden non-null assertion

Check failure on line 93 in src/pages/explore/ui/pairs.tsx

View workflow job for this annotation

GitHub Actions / Lint

Forbidden non-null assertion
item.liquidity.valueView.value!.amount!.lo = liquidityPairs[itemIndex]!;

Check failure on line 94 in src/pages/explore/ui/pairs.tsx

View workflow job for this annotation

GitHub Actions / Lint

Forbidden non-null assertion

Check failure on line 94 in src/pages/explore/ui/pairs.tsx

View workflow job for this annotation

GitHub Actions / Lint

Forbidden non-null assertion
}
});
});
}

const { observerEl } = useObserver(isLoading || isRefetching || isFetchingNextPage, () => {
void fetchNextPage();
});
Expand Down Expand Up @@ -100,7 +144,7 @@
</>
)}

{data?.pages.map(page =>
{translator?.map(page =>
page.map(summary => (
<PairCard
loading={false}
Expand Down
23 changes: 22 additions & 1 deletion src/pages/trade/model/useSummary.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useQuery } from '@tanstack/react-query';
import { useQueries, useQuery } from '@tanstack/react-query';
import { usePathSymbols } from '@/pages/trade/model/use-path.ts';
import { DurationWindow } from '@/shared/utils/duration.ts';
import { NoSummaryData, SummaryData } from '@/shared/api/server/summary/types.ts';
Expand All @@ -24,3 +24,24 @@ export const useSummary = (window: DurationWindow) => {

return query;
};

// Fetch USDC-denominated prices for each quote asset to enable cross-pair comparisons.
// Uses 'useQueries' hook for handling multiple dynamic numbers of queries
export const useMultipleSummaries = (quoteAssets: string[]) => {
const queries = useQueries<Array<{ data: SummaryData | NoSummaryData | undefined }>>({
queries: quoteAssets.map(asset => ({
queryKey: ['multipleSummaries', asset, 'USDC'] as const,
queryFn: async () => {
return apiFetch<SummaryData | NoSummaryData>('/api/summary', {
durationWindow: '1d' as DurationWindow,
Copy link
Contributor Author

@TalDerei TalDerei Dec 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that the /api/stats and /api/summaries endpoints from pindexer may be returning incorrect information, impacting metrics like "total trading volume (24h)" and the largest "trading pair (24h volume)" for the USDC/USDY pair.

previous discussions suggest that this may not simply be related to the denom exponents not being handled properly. cc @cronokirby

baseAsset: asset,
quoteAsset: 'USDC',
});
},
})),
});

// TODO: add block-based fetching

return queries;
};
42 changes: 42 additions & 0 deletions src/shared/utils/price-conversion.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Metadata } from '@penumbra-zone/protobuf/penumbra/core/asset/v1/asset_pb';
import { getDisplayDenomExponent } from '@penumbra-zone/getters/metadata';
import { SummaryData } from '../api/server/summary/types';

/**
* Calculates the display price based on the base price and display exponents.
Expand All @@ -22,3 +23,44 @@ export const calculateDisplayPrice = (
return baseCalculatedPrice / divisor;
}
};

/**
* Collect the quote assets from all trading pairs.
*/
export const collectQuoteAssets = (summaryData: SummaryData[][]): string[] => {
let quoteAssetsList: string[] = [];
if (summaryData) {
summaryData.forEach(page => {
page.forEach(item => {
const quoteAsset = item.quoteAsset.symbol;
quoteAssetsList.push(quoteAsset);
});
});
}

return quoteAssetsList;
};

/**
* Calculates USDC-normalized amounts for trading pair values (volume or liquidity)
*/
export const calculateScaledAmount = (
item: SummaryData, // trading pair data to normalize
usdcPrice: SummaryData, // USDC price data for the quote asset
type: 'directVolume' | 'liquidity',
results: bigint[],
index: number,
) => {
if (item && type in item) {
// Scale the USDC price by the quote asset's decimal places.
const scaledPrice = Math.floor(
usdcPrice.price * 100 ** getDisplayDenomExponent(usdcPrice.quoteAsset),
);

// Multiply the original amount (volume or liquidity) by the scaled USDC price.
const result = item[type].valueView.value?.amount?.lo! * BigInt(scaledPrice);

// Divide by scaling factor to get the final USDC-denominated value.
results[index] = result / BigInt(100 ** getDisplayDenomExponent(usdcPrice.quoteAsset));
}
};