From 9cc049e9da7a96f1d524b33d3ba9441043cded67 Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Fri, 24 Apr 2026 08:31:44 +0900 Subject: [PATCH 1/2] [#963] Add DB-first txHash dedup check to all indexer endpoints Co-Authored-By: Claude Opus 4.6 (1M context) --- package-lock.json | 4 ++-- package.json | 2 +- src/app/api/index/donation/route.ts | 14 ++++++++++++++ src/app/api/index/plot/route.ts | 14 ++++++++++++++ src/app/api/index/storyline/route.ts | 14 ++++++++++++++ src/app/api/index/trade/route.ts | 15 +++++++++++++++ 6 files changed, 60 insertions(+), 3 deletions(-) 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..0ab62da6 100644 --- a/src/app/api/index/trade/route.ts +++ b/src/app/api/index/trade/route.ts @@ -23,6 +23,21 @@ 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"); + // 0. DB dedup — skip RPC if already indexed + const supabaseDedup = createServerClient(); + if (supabaseDedup) { + const { data: existing } = await supabaseDedup + .from("trade_history") + .select("id") + .eq("tx_hash", txHash) + .eq("token_address", tokenAddress) + .limit(1) + .maybeSingle(); + if (existing) { + return NextResponse.json({ ok: true, cached: true }); + } + } + // 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); From 86e2895b7f1e89dca36d77b2843f174857f43e1d Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Fri, 24 Apr 2026 08:33:29 +0900 Subject: [PATCH 2/2] [#963] Remove trade route early dedup to avoid hiding partial indexing failures Co-Authored-By: Claude Opus 4.6 (1M context) --- src/app/api/index/trade/route.ts | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/app/api/index/trade/route.ts b/src/app/api/index/trade/route.ts index 0ab62da6..7a148002 100644 --- a/src/app/api/index/trade/route.ts +++ b/src/app/api/index/trade/route.ts @@ -23,20 +23,9 @@ 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"); - // 0. DB dedup — skip RPC if already indexed - const supabaseDedup = createServerClient(); - if (supabaseDedup) { - const { data: existing } = await supabaseDedup - .from("trade_history") - .select("id") - .eq("tx_hash", txHash) - .eq("token_address", tokenAddress) - .limit(1) - .maybeSingle(); - if (existing) { - return NextResponse.json({ ok: true, cached: true }); - } - } + // 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);