Skip to content

Commit 45f80e2

Browse files
TokenBriceclaude
andcommitted
feat(table): make Peg column sortable by absolute deviation
Clicking the Peg header now sorts by |price/ref - 1| × 10,000 bps. Ascending = tightest pegs first; descending = worst depegs first. NAV tokens and coins with missing prices sort last. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 42ef205 commit 45f80e2

File tree

1 file changed

+31
-3
lines changed

1 file changed

+31
-3
lines changed

src/components/stablecoin-table.tsx

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ function ColumnVisibilityDropdown({
150150

151151

152152
export function StablecoinTable({ data, isLoading, activeFilters, logos, pegRates = {}, searchQuery, pegScores, dexLiquidity, reportCards, onClearSearch, onClearFilters }: StablecoinTableProps) {
153-
type SortKey = "name" | "price" | "mcap" | "change24h" | "change7d" | "stability" | "liquidity" | "grade";
153+
type SortKey = "name" | "price" | "mcap" | "change24h" | "change7d" | "stability" | "liquidity" | "grade" | "peg";
154154
const { sortKey, sortDirection, toggleSort, getAriaSortValue, handleSortKeyDown } = useSort<SortKey>("mcap", "desc");
155155
const sort = useMemo(() => ({ key: sortKey, direction: sortDirection }), [sortKey, sortDirection]);
156156
const router = useRouter();
@@ -177,6 +177,7 @@ export function StablecoinTable({ data, isLoading, activeFilters, logos, pegRate
177177
stability: "stability",
178178
liquidity: "liquidity",
179179
grade: "grade",
180+
peg: "peg",
180181
};
181182
const colId = SORT_KEY_TO_COLUMN[sortKey];
182183
return visibleSet.has(colId) ? sortKey : "mcap" as SortKey;
@@ -267,13 +268,30 @@ export function StablecoinTable({ data, isLoading, activeFilters, logos, pegRate
267268
bVal = bGrade;
268269
break;
269270
}
271+
case "peg": {
272+
const getAbsBps = (coin: (typeof filtered)[0]) => {
273+
const m = metaById.get(coin.id);
274+
if (m?.flags.navToken) return null;
275+
const ref = getPegReference(coin.pegType, pegRates, m?.commodityOunces);
276+
const price = coin.price;
277+
return price != null && ref > 0 ? Math.abs(price / ref - 1) * 10_000 : null;
278+
};
279+
const aDev = getAbsBps(a);
280+
const bDev = getAbsBps(b);
281+
if (aDev === null && bDev === null) return 0;
282+
if (aDev === null) return 1;
283+
if (bDev === null) return -1;
284+
aVal = aDev;
285+
bVal = bDev;
286+
break;
287+
}
270288
default:
271289
aVal = getCirculatingRaw(a);
272290
bVal = getCirculatingRaw(b);
273291
}
274292
return sort.direction === "asc" ? aVal - bVal : bVal - aVal;
275293
});
276-
}, [filtered, sort, effectiveSortKey, pegScores, dexLiquidity, reportCards]);
294+
}, [filtered, sort, effectiveSortKey, pegScores, dexLiquidity, reportCards, pegRates, metaById]);
277295

278296
// Reset scroll when filters, search, or sort change
279297
const [prev, setPrev] = useState({ filtered, sort });
@@ -402,7 +420,17 @@ export function StablecoinTable({ data, isLoading, activeFilters, logos, pegRate
402420
/>
403421
)}
404422
{isVisible("peg") && (
405-
<TableHead className="text-right" title="Current peg deviation from target price">Peg</TableHead>
423+
<SortableTableHead
424+
sortKey="peg"
425+
currentSortKey={sortKey}
426+
sortDirection={sortDirection}
427+
label="Peg"
428+
toggleSort={toggleSort}
429+
getAriaSortValue={getAriaSortValue}
430+
handleSortKeyDown={handleSortKeyDown}
431+
className="text-right"
432+
title="Sort by peg deviation — ascending shows tightest pegs first, descending shows worst depegs first"
433+
/>
406434
)}
407435
{isVisible("mcap") && (
408436
<SortableTableHead

0 commit comments

Comments
 (0)