## Livepeer Explorer Maintenance - Audit Report
## 1. Performance Optimization
### 1.1 Critical Issues
#### Memory Leaks in Event Listeners
**Location:** `components/Drawer/index.tsx`, `layouts/main.tsx`
**Problem:** Router event listeners are registered on every render without cleanup, causing memory leaks and redundant work.
**Impact:**
- Memory usage grows over time
- Event handlers fire multiple times per route change
- Slower page transitions after extended use
**Fix:** move to a useEffect
~~~~tsx
// components/Drawer/index.tsx
useEffect(() => {
const onStart = () => onDrawerClose();
Router.events.on("routeChangeStart", onStart);
return () => {
Router.events.off("routeChangeStart", onStart);
};
}, [onDrawerClose]);
~~~~
---
#### Sequential Network Requests (Blocking)
**Location:** `pages/api/pending-stake/[address].tsx`, `pages/api/score/[address].tsx`, `pages/api/regions/index.ts`
**Problem:** Multiple API calls are made sequentially when they could run in parallel.
**Example Fix:**
~~~~tsx
// Before (sequential - 5s total)
const currentRound = await readContract(...);
const pendingStake = await readContract(...);
const pendingFees = await readContract(...);
// After (parallel - 2s total)
const currentRound = await readContract(...);
const [pendingStake, pendingFees] = await Promise.all([
readContract(...),
readContract(...)
]);
~~~~
We could also probably just make a single RPC ping with `viem`'s `readContracts` method, which would make uncached page loads multiple times faster.
---
#### Inefficient Block Timestamp Lookups
**Location:** `lib/utils.tsx` - `getBlocksFromTimestamps`
**Problem:** Sequential Etherscan API calls with artificial delays.
**Fix:** Implement concurrent worker pool with controlled parallelism.
**Note:** It does not appear as though `getBlocksFromTimestamps` is actually being used. So we may want to just remove this function.
---
#### Missing Network Request Deduplication
**Location:** `pages/_app.tsx` - SWR configuration
**Problem:** Multiple components can trigger the same API call simultaneously without deduplication.
**Impact:**
- Unnecessary duplicate requests
- Higher server load
**Fix:**
~~~~tsx
<SWRConfig
value={{
dedupingInterval: 5000,
revalidateOnFocus: false, // double-check this on implementation
keepPreviousData: true,
// ...existing config
}}
>
~~~~
---
### 1.2 Performance Recommendations
| Optimization | Current | Target |
|--------------|---------|--------|
| Parallelize API calls | Sequential | Parallel |
| Block lookups | Sequential | Parallel |
| Event listener cleanup | Leaks | Clean |
| SWR deduplication | None | 5s window |
| Image optimization | `<img>` | `next/image` |
| Dayjs consolidation | Per-file | Centralized |
## 2. Stability & Reliability
### 2.1 Critical Issues
#### No Retry Logic on External APIs
**Location:** All API routes calling external services
**Problem:** Single failures cause complete request failures. No timeout protection.
**Affected Endpoints:**
- `/api/usage` - Livepeer.com API
- `/api/score/*` - Metrics servers
- `/api/regions` - Multiple metrics endpoints
- `/api/ens-data` - Subgraph queries
**Impact:**
- User sees errors for transient network issues
- No graceful degradation
**Fix:** Implement shared retry utility with exponential backoff and timeouts.
~~~~typescript
export async function fetchWithRetry(
input: RequestInfo,
init?: RequestInit,
opts: { retries?: number; backoffMs?: number; timeoutMs?: number } = {}
) {
const { retries = 2, backoffMs = 500, timeoutMs = 10000 } = opts;
let lastErr: any;
for (let attempt = 0; attempt <= retries; attempt++) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
const res = await fetch(input, {
...(init || {}),
signal: controller.signal
});
clearTimeout(timer);
if (res.ok) return res;
lastErr = new Error(`HTTP ${res.status}`);
} catch (e) {
lastErr = e;
} finally {
clearTimeout(timer);
}
await new Promise((r) => setTimeout(r, backoffMs * (attempt + 1)));
}
throw lastErr;
}
~~~~
---
#### Invalid `getStaticProps` Return Values
**Location:** Multiple pages return `null` on error
**Affected Files:**
- `pages/accounts/[account]/orchestrating.tsx`
- `pages/accounts/[account]/delegating.tsx`
- `pages/accounts/[account]/history.tsx`
- `pages/orchestrators.tsx`
- `pages/index.tsx`
- `pages/transactions.tsx`
**Problem:** Returning `null` from `getStaticProps` can crash builds or cause 500 errors.
**Impact:**
- Build failures in production
- 500 errors for users
- No graceful error handling
**Fix:**
~~~~tsx
// Before
if (!data) {
return null; // ❌ Invalid
}
// After
if (!data) {
return { notFound: true, revalidate: 300 }; // ✅ Valid
}
~~~~
---
#### Legacy Address Validation
**Location:** `lib/api/api.ts` - `isValidAddress`
**Problem:** Custom regex validation can have false positives/negatives.
**Fix:** Use battle-tested `viem` library `isAddress`
---
### 2.2 Stability Recommendations
| Issue | Impact |
|-------|----------|
| Add retry logic | High |
| Fix `getStaticProps` returns | High |
| Improve address validation | Low |
## 3. Testing & Quality Assurance
### 3.1 Current State
**Test Coverage:** 0%
**CI Pipeline:** ❌ None
**Type Safety:** ✅ TypeScript enabled
**Linting:** ✅ ESLint configured
### 3.2 Testing Strategy
#### Tier 1: Essential
**Critical Unit Tests:**
1. `lib/utils.test.ts`
- `abbreviateNumber` edge cases
- `getBlocksFromTimestamps` retry logic
- `isValidEmail` validation
2. `lib/api/fetch.test.ts`
- `fetchWithRetry` success/failure scenarios
- Timeout handling
- Exponential backoff
3. `lib/api/api.test.ts`
- `getCacheControlHeader` correctness
Others will likely be included beyond these. The importance is to start a baseline for the CI pipeline.
---
### 3.2 Testing Recommendations Summary
| Test Type | Priority |
|-----------|----------|
| Linting | ✅ Done, but could be stricter |
| Type checking | ✅ Done |
| Unit tests (utils) | 🔴 Critical |
| Build test | ✅ Done through Vercel |
| CI pipeline | 🔴 Critical |
| API integration tests | 🟡 Optional |
| E2E tests | ⚪ Skip | N/A |
## 4. Monitoring & Observability
### 4.1 Current State
**Monitoring:** ❌ None
**Error Tracking:** ❌ None
**Performance Monitoring:** ❌ None
**Alerting:** ❌ None
**Risk:** Zero visibility into production issues. Users report problems before team is aware.
---
### 4.2 Recommended Monitoring (BetterStack + Vercel)
#### Essential Monitoring
**Cost:** Free (Vercel Logs + BetterStack free tier)
**Note:** If the Vercel integration doesn't work well, we can add `pino` logs
**Components:**
1. **Vercel Logs** (Built-in)
2. **BetterStack Uptime Monitors** (5 monitors)
- Homepage: Check every 15 min
- Orchestrators page: Check every 15 min
- Voting page: Check every 15 min
- Treasury page: Check every 15 min
- `/api/health`: Check every 15 min
3. **BetterStack Log Drain** (Vercel Integration)
- Captures all serverless function errors
- Logs are more permanent
4. **Critical Log Alerts**
- Threshold: 3 occurrences in 1 hour
- Channel: email or Slack or Discord or Telegram
---
#### Comprehensive Monitoring
**Additional Components:**
5. **External Dependency Monitors** (3 monitors)
- The Graph subgraph health
- Livepeer.com API status
- Metrics server availability
6. **API Performance Monitors**
- `/api/usage` - Alert if >15s
- `/api/score/*` - Alert if >15s
7. **Health Check Endpoint**
~~~~typescript
// pages/api/health.ts (NEW FILE)
export const config = { runtime: 'edge' };
export default async function handler(req: Request) {
const checks = await Promise.allSettled([
fetch(SUBGRAPH_URL, { signal: AbortSignal.timeout(3000) }),
fetch(METRICS_URL, { signal: AbortSignal.timeout(3000) }),
]);
const subgraphOk = checks[0].status === 'fulfilled' && checks[0].value.ok;
const metricsOk = checks[1].status === 'fulfilled' && checks[1].value.ok;
const allHealthy = subgraphOk && metricsOk;
const response = {
status: allHealthy ? 'healthy' : 'degraded',
timestamp: new Date().toISOString(),
checks: {
subgraph: subgraphOk ? 'ok' : 'error',
metrics: metricsOk ? 'ok' : 'error',
},
};
return new Response(JSON.stringify(response), {
status: allHealthy ? 200 : 503,
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-store, max-age=0',
},
});
}
~~~~
---
### 4.3 Monitoring Recommendations Summary
| Component | Priority |
|-----------|----------|
| Vercel Log into BetterStack | 🔴 Critical |
| BetterStack uptime monitors | 🔴 Critical |
| Critical log alerts | 🔴 Critical |
| Health check endpoint | 🟡 Important |
| External dependency monitors | 🟡 Important |
| API performance monitors | 🟡 Important |
## 5. Code Quality Improvements
### 5.1 Improvement Opportunities
#### Duplicate Code - Dayjs Plugin Setup
**Location:** Multiple files repeat dayjs configuration
**Files Affected:**
- `components/HistoryView/index.tsx`
- `components/RoundStatus/index.tsx`
- `pages/voting/index.tsx`
- `pages/whats-new.tsx`
- around 10 more files total
**Impact:**
- Increases bundle size
- Maintenance burden
**Fix:** Create centralized dayjs instance
~~~~typescript
// lib/dayjs.ts (NEW FILE)
import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';
import relativeTime from 'dayjs/plugin/relativeTime';
import utc from 'dayjs/plugin/utc';
dayjs.extend(duration);
dayjs.extend(relativeTime);
dayjs.extend(utc);
export default dayjs;
// Then everywhere else:
import dayjs from '@/lib/dayjs'; // ✅ Pre-configured
~~~~
---
#### Inconsistent Error Handling
**Problem:** API routes have varying error handling patterns
**Examples:**
~~~~typescript
// Some routes
console.error(err);
return res.status(500).end();
// Other routes
console.log(err);
return res.status(500).json(null);
// Better approach
console.error(err);
return res.status(500).json({
error: 'Internal server error',
code: 'INTERNAL_ERROR'
});
~~~~
**Recommendation:** Standardize error responses
**Impact:** Better client-side error handling
---
## 6. Recommendations Summary
### Must-Have
1. Fix all critical stability issues
2. Implement retry logic for external APIs
3. Optimize network request patterns
4. Add basic unit tests
5. Set up CI pipeline
6. Fix memory leaks
---
### Should-Have
7. Implement production monitoring
8. Set up health checks
9. Configure alerting
10. Add external dependency monitoring
11. Standardize error handling
## 7. Additional Things to Focus On
1. Livepeer overview page takes multiple seconds to load without cache
2. Treasury end dates take 1-2 seconds to load
3. Governance page takes 1-2 seconds to load
3. The `/whats-new` page loads indefinitely (maybe remove)
4. Numerous package upgrades would be good (already recorded in a Task issue ticket)
5. Multiple type issues with `@livepeer/design-system`
6. Remove unused variables (need stricter linting)
7. Add import sorting