Барлық мақалалар
7 мин оқу

TON-дағы USDT төлемін қалай сенімді анықтауға болады

TON-да USDT төлемін анықтау: get_transactions арқылы жетонның transfer_notification талдауы, get_jetton_info-дан decimals, соманы салыстыру, идемпотенттілік.

TON-дағы USDTтөлемді анықтауtransfer_notificationTEP-74 жетондарыидемпотенттілікget_transactions

TON-да төлем қабылдауды бір рет болса да құрған әркім сол бір қақпанға тап болған: төлем түсті, ал бэкенд оны «көрмеді». Клиент 50 USDT жіберді, әмиян түсімді көрсетіп тұр, ал инвойс әлі «күтуде» күйінде ілініп тұр. Он минуттан кейін — қолдау қызметіне ашулы тикет. Тағы бір сағаттан соң төлемді екі рет есепке алғаныңыз белгілі болады, өйткені поллер ретрайда қайта іске қосылған. TON-дағы кіріс USDT төлемін (incoming USDT payment) сенімді анықтау деген — «минутына бір рет баланс тексеру» емес, жетонның нақты ішкі хабарламаларын идемпотенттілікті және жалғаннан қорғанысты ескере отырып талдау. Мұны қалай дұрыс істеу керегін қадам-қадам қарастырамыз — TONNode MCP-серверінің жергілікті әрі тегін шақыруға болатын нақты құралдарында.

TON-дағы USDT төлемін анықтау неге әмиян балансы туралы емес

Аңғал тәсіл — әмиян балансын мезгіл-мезгіл оқып, ол өссе, төлем қабылданды деп есептеу. Бұл — жәшіктегі жалпы сомаға ғана қарай алатын кассаға ұқсайды: +10 USDT түсті — бірақ кімнен, қай шот бойынша, ескі тапсырыс үшін бе, әлде жаңасы үшін бе? Ал егер екі клиент бір мезгілде 10-нан төлесе, сіз бір-ақ қимылмен +20 көресіз.

TON-да мұндай тәсіл бірден бірнеше себеппен күйрейді:

  • USDT — нативті монета емес, жетон (TEP-74 стандарты). Қаражат негізгі әмиянға тікелей емес, оның жетон-әмиянына түседі — бұл нақты бір жетонның мастер-контрактісіне байланған бөлек контракт. Негізгі әмиянның GRAM-дағы балансы бұл кезде мүлдем өзгермейді.
  • Баланс дельтасы кімнің не үшін төлегенін айтпайды. Баланста инвойспен байланыс сақталмайды.
  • Жарыс пен агрегация. Екі сұрау аралығында бірнеше төлем түседі — сіз жиынтық дельтаны көресіз, жеке оқиғаларды емес.
  • Қосарланған есепке алу. Поллинг ретрайы немесе воркердің қайта іске қосылуы — бір төлем екі рет есептеліп кетеді.
  • Decimals. USDT-де олар 6, жетондар үшін әдеттегі 9 емес. Бір бөлгіш қате — клиентке 1000 есе жаңылыс «есептеліп» қалады.

Дұрыс жол — баланспен емес, алушының жетон-әмиянының транзакциялар ағынымен жұмыс істеп, әр аударым оқиғасын бөлек талдау. Оқудың бәрі жергілікті әрі тегін қолжетімді:

{ "mcpServers": { "ton": { "command": "npx", "args": ["-y", "@tonnode/mcp"] } } }

Кіріс USDT төлемі іште қалай көрінеді: жетонның internal_transfer-і (op 0x178d4519)

Біреу сізге USDT аударғанда мынадай тізбек орындалады:

  1. Жіберушінің жетон-әмияны соманы шегеріп, сіздің жетон-әмияныңызға internal_transfer internal-хабарламасын жібереді (op 0x178d4519).
  2. Сіздің жетон-әмияныңыз қаражатты есепке алады және — тек жіберуші forward_ton_amount > 0 қосқан жағдайда ғана — қосымша сіздің негізгі әмияныңызға transfer_notification ішкі хабарламасын жібереді (op 0x7362d09c).

Басты айырық осында жасырынып жатыр. transfer_notification — «сізге жетон түсті» деген ыңғайлы хабарлама, бірақ ол негізгі иеленуші-әмиянға және тек forward_ton_amount > 0 болғанда ғана кетеді. Егер жіберуші нөл қойса — жетон-әмиян қаражатты бәрібір есепке алады, бірақ иесіне хабарлама болмайды.

Ал жетон-әмиянның өз тарихында бұл есепке алу әрқашан тіркеледі — forward_ton_amount-қа тәуелсіз, кіріс internal_transfer түрінде. Сондықтан сенімді анықтау алушының жетон-әмиянының тарихын сұрауға және дәл internal_transfer-ді талдауға негізделеді. Оның TEP-74 бойынша құрылымы:

internal_transfer#178d4519
  query_id:           uint64
  amount:             (VarUInteger 16)     // жетонның raw-бірліктері
  from:               MsgAddress           // ТӨЛЕУШІ-иеленушінің мекенжайы (адам)
  response_address:   MsgAddress
  forward_ton_amount: (VarUInteger 16)
  forward_payload:    (Either Cell ^Cell)  // түсініктеме/memo

Екі маңызды тұс:

  • from өрісі — бұл нақты төлеушінің (адам-иеленушінің) мекенжайы, оның жетон-әмиянының мекенжайы емес. Дәл соны жіберуші ретінде логқа жазу керек. Назар аударыңыз: хабарлама деңгейіндегі мекенжай (tx.in_msg.source) мұнда — төлеушінің жетон-әмияны, ал адам-төлеуші хабарлама денесіндегі from өрісінде тұр.
  • amount өрісіндегі сома — raw-бірліктерде, қайта есептеуге төменде ораламыз.

Осыдан шығатын басты архитектуралық қорытынды

Сенімді анықтау негізгі әмияндағы notification-ды күтуге емес, дәл алушының жетон-әмиянының тарихын сұрауға негізделеді. Оның тарихында есепке алу forward_ton_amount-қа тәуелсіз әрқашан көрінеді. Егер қосымша негізгі әмиянды да тыңдасаңыз, онда transfer_notification-ды ұстайсыз (op 0x7362d09c, жіберуші — sender өрісінде) — бірақ бұл тек forward_ton_amount > 0 жағдайына арналған бонус, шындықтың жалғыз көзі емес.

get_transactions арқылы тарихты оқып, internal_transfer-ді талдаймыз

Алдымен алушының жетон-әмиянының мекенжайы керек. Оны офлайн есептеп шығармаңыз және тикерге сенбеңіз — USDT мастер-контрактісінің өзінен сұраңыз: USDT мастерінде get_wallet_address шақыруы (run_get_method арқылы) сіздің USDT жетон-әмияныңыздың мекенжайын, шынайы мастерге байланған күйінде, детерминистік түрде қайтарады. Содан кейін осы жетон-әмиянның транзакцияларын get_transactions арқылы сұраймыз.

Поллинг циклындағы агентке арналған промпт:

EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs мекенжайындағы
USDT мастерінде run_get_method шақыр: әдіс — get_wallet_address,
аргумент — UQ...myservice иеленуші мекенжайы.
Біздің USDT жетон-әмиянымыздың мекенжайын қайтар. Содан кейін
get_transactions арқылы осы жетон-әмиянның соңғы транзакцияларын
оқы. Әрбір кіріс internal_transfer (op 0x178d4519) үшін query_id,
amount (raw), from өрісін (төлеушінің мекенжайы), forward_payload
ішіндегі мәтіндік түсініктемені, сондай-ақ транзакцияның lt және hash
мәндерін қайтар.

Бір транзакцияны өңдеудің псевдокоды:

for (const tx of txs) {
  const body = tx.in_msg?.decoded;          // кіріс хабарламаның талданған денесі
  if (body?.op !== 0x178d4519) continue;    // internal_transfer емес — өткізіп жібереміз

  const rawAmount = BigInt(body.amount);    // raw-бірліктер, адам оқитын түрде ЕМЕС
  const payer     = body.from;              // төлеуші-иеленушінің мекенжайы
  const memo      = parseComment(body.forward_payload);
  const key       = `${tx.lt}:${tx.hash}`;  // дедуп үшін идентификатор
  // …салыстыру мен есепке алу төменде
}

Сондай-ақ негізгі аккаунттың күйін get_account_state арқылы бір рет тексеріп алған пайдалы (белсенді ме, соңғы транзакция қашан болған) — бұл поллердің «тірі» екенін және артта қалып қоймағанын түсінуге көмектеседі.

Бәрін decimals шешеді: get_jetton_info және raw-соманы қайта есептеу (USDT = 6, 9 емес)

Аударымдағы amount өрісі — жетонның raw-бірліктерінде, үтірсіз бүтін сан. Оны адам оқитын сомаға айналдыру үшін decimals керек. Дәл осы жерде интеграциялардың басым бөлігі бұзылады.

Жетондардың көпшілігінде decimals = 9. TON-дағы USDT-де (Tether) — decimals = 6. Яғни:

1 USDT = 1 000 000 raw   (10^6, 10^9 емес)

Егер әдетпен USDT-дің raw-сомасын 10^9-ға бөлсеңіз, клиенттің 50 USDT төлемі сізде 0.05-ке айналады — 1000 есе аз. Кері бағыттағы қате дәл солай оңай 1000 есе көп есептеп жібереді. Бөлгішті хардкод жасамаңыз — decimals-ті get_jetton_info арқылы он-чейн метадеректерден алыңыз:

const info = await getJettonInfo(USDT_MASTER); // name, symbol, decimals, эмиссия
const decimals = info.decimals;                // USDT үшін = 6
const human = Number(rawAmount) / 10 ** decimals; // 50000000 → 50.0

decimals-ті кэште тикер бойынша емес, мастер мекенжайы бойынша ұстаңыз: тикерді қолдан жасауға болады, ал decimals-ті нақты контрактпен байланыстыру керек. Ереже қарапайым: decimals әрқашан get_jetton_info-дан, ал сома көрсету сәтіне дейін тек BigInt/raw түрінде.

Шығу тегін, соманы және түсініктемені салыстырамыз — жалған жетондарды кесіп тастаймыз

Енді қауіпсіздік үшін ең маңыздысы. Мыналарсыз есепке алуға болмайтын тексерулер.

1. Мастер-контракт — тек шынайы USDT. Кез келген адам «USDT» атауы мен «USD₮» символы бар жетон шығарып, сізге 1000 «USDT»-ға аударым жібере алады. Егер төлемді тикер бойынша сәйкестендірсеңіз — сізді бес минутта алдап кетеді. Шынайы Tether-ге жалғыз сенімді байланыс — шынайы USDT мастер-контрактісі сіздің иеленушіңіз үшін есептеп шығарған жетон-әмиянмен жұмыс істеу:

Tether USD₮ master: EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs

Дәл сондықтан жетон-әмиянның мекенжайын осы мастердегі get_wallet_address он-чейн шақыруынан аламыз (жоғарыда қараңыз), ал тарихты тек осы — басқа ешбір емес — контракттан сұраймыз. Оған есепке алынған кез келген internal_transfer сол мастердің заңды бауырлас жетон-әмиянынан келетініне кепілдік бар: жалған «USDT» басқа мастердің әмияндарында өмір сүреді және сіздің мекенжайыңызға жетпейді. Сондықтан бұл архитектурада tx.in_msg.source-ты бір нәрсемен салыстырудың қажеті жоқ — қорғанысты бақылау нүктесін таңдаудың өзі қамтамасыз етеді. Және ескеріңіз: parse_address — бұл тек EQ/UQ/raw форматтарын офлайн түрлендіру, ол жетон-әмиянның мекенжайын есептеп шығармайды; ол үшін дәл get_wallet_address он-чейн шақыруы қажет. Оны салыстыру алдында мекенжайларды нормализациялау үшін қолданған ыңғайлы — бір мекенжайдың әртүрлі көрінісін бөлек-бөлек деп есептеп қалмау үшін.

2. Инвойспен сәйкестендіруге арналған түсініктеме (memo). Мәтіндік түсініктеме forward_payload ішінде стандартты форматта сақталады: 0x00000000 op-префиксі (4 нөлдік байт) + UTF-8 жол. Соның көмегімен кіріс аударымды нақты тапсырыспен байланыстырасыз.

3. Сома мен төлеуші. Қайта есептелген соманы инвойс бойынша күтілетін сомамен салыстырыңыз; қажет болса, тарих пен антифрод үшін from (төлеушінің мекенжайы) мәнін тіркеп қойыңыз.

// 1. Шығу тегіне кепілдік берілген: біз ДӘЛ шынайы USDT мастерінің
//    get_wallet_address-і қайтарған жетон-әмиянның тарихын
//    оқимыз — демек, мұндағы кез келген аударым шынайы.

// 2. Түсініктеме → инвойс
const invoice = invoices.get(memo);
if (!invoice) continue;

// 3. Сома сәйкес пе?
if (human < invoice.expectedAmount) markUnderpaid(invoice);

// 4. Төлеуші — логтар/антифрод үшін
log({ payer, memo, human, lt: tx.lt, hash: tx.hash });

Идемпотенттілік: төлемді екі рет есепке алмау үшін lt + hash бойынша дедуп

Поллинг әрқашан өз-өзімен қиылысады: кестемен іске қосылады, құлайды, ретрай жасайды, сұрау терезелері бір-бірін қабаттастырады. Дедупсыз ертелі-кеш бір төлемді екі рет есепке аласыз.

TON-да әр транзакция logical time (lt) + hash жұбымен бірегей түрде идентификацияланады. Бұл — сіздің табиғи идемпотенттілік кілтіңіз:

const key = `${tx.lt}:${tx.hash}`;
if (await seen.has(key)) continue;   // өңдеп қойғанбыз — шығамыз
await creditInvoice(invoice, human); // есепке алу
await seen.add(key);                 // сол ДҚ транзакциясында тіркейміз

Кілт ретінде аударымдағы query_id-ді де қолдануға болады, бірақ (lt, hash) жетон-әмиянның кез келген кіріс транзакциясы үшін, оның ішінде forward_ton_amount = 0 жағдайында да жұмыс істейді. Өңделген кілттерді персистентті сақтаңыз және есепке алуды бірегейлікті тексерумен бірге бір ДҚ транзакциясында жасаңыз — сонда параллель воркерлер дубль жасамайды.

Мезгіл-мезгіл реконсиляция жасаған жөн: N минут сайын жетон-әмиянның нақты балансын (get_jetton_balance арқылы — ол мекенжайды он-чейн есептеп, балансты бір шақырумен береді) есепке алынған барлық төлемдердің сомасымен салыстырыңыз. Алшақтық — бір жерде аударым өткізіп жіберілгенінің немесе қосарланғанының белгісі, тіпті бір ғана поллинг циклы бұзылған жағдайда да.

Жүктеме кезіндегі тұрақты throughput: ашық лимиттердің орнына өз кілтіңіз

Төлем поллингі — бұл тұрақты, біркелкі сұраныс ағыны: N әмиян × сұрау жиілігі. Дәл осы жерде ашық инфрақұрылым тар өткелге айналады.

Глобал конфигтегі ашық лайтсерверлер — ортақ әрі лимитті: жүктеме кезінде not ready деп жауап береді немесе ADNL-таймаутқа кетеді. Кілтсіз ашық HTTP-API-лар (toncenter, tonapi.io) секундына шамамен 1 сұрау ұстайды және лимиттен асқанда адал HTTP 429 «Too Many Requests» қайтарады. Байқаңыз: лимиттің нақты коды — дәл 429; TON қауымдастығында жүрген мемдік «228» емес — ол API коды емес. Тарихты бірнеше секунд сайын сұрайтын касса үшін бұл лимиттер өткізіп жіберілген және кешіккен есепке алуларды білдіреді. Өз кілті бар провайдерді қалай таңдау керегі — жеке талдауда.

Төлемдерді анықтауға қажет оқу құралдарының бәрі — get_transactions, run_get_method, get_jetton_balance, get_jetton_info, parse_address, get_account_stateжергілікті әрі тегін қолжетімді: npx -y @tonnode/mcp. @tonnode/mcp пакеті — open source (MIT), TON-ның нативті ADNL-протоколы бойынша HTTP-қабаттарсыз жұмыс істейді. Поллинг продқа шыққанда және кепілдендірілген throughput керек болғанда, өз кілтіңізбен hosted-эндпоинт қосылады:

{
  "mcpServers": {
    "ton": {
      "type": "http",
      "url": "https://mcp.tonnode.io/mcp",
      "headers": { "Authorization": "Bearer tn_live_…" }
    }
  }
}

Тарифтер тек минутына сұраныс лимитімен ғана ерекшеленеді — 16 құралдың бәрі әрқайсысында қолжетімді:

  • Hobby — мәңгі тегін, минутына 60 сұраныс. Кілт кірген бойда, картасыз беріледі.
  • Pro — айына $29, минутына 300 сұраныс.
  • Scale — айына $199, минутына 1200 сұраныс.

Төлем поллингін бастау үшін тегін 60 сұраныс/мин 429-сыз тұрақты сұрау ұстауға жетеді. Егер осының үстіне төлемдерді өзі талдайтын ЖИ-агент құрып жатсаңыз — оның қалай ұйымдастырылғаны TON-дағы ЖИ-агенттерге арналған MCP нұсқаулығында талданған.

Тегін Hobby кілтінен бастаңыз — төлем поллингіне 60 req/min, картасыз: tonnode.io/dashboard?plan=hobby. Жүктеме өсіп, бір воркер жеткіліксіз болғанда — Pro мен Scale лимиттерін салыстырыңыз.


TON-дағы USDT-ні сенімді анықтаудың чек-парағы:

  1. Негізгі әмиянның балансын емес, алушының жетон-әмиянының тарихын get_transactions арқылы сұраңыз.
  2. Осы жетон-әмиянның мекенжайын шынайы USD₮ мастерінде (EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs) get_wallet_address (run_get_method) он-чейн шақыруынан алыңыз — сонда оған келген кез келген аударым міндетті түрде шынайы USDT болады.
  3. 0x178d4519 op-ы бар кіріс internal_transfer-лерді іздеңіз — дәл солар forward_ton_amount-қа тәуелсіз жетон-әмиянның тарихында әрқашан сақталады. transfer_notification (0x7362d09c) негізгі әмиянға және тек forward_ton_amount > 0 болғанда ғана кетеді.
  4. Төлеушінің мекенжайын from өрісінен, соманы amount-тан (raw) алыңыз.
  5. decimals-ті get_jetton_info-дан алыңыз. USDT = 6, 9 емес.
  6. Инвойсты forward_payload ішіндегі түсініктеме бойынша сәйкестендіріңіз (op 0x00000000 + UTF-8).
  7. (lt, hash) бойынша дедуп — қатаң түрде бір рет қана есепке алыңыз.
  8. Тұрақты throughput-ты ашық лимиттермен емес, өз кілтіңізбен ұстаңыз.

Агентіңізге TON-ға қолжетімділік беріңіз

16 MCP-құрал: оқу, кастодиалды емес сваптар, кроссчейн және әмияндар. Тегін тариф — 60 сұрау/мин, карта керек емес.