Summary
Home page still shows 429 errors after multicall PR #392. Two issues:
Issue 1: Race condition in StoryCardTVL
`BatchTokenDataProvider` uses React Query (async). On initial render, batch data isn't available yet, so `StoryCardTVL` sees `!batchEntry` = true and immediately fires individual `getTokenTVL()` calls. The batch arrives moments later but the individual calls are already in flight.
Fix: Don't enable the individual fallback query until the batch query has settled (loaded or errored). Add a `batchReady` flag to the context:
```typescript
// BatchTokenDataProvider.tsx
const BatchTokenDataContext = createContext<{
data: BatchTokenDataMap;
isReady: boolean;
}>({ data: new Map(), isReady: false });
const { data, isLoading } = useQuery({...});
<BatchTokenDataContext.Provider value={{ data: data ?? new Map(), isReady: !isLoading }}>
```
```typescript
// StoryCardTVL
const { data: batchMap, isReady } = useBatchTokenContext();
const batchEntry = batchMap.get(tokenAddress.toLowerCase());
const { data: individualTvl } = useQuery({
...
enabled: isReady && !batchEntry, // only fallback AFTER batch has loaded
});
```
Issue 2: Individual calls use server-side publicClient
`getTokenPrice()` and `getTokenTVL()` in `lib/price.ts` use the server-side `publicClient` which has no CORS headers. When called from browser components, the first endpoint (`mainnet.base.org`) gets hit and 429s show in console.
Fix: Make these functions accept an optional client parameter (like `getBatchTokenData` already does):
```typescript
export async function getTokenTVL(
tokenAddress: Address,
client: PublicClient = publicClient, // default to server, override for browser
) {
const result = await client.readContract({...});
}
```
Then in browser components, pass `browserClient`:
```typescript
queryFn: () => getTokenTVL(addr, browserClient),
```
Files to update
| File |
Change |
| `src/components/BatchTokenDataProvider.tsx` |
Add `isReady` flag to context |
| `src/components/StoryCardStats.tsx` |
Wait for batch before individual fallback |
| `lib/price.ts` |
Add optional `client` param to `getTokenPrice()`, `getTokenTVL()`, `get24hPriceChange()` |
| `src/components/WriterTradingStats.tsx` |
Pass `browserClient` to `getTokenTVL()` |
| `src/components/ClaimRoyalties.tsx` |
Pass `browserClient` to `getTokenTVL()` |
| `src/components/ReaderPortfolio.tsx` |
Pass `browserClient` to `get24hPriceChange()`, `getTokenTVL()` |
Server-side callers (API routes, story page SSR) keep using the default `publicClient` — no change needed.
Acceptance Criteria
Branch
`task/{issue-number}-fix-rpc-race`
Summary
Home page still shows 429 errors after multicall PR #392. Two issues:
Issue 1: Race condition in StoryCardTVL
`BatchTokenDataProvider` uses React Query (async). On initial render, batch data isn't available yet, so `StoryCardTVL` sees `!batchEntry` = true and immediately fires individual `getTokenTVL()` calls. The batch arrives moments later but the individual calls are already in flight.
Fix: Don't enable the individual fallback query until the batch query has settled (loaded or errored). Add a `batchReady` flag to the context:
```typescript
// BatchTokenDataProvider.tsx
const BatchTokenDataContext = createContext<{
data: BatchTokenDataMap;
isReady: boolean;
}>({ data: new Map(), isReady: false });
const { data, isLoading } = useQuery({...});
<BatchTokenDataContext.Provider value={{ data: data ?? new Map(), isReady: !isLoading }}>
```
```typescript
// StoryCardTVL
const { data: batchMap, isReady } = useBatchTokenContext();
const batchEntry = batchMap.get(tokenAddress.toLowerCase());
const { data: individualTvl } = useQuery({
...
enabled: isReady && !batchEntry, // only fallback AFTER batch has loaded
});
```
Issue 2: Individual calls use server-side publicClient
`getTokenPrice()` and `getTokenTVL()` in `lib/price.ts` use the server-side `publicClient` which has no CORS headers. When called from browser components, the first endpoint (`mainnet.base.org`) gets hit and 429s show in console.
Fix: Make these functions accept an optional client parameter (like `getBatchTokenData` already does):
```typescript
export async function getTokenTVL(
tokenAddress: Address,
client: PublicClient = publicClient, // default to server, override for browser
) {
const result = await client.readContract({...});
}
```
Then in browser components, pass `browserClient`:
```typescript
queryFn: () => getTokenTVL(addr, browserClient),
```
Files to update
Server-side callers (API routes, story page SSR) keep using the default `publicClient` — no change needed.
Acceptance Criteria
Branch
`task/{issue-number}-fix-rpc-race`