diff --git a/lib/price.ts b/lib/price.ts index 0277d6d6..3f644a86 100644 --- a/lib/price.ts +++ b/lib/price.ts @@ -143,16 +143,18 @@ export interface TokenPriceInfo { */ export async function getTokenPrice( tokenAddress: Address, + client?: typeof publicClient, ): Promise { + const rpc = client ?? publicClient; try { const [priceRaw, totalSupplyRaw] = await Promise.all([ - publicClient.readContract({ + rpc.readContract({ address: MCV2_BOND, abi: mcv2BondAbi, functionName: "priceForNextMint", args: [tokenAddress], }), - publicClient.readContract({ + rpc.readContract({ address: tokenAddress, abi: erc20Abi, functionName: "totalSupply", @@ -181,19 +183,21 @@ const BLOCKS_PER_24H = BigInt(43200); */ export async function get24hPriceChange( tokenAddress: Address, + client?: typeof publicClient, ): Promise<{ changePercent: number; currentPrice: bigint; previousPrice: bigint } | null> { + const rpc = client ?? publicClient; try { - const currentBlock = await publicClient.getBlockNumber(); + const currentBlock = await rpc.getBlockNumber(); const pastBlock = currentBlock - BLOCKS_PER_24H; const [currentPrice, previousPrice] = await Promise.all([ - publicClient.readContract({ + rpc.readContract({ address: MCV2_BOND, abi: mcv2BondAbi, functionName: "priceForNextMint", args: [tokenAddress], }), - publicClient.readContract({ + rpc.readContract({ address: MCV2_BOND, abi: mcv2BondAbi, functionName: "priceForNextMint", @@ -236,9 +240,11 @@ const erc20DecimalsAbi = [ */ export async function getTokenTVL( tokenAddress: Address, + client?: typeof publicClient, ): Promise<{ tvl: string; tvlRaw: bigint; reserveToken: Address; decimals: number } | null> { + const rpc = client ?? publicClient; try { - const result = await publicClient.readContract({ + const result = await rpc.readContract({ address: MCV2_BOND, abi: mcv2BondAbi, functionName: "tokenBond", @@ -248,7 +254,7 @@ export async function getTokenTVL( const [, , , , reserveToken, reserveBalance] = result; const reserveAddr = reserveToken as Address; - const decimals = await publicClient.readContract({ + const decimals = await rpc.readContract({ address: reserveAddr, abi: erc20DecimalsAbi, functionName: "decimals", diff --git a/src/app/dashboard/writer/page.tsx b/src/app/dashboard/writer/page.tsx index 201ddfda..3d9913a8 100644 --- a/src/app/dashboard/writer/page.tsx +++ b/src/app/dashboard/writer/page.tsx @@ -6,6 +6,7 @@ import { useQuery, useQueryClient, useInfiniteQuery } from "@tanstack/react-quer import { formatUnits } from "viem"; import { supabase, type Storyline, type Donation } from "../../../../lib/supabase"; import { getTokenTVL } from "../../../../lib/price"; +import { browserClient } from "../../../../lib/rpc"; import { RESERVE_LABEL, STORY_FACTORY, EXPLORER_URL } from "../../../../lib/contracts/constants"; import { GENRES, LANGUAGES } from "../../../../lib/genres"; import { DeadlineCountdown } from "../../../components/DeadlineCountdown"; @@ -370,7 +371,7 @@ function DonationCount({ storylineId, tokenAddress }: { storylineId: number; tok queryKey: ["donation-count", storylineId, tokenAddress], queryFn: async () => { const [tvlData, rows] = await Promise.all([ - getTokenTVL(tokenAddress as Address), + getTokenTVL(tokenAddress as Address, browserClient), supabase ? supabase.from("donations") .select("amount") diff --git a/src/components/BatchTokenDataProvider.tsx b/src/components/BatchTokenDataProvider.tsx index f79b5d0c..893637b1 100644 --- a/src/components/BatchTokenDataProvider.tsx +++ b/src/components/BatchTokenDataProvider.tsx @@ -8,11 +8,22 @@ import { browserClient } from "../../lib/rpc"; type BatchTokenDataMap = Map; -const BatchTokenDataContext = createContext(new Map()); +interface BatchTokenDataContextValue { + data: BatchTokenDataMap; + isReady: boolean; +} + +const BatchTokenDataContext = createContext({ + data: new Map(), + isReady: false, +}); -export function useBatchTokenData(tokenAddress: string): BatchTokenEntry | undefined { - const map = useContext(BatchTokenDataContext); - return map.get(tokenAddress.toLowerCase()); +export function useBatchTokenData(tokenAddress: string): { + entry: BatchTokenEntry | undefined; + isReady: boolean; +} { + const { data, isReady } = useContext(BatchTokenDataContext); + return { entry: data.get(tokenAddress.toLowerCase()), isReady }; } /** @@ -26,7 +37,7 @@ export function BatchTokenDataProvider({ tokenAddresses: Address[]; children: ReactNode; }) { - const { data } = useQuery({ + const { data, isLoading } = useQuery({ queryKey: ["batch-token-data", tokenAddresses.join(",")], queryFn: () => getBatchTokenData(tokenAddresses, browserClient), staleTime: 60000, @@ -34,7 +45,7 @@ export function BatchTokenDataProvider({ }); return ( - + {children} ); diff --git a/src/components/ClaimRoyalties.tsx b/src/components/ClaimRoyalties.tsx index f2a79381..3dd23ffb 100644 --- a/src/components/ClaimRoyalties.tsx +++ b/src/components/ClaimRoyalties.tsx @@ -4,7 +4,7 @@ import { useState, useCallback, useEffect, useRef } from "react"; import { useWriteContract } from "wagmi"; import { useQuery } from "@tanstack/react-query"; import { formatUnits, type Address } from "viem"; -import { browserClient as publicClient } from "../../lib/rpc"; +import { browserClient } from "../../lib/rpc"; import { mcv2BondAbi, getTokenTVL } from "../../lib/price"; import { MCV2_BOND, RESERVE_LABEL, EXPLORER_URL, PLOT_TOKEN } from "../../lib/contracts/constants"; @@ -37,7 +37,7 @@ export function ClaimRoyalties({ tokenAddress, plotCount, beneficiary }: ClaimRo const { data: royaltyInfo } = useQuery({ queryKey: ["royalty-info", tokenAddress, beneficiary], queryFn: async () => { - const [balance, claimed] = await publicClient.readContract({ + const [balance, claimed] = await browserClient.readContract({ address: MCV2_BOND, abi: mcv2BondAbi, functionName: "getRoyaltyInfo", @@ -51,7 +51,7 @@ export function ClaimRoyalties({ tokenAddress, plotCount, beneficiary }: ClaimRo // Fetch reserve token decimals dynamically const { data: tvlData } = useQuery({ queryKey: ["claim-decimals", tokenAddress], - queryFn: () => getTokenTVL(tokenAddress), + queryFn: () => getTokenTVL(tokenAddress, browserClient), }); const decimals = tvlData?.decimals ?? 18; @@ -92,7 +92,7 @@ export function ClaimRoyalties({ tokenAddress, plotCount, beneficiary }: ClaimRo setTxHash(hash); setTxState("pending"); - await publicClient.waitForTransactionReceipt({ hash }); + await browserClient.waitForTransactionReceipt({ hash }); setTxState("done"); } catch (err) { diff --git a/src/components/ReaderPortfolio.tsx b/src/components/ReaderPortfolio.tsx index 9c3616a7..f891c05b 100644 --- a/src/components/ReaderPortfolio.tsx +++ b/src/components/ReaderPortfolio.tsx @@ -4,7 +4,7 @@ import { useAccount } from "wagmi"; import { useQuery } from "@tanstack/react-query"; import { formatUnits, type Address } from "viem"; import { formatPrice, formatSupply } from "../../lib/format"; -import { browserClient as publicClient } from "../../lib/rpc"; +import { browserClient } from "../../lib/rpc"; import { erc20Abi, mcv2BondAbi, get24hPriceChange, getTokenTVL } from "../../lib/price"; import { MCV2_BOND, RESERVE_LABEL, STORY_FACTORY } from "../../lib/contracts/constants"; import { supabase, type Storyline } from "../../lib/supabase"; @@ -45,7 +45,7 @@ export function ReaderPortfolio() { args: [address], })); - const balanceResults = await publicClient.multicall({ contracts: balanceCalls }); + const balanceResults = await browserClient.multicall({ contracts: balanceCalls }); // Filter to only storylines with non-zero balance const held = storylines @@ -61,14 +61,14 @@ export function ReaderPortfolio() { const balance = balanceResult.result as bigint; try { const [price, priceChangeResult, tvlResult] = await Promise.all([ - publicClient.readContract({ + browserClient.readContract({ address: MCV2_BOND, abi: mcv2BondAbi, functionName: "priceForNextMint", args: [tokenAddr], }), - get24hPriceChange(tokenAddr).catch(() => null), - getTokenTVL(tokenAddr).catch(() => null), + get24hPriceChange(tokenAddr, browserClient).catch(() => null), + getTokenTVL(tokenAddr, browserClient).catch(() => null), ]); const priceBI = BigInt(price); diff --git a/src/components/StoryCardStats.tsx b/src/components/StoryCardStats.tsx index 885667c2..1c652c86 100644 --- a/src/components/StoryCardStats.tsx +++ b/src/components/StoryCardStats.tsx @@ -3,6 +3,7 @@ import { useQuery } from "@tanstack/react-query"; import { type Address } from "viem"; import { getTokenTVL, getTokenPrice } from "../../lib/price"; +import { browserClient } from "../../lib/rpc"; import { RESERVE_LABEL } from "../../lib/contracts/constants"; import { useBatchTokenData } from "./BatchTokenDataProvider"; @@ -21,13 +22,13 @@ export function StoryCardStats({ tokenAddress }: { tokenAddress: string }) { const { data: priceInfo } = useQuery({ queryKey: ["card-price", tokenAddress], - queryFn: () => getTokenPrice(addr), + queryFn: () => getTokenPrice(addr, browserClient), staleTime: 60000, }); const { data: tvlData } = useQuery({ queryKey: ["card-tvl", tokenAddress], - queryFn: () => getTokenTVL(addr), + queryFn: () => getTokenTVL(addr, browserClient), staleTime: 60000, }); @@ -49,15 +50,15 @@ export function StoryCardStats({ tokenAddress }: { tokenAddress: string }) { /** TVL-only display for home page book cards. * Uses batch context when available (home page), falls back to individual fetch. */ export function StoryCardTVL({ tokenAddress }: { tokenAddress: string }) { - const batchEntry = useBatchTokenData(tokenAddress); + const { entry: batchEntry, isReady } = useBatchTokenData(tokenAddress); const addr = tokenAddress as Address; - // Fall back to individual fetch only if batch data not available + // Only fall back to individual fetch AFTER batch has settled const { data: individualTvl } = useQuery({ queryKey: ["card-tvl", tokenAddress], - queryFn: () => getTokenTVL(addr), + queryFn: () => getTokenTVL(addr, browserClient), staleTime: 60000, - enabled: !batchEntry, + enabled: isReady && !batchEntry, }); const tvlData = batchEntry?.tvl ?? individualTvl; diff --git a/src/components/WriterTradingStats.tsx b/src/components/WriterTradingStats.tsx index 7421548b..cfa09b62 100644 --- a/src/components/WriterTradingStats.tsx +++ b/src/components/WriterTradingStats.tsx @@ -2,7 +2,7 @@ import { useQuery } from "@tanstack/react-query"; import { formatUnits, type Address } from "viem"; -import { browserClient as publicClient } from "../../lib/rpc"; +import { browserClient } from "../../lib/rpc"; import { mcv2BondAbi, getTokenTVL } from "../../lib/price"; import { MCV2_BOND, RESERVE_LABEL } from "../../lib/contracts/constants"; import { formatPrice } from "../../lib/format"; @@ -20,13 +20,13 @@ export function WriterTradingStats({ storyline }: WriterTradingStatsProps) { queryKey: ["writer-stats", tokenAddress], queryFn: async () => { const [priceRaw, tvlData] = await Promise.all([ - publicClient.readContract({ + browserClient.readContract({ address: MCV2_BOND, abi: mcv2BondAbi, functionName: "priceForNextMint", args: [tokenAddress], }), - getTokenTVL(tokenAddress), + getTokenTVL(tokenAddress, browserClient), ]); const decimals = tvlData?.decimals ?? 18; return {