fix(check-deposits): count only TON transfers sent to the deposit address (#38)

The TON check walked every jetton transfer in the deposit address's event
feed, including the ones it sends. An outgoing transfer whose comment
matched a user's deposit code would have been credited to that user; the
2026-02-13 sweep of 41.9 USDT was reported as an unknown-memo deposit on
every run.

Resolve the configured address to its raw form and skip any transfer
whose recipient is not the deposit address.
This commit is contained in:
SatoshiQaziMuhammed authored and GitHub committed 2026-10-18 19:48:14 +04:00
1 parent 2b255c56fc
commit e0d56a5fb5
3 files changed
+47

No files matched your search

@@ -12,6 +12,7 @@ import { keccak_256 } from 'https://esm.sh/@noble/hashes@1.3.2/sha3';
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';
const ALLOWED_ORIGINS = ['https://telegram.pezkuwichain.io', 'https://telegram.pezkiwi.app'];
const MIN_DEPOSIT = 10; // Minimum 10 USDT
@@ -138,6 +139,17 @@ async function checkTonDeposits(
const result = { found: 0, processed: 0, errors: [] as string[] };
try {
// tonapi reports recipients in raw form; the configured address may be
// user-friendly, so resolve it before comparing.
const parsed = await fetch(`${TON_API}/address/${depositAddress}/parse`, {
headers: tonHeaders(),
});
if (!parsed.ok) {
result.errors.push(await apiError('TON address parse error:', parsed));
return result;
}
const depositRawAddress: string = (await parsed.json()).raw_form;
// Get recent transactions
const response = await fetch(`${TON_API}/accounts/${depositAddress}/events?limit=50`, {
headers: tonHeaders(),
@@ -156,6 +168,7 @@ async function checkTonDeposits(
for (const action of event.actions || []) {
if (action.type !== 'JettonTransfer') continue;
if (action.JettonTransfer?.jetton?.address !== TON_USDT_MASTER) continue;
if (!isIncomingTonTransfer(action.JettonTransfer, depositRawAddress)) continue;
const txHash = event.event_id;
const amount = parseInt(action.JettonTransfer.amount) / 1e6; // USDT has 6 decimals
+13
View File
@@ -0,0 +1,13 @@
// A deposit address's event feed also carries the transfers it sends, such as
// sweeps to the hot wallet. Only a transfer whose recipient is the deposit
// address itself is a deposit.
export interface TonJettonTransfer {
recipient?: { address?: string };
}
export function isIncomingTonTransfer(
transfer: TonJettonTransfer | undefined,
depositRawAddress: string
): boolean {
return transfer?.recipient?.address === depositRawAddress;
}
@@ -0,0 +1,21 @@
import { assert, assertFalse } from 'jsr:@std/assert@1';
import { isIncomingTonTransfer } from './ton.ts';
const DEPOSIT = '0:c35f028ad76a7003bcb164decaec61230a77ad05c724856409e9445014b0b7be';
Deno.test('a transfer to the deposit address is a deposit', () => {
assert(isIncomingTonTransfer({ recipient: { address: DEPOSIT } }, DEPOSIT));
});
Deno.test('a sweep out of the deposit address is not a deposit', () => {
// 2026-02-13: 41.9 USDT sent from the deposit address, no memo.
const sweep = {
recipient: { address: '0:ef28248f185d0000000000000000000000000000000000000000000000000000' },
};
assertFalse(isIncomingTonTransfer(sweep, DEPOSIT));
});
Deno.test('a transfer with no recipient is not a deposit', () => {
assertFalse(isIncomingTonTransfer({}, DEPOSIT));
assertFalse(isIncomingTonTransfer(undefined, DEPOSIT));
});