diff --git a/package-lock.json b/package-lock.json index 62e3e3db..ceefd287 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "plotlink", - "version": "0.1.50", + "version": "0.1.51", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "plotlink", - "version": "0.1.50", + "version": "0.1.51", "workspaces": [ "packages/*" ], diff --git a/package.json b/package.json index 4651e330..ebd8a71e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "plotlink", - "version": "0.1.50", + "version": "0.1.51", "private": true, "workspaces": [ "packages/*" diff --git a/src/app/api/index/donation/route.ts b/src/app/api/index/donation/route.ts index 493aa69c..f513b93b 100644 --- a/src/app/api/index/donation/route.ts +++ b/src/app/api/index/donation/route.ts @@ -28,6 +28,20 @@ export async function POST(req: Request) { return error("Missing or invalid txHash"); } + // 0. DB dedup — skip RPC/IPFS if already indexed + const supabaseDedup = createServerClient(); + if (supabaseDedup) { + const { data: existing } = await supabaseDedup + .from("donations") + .select("id") + .eq("tx_hash", txHash) + .limit(1) + .maybeSingle(); + if (existing) { + return NextResponse.json({ ok: true, cached: true }); + } + } + // 1. Validate tx exists and is recent (< 5 min) — prevents spam const receipt = await validateRecentTx(txHash); if (!receipt) { diff --git a/src/app/api/index/plot/route.ts b/src/app/api/index/plot/route.ts index 83aadc72..a5fca822 100644 --- a/src/app/api/index/plot/route.ts +++ b/src/app/api/index/plot/route.ts @@ -34,6 +34,20 @@ export async function POST(req: Request) { return error("Missing or invalid txHash"); } + // 0. DB dedup — skip RPC/IPFS if already indexed + const supabaseDedup = createServerClient(); + if (supabaseDedup) { + const { data: existing } = await supabaseDedup + .from("plots") + .select("id") + .eq("tx_hash", txHash) + .limit(1) + .maybeSingle(); + if (existing) { + return NextResponse.json({ ok: true, cached: true }); + } + } + // 1. Validate tx exists and is recent (< 5 min) — prevents spam const receipt = await validateRecentTx(txHash); if (!receipt) { diff --git a/src/app/api/index/storyline/route.ts b/src/app/api/index/storyline/route.ts index eafb6822..9cccbee2 100644 --- a/src/app/api/index/storyline/route.ts +++ b/src/app/api/index/storyline/route.ts @@ -41,6 +41,20 @@ export async function POST(req: Request) { return error("Missing or invalid txHash"); } + // 0. DB dedup — skip RPC/IPFS if already indexed + const supabaseDedup = createServerClient(); + if (supabaseDedup) { + const { data: existing } = await supabaseDedup + .from("storylines") + .select("id") + .eq("tx_hash", txHash) + .limit(1) + .maybeSingle(); + if (existing) { + return NextResponse.json({ ok: true, cached: true }); + } + } + // 1. Validate tx exists and is recent (< 5 min) — prevents spam const receipt = await validateRecentTx(txHash); if (!receipt) { diff --git a/src/app/api/index/trade/route.ts b/src/app/api/index/trade/route.ts index f53cafad..7a148002 100644 --- a/src/app/api/index/trade/route.ts +++ b/src/app/api/index/trade/route.ts @@ -23,6 +23,10 @@ export async function POST(req: Request) { if (!txHash || !/^0x[0-9a-fA-F]{64}$/.test(txHash)) return error("Missing or invalid txHash"); if (!tokenAddress) return error("tokenAddress required"); + // Trade route skips early dedup: a single tx can produce multiple trade logs, + // and partial indexing on a previous attempt must not block retries. + // Per-log upserts handle duplicates safely without IPFS cost. + // Validate tx exists and is recent (< 5 min) — prevents spam with fake hashes const validatedReceipt = await validateRecentTx(txHash); if (!validatedReceipt) return error("Transaction not found, failed, or too old", 400);