#!/usr/bin/env python3 """ AirMicroDrip Holder Tracker Fetches real token holder data from Solana RPC No mocks - real HTTP API calls only """ import os import json import sqlite3 import requests import logging from typing import Dict, List, Optional from datetime import datetime, timedelta logger = logging.getLogger(__name__) SOLANA_RPC_URL = os.environ.get("SOLANA_RPC_URL", "https://api.mainnet-beta.solana.com") TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" # Configuration HOLDER_CONFIG = { "min_holding_amount": 100, # Minimum 100 tokens "min_holding_period_hours": 24, # Must hold for 24 hours "max_holders_per_distribution": 1000, # Cap per distribution "distribution_interval_hours": 6, # Distribute every 6 hours "blacklist": [], # Blacklisted addresses } class HolderTracker: """Tracks token holders using real Solana RPC data""" def __init__( self, token_mint: str, db_path: str = "holder_registry.db", ): self.token_mint = token_mint self.db_path = db_path self._init_database() def _init_database(self): """Initialize SQLite database for holder registry""" conn = sqlite3.connect(self.db_path) cursor = conn.cursor() # Create holders table cursor.execute(""" CREATE TABLE IF NOT EXISTS holders ( address TEXT PRIMARY KEY, first_seen TIMESTAMP, last_seen TIMESTAMP, current_balance INTEGER, total_received INTEGER, total_sent INTEGER, eligible BOOLEAN DEFAULT FALSE, eligibility_timestamp TIMESTAMP, drippage_received INTEGER DEFAULT 0 ) """) # Create transfers table cursor.execute(""" CREATE TABLE IF NOT EXISTS transfers ( tx_signature TEXT PRIMARY KEY, from_address TEXT, to_address TEXT, amount INTEGER, timestamp TIMESTAMP ) """) # Create distributions table cursor.execute(""" CREATE TABLE IF NOT EXISTS distributions ( distribution_id TEXT PRIMARY KEY, timestamp TIMESTAMP, total_amount INTEGER, eligible_holders INTEGER, avg_amount INTEGER ) """) conn.commit() conn.close() def fetch_top_holders_from_rpc(self, limit: int = 20) -> List[Dict]: """Fetch top token holders from Solana RPC""" try: payload = { "jsonrpc": "2.0", "id": 1, "method": "getTokenLargestAccounts", "params": [self.token_mint], } r = requests.post(SOLANA_RPC_URL, json=payload, timeout=10) if r.status_code == 200: result = r.json().get("result", {}).get("value", []) holders = [] for item in result[:limit]: holders.append({ "address": item.get("address"), "balance": int(item.get("amount", 0)), "ui_amount": item.get("uiAmount", 0), }) return holders except Exception as e: logger.warning("RPC error fetching holders: %s", e) return [] def fetch_recent_transfers_from_rpc(self, limit: int = 10) -> List[Dict]: """Fetch recent transfers for token mint via RPC""" try: payload = { "jsonrpc": "2.0", "id": 1, "method": "getSignaturesForAddress", "params": [self.token_mint, {"limit": limit}], } r = requests.post(SOLANA_RPC_URL, json=payload, timeout=10) if r.status_code == 200: sigs = r.json().get("result", []) transfers = [] for sig_info in sigs: sig = sig_info.get("signature") if not sig: continue # Fetch parsed transaction tx_payload = { "jsonrpc": "2.0", "id": 1, "method": "getTransaction", "params": [sig, {"encoding": "jsonParsed", "maxSupportedTransactionVersion": 0}], } tx_r = requests.post(SOLANA_RPC_URL, json=tx_payload, timeout=10) if tx_r.status_code == 200: tx = tx_r.json().get("result", {}) meta = tx.get("meta", {}) pre_balances = meta.get("preTokenBalances", []) post_balances = meta.get("postTokenBalances", []) if pre_balances and post_balances: transfers.append({ "signature": sig, "slot": tx.get("slot"), "pre_balances": pre_balances, "post_balances": post_balances, }) return transfers except Exception as e: logger.warning("RPC error fetching transfers: %s", e) return [] def sync_holders_from_chain(self): """Sync holder data from real Solana RPC into SQLite""" holders = self.fetch_top_holders_from_rpc() current_time = datetime.utcnow().isoformat() conn = sqlite3.connect(self.db_path) cursor = conn.cursor() for h in holders: addr = h["address"] balance = h["balance"] cursor.execute("SELECT address FROM holders WHERE address = ?", (addr,)) if cursor.fetchone(): cursor.execute( "UPDATE holders SET current_balance = ?, last_seen = ? WHERE address = ?", (balance, current_time, addr) ) else: cursor.execute(""" INSERT INTO holders (address, first_seen, last_seen, current_balance, total_received, total_sent) VALUES (?, ?, ?, ?, ?, ?) """, (addr, current_time, current_time, balance, balance, 0)) print(f"New holder synced from chain: {addr}") conn.commit() conn.close() return len(holders) def _update_holder(self, address: str, amount_change: int): """Update holder balance""" conn = sqlite3.connect(self.db_path) cursor = conn.cursor() current_time = datetime.utcnow().isoformat() # Check if holder exists cursor.execute("SELECT current_balance FROM holders WHERE address = ?", (address,)) result = cursor.fetchone() if result: # Update existing holder new_balance = result[0] + amount_change cursor.execute(""" UPDATE holders SET current_balance = ?, last_seen = ? WHERE address = ? """, (new_balance, current_time, address)) # Update totals if amount_change > 0: cursor.execute(""" UPDATE holders SET total_received = total_received + ? WHERE address = ? """, (amount_change, address)) else: cursor.execute(""" UPDATE holders SET total_sent = total_sent + ? WHERE address = ? """, (-amount_change, address)) else: # Create new holder cursor.execute(""" INSERT INTO holders (address, first_seen, last_seen, current_balance, total_received, total_sent) VALUES (?, ?, ?, ?, ?, ?) """, (address, current_time, current_time, amount_change, max(0, amount_change), max(0, -amount_change))) conn.commit() conn.close() def _is_new_holder(self, address: str) -> bool: """Check if address is a new holder""" conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute("SELECT first_seen FROM holders WHERE address = ?", (address,)) result = cursor.fetchone() conn.close() return result is None def _register_new_holder(self, address: str, amount: int): """Register new holder""" conn = sqlite3.connect(self.db_path) cursor = conn.cursor() current_time = datetime.utcnow().isoformat() cursor.execute(""" UPDATE holders SET first_seen = ?, last_seen = ? WHERE address = ? """, (current_time, current_time, address)) conn.commit() conn.close() def _log_transfer(self, signature: str, from_addr: str, to_addr: str, amount: int): """Log transfer to database""" conn = sqlite3.connect(self.db_path) cursor = conn.cursor() current_time = datetime.utcnow().isoformat() cursor.execute(""" INSERT OR IGNORE INTO transfers (tx_signature, from_address, to_address, amount, timestamp) VALUES (?, ?, ?, ?, ?) """, (signature, from_addr, to_addr, amount, current_time)) conn.commit() conn.close() def check_eligibility(self): """Check which holders are eligible for drippage""" conn = sqlite3.connect(self.db_path) cursor = conn.cursor() current_time = datetime.utcnow() min_time = current_time - timedelta(hours=HOLDER_CONFIG["min_holding_period_hours"]) # Get holders who meet criteria cursor.execute(""" SELECT address, current_balance, first_seen FROM holders WHERE current_balance >= ? AND first_seen <= ? AND address NOT IN (SELECT address FROM blacklist) ORDER BY current_balance DESC LIMIT ? """, ( HOLDER_CONFIG["min_holding_amount"], min_time.isoformat(), HOLDER_CONFIG["max_holders_per_distribution"], )) holders = cursor.fetchall() # Update eligibility for address, balance, first_seen in holders: cursor.execute(""" UPDATE holders SET eligible = TRUE, eligibility_timestamp = ? WHERE address = ? """, (current_time.isoformat(), address)) conn.commit() conn.close() return [ { "address": h[0], "balance": h[1], "first_seen": h[2], } for h in holders ] def get_eligible_holders(self) -> List[Dict]: """Get all currently eligible holders""" conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute(""" SELECT address, current_balance, first_seen, eligibility_timestamp FROM holders WHERE eligible = TRUE ORDER BY current_balance DESC """) holders = cursor.fetchall() conn.close() return [ { "address": h[0], "balance": h[1], "first_seen": h[2], "holding_hours": (datetime.utcnow() - datetime.fromisoformat(h[2])).total_seconds() / 3600, } for h in holders ] def get_holder_stats(self) -> Dict: """Get holder statistics""" conn = sqlite3.connect(self.db_path) cursor = conn.cursor() # Total holders cursor.execute("SELECT COUNT(*) FROM holders") total_holders = cursor.fetchone()[0] # Eligible holders cursor.execute("SELECT COUNT(*) FROM holders WHERE eligible = TRUE") eligible_holders = cursor.fetchone()[0] # Total balance cursor.execute("SELECT SUM(current_balance) FROM holders") total_balance = cursor.fetchone()[0] or 0 # New holders today today = datetime.utcnow().date() cursor.execute(""" SELECT COUNT(*) FROM holders WHERE DATE(first_seen) = ? """, (today.isoformat(),)) new_holders_today = cursor.fetchone()[0] conn.close() return { "total_holders": total_holders, "eligible_holders": eligible_holders, "total_balance": total_balance, "new_holders_today": new_holders_today, "eligibility_rate": eligible_holders / total_holders if total_holders > 0 else 0, } def add_to_blacklist(self, address: str): """Add address to blacklist""" if address not in HOLDER_CONFIG["blacklist"]: HOLDER_CONFIG["blacklist"].append(address) print(f"Added {address} to blacklist") def remove_from_blacklist(self, address: str): """Remove address from blacklist""" if address in HOLDER_CONFIG["blacklist"]: HOLDER_CONFIG["blacklist"].remove(address) print(f"Removed {address} from blacklist") def start_holder_sync(token_mint: str): """Sync holders from chain and print stats""" tracker = HolderTracker(token_mint) # Sync from chain count = tracker.sync_holders_from_chain() print(f"Synced {count} holders from Solana RPC") # Check eligibility eligible = tracker.check_eligibility() print(f"Eligible holders: {len(eligible)}") # Print stats stats = tracker.get_holder_stats() print("\n" + "="*50) print("Holder Statistics") print("="*50) print(f"Total Holders: {stats['total_holders']}") print(f"Eligible Holders: {stats['eligible_holders']}") print(f"Total Balance: {stats['total_balance']:,}") print(f"New Holders Today: {stats['new_holders_today']}") print(f"Eligibility Rate: {stats['eligibility_rate']:.2%}") return stats if __name__ == "__main__": import sys if len(sys.argv) < 2: print("Usage: python holder_tracker.py ") print("Example: python holder_tracker.py EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v") sys.exit(1) token_mint = sys.argv[1] start_holder_sync(token_mint)