From b00eb112da82431254b82660291ef7bd7bd02f62 Mon Sep 17 00:00:00 2001 From: e35ventura Date: Mon, 4 May 2026 22:34:15 -0500 Subject: [PATCH 1/3] Active Rates: stack filters under the title Toolbar crowded the title against the filter row + search box. Two-row layout: title on top, direction toggles + search below with space-between so they sit at opposite edges. --- src/components/dashboard/MinerRatesTable.tsx | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/components/dashboard/MinerRatesTable.tsx b/src/components/dashboard/MinerRatesTable.tsx index d871005..a0b693d 100644 --- a/src/components/dashboard/MinerRatesTable.tsx +++ b/src/components/dashboard/MinerRatesTable.tsx @@ -356,16 +356,7 @@ const MinerRatesTable: React.FC = () => { minHeight: 0, }} > - + { sx={{ display: 'flex', alignItems: 'center', - justifyContent: 'flex-end', + justifyContent: 'space-between', gap: 1.5, flexWrap: 'wrap', - ml: 'auto', }} > { ), }} sx={{ - width: 200, - ml: 'auto', + width: 220, '& .MuiOutlinedInput-root': { fontFamily: FONTS.mono, fontSize: '0.75rem', From 903e3547f66cd5988a588e2f2d2d9010e80e3563 Mon Sep 17 00:00:00 2001 From: e35ventura Date: Mon, 4 May 2026 22:34:19 -0500 Subject: [PATCH 2/3] Depth of Market: one direction at a time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Combined forward+reverse ladder showed the same miner's capacity twice (once at the forward rate, again at the reverse) and stacked two overlapping depth bars. Dropdown now lists each direction explicitly (e.g. BTC → TAO, TAO → BTC); the ladder, capacity column, and depth bar all reflect just the selected side. --- src/components/dashboard/OrderbookDepth.tsx | 250 +++++++++----------- 1 file changed, 109 insertions(+), 141 deletions(-) diff --git a/src/components/dashboard/OrderbookDepth.tsx b/src/components/dashboard/OrderbookDepth.tsx index db64b4b..bde92fd 100644 --- a/src/components/dashboard/OrderbookDepth.tsx +++ b/src/components/dashboard/OrderbookDepth.tsx @@ -100,109 +100,101 @@ const OrderbookDepth: React.FC = () => { }; const { data: miners, isLoading } = useMiners(); - const [selectedPair, setSelectedPair] = useState(''); + type Direction = 'forward' | 'reverse'; + type DirectionOption = { + asset: string; + direction: Direction; + key: string; + label: string; + }; + const [selectedKey, setSelectedKey] = useState(''); - const uniqueAssets = useMemo(() => { - const assets = new Set(); + const directionOptions = useMemo(() => { + const seen = new Map(); miners?.forEach((m) => { const s = m.sourceChain?.toLowerCase(); const d = m.destChain?.toLowerCase(); - if (!s || !d) return; - // After canonicalization from the scraper, TAO is always destChain when present, - // so the asset side is always sourceChain. Keeping the tao→asset branch as a - // defensive fallback in case pre-migration rows are still in flight. - if (d === 'tao') assets.add(s.toUpperCase()); - else if (s === 'tao') assets.add(d.toUpperCase()); + if (!s || d !== 'tao' || s === 'tao') return; + const asset = s.toUpperCase(); + const fwd = m.rate ? parseFloat(m.rate) : 0; + const rev = m.counterRate ? parseFloat(m.counterRate) : 0; + if (fwd > 0) { + const key = `${asset}>forward`; + if (!seen.has(key)) + seen.set(key, { + asset, + direction: 'forward', + key, + label: `${asset} → TAO`, + }); + } + if (rev > 0) { + const key = `${asset}>reverse`; + if (!seen.has(key)) + seen.set(key, { + asset, + direction: 'reverse', + key, + label: `TAO → ${asset}`, + }); + } }); - return Array.from(assets).sort(); + return Array.from(seen.values()).sort((a, b) => + a.label.localeCompare(b.label), + ); }, [miners]); useEffect(() => { - if (!selectedPair && uniqueAssets.length > 0) { - setSelectedPair(uniqueAssets[0]); - } else if ( - uniqueAssets.length > 0 && - !uniqueAssets.includes(selectedPair) - ) { - setSelectedPair(uniqueAssets[0]); + if (directionOptions.length === 0) return; + if (!directionOptions.find((o) => o.key === selectedKey)) { + setSelectedKey(directionOptions[0].key); } - }, [uniqueAssets, selectedPair]); + }, [directionOptions, selectedKey]); - const depthData = useMemo(() => { - if (!miners?.length || !selectedPair) { - return []; - } + const selected = directionOptions.find((o) => o.key === selectedKey) ?? null; - const asset = selectedPair.toLowerCase(); - // Two independent rate ladders — the forward (asset→TAO) and reverse (TAO→asset) - // quotes are now distinct per miner, so they each get their own aggregation. - const forwardGroups: Record = {}; // key = rate, val = capacity TAO - const reverseGroups: Record = {}; // key = counterRate, val = capacity TAO + const depthData = useMemo(() => { + if (!miners?.length || !selected) return []; + const asset = selected.asset.toLowerCase(); + const groups: Record = {}; // key = rate, val = collateral TAO miners.forEach((m) => { if (!m.collateralRao) return; const s = m.sourceChain?.toLowerCase(); const d = m.destChain?.toLowerCase(); - // Canonical order: asset is source, tao is dest. rate = asset→TAO, counterRate = TAO→asset. if (s !== asset || d !== 'tao') return; - const capacityTao = parseInt(m.collateralRao, 10) / 1e9; if (isNaN(capacityTao) || capacityTao <= 0) return; - - const forward = m.rate ? parseFloat(m.rate) : 0; - if (!isNaN(forward) && forward > 0) { - const key = forward.toFixed(2); - forwardGroups[key] = (forwardGroups[key] || 0) + capacityTao; - } - - const reverse = m.counterRate ? parseFloat(m.counterRate) : 0; - if (!isNaN(reverse) && reverse > 0) { - const key = reverse.toFixed(2); - reverseGroups[key] = (reverseGroups[key] || 0) + capacityTao; - } + const raw = + selected.direction === 'forward' + ? m.rate + : m.counterRate; + const r = raw ? parseFloat(raw) : 0; + if (!isFinite(r) || r <= 0) return; + const key = r.toFixed(2); + groups[key] = (groups[key] || 0) + capacityTao; }); - // Union the rate axis so both ladders render against the same rows. - const allRates = Array.from( - new Set([...Object.keys(forwardGroups), ...Object.keys(reverseGroups)]), - ).sort((a, b) => parseFloat(b) - parseFloat(a)); - - // Forward book cumulates top-down (best asset→TAO rate first = highest TAO per asset). - let cumAssetToTao = 0; - const forwardCum: number[] = []; - for (const key of allRates) { - cumAssetToTao += forwardGroups[key] || 0; - forwardCum.push(cumAssetToTao); - } - - // Reverse book cumulates bottom-up (best TAO→asset rate = lowest TAO per asset). - let cumTaoToAsset = 0; - const reverseCum = new Array(allRates.length); - for (let i = allRates.length - 1; i >= 0; i--) { - cumTaoToAsset += reverseGroups[allRates[i]] || 0; - reverseCum[i] = cumTaoToAsset; - } + // Best rate first: forward wants highest TAO/asset, reverse wants lowest. + const rates = Object.keys(groups).sort((a, b) => + selected.direction === 'forward' + ? parseFloat(b) - parseFloat(a) + : parseFloat(a) - parseFloat(b), + ); - return allRates.map((key, i) => ({ - rate: key, - forwardCapacity: forwardGroups[key] || 0, - reverseCapacity: reverseGroups[key] || 0, - cumAssetToTao: forwardCum[i], - cumTaoToAsset: reverseCum[i], - })); - }, [miners, selectedPair]); + let cum = 0; + return rates.map((key) => { + const capacity = groups[key]; + cum += capacity; + return { rate: key, capacity, cumCapacity: cum }; + }); + }, [miners, selected]); - const maxCum = useMemo(() => { - if (depthData.length === 0) return 1; - let m = 0; - for (const row of depthData) { - if (row.cumAssetToTao > m) m = row.cumAssetToTao; - if (row.cumTaoToAsset > m) m = row.cumTaoToAsset; - } - return m > 0 ? m : 1; - }, [depthData]); - const getAssetSymbol = () => - selectedPair ? selectedPair.replace('/TAO', '').trim() : ''; + const maxCum = useMemo( + () => + depthData.reduce((m, r) => (r.cumCapacity > m ? r.cumCapacity : m), 1), + [depthData], + ); return isLoading || !miners ? ( @@ -256,13 +248,13 @@ const OrderbookDepth: React.FC = () => { - {uniqueAssets.length > 0 && ( + {directionOptions.length > 0 && ( @@ -306,7 +298,7 @@ const OrderbookDepth: React.FC = () => { @@ -317,36 +309,18 @@ const OrderbookDepth: React.FC = () => { - Capacity + Capacity (TAO) - - - - - - - @@ -358,7 +332,19 @@ const OrderbookDepth: React.FC = () => { cursor: 'help', }} > - + {selected?.direction === 'reverse' ? ( + <> + {'→'}{' '} + + + ) : selected ? ( + <> + {'→'}{' '} + + + ) : ( + Cumulative + )} @@ -366,31 +352,23 @@ const OrderbookDepth: React.FC = () => { {depthData.map((row) => { - const pctAssetToTao = (row.cumAssetToTao / maxCum) * 100; - const pctTaoToAsset = (row.cumTaoToAsset / maxCum) * 100; - - const isBtc = getAssetSymbol().toUpperCase() === 'BTC'; - + const pct = (row.cumCapacity / maxCum) * 100; + const isBtc = selected?.asset.toUpperCase() === 'BTC'; const assetThemeColor = isBtc ? BTC_COLOR : theme.palette.primary.main; - const taoThemeColor = TAO_COLOR; - - const leftGradColor = `color-mix(in srgb, ${assetThemeColor} 10%, transparent)`; - const rightGradColor = - theme.palette.mode === 'dark' - ? 'color-mix(in srgb, var(--color-white) 10%, transparent)' - : 'color-mix(in srgb, var(--color-woodsmoke) 8%, transparent)'; + const fillColor = + selected?.direction === 'forward' + ? assetThemeColor + : TAO_COLOR; + const gradColor = `color-mix(in srgb, ${fillColor} 14%, transparent)`; return ( { sx={{ ...cellSx, color: 'text.primary' }} align="right" > - {(row.forwardCapacity + row.reverseCapacity).toFixed(2)} - - - {row.cumAssetToTao > 0 - ? (row.cumAssetToTao / parseFloat(row.rate)).toFixed(6) - : '\u2014'} + {row.capacity.toFixed(2)} - {row.cumTaoToAsset > 0 - ? row.cumTaoToAsset.toFixed(2) - : '\u2014'} + {row.cumCapacity.toFixed(2)} ); @@ -428,7 +396,7 @@ const OrderbookDepth: React.FC = () => { {depthData.length === 0 && ( Date: Tue, 5 May 2026 03:35:04 +0000 Subject: [PATCH 3/3] style: auto-format code with prettier/eslint --- src/components/dashboard/OrderbookDepth.tsx | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/src/components/dashboard/OrderbookDepth.tsx b/src/components/dashboard/OrderbookDepth.tsx index bde92fd..6643243 100644 --- a/src/components/dashboard/OrderbookDepth.tsx +++ b/src/components/dashboard/OrderbookDepth.tsx @@ -165,10 +165,7 @@ const OrderbookDepth: React.FC = () => { if (s !== asset || d !== 'tao') return; const capacityTao = parseInt(m.collateralRao, 10) / 1e9; if (isNaN(capacityTao) || capacityTao <= 0) return; - const raw = - selected.direction === 'forward' - ? m.rate - : m.counterRate; + const raw = selected.direction === 'forward' ? m.rate : m.counterRate; const r = raw ? parseFloat(raw) : 0; if (!isFinite(r) || r <= 0) return; const key = r.toFixed(2); @@ -334,13 +331,11 @@ const OrderbookDepth: React.FC = () => { > {selected?.direction === 'reverse' ? ( <> - {'→'}{' '} - + {'→'} ) : selected ? ( <> - {'→'}{' '} - + {'→'} ) : ( Cumulative @@ -358,9 +353,7 @@ const OrderbookDepth: React.FC = () => { ? BTC_COLOR : theme.palette.primary.main; const fillColor = - selected?.direction === 'forward' - ? assetThemeColor - : TAO_COLOR; + selected?.direction === 'forward' ? assetThemeColor : TAO_COLOR; const gradColor = `color-mix(in srgb, ${fillColor} 14%, transparent)`; return ( @@ -383,10 +376,7 @@ const OrderbookDepth: React.FC = () => { > {row.capacity.toFixed(2)} - + {row.cumCapacity.toFixed(2)}