Skip to content

Commit d403366

Browse files
odysseus0claude
andcommitted
feat: add MEV refund metrics widget component
Add a new React component that fetches and displays real-time MEV and gas refund metrics from the Dune Analytics API. The widget shows loading states and falls back to mock data on errors. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 6015f3b commit d403366

File tree

2 files changed

+103
-0
lines changed

2 files changed

+103
-0
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
.container {
2+
display: flex;
3+
align-items: center;
4+
gap: 0.75rem;
5+
font-size: 0.875rem;
6+
color: var(--ifm-navbar-link-color);
7+
margin-right: 0.75rem;
8+
}
9+
10+
.metric {
11+
display: flex;
12+
align-items: center;
13+
gap: 0.25rem;
14+
}
15+
16+
.label {
17+
font-weight: 400;
18+
}
19+
20+
.value {
21+
font-family: monospace;
22+
font-weight: 600;
23+
transition: opacity 0.3s ease;
24+
}
25+
26+
.loading {
27+
opacity: 0.5;
28+
}
29+
30+
.separator {
31+
color: var(--ifm-navbar-link-color);
32+
opacity: 0.3;
33+
}
34+
35+
@media (max-width: 996px) {
36+
.container {
37+
display: none !important;
38+
}
39+
}

src/components/MevMetrics.tsx

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import React, { useEffect, useState } from 'react';
2+
import styles from './MevMetrics.module.css';
3+
4+
interface MetricsResponse {
5+
totalMevRefund: number;
6+
totalGasRefund: number;
7+
fetchedAt: string;
8+
stale: boolean;
9+
}
10+
11+
export default function MevMetrics(): JSX.Element {
12+
const [data, setData] = useState<MetricsResponse | null>(null);
13+
const [loading, setLoading] = useState(true);
14+
15+
useEffect(() => {
16+
const fetchMetrics = async () => {
17+
try {
18+
const response = await fetch('https://refund-metrics-dune-api.vercel.app/api/metrics');
19+
if (!response.ok) {
20+
throw new Error(`HTTP error! status: ${response.status}`);
21+
}
22+
const metrics: MetricsResponse = await response.json();
23+
setData(metrics);
24+
} catch (error) {
25+
console.error('Error fetching MEV metrics:', error);
26+
// Mock data as fallback
27+
setData({
28+
totalMevRefund: 380.29,
29+
totalGasRefund: 444.24,
30+
fetchedAt: new Date().toISOString(),
31+
stale: true
32+
});
33+
} finally {
34+
setLoading(false);
35+
}
36+
};
37+
38+
fetchMetrics();
39+
}, []);
40+
41+
const formatValue = (value: number): string => {
42+
return `${value.toFixed(2)} ETH`;
43+
};
44+
45+
return (
46+
<div className={styles.container}>
47+
<span className={styles.label}>Refund</span>
48+
<span className={styles.separator}>|</span>
49+
<div className={styles.metric}>
50+
<span className={styles.label}>MEV:</span>
51+
<span className={`${styles.value} ${loading ? styles.loading : ''}`}>
52+
{loading ? '...' : data && formatValue(data.totalMevRefund)}
53+
</span>
54+
</div>
55+
<span className={styles.separator}>|</span>
56+
<div className={styles.metric}>
57+
<span className={styles.label}>Gas:</span>
58+
<span className={`${styles.value} ${loading ? styles.loading : ''}`}>
59+
{loading ? '...' : data && formatValue(data.totalGasRefund)}
60+
</span>
61+
</div>
62+
</div>
63+
);
64+
}

0 commit comments

Comments
 (0)