Fix 429 rate limiting: add retry backoff and stagger API requests
- fetchJSON retries up to 4 times with exponential backoff on 429 - detectSafeChains checks chains sequentially with 150ms delay - fetchAllChainsData fetches chains sequentially with 200ms delay Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c6264af4cb
commit
c6df6de6d3
|
|
@ -27,11 +27,24 @@ const SafeAPI = (() => {
|
||||||
return `${getChain(chainId).txService}/api/v1${path}`;
|
return `${getChain(chainId).txService}/api/v1${path}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchJSON(url) {
|
async function sleep(ms) {
|
||||||
const res = await fetch(url);
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
if (res.status === 404) return null;
|
}
|
||||||
if (!res.ok) throw new Error(`API error ${res.status}: ${res.statusText} (${url})`);
|
|
||||||
return res.json();
|
async function fetchJSON(url, retries = 4) {
|
||||||
|
for (let attempt = 0; attempt <= retries; attempt++) {
|
||||||
|
const res = await fetch(url);
|
||||||
|
if (res.status === 404) return null;
|
||||||
|
if (res.status === 429) {
|
||||||
|
const delay = Math.min(1000 * Math.pow(2, attempt), 10000);
|
||||||
|
console.warn(`Rate limited (429), retrying in ${delay}ms... (${url})`);
|
||||||
|
await sleep(delay);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!res.ok) throw new Error(`API error ${res.status}: ${res.statusText} (${url})`);
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
|
throw new Error(`Rate limited after ${retries} retries: ${url}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Core API Methods ──────────────────────────────────────────
|
// ─── Core API Methods ──────────────────────────────────────────
|
||||||
|
|
@ -138,18 +151,21 @@ const SafeAPI = (() => {
|
||||||
* Returns array of { chainId, chain, safeInfo }
|
* Returns array of { chainId, chain, safeInfo }
|
||||||
*/
|
*/
|
||||||
async function detectSafeChains(address) {
|
async function detectSafeChains(address) {
|
||||||
const checks = Object.entries(CHAINS).map(async ([chainId, chain]) => {
|
const entries = Object.entries(CHAINS);
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
// Check chains sequentially with small delay to avoid rate limits
|
||||||
|
for (const [chainId, chain] of entries) {
|
||||||
try {
|
try {
|
||||||
const info = await getSafeInfo(address, parseInt(chainId));
|
const info = await getSafeInfo(address, parseInt(chainId));
|
||||||
if (info) return { chainId: parseInt(chainId), chain, safeInfo: info };
|
if (info) results.push({ chainId: parseInt(chainId), chain, safeInfo: info });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Chain doesn't have this Safe or API error - skip
|
// Chain doesn't have this Safe or API error - skip
|
||||||
}
|
}
|
||||||
return null;
|
await sleep(150);
|
||||||
});
|
}
|
||||||
|
|
||||||
const results = await Promise.all(checks);
|
return results;
|
||||||
return results.filter(Boolean);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -174,12 +190,13 @@ const SafeAPI = (() => {
|
||||||
async function fetchAllChainsData(address, detectedChains) {
|
async function fetchAllChainsData(address, detectedChains) {
|
||||||
const dataMap = new Map();
|
const dataMap = new Map();
|
||||||
|
|
||||||
const fetches = detectedChains.map(async ({ chainId }) => {
|
// Fetch chains sequentially to avoid rate limits
|
||||||
|
for (const { chainId } of detectedChains) {
|
||||||
const data = await fetchChainData(address, chainId);
|
const data = await fetchChainData(address, chainId);
|
||||||
dataMap.set(chainId, data);
|
dataMap.set(chainId, data);
|
||||||
});
|
await sleep(200);
|
||||||
|
}
|
||||||
|
|
||||||
await Promise.all(fetches);
|
|
||||||
return dataMap;
|
return dataMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue