Skip to content

Commit c5fda6f

Browse files
refactor(discover): adapt #7541 perp renderer to the cleaned contract + fixes
Restacked onto the cleaned #7540 and reconciled with the source-neutral contract, then applied the perp cleanups and review fixes. Cascade adaptation (compile against the new contract): - Drop the analytics plumbing (DiscoverCardAnalyticsContext, the analyticsContext renderItem arg and card props); perp descriptors now match the generic (item, width) / (item) callbacks. - Remove navigateDiscoverDestination; the perp "See All" header press uses onTapSearch() gated to perp-owned destinations, mirroring the token path. Non-perp CMS routing is deferred to the #7553 cutover. - Stop setting onNavigate/pressMetadata on the perp MarketDisplayItem and drop the dead placement/surfaceId arguments to renderSectionLayout. Perp-renderer fixes: - Fix the perp price-change units. Perps store priceChange['24h'] with an extra /100 baked in (so a +5.23% move is "0.000523"), and the Hyperliquid live adapter feeds change24hPct from that same stored value. The old generic card multiplied by 10_000; #7539 removed that, so usePerpMarketDisplay now converts to percent via convertStoredPerpPriceChangeToPercent for the initial value, the live priceChangeSelector, and the pill-width input. Importing the perps util here is correct -- this is the perp source hook, not a generic card. - Restore the forced token-ref cache bypass. The Hyperliquid refresh addition had dropped clearTokenRefCache() from the rainbow branch of refreshDiscoverSurface; without it a forced refresh within TOKEN_REFS_STALE_TIME serves stale token data and the helper is dead. Re-add the cache clear and keep the force-aware Hyperliquid refetch alongside it. Keep perps_enabled fallback true and the token/perp renderers as separate source-specific code (no consolidation), per plan.
1 parent 84dce68 commit c5fda6f

3 files changed

Lines changed: 55 additions & 82 deletions

File tree

src/features/discover/components/MarketSection.tsx

Lines changed: 38 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useCallback, useMemo } from 'react';
22

33
import { useDiscoverScreenContext } from '@/components/Discover/DiscoverScreenContext';
4+
import { type NativeCurrencyKey } from '@/entities/nativeCurrencyTypes';
45
import { MarketCell, MarketCellSkeleton } from '@/features/discover/components/markets/cards/MarketCell';
56
import {
67
computeMarketPillWidth,
@@ -16,16 +17,14 @@ import {
1617
} from '@/features/discover/components/markets/cards/MarketTileCard';
1718
import { perpToMarketPillWidthInput, usePerpMarketDisplay } from '@/features/discover/components/markets/hooks/usePerpMarketDisplay';
1819
import { tokenToMarketPillWidthInput, useTokenMarketDisplay } from '@/features/discover/components/markets/hooks/useTokenMarketDisplay';
19-
import { getHeaderPress, renderSectionLayout } from '@/features/discover/components/SectionLayout';
20+
import { renderSectionLayout } from '@/features/discover/components/SectionLayout';
2021
import { type MarketDisplayItem } from '@/features/discover/types/marketDisplayItem';
2122
import {
22-
type DiscoverCardAnalyticsContext,
2323
type MarketDisplay,
2424
type PlacementBackedSurfaceLeafWithDisplay,
2525
type SectionDescriptor,
2626
type SurfaceLeafWithDisplay,
2727
} from '@/features/discover/types/sectionLayout';
28-
import { navigateDiscoverDestination } from '@/features/discover/utils/navigation';
2928
import { usePerpsPlacement, type PerpMarketPlacementItem } from '@/features/placements/stores/derived/perpsPlacementStore';
3029
import { useTokensPlacement, type TokenPlacementItem } from '@/features/placements/stores/derived/tokensPlacementStore';
3130
import { usePlacementsV2Store } from '@/features/placements/stores/placementsStore';
@@ -105,10 +104,8 @@ function MarketPlacementContent({
105104
data: [],
106105
descriptor: MARKET_SECTION_DESCRIPTORS[surface.display],
107106
loading: true,
108-
onPressSeeAll: getHeaderPress(surface.destination),
109-
placement: undefined,
107+
onPressSeeAll: undefined,
110108
surface,
111-
surfaceId,
112109
});
113110
}
114111

@@ -117,54 +114,48 @@ function MarketPlacementContent({
117114

118115
function PerpsMarketPlacementContent({
119116
surface,
120-
surfaceId,
121117
}: {
122118
surface: PlacementBackedSurfaceLeafWithDisplay<MarketDisplay>;
119+
// surfaceId is threaded from MarketPlacementContent for future use (#7553)
123120
surfaceId: string;
124121
}) {
122+
const { onTapSearch } = useDiscoverScreenContext();
125123
const perpsResult = usePerpsPlacement(surface.placement);
126124
const perpDescriptor = useMemo<SectionDescriptor<PerpMarketPlacementItem>>(() => {
127125
switch (surface.display) {
128126
case 'market_pill.carousel':
129127
return {
130128
...MARKET_SECTION_DESCRIPTORS[surface.display],
131129
getItemWidth: (item: PerpMarketPlacementItem) => computeMarketPillWidth(perpToMarketPillWidthInput(item)),
132-
renderItem: (item: PerpMarketPlacementItem, _: number, analyticsContext: DiscoverCardAnalyticsContext) => (
133-
<PerpMarketItem analyticsContext={analyticsContext} item={item} variant="pill" />
134-
),
130+
renderItem: (item: PerpMarketPlacementItem) => <PerpMarketItem item={item} variant="pill" />,
135131
};
136132
case 'market_tile.carousel':
137133
return {
138134
...MARKET_SECTION_DESCRIPTORS[surface.display],
139-
renderItem: (item: PerpMarketPlacementItem, _: number, analyticsContext: DiscoverCardAnalyticsContext) => (
140-
<PerpMarketItem analyticsContext={analyticsContext} item={item} variant="tile" />
141-
),
135+
renderItem: (item: PerpMarketPlacementItem) => <PerpMarketItem item={item} variant="tile" />,
142136
};
143137
case 'market_tile.grid':
144138
return {
145139
...MARKET_SECTION_DESCRIPTORS[surface.display],
146-
renderItem: (item: PerpMarketPlacementItem, width: number, analyticsContext: DiscoverCardAnalyticsContext) => (
147-
<PerpMarketItem analyticsContext={analyticsContext} item={item} variant="tile" width={width} />
148-
),
140+
renderItem: (item: PerpMarketPlacementItem, width: number) => <PerpMarketItem item={item} variant="tile" width={width} />,
149141
};
150142
case 'market_cell.list':
151143
return {
152144
...MARKET_SECTION_DESCRIPTORS[surface.display],
153-
renderItem: (item: PerpMarketPlacementItem, analyticsContext: DiscoverCardAnalyticsContext) => (
154-
<PerpMarketItem analyticsContext={analyticsContext} item={item} variant="cell" />
155-
),
145+
renderItem: (item: PerpMarketPlacementItem) => <PerpMarketItem item={item} variant="cell" />,
156146
};
157147
}
158148
}, [surface.display]);
149+
// Only passed when the destination is perp-owned (`['perps']`); non-perp CMS
150+
// destinations are wired in #7553.
151+
const onPressSeeAll = useCallback(() => onTapSearch(), [onTapSearch]);
159152

160153
return renderSectionLayout({
161154
data: perpsResult.items,
162155
descriptor: perpDescriptor,
163156
loading: perpsResult.isLoading,
164-
onPressSeeAll: getHeaderPress(surface.destination),
165-
placement: perpsResult.placement,
157+
onPressSeeAll: surface.destination?.[0] === 'perps' ? onPressSeeAll : undefined,
166158
surface,
167-
surfaceId,
168159
});
169160
}
170161

@@ -184,120 +175,96 @@ function TokenMarketPlacementContent({
184175
return {
185176
...MARKET_SECTION_DESCRIPTORS[surface.display],
186177
getItemWidth: (item: TokenPlacementItem) => computeMarketPillWidth(tokenToMarketPillWidthInput({ item, nativeCurrency })),
187-
renderItem: (item: TokenPlacementItem, _: number, analyticsContext: DiscoverCardAnalyticsContext) => (
188-
<TokenMarketItem analyticsContext={analyticsContext} item={item} nativeCurrency={nativeCurrency} variant="pill" />
189-
),
178+
renderItem: (item: TokenPlacementItem) => <TokenMarketItem item={item} nativeCurrency={nativeCurrency} variant="pill" />,
190179
};
191180
case 'market_tile.carousel':
192181
return {
193182
...MARKET_SECTION_DESCRIPTORS[surface.display],
194-
renderItem: (item: TokenPlacementItem, _: number, analyticsContext: DiscoverCardAnalyticsContext) => (
195-
<TokenMarketItem analyticsContext={analyticsContext} item={item} nativeCurrency={nativeCurrency} variant="tile" />
196-
),
183+
renderItem: (item: TokenPlacementItem) => <TokenMarketItem item={item} nativeCurrency={nativeCurrency} variant="tile" />,
197184
};
198185
case 'market_tile.grid':
199186
return {
200187
...MARKET_SECTION_DESCRIPTORS[surface.display],
201-
renderItem: (item: TokenPlacementItem, width: number, analyticsContext: DiscoverCardAnalyticsContext) => (
202-
<TokenMarketItem analyticsContext={analyticsContext} item={item} nativeCurrency={nativeCurrency} variant="tile" width={width} />
188+
renderItem: (item: TokenPlacementItem, width: number) => (
189+
<TokenMarketItem item={item} nativeCurrency={nativeCurrency} variant="tile" width={width} />
203190
),
204191
};
205192
case 'market_cell.list':
206193
return {
207194
...MARKET_SECTION_DESCRIPTORS[surface.display],
208-
renderItem: (item: TokenPlacementItem, analyticsContext: DiscoverCardAnalyticsContext) => (
209-
<TokenMarketItem analyticsContext={analyticsContext} item={item} nativeCurrency={nativeCurrency} variant="cell" />
210-
),
195+
renderItem: (item: TokenPlacementItem) => <TokenMarketItem item={item} nativeCurrency={nativeCurrency} variant="cell" />,
211196
};
212197
}
213198
}, [nativeCurrency, surface.display]);
214-
const onPressSeeAll = useCallback(() => {
215-
if (surface.destination?.[0] === 'tokens') {
216-
onTapSearch();
217-
return;
218-
}
219-
navigateDiscoverDestination(surface.destination);
220-
}, [onTapSearch, surface.destination]);
199+
// Only passed when the destination is token-owned (`['tokens']`); non-token CMS
200+
// destinations are wired in #7553.
201+
const onPressSeeAll = useCallback(() => onTapSearch(), [onTapSearch]);
221202

222203
return renderSectionLayout({
223204
data: tokensResult.items,
224205
descriptor: tokenDescriptor,
225206
loading: tokensResult.isLoading,
226-
onPressSeeAll: surface.destination ? onPressSeeAll : undefined,
227-
placement: tokensResult.placement,
207+
onPressSeeAll: surface.destination?.[0] === 'tokens' ? onPressSeeAll : undefined,
228208
surface,
229-
surfaceId,
230209
});
231210
}
232211

233212
function hasPlacement(surface: SurfaceLeafWithDisplay<MarketDisplay>): surface is PlacementBackedSurfaceLeafWithDisplay<MarketDisplay> {
234213
return typeof surface.placement === 'string' && surface.placement.length > 0;
235214
}
236215

237-
function renderMarketPill(item: MarketDisplayItem, _: number, analyticsContext: DiscoverCardAnalyticsContext) {
238-
return <MarketPill analyticsContext={analyticsContext} item={item} />;
216+
function renderMarketPill(item: MarketDisplayItem) {
217+
return <MarketPill item={item} />;
239218
}
240219

241-
function renderMarketTile(item: MarketDisplayItem, _: number, analyticsContext: DiscoverCardAnalyticsContext) {
242-
return <MarketTileCard analyticsContext={analyticsContext} item={item} />;
220+
function renderMarketTile(item: MarketDisplayItem) {
221+
return <MarketTileCard item={item} />;
243222
}
244223

245-
function renderMarketGridTile(item: MarketDisplayItem, width: number, analyticsContext: DiscoverCardAnalyticsContext) {
246-
return <MarketTileCard analyticsContext={analyticsContext} item={item} width={width} />;
224+
function renderMarketGridTile(item: MarketDisplayItem, width: number) {
225+
return <MarketTileCard item={item} width={width} />;
247226
}
248227

249228
function renderMarketGridTileSkeleton(width: number) {
250229
return <MarketTileCardSkeleton width={width} />;
251230
}
252231

253-
function renderMarketCell(item: MarketDisplayItem, analyticsContext: DiscoverCardAnalyticsContext) {
254-
return <MarketCell analyticsContext={analyticsContext} item={item} />;
232+
function renderMarketCell(item: MarketDisplayItem) {
233+
return <MarketCell item={item} />;
255234
}
256235

257236
function TokenMarketItem({
258-
analyticsContext,
259237
item,
260238
nativeCurrency,
261239
variant,
262240
width,
263241
}: {
264-
analyticsContext: DiscoverCardAnalyticsContext;
265242
item: TokenPlacementItem;
266-
nativeCurrency: ReturnType<typeof userAssetsStoreManager.getState>['currency'];
243+
nativeCurrency: NativeCurrencyKey;
267244
variant: 'cell' | 'pill' | 'tile';
268245
width?: number;
269246
}) {
270247
const displayItem = useTokenMarketDisplay({ item, nativeCurrency });
271248

272249
switch (variant) {
273250
case 'cell':
274-
return <MarketCell analyticsContext={analyticsContext} item={displayItem} />;
251+
return <MarketCell item={displayItem} />;
275252
case 'pill':
276-
return <MarketPill analyticsContext={analyticsContext} item={displayItem} />;
253+
return <MarketPill item={displayItem} />;
277254
case 'tile':
278-
return <MarketTileCard analyticsContext={analyticsContext} item={displayItem} width={width} />;
255+
return <MarketTileCard item={displayItem} width={width} />;
279256
}
280257
}
281258

282-
function PerpMarketItem({
283-
analyticsContext,
284-
item,
285-
variant,
286-
width,
287-
}: {
288-
analyticsContext: DiscoverCardAnalyticsContext;
289-
item: PerpMarketPlacementItem;
290-
variant: 'cell' | 'pill' | 'tile';
291-
width?: number;
292-
}) {
259+
function PerpMarketItem({ item, variant, width }: { item: PerpMarketPlacementItem; variant: 'cell' | 'pill' | 'tile'; width?: number }) {
293260
const displayItem = usePerpMarketDisplay(item);
294261

295262
switch (variant) {
296263
case 'cell':
297-
return <MarketCell analyticsContext={analyticsContext} item={displayItem} />;
264+
return <MarketCell item={displayItem} />;
298265
case 'pill':
299-
return <MarketPill analyticsContext={analyticsContext} item={displayItem} />;
266+
return <MarketPill item={displayItem} />;
300267
case 'tile':
301-
return <MarketTileCard analyticsContext={analyticsContext} item={displayItem} width={width} />;
268+
return <MarketTileCard item={displayItem} width={width} />;
302269
}
303270
}

src/features/discover/components/markets/hooks/usePerpMarketDisplay.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { type MarketDisplayItem } from '@/features/discover/types/marketDisplayI
66
import { HYPERLIQUID_COLORS } from '@/features/perps/constants';
77
import { useHyperliquidLineChartsStore } from '@/features/perps/stores/hyperliquidLineChartsStore';
88
import { type PerpMarketWithMetadata } from '@/features/perps/types';
9-
import { getHyperliquidTokenId, navigateToPerpDetailScreen } from '@/features/perps/utils';
9+
import { convertStoredPerpPriceChangeToPercent, getHyperliquidTokenId } from '@/features/perps/utils';
1010
import { formatPerpAssetPrice } from '@/features/perps/utils/formatPerpsAssetPrice';
1111
import { type PerpMarketPlacementItem } from '@/features/placements/stores/derived/perpsPlacementStore';
1212
import { getPriceChangeColor, getPriceChangeColors } from '@/framework/ui/price/usePriceChangeColors';
@@ -22,12 +22,19 @@ const MARKET_CHART_PRICE_CHANGE_COLORS = {
2222
* Builds the {@link MarketDisplayItem} for a perp market placement item, memoizing
2323
* the result. The perp analogue of {@link useTokenMarketDisplay}; charts are fetched
2424
* by `market.symbol` with no metadata gating.
25+
*
26+
* Perps store `market.priceChange['24h']` with an extra /100 baked in by
27+
* `calculatePerpPriceChange24h`, so a +5.23% move is stored as `"0.000523"`.
28+
* The shared card contract requires percent units (`"5.23"` == 5.23%), so this
29+
* source hook converts via `convertStoredPerpPriceChangeToPercent` (× 10_000)
30+
* before populating `initialPriceChange` and `priceChangeSelector`.
2531
*/
2632
export function usePerpMarketDisplay(item: PerpMarketPlacementItem): MarketDisplayItem {
2733
return useMemo<MarketDisplayItem>(() => {
2834
const { market } = item;
2935
const displayName = market.baseSymbol;
30-
const initialPriceChange = market.priceChange['24h'];
36+
// Convert fractional stored value → percent units for the shared card contract.
37+
const initialPriceChange = String(convertStoredPerpPriceChangeToPercent(market.priceChange['24h']));
3138

3239
return {
3340
id: item.id,
@@ -41,13 +48,8 @@ export function usePerpMarketDisplay(item: PerpMarketPlacementItem): MarketDispl
4148
initialPriceChange,
4249
leverage: market.maxLeverage,
4350
liveTokenId: getHyperliquidTokenId(market.symbol),
44-
onNavigate: () => navigateToPerpDetailScreen(market.symbol),
45-
pressMetadata: {
46-
marketId: market.symbol,
47-
marketName: market.metadata?.name ?? displayName,
48-
marketSymbol: displayName,
49-
},
50-
priceChangeSelector: token => token.change.change24hPct,
51+
// Live price-change: convert fraction → percent, matching the stored-value contract.
52+
priceChangeSelector: token => String(convertStoredPerpPriceChangeToPercent(token.change.change24hPct)),
5153
priceSelector: token => formatPerpAssetPrice(token.midPrice ?? token.price),
5254
};
5355
}, [item]);
@@ -58,7 +60,8 @@ export function perpToMarketPillWidthInput(item: PerpMarketPlacementItem): Marke
5860
return {
5961
displayName: market.baseSymbol,
6062
initialPrice: formatPerpAssetPrice(market.midPrice ?? market.price),
61-
initialPriceChange: market.priceChange['24h'],
63+
// Convert to percent so pill-width calculation matches the displayed value.
64+
initialPriceChange: String(convertStoredPerpPriceChangeToPercent(market.priceChange['24h'])),
6265
};
6366
}
6467

src/features/discover/utils/refreshDiscoverSurface.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { IS_TEST } from '@/env';
22
import { useHyperliquidMarketsStore } from '@/features/perps/stores/hyperliquidMarketsStore';
3-
import { useTokenRefsStore } from '@/features/placements/stores/derived/tokensPlacementStore';
3+
import { clearTokenRefCache, useTokenRefsStore } from '@/features/placements/stores/derived/tokensPlacementStore';
44
import { usePlacementsV2Store } from '@/features/placements/stores/placementsStore';
55
import { useDiscoverSurfacePlacementRefs } from '@/features/placements/surfaces/stores/discoverSurfaceStore';
66
import { getSurfaceStore } from '@/features/placements/surfaces/stores/surfaceStore';
@@ -22,6 +22,9 @@ export async function refreshDiscoverSurface(surfaceId: string): Promise<void> {
2222
}
2323

2424
if (refs.rainbow.length) {
25+
// Clear the module-level token-ref cache so a forced refresh always fetches
26+
// fresh token data from the network, even within TOKEN_REFS_STALE_TIME.
27+
clearTokenRefCache();
2528
refreshes.push(useTokenRefsStore.getState().fetch(undefined, { force: true }));
2629
}
2730

0 commit comments

Comments
 (0)