#!/usr/bin/env python3 """ BTC Blockchain Monitor — polls blockstream.info for real transactions. Stores all TXs in SQLite. Detects new deposits. Feeds the dashboard. """ import requests import sqlite3 import time import json from datetime import datetime BTC_ADDRESS = "35vTSjPCaqudcL5cj5ChpNDmqrpPFmEnss" DB_PATH = "/Users/drjones/ccsite-manager/btc_chain.db" BLOCKSTREAM_URL = f"https://blockstream.info/api/address/{BTC_ADDRESS}/txs" def init_db(): conn = sqlite3.connect(DB_PATH) c = conn.cursor() c.execute('''CREATE TABLE IF NOT EXISTS transactions ( txid TEXT PRIMARY KEY, timestamp INTEGER, amount_btc REAL, amount_sats INTEGER, confirmations INTEGER, fee INTEGER, raw_json TEXT, first_seen REAL, alerted INTEGER DEFAULT 0 )''') c.execute('CREATE INDEX IF NOT EXISTS idx_tx_time ON transactions(timestamp)') conn.commit() conn.close() def check_blockchain(): """Poll Blockstream API for transactions. Returns list of new TXs.""" try: r = requests.get(BLOCKSTREAM_URL, timeout=15) if r.status_code != 200: return [] txs = r.json() except Exception as e: print(f"Blockstream API error: {e}") return [] conn = sqlite3.connect(DB_PATH) c = conn.cursor() new_txs = [] for tx in txs: txid = tx['txid'] # Check if we've already seen this TX c.execute("SELECT txid FROM transactions WHERE txid = ?", (txid,)) if c.fetchone(): continue # Calculate BTC amount sent TO our address (only count vouts where our address receives) total_out = 0 for vout in tx.get('vout', []): addr = vout.get('scriptpubkey_address', '') if addr == BTC_ADDRESS: total_out += vout.get('value', 0) if total_out == 0: continue # Skip transactions where our address isn't receiving # Get fee and confirmations fee = tx.get('fee', 0) confs = tx.get('status', {}).get('confirmed', False) conf_count = tx.get('status', {}).get('block_height', 0) ts = tx.get('status', {}).get('block_time', int(time.time())) c.execute('''INSERT INTO transactions (txid, timestamp, amount_btc, amount_sats, confirmations, fee, raw_json, first_seen) VALUES (?, ?, ?, ?, ?, ?, ?, ?)''', (txid, ts, total_out, int(total_out * 100000000), conf_count if confs else 0, fee, json.dumps(tx), time.time())) new_txs.append({ 'txid': txid, 'amount_btc': total_out, 'amount_usd': round(total_out * 68000, 2), # Approximate BTC price 'confirmations': conf_count if confs else 0, 'time': ts, }) conn.commit() conn.close() return new_txs def auto_complete_orders(new_txs): """When new TXs arrive, try to match them to pending orders on all 10 sites.""" SITE_IPS = [f"10.30.20.{ip}" for ip in ['210','211','212','213','214','217','218','219','220','221']] for tx in new_txs: btc_amount = tx['amount_btc'] if btc_amount <= 0: continue # Try each site for ip in SITE_IPS: try: r = requests.get(f"http://{ip}:5000/api/products", timeout=5) if r.status_code != 200: continue # Check for pending orders via health endpoint # We need to tell the site "a payment of X BTC arrived, check pending orders" r2 = requests.post(f"http://{ip}:5000/api/btc-check", json={"amount_btc": btc_amount, "txid": tx['txid']}, timeout=5) if r2.status_code == 200: result = r2.json() if result.get('matched'): print(f" ✅ Auto-completed order on {ip}: {result.get('order_code','?')}") except: pass def get_stats(): """Get aggregate stats from stored transactions.""" conn = sqlite3.connect(DB_PATH) c = conn.cursor() c.execute("SELECT COUNT(*), COALESCE(SUM(amount_btc),0), COALESCE(SUM(amount_sats),0) FROM transactions") count, total_btc, total_sats = c.fetchone() c.execute("SELECT COUNT(*), COALESCE(SUM(amount_btc),0) FROM transactions WHERE first_seen > ?", (time.time() - 86400,)) day_count, day_btc = c.fetchone() c.execute("SELECT txid, amount_btc, timestamp FROM transactions ORDER BY timestamp DESC LIMIT 10") recent = [{'txid': r[0][:16]+'...', 'btc': r[1], 'ts': r[2]} for r in c.fetchall()] conn.close() return { 'total_txs': count, 'total_btc': round(total_btc, 8), 'total_sats': total_sats, 'total_usd': round(total_btc * 68000, 2), 'day_txs': day_count, 'day_btc': round(day_btc, 8), 'day_usd': round(day_btc * 68000, 2), 'recent': recent, 'address': BTC_ADDRESS, } def monitor_loop(callback=None): """Run continuously, calling callback with new TXs.""" init_db() print(f"₿ BTC Monitor started — watching {BTC_ADDRESS[:16]}...") print(f" Polling blockstream.info every 60s") while True: try: new = check_blockchain() if new: for tx in new: print(f" 💰 NEW TX: {tx['amount_btc']:.8f} BTC (${tx['amount_usd']:.2f}) — {tx['txid'][:16]}...") if callback: callback(new) else: # Silent tick — no new TXs pass except Exception as e: print(f"Monitor error: {e}") time.sleep(60) if __name__ == '__main__': import sys if len(sys.argv) > 1 and sys.argv[1] == '--once': new = check_blockchain() stats = get_stats() print(json.dumps({'new_txs': len(new), 'stats': stats}, indent=2)) elif len(sys.argv) > 1 and sys.argv[1] == '--stats': stats = get_stats() print(json.dumps(stats, indent=2)) else: monitor_loop()