diff --git a/lib/rpc.ts b/lib/rpc.ts index df6a9d5a..4a873171 100644 --- a/lib/rpc.ts +++ b/lib/rpc.ts @@ -1,4 +1,4 @@ -import { createPublicClient, http, fallback } from "viem"; +import { createPublicClient, http, fallback, type Hex } from "viem"; import { base, baseSepolia } from "viem/chains"; const chainId = Number(process.env.NEXT_PUBLIC_CHAIN_ID || "84532"); @@ -20,3 +20,20 @@ export const publicClient = createPublicClient({ chain, transport, }); + +/** + * Fetch a transaction receipt with retries and backoff. + * Load-balanced RPC nodes may not have the receipt immediately after + * `waitForTransactionReceipt` completes on the client side. + */ +export async function getReceiptWithRetry(hash: Hex, maxAttempts = 3) { + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + try { + return await publicClient.getTransactionReceipt({ hash }); + } catch (err) { + if (attempt === maxAttempts) throw err; + await new Promise((r) => setTimeout(r, attempt * 1000)); + } + } + throw new Error("unreachable"); +} diff --git a/src/app/api/index/donation/route.ts b/src/app/api/index/donation/route.ts index b783ae53..415b73d1 100644 --- a/src/app/api/index/donation/route.ts +++ b/src/app/api/index/donation/route.ts @@ -1,6 +1,6 @@ import { NextResponse } from "next/server"; import { type Hex, decodeEventLog, encodeEventTopics } from "viem"; -import { publicClient } from "../../../../../lib/viem"; +import { publicClient, getReceiptWithRetry } from "../../../../../lib/rpc"; import { createServerClient } from "../../../../../lib/supabase"; import { storyFactoryAbi, @@ -26,10 +26,10 @@ export async function POST(req: Request) { return error("Missing or invalid txHash"); } - // 1. Fetch receipt + // 1. Fetch receipt (with retry for load-balanced RPC nodes) let receipt; try { - receipt = await publicClient.getTransactionReceipt({ hash: txHash }); + receipt = await getReceiptWithRetry(txHash); } catch { return error("Failed to fetch transaction receipt", 502); } diff --git a/src/app/api/index/plot/route.ts b/src/app/api/index/plot/route.ts index 7a12dfbe..7c7814c7 100644 --- a/src/app/api/index/plot/route.ts +++ b/src/app/api/index/plot/route.ts @@ -1,6 +1,6 @@ import { NextResponse } from "next/server"; import { type Hex, decodeEventLog, encodeEventTopics } from "viem"; -import { publicClient } from "../../../../../lib/viem"; +import { publicClient, getReceiptWithRetry } from "../../../../../lib/rpc"; import { createServerClient } from "../../../../../lib/supabase"; import { storyFactoryAbi, @@ -31,10 +31,10 @@ export async function POST(req: Request) { return error("Missing or invalid txHash"); } - // 1. Fetch receipt + // 1. Fetch receipt (with retry for load-balanced RPC nodes) let receipt; try { - receipt = await publicClient.getTransactionReceipt({ hash: txHash }); + receipt = await getReceiptWithRetry(txHash); } catch { return error("Failed to fetch transaction receipt", 502); } diff --git a/src/app/api/index/storyline/route.ts b/src/app/api/index/storyline/route.ts index 8aec030b..9bcc3b4d 100644 --- a/src/app/api/index/storyline/route.ts +++ b/src/app/api/index/storyline/route.ts @@ -1,6 +1,6 @@ import { NextResponse } from "next/server"; import { type Hex, decodeEventLog, encodeEventTopics } from "viem"; -import { publicClient } from "../../../../../lib/viem"; +import { publicClient, getReceiptWithRetry } from "../../../../../lib/rpc"; import { createServerClient } from "../../../../../lib/supabase"; import { storyFactoryAbi, @@ -32,10 +32,10 @@ export async function POST(req: Request) { return error("Missing or invalid txHash"); } - // 1. Fetch receipt + // 1. Fetch receipt (with retry for load-balanced RPC nodes) let receipt; try { - receipt = await publicClient.getTransactionReceipt({ hash: txHash }); + receipt = await getReceiptWithRetry(txHash); } catch { return error("Failed to fetch transaction receipt", 502); }