Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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
8 changes: 6 additions & 2 deletions src/pages/explore/ui/pair-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Skeleton } from '@/shared/ui/skeleton';
import SparklineChart from './sparkline-chart.svg';
import ChevronDown from './chevron-down.svg';
import { PreviewChart } from './preview-chart';
import { useRegistryAssets } from '@/shared/api/registry';

const ShimmeringBars = () => {
return (
Expand Down Expand Up @@ -65,6 +66,9 @@ export const PairCard = ({ loading, summary }: PairCardProps) => {
const today = new Date();
const yesterday = subDays(new Date(), 1);

const { data: assets } = useRegistryAssets();
const usdcMetadata = assets?.find(asset => asset.symbol === 'USDC');

return (
<Link
href={loading ? `/trade` : `/trade/${summary.baseAsset.symbol}/${summary.quoteAsset.symbol}`}
Expand Down Expand Up @@ -119,7 +123,7 @@ export const PairCard = ({ loading, summary }: PairCardProps) => {
{shortify(Number(getFormattedAmtFromValueView(summary.liquidity)))}
</Text>
<Text detail color='text.secondary'>
{summary.quoteAsset.symbol}
{usdcMetadata?.symbol}
</Text>
</>
)}
Expand All @@ -134,7 +138,7 @@ export const PairCard = ({ loading, summary }: PairCardProps) => {
{shortify(Number(getFormattedAmtFromValueView(summary.directVolume)))}
</Text>
<Text detail color='text.secondary'>
{summary.quoteAsset.symbol}
{usdcMetadata?.symbol}
</Text>
</>
)}
Expand Down
17 changes: 10 additions & 7 deletions src/pages/explore/ui/stats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ import { pluralizeAndShortify } from '@/shared/utils/pluralize';
import { shortify } from '@penumbra-zone/types/shortify';
import { getFormattedAmtFromValueView } from '@penumbra-zone/types/value-view';
import { useStats } from '@/pages/explore/api/use-stats';
import { useRegistryAssets } from '@/shared/api/registry';

export const ExploreStats = () => {
const { data: stats, isLoading, error } = useStats();
const { data: assets } = useRegistryAssets();
const usdcMetadata = assets?.find(asset => asset.symbol === 'USDC');

if (error) {
return (
Expand All @@ -23,10 +26,9 @@ export const ExploreStats = () => {
<div className='grid grid-cols-1 tablet:grid-cols-2 desktop:grid-cols-3 gap-2'>
<InfoCard title='Total Trading Volume (24h)' loading={isLoading}>
{stats && (
<Text large color='text.primary'>
<Text large color='text.primary'>
{shortify(Number(getFormattedAmtFromValueView(stats.directVolume)))}
</Text>
<Text large color='success.light'>
{shortify(Number(getFormattedAmtFromValueView(stats.directVolume)))}{' '}
{usdcMetadata?.symbol}
</Text>
)}
</InfoCard>
Expand All @@ -45,7 +47,8 @@ export const ExploreStats = () => {
</Text>
{stats.largestPairLiquidity && (
<Text large color='success.light'>
{shortify(Number(getFormattedAmtFromValueView(stats.largestPairLiquidity)))}
{shortify(Number(getFormattedAmtFromValueView(stats.largestPairLiquidity)))}{' '}
{usdcMetadata?.symbol}
</Text>
)}
</>
Expand All @@ -57,8 +60,8 @@ export const ExploreStats = () => {
</InfoCard>
<InfoCard title='Total Liquidity Available' loading={isLoading}>
{stats && (
<Text large color='text.primary'>
{shortify(Number(getFormattedAmtFromValueView(stats.liquidity)))}
<Text large color='success.light'>
{shortify(Number(getFormattedAmtFromValueView(stats.liquidity)))} {usdcMetadata?.symbol}
</Text>
)}
</InfoCard>
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion src/pages/trade/model/useMarketPrice.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useSummary } from './useSummary';
import { useSummary } from '../api/use-summary';

export const useMarketPrice = (baseSymbol?: string, quoteSymbol?: string) => {
const { data: summary } = useSummary('1d', baseSymbol, quoteSymbol);
Expand Down
2 changes: 1 addition & 1 deletion src/pages/trade/ui/summary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import cn from 'clsx';
import { useAutoAnimate } from '@formkit/auto-animate/react';
import { Text } from '@penumbra-zone/ui/Text';
import { Skeleton } from '@/shared/ui/skeleton';
import { useSummary } from '../model/useSummary';
import { useSummary } from '../api/use-summary';
import { ValueViewComponent } from '@penumbra-zone/ui/ValueView';
import { round } from '@penumbra-zone/types/round';
import { Density } from '@penumbra-zone/ui/Density';
Expand Down
79 changes: 57 additions & 22 deletions src/shared/api/server/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { pindexer } from '@/shared/database';
import { DurationWindow } from '@/shared/utils/duration';
import { toValueView } from '@/shared/utils/value-view';
import { Serialized, serialize } from '@/shared/utils/serializer';
import { calculateEquivalentInUSDC } from '@/shared/utils/price-conversion';

interface StatsDataBase {
activePairs: number;
Expand All @@ -31,25 +32,26 @@ export const getStats = async (): Promise<Serialized<StatsResponse>> => {
}

const registryClient = new ChainRegistryClient();

const [registry, results] = await Promise.all([
registryClient.remote.get(chainId),
pindexer.stats(STATS_DURATION_WINDOW),
]);

const stats = results[0];
if (!stats) {
return { error: `No stats found` };
}
const registry = await registryClient.remote.get(chainId);

// TODO: Add getMetadataBySymbol() helper to registry npm package
const allAssets = registry.getAllAssets();
// TODO: what asset should be used here?
const usdcMetadata = allAssets.find(asset => asset.symbol.toLowerCase() === 'usdc');
if (!usdcMetadata) {
return { error: 'USDC not found in registry' };
}

const results = await pindexer.stats(
STATS_DURATION_WINDOW,
// eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style -- usdc is defined
usdcMetadata.penumbraAssetId as AssetId,
);

const stats = results[0];
if (!stats) {
return { error: `No stats found` };
}

const topPriceMoverStart = allAssets.find(asset => {
return asset.penumbraAssetId?.equals(new AssetId({ inner: stats.top_price_mover_start }));
});
Expand Down Expand Up @@ -79,23 +81,56 @@ export const getStats = async (): Promise<Serialized<StatsResponse>> => {
end: largestPairEnd.symbol,
};

let liquidity = toValueView({
amount: Math.floor(stats.liquidity),
metadata: usdcMetadata,
});

let directVolume = toValueView({
amount: Math.floor(stats.direct_volume),
metadata: usdcMetadata,
});

let largestPairLiquidity =
largestPairEnd &&
toValueView({
amount: Math.floor(stats.largest_dv_trading_pair_volume),
metadata: largestPairEnd,
});

// Converts liquidity and trading volume to their equivalent USDC prices if `usdc_price` is available
if (stats.usdc_price && largestPairEnd) {
Copy link
Contributor Author

@TalDerei TalDerei Jan 12, 2025

Choose a reason for hiding this comment

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

todo: api/stats not displaying correct values currently

Screenshot 2025-01-12 at 2 03 42 PM

Copy link
Contributor Author

Choose a reason for hiding this comment

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

looks more normal after recent reindexing

Screenshot 2025-01-13 at 3 00 22 PM

liquidity = calculateEquivalentInUSDC(
stats.liquidity,
stats.usdc_price,
largestPairEnd,
usdcMetadata,
);

directVolume = calculateEquivalentInUSDC(
stats.direct_volume,
stats.usdc_price,
largestPairEnd,
usdcMetadata,
);

largestPairLiquidity = calculateEquivalentInUSDC(
stats.largest_dv_trading_pair_volume,
stats.usdc_price,
largestPairEnd,
usdcMetadata,
);
}

return serialize({
activePairs: stats.active_pairs,
trades: stats.trades,
largestPair,
topPriceMover,
time: new Date(),
largestPairLiquidity:
largestPairEnd &&
toValueView({
amount: stats.largest_dv_trading_pair_volume,
metadata: largestPairEnd,
}),
liquidity: toValueView({
amount: parseInt(`${stats.liquidity}`),
metadata: usdcMetadata,
}),
directVolume: toValueView({ amount: stats.direct_volume, metadata: usdcMetadata }),
largestPairLiquidity,
liquidity,
directVolume,
});
} catch (error) {
return { error: (error as Error).message };
Expand Down
22 changes: 15 additions & 7 deletions src/shared/api/server/summary/all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,18 @@ export const getAllSummaries = async (

const registryClient = new ChainRegistryClient();
const registry = await registryClient.remote.get(chainId);
const allAssets = registry.getAllAssets();

const stablecoins = registry
.getAllAssets()
.filter(asset => ['USDT', 'USDC', 'USDY'].includes(asset.symbol))
.map(asset => asset.penumbraAssetId) as AssetId[];
const stablecoins = allAssets.filter(asset => ['USDT', 'USDC', 'USDY'].includes(asset.symbol));
const usdc = stablecoins.find(asset => asset.symbol === 'USDC');

const results = await pindexer.summaries({
...params,
stablecoins,
stablecoins: stablecoins.map(asset => asset.penumbraAssetId) as AssetId[],
// eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style -- usdc is defined
usdc: usdc?.penumbraAssetId as AssetId,
});

const allAssets = registry.getAllAssets();

const summaries = await Promise.all(
results.map(summary => {
const baseAsset = getAssetById(allAssets, summary.asset_start);
Expand All @@ -54,10 +53,19 @@ export const getAllSummaries = async (
summary,
baseAsset,
quoteAsset,
usdc,
summary.candles,
summary.candle_times,
);

// Filter out pairs with zero liquidity and trading volume
if (
(data.liquidity.valueView.value?.amount?.lo &&
data.directVolume.valueView.value?.amount?.lo) === 0n
) {
return;
}

return serialize(data);
}),
);
Expand Down
4 changes: 2 additions & 2 deletions src/shared/api/server/summary/pairs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,14 @@ export async function GET(): Promise<NextResponse<PairsResponse>> {
return undefined;
}

// TODO: should this be `direct_volume_over_window`?
let volume = toValueView({
amount: summary.liquidity,
metadata: quoteAsset,
});
Comment on lines +54 to 58
Copy link
Contributor Author

@TalDerei TalDerei Jan 12, 2025

Choose a reason for hiding this comment

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

question: should this have been summary.direct_volume_over_window rather than summary.liquidity? cc @VanishMax

Copy link
Contributor

Choose a reason for hiding this comment

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

i thought the stats should display the total existing liquidity (that's probably what i'd like to see on such page) but we sure can always change it


// Converts liquidity and trading volume to their equivalent USDC prices if `usdc_price` is available
if (summary.usdc_price) {
// Custom volume calculation for USDC pairs
// TODO: change to a better method (probably create `pnum.multiply()` method and use it)
const expDiff = Math.abs(
getDisplayDenomExponent(quoteAsset) - getDisplayDenomExponent(usdc),
);
Expand Down
5 changes: 4 additions & 1 deletion src/shared/api/server/summary/single.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ export async function GET(req: NextRequest): Promise<NextResponse<Serialized<Sum

// TODO: Add getMetadataBySymbol() helper to registry npm package
const allAssets = registry.getAllAssets();
const stablecoins = allAssets.filter(asset => ['USDT', 'USDC', 'USDY'].includes(asset.symbol));
const usdc = stablecoins.find(asset => asset.symbol === 'USDC');

const baseAssetMetadata = allAssets.find(
a => a.symbol.toLowerCase() === baseAssetSymbol.toLowerCase(),
);
Expand All @@ -58,6 +61,6 @@ export async function GET(req: NextRequest): Promise<NextResponse<Serialized<Sum
return NextResponse.json({ window: durationWindow, noData: true });
}

const adapted = adaptSummary(summary, baseAssetMetadata, quoteAssetMetadata);
const adapted = adaptSummary(summary, baseAssetMetadata, quoteAssetMetadata, usdc);
return NextResponse.json(serialize(adapted));
}
23 changes: 18 additions & 5 deletions src/shared/api/server/summary/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { DurationWindow } from '@/shared/utils/duration.ts';
import { Metadata, ValueView } from '@penumbra-zone/protobuf/penumbra/core/asset/v1/asset_pb';
import { DexExPairsSummary } from '@/shared/database/schema';
import { calculateDisplayPrice } from '@/shared/utils/price-conversion';
import { calculateDisplayPrice, calculateEquivalentInUSDC } from '@/shared/utils/price-conversion';
import { round } from '@penumbra-zone/types/round';
import { toValueView } from '@/shared/utils/value-view';

Expand Down Expand Up @@ -39,19 +39,32 @@ export const adaptSummary = (
summary: DexExPairsSummary,
baseAsset: Metadata,
quoteAsset: Metadata,
usdc: Metadata | undefined,
candles?: number[],
candleTimes?: Date[],
): SummaryData => {
const directVolume = toValueView({
amount: Math.floor(summary.direct_volume_over_window),
let liquidity = toValueView({
amount: Math.floor(summary.liquidity),
metadata: quoteAsset,
});

const liquidity = toValueView({
amount: Math.floor(summary.liquidity),
let directVolume = toValueView({
amount: Math.floor(summary.direct_volume_over_window),
metadata: quoteAsset,
});

// Converts liquidity and trading volume to their equivalent USDC prices if `usdc_price` is available
if (summary.usdc_price && usdc) {
liquidity = calculateEquivalentInUSDC(summary.liquidity, summary.usdc_price, quoteAsset, usdc);

directVolume = calculateEquivalentInUSDC(
summary.direct_volume_over_window,
summary.usdc_price,
quoteAsset,
usdc,
);
}
Comment on lines +56 to +66
Copy link
Contributor Author

@TalDerei TalDerei Jan 12, 2025

Choose a reason for hiding this comment

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

todo: BTC and USDY still have strange denom coming out of pindexer – we should check if penumbra-zone/penumbra#4981 sufficiently addresses this. cc @cronokirby

Screenshot 2025-01-12 at 2 01 37 PM

Copy link
Contributor Author

Choose a reason for hiding this comment

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

pointing my local dex-explorer dev env at the prod pindexer db after conor's recent reindexing resolves this

Screenshot 2025-01-13 at 2 59 06 PM


const priceDiff = summary.price - summary.price_then;
const change = {
value: calculateDisplayPrice(priceDiff, baseAsset, quoteAsset),
Expand Down
Loading