mirror of
https://github.com/pezkuwichain/pezkuwi-telegram-miniapp.git
synced 2026-11-03 17:31:08 +00:00
feat(check-deposits): check TRC20 addresses of recently active users only (#41)
TronGrid's free plan allows 10,000 requests a day. Checking every user's TRC20 address on every five-minute run needs ~29,000 a day at 101 users, so the key ran out at 03:00 UTC and every later run was refused. get-deposit-info now records when a user opened the deposit screen. check-deposits checks users seen in the last 24 hours on every run and everyone in one run every six hours (00, 06, 12, 18 UTC): about 3,300 requests a day at 10 active users. A deposit made without opening the screen within the past day waits for the next full scan. The user-list query now reports its error instead of ending the TRC20 check as if there were no users.
This commit is contained in:
1 parent
90955269df
commit
4f00d3f696
5 files changed
+87
-3
No files matched your search
@@ -13,6 +13,7 @@ import { sha256 } from 'https://esm.sh/@noble/hashes@1.3.2/sha256';
|
||||
import { bytesToHex } from 'https://esm.sh/@noble/hashes@1.3.2/utils';
|
||||
import { secp256k1 } from 'https://esm.sh/@noble/curves@1.2.0/secp256k1';
|
||||
import { isIncomingTonTransfer } from './ton.ts';
|
||||
import { selectTrc20Users } from './trc20.ts';
|
||||
|
||||
const ALLOWED_ORIGINS = ['https://telegram.pezkuwichain.io', 'https://telegram.pezkiwi.app'];
|
||||
const MIN_DEPOSIT = 10; // Minimum 10 USDT
|
||||
@@ -304,14 +305,20 @@ async function checkTrc20Deposits(
|
||||
// platform admin account and doubles as the treasury sweep destination -
|
||||
// excluded here so internal sweep transfers landing on it are never
|
||||
// mistaken for a customer deposit.
|
||||
const { data: users } = await supabase
|
||||
const { data: allUsers, error: usersError } = await supabase
|
||||
.from('tg_users')
|
||||
.select('id, deposit_index')
|
||||
.select('id, deposit_index, deposit_info_seen_at')
|
||||
.not('deposit_index', 'is', null)
|
||||
.neq('deposit_index', 0)
|
||||
.order('deposit_index', { ascending: true });
|
||||
|
||||
if (!users || users.length === 0) return result;
|
||||
if (usersError) {
|
||||
result.errors.push(`TRC20 user list error: ${usersError.message}`);
|
||||
return result;
|
||||
}
|
||||
type Trc20Row = { id: string; deposit_index: number; deposit_info_seen_at: string | null };
|
||||
const users = selectTrc20Users((allUsers ?? []) as Trc20Row[], new Date());
|
||||
if (users.length === 0) return result;
|
||||
|
||||
// TronGrid allows a key 15 requests a second and, past that, refuses every
|
||||
// request for about 30 s ("The key exceeds the frequency limit(15), and the
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// The TRC20 check costs one TronGrid request per user, and the free plan
|
||||
// allows 10,000 a day ("Exceed the user daily usage (10000)", logged
|
||||
// 2026-10-19). Checking every user every five minutes needs ~29,000 a day at
|
||||
// 101 users, so only users who opened the deposit screen recently are checked
|
||||
// on every run; everyone else is checked by a full scan every six hours.
|
||||
|
||||
export const ACTIVE_WINDOW_MS = 24 * 60 * 60 * 1000;
|
||||
export const FULL_SCAN_EVERY_HOURS = 6;
|
||||
// The cron runs every five minutes, so exactly one run per full-scan hour
|
||||
// starts inside this window.
|
||||
export const FULL_SCAN_WINDOW_MINUTES = 5;
|
||||
|
||||
export function isFullScanRun(now: Date): boolean {
|
||||
return (
|
||||
now.getUTCHours() % FULL_SCAN_EVERY_HOURS === 0 &&
|
||||
now.getUTCMinutes() < FULL_SCAN_WINDOW_MINUTES
|
||||
);
|
||||
}
|
||||
|
||||
export interface Trc20User {
|
||||
deposit_info_seen_at: string | null;
|
||||
}
|
||||
|
||||
export function selectTrc20Users<T extends Trc20User>(users: T[], now: Date): T[] {
|
||||
if (isFullScanRun(now)) return users;
|
||||
const since = now.getTime() - ACTIVE_WINDOW_MS;
|
||||
return users.filter((user) => {
|
||||
if (!user.deposit_info_seen_at) return false;
|
||||
const seen = Date.parse(user.deposit_info_seen_at);
|
||||
return !Number.isNaN(seen) && seen >= since;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { assertEquals } from 'jsr:@std/assert@1';
|
||||
import { isFullScanRun, selectTrc20Users } from './trc20.ts';
|
||||
|
||||
const at = (iso: string) => new Date(iso);
|
||||
const users = [
|
||||
{ id: 'recent', deposit_info_seen_at: '2026-10-19T09:00:00Z' },
|
||||
{ id: 'stale', deposit_info_seen_at: '2026-10-17T09:00:00Z' },
|
||||
{ id: 'never', deposit_info_seen_at: null },
|
||||
{ id: 'garbled', deposit_info_seen_at: 'not a date' },
|
||||
];
|
||||
const ids = (list: { id: string }[]) => list.map((u) => u.id);
|
||||
|
||||
Deno.test('an ordinary run checks only users seen in the last 24 hours', () => {
|
||||
assertEquals(ids(selectTrc20Users(users, at('2026-10-19T10:35:00Z'))), ['recent']);
|
||||
});
|
||||
|
||||
Deno.test('a user seen exactly 24 hours ago is still checked', () => {
|
||||
const edge = [{ id: 'edge', deposit_info_seen_at: '2026-10-18T10:35:00Z' }];
|
||||
assertEquals(ids(selectTrc20Users(edge, at('2026-10-19T10:35:00Z'))), ['edge']);
|
||||
});
|
||||
|
||||
Deno.test('the full-scan run checks everyone', () => {
|
||||
assertEquals(ids(selectTrc20Users(users, at('2026-10-19T12:00:20Z'))), ids(users));
|
||||
});
|
||||
|
||||
Deno.test('exactly one five-minute run per full-scan hour is a full scan', () => {
|
||||
assertEquals(isFullScanRun(at('2026-10-19T00:00:15Z')), true);
|
||||
assertEquals(isFullScanRun(at('2026-10-19T06:04:59Z')), true);
|
||||
assertEquals(isFullScanRun(at('2026-10-19T06:05:00Z')), false);
|
||||
assertEquals(isFullScanRun(at('2026-10-19T07:00:15Z')), false);
|
||||
assertEquals(isFullScanRun(at('2026-10-19T18:00:15Z')), true);
|
||||
});
|
||||
@@ -255,6 +255,15 @@ serve(async (req) => {
|
||||
userId = newUser.id;
|
||||
}
|
||||
|
||||
// check-deposits checks this user's TRC20 address on every run for the
|
||||
// next 24 hours, so a deposit made from this screen is found within
|
||||
// minutes rather than at the next six-hourly full scan.
|
||||
const { error: seenError } = await supabase
|
||||
.from('tg_users')
|
||||
.update({ deposit_info_seen_at: new Date().toISOString() })
|
||||
.eq('id', userId);
|
||||
if (seenError) console.error('Failed to record deposit_info_seen_at:', seenError.message);
|
||||
|
||||
// Get or create deposit code
|
||||
let depositCode: string;
|
||||
const { data: existingCode } = await supabase
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
-- When the user last opened the deposit screen. check-deposits checks the
|
||||
-- TRC20 addresses of users seen in the last 24 hours on every run and all
|
||||
-- users only every six hours, to stay inside TronGrid's free daily quota.
|
||||
ALTER TABLE tg_users ADD COLUMN IF NOT EXISTS deposit_info_seen_at timestamptz;
|
||||
Reference in new issue
Block a user