Skip to content

Commit 3874eeb

Browse files
authored
Merge pull request #221 from r4topunk/fix/etherscan-3rps-cleanup
fix(stake): Etherscan 3/s throttle + retry fix; remove diagnostics
2 parents 11b0aa4 + 3fa5c0d commit 3874eeb

2 files changed

Lines changed: 10 additions & 15 deletions

File tree

src/app/api/stake-graph/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { NextResponse } from "next/server";
2-
import { getStakeGraph, lastMorNote } from "@/services/stake-graph";
2+
import { getStakeGraph } from "@/services/stake-graph";
33

44
// Recompute at most once a minute; serve stale for 5 more while revalidating.
55
export const revalidate = 60;
66

77
export async function GET() {
88
try {
99
const graph = await getStakeGraph();
10-
return NextResponse.json({ ...graph, _morNote: lastMorNote() }, {
10+
return NextResponse.json(graph, {
1111
headers: { "Cache-Control": "public, s-maxage=60, stale-while-revalidate=300" },
1212
});
1313
} catch {

src/services/stake-graph.ts

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -146,12 +146,6 @@ const ETHERSCAN_KEY = process.env.ETHERSCAN_API_KEY;
146146
const USER_REFERRED_SIG = keccak256(toHex("UserReferred(uint256,address,address,uint256)"));
147147
const pad32 = (a: Address) => `0x000000000000000000000000${a.slice(2).toLowerCase()}`;
148148

149-
// Temporary diag: which key env is set + the last Etherscan response.
150-
let _morNote = "init";
151-
export function lastMorNote() {
152-
return `esk=${process.env.ETHERSCAN_API_KEY ? "Y" : "n"} bsk=${process.env.BASESCAN_API_KEY ? "Y" : "n"} | ${_morNote}`;
153-
}
154-
155149
/** A rider's referred stakes on a pool, straight from the UserReferred events.
156150
* Needs a mainnet-capable Etherscan key (a Basescan-only key returns NOTOK for
157151
* chainid=1) — set ETHERSCAN_API_KEY in the env. */
@@ -160,15 +154,16 @@ async function etherscanReferred(pool: Address, referrer: Address, key: string):
160154
`https://api.etherscan.io/v2/api?chainid=1&module=logs&action=getLogs&address=${pool}` +
161155
`&topic0=${USER_REFERRED_SIG}&topic0_3_opr=and&topic3=${pad32(referrer)}` +
162156
`&fromBlock=0&toBlock=latest&page=1&offset=1000&apikey=${key}`;
163-
for (let i = 0; i < 3; i++) {
157+
for (let i = 0; i < 4; i++) {
164158
try {
165159
const res = await fetch(url, { cache: "no-store" });
166160
const j = (await res.json()) as { status?: string; message?: string; result?: unknown };
167-
_morNote = `${j.status ?? "?"}|${String(j.message ?? "?").slice(0, 24)}|${Array.isArray(j.result) ? `n=${j.result.length}` : String(j.result).slice(0, 50)}`;
168161
if (j.status === "1" && Array.isArray(j.result)) {
169162
return (j.result as Array<{ topics: string[]; data: string }>).map((l) => ({ user: getAddress(`0x${l.topics[2].slice(26)}`), amount: BigInt(l.data) }));
170163
}
171-
if (typeof j.message === "string" && /rate limit/i.test(j.message)) { await sleep(500); continue; }
164+
// The rate-limit note lives in `result` ("Max calls per sec rate limit
165+
// reached (3/sec)"), not `message` — retry with backoff before giving up.
166+
if (/rate limit/i.test(String(j.message)) || /rate limit/i.test(String(j.result))) { await sleep(600); continue; }
172167
return []; // "No records found" / bad key
173168
} catch { await sleep(300); }
174169
}
@@ -192,10 +187,10 @@ async function morBackersByRider(ethUsd: number): Promise<Record<string, OrbitBa
192187
];
193188
const pairs = escPools.flatMap((p) => idWallets.map(([id, ref]) => ({ ...p, id, ref })));
194189
const rows: Array<{ id: RiderId; backer: OrbitBacker }>[] = [];
195-
// Etherscan's free tier caps at ~5 req/s — run the calls in small waves with
196-
// a gap so they don't get rate-limited into empty results. The route caches
197-
// the whole graph for 60s, so this runs at most once a minute.
198-
const BATCH = 4;
190+
// The key's tier caps at ~3 req/s — run the calls in waves of 3 with a gap
191+
// so they don't get rate-limited into empty results (the retry backoff
192+
// covers the rest). The route caches the whole graph for 60s.
193+
const BATCH = 3;
199194
for (let i = 0; i < pairs.length; i += BATCH) {
200195
const wave = await Promise.all(
201196
pairs.slice(i, i + BATCH).map(async ({ asset, pool, decimals, id, ref }) => {

0 commit comments

Comments
 (0)