From d67558258f2201e457c4bdb25fc830709ccfe405 Mon Sep 17 00:00:00 2001 From: Satoshi Qazi Muhammed Date: Sat, 17 Oct 2026 20:52:13 +0400 Subject: [PATCH] fix(check-deposits): pace TronGrid calls and retry 429 so later users are checked check-deposits polls every user's TRC20 address each minute in unpaced batches of 8. Since the previous change logged TronGrid failures, two consecutive runs showed HTTP 429 from about the 24th request on: deposit indexes 24-53, 30 of 53 users, were skipped on every run, so their TRC20 deposits were never seen. The old code returned silently on a non-2xx reply. - batches start at least 1.1 s apart - a 429 is retried twice, after 1 s and 2 s - per-user failures, including thrown errors, are logged and returned in result.errors instead of being dropped Deposits are found from the last 20 transfers per address and deduplicated by tx_hash, so once users are checked again a skipped deposit is picked up on the next run. --- supabase/functions/check-deposits/index.ts | 35 +++++++++++++++++----- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/supabase/functions/check-deposits/index.ts b/supabase/functions/check-deposits/index.ts index 184199f..23bbdad 100644 --- a/supabase/functions/check-deposits/index.ts +++ b/supabase/functions/check-deposits/index.ts @@ -38,6 +38,19 @@ function tronHeaders(): Record { return {}; } +const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + +// A 429 is TronGrid's per-second limit, not a verdict on the address: wait and +// ask again rather than skip the user until the next run hits the same wall. +async function fetchTron(url: string, retries = 2): Promise { + for (let attempt = 0; ; attempt++) { + const response = await fetch(url, { headers: tronHeaders() }); + if (response.status !== 429 || attempt >= retries) return response; + await response.body?.cancel(); + await sleep(1000 * (attempt + 1)); + } +} + const SUBSCAN_API = 'https://assethub-polkadot.api.subscan.io'; // Contract addresses - raw format from TonAPI @@ -284,24 +297,29 @@ async function checkTrc20Deposits( // Check each user's derived address, with bounded concurrency so we // don't exceed the edge function's CPU/time budget on large user counts. + // Batches are also paced: TronGrid rate-limits a key per second, and + // unpaced batches of 8 got HTTP 429 from about the 24th request on, which + // skipped every later user (measured on two consecutive runs, 2026-10-17). const CONCURRENCY = 8; + const BATCH_INTERVAL_MS = 1100; for (let i = 0; i < users.length; i += CONCURRENCY) { const batch = users.slice(i, i + CONCURRENCY); + const batchStart = Date.now(); await Promise.all( batch.map(async (user: { id: string; deposit_index: number }) => { try { const address = deriveTronAddress(hdMnemonic, user.deposit_index); // Get TRC20 transfers to this address - const response = await fetch( - `${TRON_API}/v1/accounts/${address}/transactions/trc20?limit=20&contract_address=${TRON_USDT_CONTRACT}`, - { - headers: tronHeaders(), - } + const response = await fetchTron( + `${TRON_API}/v1/accounts/${address}/transactions/trc20?limit=20&contract_address=${TRON_USDT_CONTRACT}` ); if (!response.ok) { console.error(`TronGrid ${response.status} for deposit index ${user.deposit_index}`); + result.errors.push( + `TronGrid ${response.status} for deposit index ${user.deposit_index}` + ); return; } @@ -343,11 +361,14 @@ async function checkTrc20Deposits( } } } catch (err) { - // Skip individual user errors - return; + // One user's failure must not stop the others, but it must be seen. + console.error(`TRC20 check failed for deposit index ${user.deposit_index}: ${err}`); + result.errors.push(`TRC20 check failed for deposit index ${user.deposit_index}`); } }) ); + const wait = BATCH_INTERVAL_MS - (Date.now() - batchStart); + if (wait > 0 && i + CONCURRENCY < users.length) await sleep(wait); } } catch (err) { result.errors.push(`TRC20 error: ${err}`);