FIELD NOTE · ARCHITECTURE · AUTOMATION · JUN 2026

Multi-Chain Scraper: EVM, TON, SOL

Tiga blockchain, tiga API berbeda, tiga format data yang nggak nyambung. Tantangannya bukan cuma "ambil data" — tapi gimana satu sistem bisa monitor tiga chain tanpa jadi berisik, tanpa kirim alert duplikat, dan tanpa nge-burn API credit pas startup.

CHAINS: 3 MODE: Event-driven DIFF: NEW only LEVEL: Architecture
— TL;DR

Satu scraper, tiga chain (EVM + TON + SOL), modul deteksi terpisah per chain dengan state file masing-masing. Kunci biar nggak berisik: silent baseline, diff NEW only — pas pertama jalan, rekam state sekarang tanpa alert apa-apa, baru mulai alert untuk yang muncul setelahnya. Plus satu pelajaran mahal: fallback resolve username yang nggak di-cache bisa nembak ~165.000 API call cuma buat satu nama.

01

Masalah: Tiga Chain, Tiga Dunia

EVM, TON, dan SOL nggak cuma beda nama. Cara baca transaksi-nya beda, format address-nya beda, dan cara nentuin "ada yang baru" juga beda. EVM pakai block number. TON dan SOL punya kursor sendiri. Kalau dipaksa satu logika buat ketiganya, hasilnya rapuh.

Tujuannya satu: monitor source di tiga chain, dan kirim notifikasi cuma pas ada aktivitas baru. Yang gampang diomongin, susah dibikin bener, karena dua jebakan: alert duplikat (notif yang sama dikirim berkali-kali) dan alert banjir (pas pertama jalan, semua data lama keanggep "baru" dan dikirim sekaligus).

02

Arsitektur: Modul Terpisah, State Terpisah

Solusinya: satu modul deteksi per chain, masing-masing simpan state-nya sendiri. Nggak ada logika chain yang bocor ke chain lain.

# Struktur detection/ evm.py # logika EVM, pakai block number ton.py # logika TON, pakai kursor sendiri sol.py # logika SOL, pakai kursor sendiri state_evm.json # last_ids, cooldowns, paused state_sol.json state_ton.json

Tiap state file nyimpen last_ids (penanda data terakhir yang udah diproses), cooldowns (biar source yang sama nggak nge-spam), dan daftar paused (source atau user yang lagi di-mute). Karena terpisah, satu chain bisa di-pause atau di-reset tanpa ganggu yang lain.

Bot-nya sendiri event-driven murni: nggak ada background polling loop yang muter terus. Semua jalan dari command dan callback handler. Ini sengaja, biar nggak ada thread liar yang nge-burn API di latar belakang.

03

Silent Baseline, Diff NEW Only

Ini pola yang bikin scraper-nya nggak berisik. Pas pertama kali jalan (atau pas source baru ditambah), sistem nggak langsung kirim semua yang dia liat. Dia rekam dulu state sekarang sebagai baseline, diam. Baru setelah itu, yang muncul di atas baseline dianggap "baru" dan di-alert.

# Pseudo-logika tiap cycle def check(source): items = fetch_latest(source) last = state["last_ids"].get(source) if last is None: # baseline pertama: rekam, JANGAN alert state["last_ids"][source] = items[0].id return [] # alert cuma yang lebih baru dari baseline new = [i for i in items if i.id > last] if new: state["last_ids"][source] = new[0].id return new

Bedanya kerasa banget. Tanpa baseline, nambah satu source yang punya ribuan transaksi historis bakal ngirim ribuan notif sekaligus — langsung kena rate limit Telegram, dan user-nya di-spam. Dengan baseline, source baru masuk diam-diam, terus cuma lapor pas ada yang beneran baru.

04

Bug Mahal: 165K API Call buat Satu Nama

Ini bagian yang bikin gw kapok. Buat nampilin nama source yang enak dibaca (bukan cuma ID mentah), ada fungsi resolve username. Jalur normalnya satu API call: minta entity, dapet nama. Masalahnya ada di fallback-nya pas jalur normal gagal.

# Pola yang berbahaya (disederhanakan) async def resolve_username(uid): try: return await client.get_entity(uid) # 1 call except: # FALLBACK: scan semua peserta + semua pesan for scraper in scrapers.values(): for src in scraper.sources: async for p in iter_participants(src, limit=5000): ... async for m in iter_messages(src, limit=500): ...

Hitung kasarnya: 3 scraper, masing-masing sekitar 10 source. Itu 30 source. Tiap source di-scan sampai 5.000 peserta plus 500 pesan, jadi ~5.500 item per source. 30 × 5.500 = sekitar 165.000 API call. Cuma buat resolve satu username yang gagal di jalur normal.

Lebih parah: kegagalan-nya nggak di-cache. Jadi tiap kali user buka detail source yang username-nya nggak bisa di-resolve, seluruh scan 165K itu jalan lagi dari nol. Satu klik tombol = badai API call.

— FIX

Dua hal: (1) cache hasil resolve termasuk yang gagal, biar nggak diulang; (2) buang fallback brute-force, ganti jadi nampilin ID mentah kalau resolve gagal. Nama yang cantik nggak sepadan sama 165K API call. Pelajaran umumnya: tiap fallback yang nge-loop di atas "semua source × semua item" itu bom waktu — selalu kasih batas dan cache hasilnya.