| """ |
| Hugging Face Space β Solana Key Generator & Miner |
| Simple, reliable implementation optimized for cloud deployment |
| """ |
| import os |
| import queue |
| import threading |
| import time |
| import base58 |
| import json |
| import requests |
| import collections |
| import hashlib |
| import secrets |
| from typing import List, Dict, Optional, Tuple |
| from datetime import datetime |
|
|
| import gradio as gr |
| from solana.rpc.api import Client |
| from solana.rpc.core import RPCException |
| from solders.keypair import Keypair |
| from solders.pubkey import Pubkey |
|
|
| |
| def _safe_keypair() -> Keypair: |
| """Generate a valid keypair using the standard method.""" |
| |
| return Keypair() |
|
|
| |
| import logging |
| logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s") |
| log = logging.getLogger("solana_miner") |
|
|
| |
| class Config: |
| DISCORD_WEBHOOK = os.getenv("DISCORD_WEBHOOK", "") |
| MINER_ID = os.getenv("MINER_ID", f"Miner-{secrets.token_hex(4)}") |
| MINER_NAME = os.getenv("MINER_NAME", f"SolMiner-{secrets.token_hex(3)}") |
| LOCATION = os.getenv("MINER_LOCATION", "Unknown") |
|
|
| RPC_URLS = [ |
| "https://api.mainnet-beta.solana.com", |
| "https://solana-api.projectserum.com", |
| "https://rpc.ankr.com/solana", |
| "https://solana.public-rpc.com" |
| ] |
| current_rpc_index = 0 |
| MIN_SOL = float(os.getenv("MIN_SOL", "0.00001")) |
| RPC_BATCH_SIZE = int(os.getenv("RPC_BATCH_SIZE", "100")) |
| MINER_SLEEP = float(os.getenv("MINER_SLEEP", "1.0")) |
| MAX_RETRIES = 2 |
| FEED_MAX_ITEMS = 50 |
|
|
| |
| DISCORD_RETRY_LIMIT = 3 |
| DISCORD_TIMEOUT = 5 |
| DISCORD_COOLDOWN = 300 |
|
|
| @classmethod |
| def get_current_rpc(cls): |
| return cls.RPC_URLS[cls.current_rpc_index] |
|
|
| @classmethod |
| def rotate_rpc(cls): |
| cls.current_rpc_index = (cls.current_rpc_index + 1) % len(cls.RPC_URLS) |
| log.info(f"Switched to RPC: {cls.get_current_rpc()}") |
|
|
| config = Config() |
|
|
| |
| STATUS_MESSAGE_ID = None |
| STATUS_EDIT_URL = None |
| DISCORD_DISABLED = False |
| DISCORD_RETRY_COUNT = 0 |
| DISCORD_LAST_ERROR = None |
| DISCORD_LAST_SUCCESS = time.time() |
| MINER_START_TIME = datetime.now() |
| DISCORD_QUEUE = queue.Queue(maxsize=1000) |
|
|
| |
| class MinerState: |
| def __init__(self): |
| self.total_mined = 0 |
| self.funded_found = 0 |
| self.errors_count = 0 |
| self.last_funded_time = None |
| self.run_flag = threading.Event() |
| self.upload_queue = queue.Queue() |
| self.lock = threading.Lock() |
|
|
| |
| self.start_time = datetime.now() |
| self.last_batch_time = None |
| self.current_rpc = None |
| self.rpc_switches = 0 |
| self.total_batches = 0 |
| self.average_batch_time = 0.0 |
| self.best_streak = 0 |
| self.current_streak = 0 |
| self.wallets_per_second = 0.0 |
| self.connection_status = "Initializing" |
| self.last_activity = time.time() |
|
|
| |
| self.batch_times = collections.deque(maxlen=100) |
| self.rpc_performance = {} |
| |
| |
| self.cycle_start_time = time.time() |
| self.cycle_active = True |
| self.cycle_break_start = None |
| self.total_cycles = 0 |
| self.in_break_mode = False |
|
|
| def increment_mined(self): |
| with self.lock: |
| self.total_mined += 1 |
|
|
| def increment_errors(self): |
| with self.lock: |
| self.errors_count += 1 |
|
|
| def update_batch_stats(self, batch_size: int, batch_time: float, rpc_url: str): |
| with self.lock: |
| self.last_batch_time = datetime.now() |
| self.total_batches += 1 |
| self.batch_times.append(batch_time) |
| self.current_rpc = rpc_url |
| self.last_activity = time.time() |
|
|
| |
| if self.batch_times: |
| self.average_batch_time = sum(self.batch_times) / len(self.batch_times) |
| total_time = (datetime.now() - self.start_time).total_seconds() |
| if total_time > 0: |
| self.wallets_per_second = self.total_mined / total_time |
|
|
| |
| if rpc_url not in self.rpc_performance: |
| self.rpc_performance[rpc_url] = [] |
| self.rpc_performance[rpc_url].append(batch_time) |
| if len(self.rpc_performance[rpc_url]) > 50: |
| self.rpc_performance[rpc_url].pop(0) |
|
|
| def increment_funded(self): |
| with self.lock: |
| self.funded_found += 1 |
| self.last_funded_time = datetime.now() |
| self.current_streak += 1 |
| if self.current_streak > self.best_streak: |
| self.best_streak = self.current_streak |
|
|
| def reset_streak(self): |
| with self.lock: |
| self.current_streak = 0 |
|
|
| def get_comprehensive_stats(self) -> Dict: |
| with self.lock: |
| uptime = datetime.now() - self.start_time |
| return { |
| |
| 'total_mined': self.total_mined, |
| 'funded_found': self.funded_found, |
| 'errors_count': self.errors_count, |
| 'last_funded': self.last_funded_time, |
| 'queue_size': self.upload_queue.qsize(), |
| 'is_running': self.run_flag.is_set(), |
|
|
| |
| 'miner_id': config.MINER_ID, |
| 'miner_name': config.MINER_NAME, |
| 'location': config.LOCATION, |
| 'start_time': self.start_time, |
| 'uptime_seconds': uptime.total_seconds(), |
| 'uptime_str': str(uptime).split('.')[0], |
| 'current_rpc': self.current_rpc or "None", |
| 'rpc_switches': self.rpc_switches, |
| 'total_batches': self.total_batches, |
| 'average_batch_time': round(self.average_batch_time, 2), |
| 'wallets_per_second': round(self.wallets_per_second, 2), |
| 'best_streak': self.best_streak, |
| 'current_streak': self.current_streak, |
| 'connection_status': self.connection_status, |
| 'last_activity': self.last_activity, |
|
|
| |
| 'cycle_active': self.cycle_active, |
| 'in_break_mode': self.in_break_mode, |
| 'total_cycles': self.total_cycles, |
| 'cycle_time_remaining': self.get_cycle_time_remaining(), |
|
|
| |
| 'batch_times_count': len(self.batch_times), |
| 'rpc_performance': {rpc: round(sum(times)/len(times), 2) |
| for rpc, times in self.rpc_performance.items() if times} |
| } |
|
|
| def get_cycle_time_remaining(self) -> Dict: |
| """Calculate remaining time in current cycle (30 min active / 30 min break)""" |
| current_time = time.time() |
| cycle_duration = 30 * 60 |
| |
| if self.in_break_mode: |
| |
| if self.cycle_break_start: |
| elapsed_break = current_time - self.cycle_break_start |
| remaining_break = max(0, cycle_duration - elapsed_break) |
| return { |
| 'mode': 'break', |
| 'remaining_seconds': int(remaining_break), |
| 'remaining_minutes': int(remaining_break / 60), |
| 'progress_percent': min(100, (elapsed_break / cycle_duration) * 100) |
| } |
| else: |
| |
| elapsed_mining = current_time - self.cycle_start_time |
| remaining_mining = max(0, cycle_duration - elapsed_mining) |
| return { |
| 'mode': 'mining', |
| 'remaining_seconds': int(remaining_mining), |
| 'remaining_minutes': int(remaining_mining / 60), |
| 'progress_percent': min(100, (elapsed_mining / cycle_duration) * 100) |
| } |
| |
| return {'mode': 'unknown', 'remaining_seconds': 0, 'remaining_minutes': 0, 'progress_percent': 0} |
|
|
| def check_cycle_status(self) -> bool: |
| """Check if we should switch between mining and break mode. Returns True if should continue mining.""" |
| current_time = time.time() |
| cycle_duration = 30 * 60 |
| |
| if self.in_break_mode: |
| |
| if self.cycle_break_start and (current_time - self.cycle_break_start) >= cycle_duration: |
| |
| self.in_break_mode = False |
| self.cycle_active = True |
| self.cycle_start_time = current_time |
| self.cycle_break_start = None |
| log.info("π’ 30-minute break completed, resuming mining...") |
| return True |
| return False |
| else: |
| |
| if (current_time - self.cycle_start_time) >= cycle_duration: |
| |
| self.in_break_mode = True |
| self.cycle_active = False |
| self.cycle_break_start = current_time |
| self.total_cycles += 1 |
| log.info(f"π΄ 30-minute mining cycle #{self.total_cycles} completed, starting 30-minute break...") |
| return False |
| return True |
|
|
| def get_stats(self) -> Dict: |
| |
| return self.get_comprehensive_stats() |
|
|
| miner_state = MinerState() |
|
|
| |
| class LiveFeed: |
| def __init__(self, max_items: int = 100): |
| self.feed = collections.deque(maxlen=max_items) |
| self.lock = threading.Lock() |
|
|
| def add_entry(self, secret: str, sol: float, address: str = ""): |
| with self.lock: |
| timestamp = datetime.now().strftime('%H:%M:%S') |
| if sol >= config.MIN_SOL: |
| entry = f"π’ **FUNDED** {sol:.6f} SOL | `{secret[:20]}...` | {address[:20]}... | {timestamp}" |
| log.info(f"π° FUNDED WALLET: {sol:.6f} SOL") |
| else: |
| entry = f"π΄ Empty | `{secret[:20]}...` | {timestamp}" |
| self.feed.append(entry) |
|
|
| def get_feed(self) -> str: |
| with self.lock: |
| return "\n".join(self.feed) |
|
|
| def clear_feed(self): |
| with self.lock: |
| self.feed.clear() |
|
|
| live_feed = LiveFeed(config.FEED_MAX_ITEMS) |
|
|
| |
| BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" |
|
|
| def generate_secure_keypair() -> Tuple[str, str, Keypair]: |
| """Generate a secure keypair with Base58 encoding""" |
| try: |
| keypair = _safe_keypair() |
| seed = bytes(keypair)[:32] |
| private_key_b58 = base58.b58encode(seed).decode('utf-8') |
| public_key = str(keypair.pubkey()) |
| return private_key_b58, public_key, keypair |
| except Exception as e: |
| log.error(f"Keypair generation failed: {e}") |
| raise Exception(f"Failed to generate keypair: {e}") |
|
|
| def get_key_info(private_key: str) -> Dict: |
| try: |
| decoded = base58.b58decode(private_key) |
| keypair = _safe_keypair() |
| return { |
| "private_key": private_key, |
| "public_key": str(keypair.pubkey()), |
| "key_length": len(private_key), |
| "is_valid": True, |
| "private_key_hex": decoded.hex(), |
| "public_key_hex": bytes(keypair.pubkey()).hex(), |
| } |
| except Exception as e: |
| return {"private_key": private_key, "error": str(e), "is_valid": False} |
|
|
| |
| def create_solana_client() -> Client: |
| """Create client with simple RPC rotation""" |
| for attempt in range(len(config.RPC_URLS)): |
| rpc_url = config.get_current_rpc() |
| try: |
| client = Client(rpc_url.strip()) |
| client.get_slot() |
| log.info(f"β
Connected to Solana RPC: {rpc_url}") |
| return client |
| except Exception as e: |
| log.warning(f"β RPC {rpc_url} failed: {e}") |
| config.rotate_rpc() |
| time.sleep(2) |
|
|
| raise Exception("All RPC endpoints failed") |
|
|
| def get_balances_with_retry(client: Client, addresses: List[str]) -> List[float]: |
| """Simple balance checking with retry logic""" |
| chunk_size = min(50, len(addresses)) |
|
|
| for attempt in range(config.MAX_RETRIES): |
| try: |
| all_balances = [] |
|
|
| for i in range(0, len(addresses), chunk_size): |
| chunk = addresses[i:i + chunk_size] |
| pubkeys = [Pubkey.from_string(addr) for addr in chunk] |
|
|
| time.sleep(0.5) |
| resp = client.get_multiple_accounts(pubkeys) |
|
|
| chunk_balances = [] |
| for acc in resp.value: |
| chunk_balances.append((acc.lamports / 1e9) if acc else 0.0) |
| all_balances.extend(chunk_balances) |
|
|
| |
| if i + chunk_size < len(addresses): |
| time.sleep(0.2) |
|
|
| return all_balances |
|
|
| except Exception as e: |
| log.warning(f"Balance check attempt {attempt + 1} failed: {e}") |
| miner_state.increment_errors() |
| config.rotate_rpc() |
| time.sleep(2) |
|
|
| return [0.0] * len(addresses) |
|
|
| |
| def queue_discord_message(message_type: str, data: dict): |
| """Queue Discord message for isolated processing""" |
| if not config.DISCORD_WEBHOOK: |
| return |
|
|
| try: |
| DISCORD_QUEUE.put_nowait({ |
| 'type': message_type, |
| 'data': data, |
| 'timestamp': time.time() |
| }) |
| except queue.Full: |
| |
| try: |
| DISCORD_QUEUE.get_nowait() |
| DISCORD_QUEUE.put_nowait({ |
| 'type': message_type, |
| 'data': data, |
| 'timestamp': time.time() |
| }) |
| except: |
| pass |
|
|
| def send_funded_ping(private_key: str, public_key: str, balance: float): |
| """Queue funded wallet notification - never blocks mining""" |
| stats = miner_state.get_comprehensive_stats() |
| queue_discord_message('funded_wallet', { |
| 'private_key': private_key, |
| 'public_key': public_key, |
| 'balance': balance, |
| 'miner_stats': stats, |
| 'discovery_time': datetime.now().isoformat() |
| }) |
|
|
| def process_funded_wallet_message(data: dict): |
| """Process funded wallet Discord message""" |
| stats = data['miner_stats'] |
| payload = { |
| "content": f"@everyone π¨ **{stats['miner_name']}** found FUNDED wallet!", |
| "embeds": [{ |
| "title": f"π° FUNDED WALLET DISCOVERED - {stats['miner_name']}", |
| "color": 0x00ff00, |
| "thumbnail": {"url": "https://cryptologos.cc/logos/solana-sol-logo.png"}, |
| "fields": [ |
| {"name": "πΌ Wallet Details", "value": |
| f"**Balance:** {data['balance']:.8f} SOL\n" |
| f"**Public Key:** `{data['public_key']}`\n" |
| f"**Private Key:** ||{data['private_key']}||", "inline": False}, |
|
|
| {"name": "π Miner Performance", "value": |
| f"**Total Mined:** {stats['total_mined']:,} wallets\n" |
| f"**Total Funded:** {stats['funded_found']} wallets\n" |
| f"**Success Rate:** {(stats['funded_found']/max(stats['total_mined'],1)*100):.6f}%", "inline": True}, |
|
|
| {"name": "β‘ Current Stats", "value": |
| f"**Speed:** {stats['wallets_per_second']:.1f} wallets/sec\n" |
| f"**Uptime:** {stats['uptime_str']}\n" |
| f"**Current Streak:** {stats['current_streak']}", "inline": True}, |
|
|
| {"name": "π Miner Info", "value": |
| f"**ID:** {stats['miner_id']}\n" |
| f"**Location:** {stats['location']}\n" |
| f"**RPC:** {stats['current_rpc'].split('//')[-1][:25]}", "inline": True}, |
|
|
| {"name": "π Network Stats", "value": |
| f"**Batches Processed:** {stats['total_batches']}\n" |
| f"**Avg Batch Time:** {stats['average_batch_time']}s\n" |
| f"**RPC Switches:** {stats['rpc_switches']}", "inline": True}, |
|
|
| {"name": "π Discovery Time", "value": f"<t:{int(time.time())}:F>", "inline": False} |
| ], |
| "footer": {"text": f"Miner: {stats['miner_name']} | Best Streak: {stats['best_streak']}"} |
| }] |
| } |
|
|
| return requests.post(config.DISCORD_WEBHOOK, data=json.dumps(payload), |
| headers={"Content-Type": "application/json"}, |
| timeout=config.DISCORD_TIMEOUT) |
|
|
| def queue_status_update(): |
| """Queue status update - never blocks mining""" |
| stats = miner_state.get_comprehensive_stats() |
| queue_discord_message('status_update', { |
| 'stats': stats, |
| 'timestamp': time.time() |
| }) |
|
|
| def process_status_update(data: dict): |
| """Process comprehensive status update""" |
| global STATUS_MESSAGE_ID, STATUS_EDIT_URL |
| stats = data['stats'] |
|
|
| |
| embed = { |
| "title": f"πͺ {stats['miner_name']} - Live Mining Status", |
| "color": 0x0099ff if stats['is_running'] else 0xff4444, |
| "thumbnail": {"url": "https://cryptologos.cc/logos/solana-sol-logo.png"}, |
| "fields": [ |
| {"name": "π Core Statistics", "value": |
| f"**Status:** {'π’ MINING' if stats['is_running'] else 'π΄ STOPPED'}\n" |
| f"**Total Mined:** {stats['total_mined']:,} wallets\n" |
| f"**Funded Found:** {stats['funded_found']} wallets\n" |
| f"**Success Rate:** {(stats['funded_found']/max(stats['total_mined'],1)*100):.6f}%", "inline": True}, |
|
|
| {"name": "β‘ Performance Metrics", "value": |
| f"**Speed:** {stats['wallets_per_second']:.2f} wallets/sec\n" |
| f"**Uptime:** {stats['uptime_str']}\n" |
| f"**Batches:** {stats['total_batches']}\n" |
| f"**Avg Batch Time:** {stats['average_batch_time']}s", "inline": True}, |
|
|
| {"name": "π Streak & Records", "value": |
| f"**Current Streak:** {stats['current_streak']}\n" |
| f"**Best Streak:** {stats['best_streak']}\n" |
| f"**Errors:** {stats['errors_count']}\n" |
| f"**Queue Size:** {stats['queue_size']}", "inline": True}, |
|
|
| {"name": "π Network & Infrastructure", "value": |
| f"**Current RPC:** {stats['current_rpc'].split('//')[-1][:30] if stats['current_rpc'] else 'None'}\n" |
| f"**RPC Switches:** {stats['rpc_switches']}\n" |
| f"**Connection:** {stats['connection_status']}\n" |
| f"**Location:** {stats['location']}", "inline": False}, |
|
|
| {"name": "π°οΈ Timing Information", "value": |
| f"**Started:** <t:{int(stats['start_time'].timestamp())}:R>\n" |
| f"**Last Activity:** <t:{int(stats['last_activity'])}:R>\n" |
| f"**Last Funded:** {'<t:' + str(int(stats['last_funded'].timestamp())) + ':R>' if stats['last_funded'] else 'Never'}\n" |
| f"**Update:** <t:{int(time.time())}:f>", "inline": False} |
| ], |
| "footer": { |
| "text": f"Miner ID: {stats['miner_id']} | Batch #{stats['total_batches']}", |
| "icon_url": "https://cryptologos.cc/logos/solana-sol-logo.png" |
| }, |
| "timestamp": datetime.utcnow().isoformat() |
| } |
|
|
| |
| if stats['rpc_performance']: |
| rpc_perf = "\n".join([f"**{rpc.split('//')[-1][:20]}:** {avg_time}s" |
| for rpc, avg_time in stats['rpc_performance'].items()]) |
| embed["fields"].append({ |
| "name": "π RPC Performance", |
| "value": rpc_perf[:1000], |
| "inline": False |
| }) |
|
|
| |
| if STATUS_MESSAGE_ID is None: |
| payload = {"embeds": [embed]} |
| r = requests.post(config.DISCORD_WEBHOOK + "?wait=true", |
| data=json.dumps(payload), |
| headers={"Content-Type": "application/json"}, |
| timeout=config.DISCORD_TIMEOUT) |
| if r.status_code == 200: |
| data = r.json() |
| STATUS_MESSAGE_ID = data['id'] |
| STATUS_EDIT_URL = f"{config.DISCORD_WEBHOOK}/messages/{STATUS_MESSAGE_ID}" |
| return r |
|
|
| if STATUS_EDIT_URL: |
| return requests.patch(STATUS_EDIT_URL, data=json.dumps({"embeds": [embed]}), |
| headers={"Content-Type": "application/json"}, |
| timeout=config.DISCORD_TIMEOUT) |
| return None |
|
|
| def isolated_discord_worker(): |
| """Completely isolated Discord worker - never affects mining""" |
| global DISCORD_DISABLED, DISCORD_RETRY_COUNT, DISCORD_LAST_ERROR, DISCORD_LAST_SUCCESS |
|
|
| log.info(f"π Discord worker started for miner: {config.MINER_NAME}") |
|
|
| while True: |
| try: |
| |
| try: |
| message = DISCORD_QUEUE.get(timeout=1) |
|
|
| |
| if time.time() - message['timestamp'] > 300: |
| continue |
|
|
| |
| response = None |
| if message['type'] == 'funded_wallet': |
| response = process_funded_wallet_message(message['data']) |
| elif message['type'] == 'status_update': |
| response = process_status_update(message['data']) |
| elif message['type'] == 'miner_startup': |
| response = process_miner_startup(message['data']) |
|
|
| |
| if response and response.status_code in [200, 204]: |
| DISCORD_RETRY_COUNT = 0 |
| DISCORD_LAST_SUCCESS = time.time() |
| DISCORD_DISABLED = False |
| log.info(f"π¬ Discord message sent: {message['type']}") |
| elif response: |
| raise Exception(f"Discord API returned {response.status_code}: {response.text[:100]}") |
|
|
| except queue.Empty: |
| |
| pass |
| except requests.exceptions.RequestException as e: |
| |
| DISCORD_RETRY_COUNT += 1 |
| DISCORD_LAST_ERROR = f"Network error: {str(e)[:100]}" |
|
|
| if DISCORD_RETRY_COUNT >= config.DISCORD_RETRY_LIMIT: |
| if not DISCORD_DISABLED: |
| log.info(f"π Discord temporarily disabled after {DISCORD_RETRY_COUNT} failures. Will retry in {config.DISCORD_COOLDOWN}s") |
| DISCORD_DISABLED = True |
| time.sleep(config.DISCORD_COOLDOWN) |
| DISCORD_RETRY_COUNT = 0 |
| else: |
| time.sleep(min(10, DISCORD_RETRY_COUNT * 2)) |
|
|
| except Exception as e: |
| |
| DISCORD_LAST_ERROR = f"Processing error: {str(e)[:100]}" |
| log.warning(f"β οΈ Discord processing error: {e}") |
| time.sleep(5) |
|
|
| |
| if not DISCORD_DISABLED and time.time() - DISCORD_LAST_SUCCESS > 120: |
| queue_status_update() |
|
|
| except Exception as e: |
| |
| log.error(f"π¨ Critical Discord worker error: {e}") |
| time.sleep(30) |
|
|
| time.sleep(0.1) |
|
|
| def process_miner_startup(data: dict): |
| """Process miner startup notification""" |
| stats = data['stats'] |
| payload = { |
| "content": f"π **{stats['miner_name']}** has started mining!", |
| "embeds": [{ |
| "title": f"π New Miner Online - {stats['miner_name']}", |
| "color": 0x00ff88, |
| "fields": [ |
| {"name": "π Miner Information", "value": |
| f"**Name:** {stats['miner_name']}\n" |
| f"**ID:** {stats['miner_id']}\n" |
| f"**Location:** {stats['location']}\n" |
| f"**Started:** <t:{int(time.time())}:F>", "inline": True}, |
|
|
| {"name": "βοΈ Configuration", "value": |
| f"**Min SOL:** {config.MIN_SOL}\n" |
| f"**Batch Size:** {config.RPC_BATCH_SIZE}\n" |
| f"**Sleep Time:** {config.MINER_SLEEP}s\n" |
| f"**RPC Endpoints:** {len(config.RPC_URLS)}", "inline": True} |
| ], |
| "footer": {"text": f"Miner ready to start discovering funded wallets!"} |
| }] |
| } |
|
|
| return requests.post(config.DISCORD_WEBHOOK, data=json.dumps(payload), |
| headers={"Content-Type": "application/json"}, |
| timeout=config.DISCORD_TIMEOUT) |
|
|
| |
| threading.Thread(target=isolated_discord_worker, daemon=True).start() |
|
|
| |
| if config.DISCORD_WEBHOOK: |
| queue_discord_message('miner_startup', { |
| 'stats': { |
| 'miner_name': config.MINER_NAME, |
| 'miner_id': config.MINER_ID, |
| 'location': config.LOCATION |
| } |
| }) |
|
|
| |
|
|
| def upload_daemon(): |
| """Background uploader for funded wallets - integrated with new Discord system""" |
| while True: |
| try: |
| private_key, public_key, balance = miner_state.upload_queue.get(timeout=1) |
| |
| send_funded_ping(private_key, public_key, balance) |
| miner_state.upload_queue.task_done() |
| except queue.Empty: |
| continue |
| except Exception as e: |
| log.error(f"Upload daemon error: {e}") |
|
|
| threading.Thread(target=upload_daemon, daemon=True).start() |
|
|
| |
| def mining_loop(): |
| """Main mining loop with 30-minute cycle management""" |
| log.info("π Mining loop started with 30-minute cycles") |
| log.info(f"π Run flag status: {miner_state.run_flag.is_set()}") |
| client = None |
| consecutive_errors = 0 |
|
|
| batch_private_keys, batch_addresses = [], [] |
|
|
| while miner_state.run_flag.is_set(): |
| try: |
| |
| should_mine = miner_state.check_cycle_status() |
| if not should_mine: |
| |
| miner_state.connection_status = "On Break (30 min cycle)" |
| log.info(f"π€ In break mode, sleeping for 60 seconds...") |
| time.sleep(60) |
| continue |
| |
| miner_state.connection_status = "Mining Active" |
| log.info(f"βοΈ Starting mining batch of {config.RPC_BATCH_SIZE} wallets...") |
| |
| if client is None: |
| try: |
| client = create_solana_client() |
| consecutive_errors = 0 |
| except Exception as e: |
| log.error(f"Cannot connect to RPC: {e}") |
| time.sleep(10) |
| continue |
|
|
| |
| log.info(f"π Generating batch of {config.RPC_BATCH_SIZE} keypairs...") |
| while len(batch_addresses) < config.RPC_BATCH_SIZE and miner_state.run_flag.is_set(): |
| try: |
| private_key, public_key, _ = generate_secure_keypair() |
| batch_private_keys.append(private_key) |
| batch_addresses.append(public_key) |
| miner_state.increment_mined() |
|
|
| if len(batch_addresses) % 20 == 0: |
| log.info(f"Generated {len(batch_addresses)} keypairs so far...") |
| time.sleep(0.01) |
|
|
| except Exception as e: |
| log.error(f"Keypair generation error: {e}") |
| continue |
| |
| log.info(f"β
Generated {len(batch_addresses)} keypairs, checking balances...") |
|
|
| |
| if batch_addresses: |
| batch_start_time = time.time() |
| log.info(f"π Checking {len(batch_addresses)} addresses...") |
| balances = get_balances_with_retry(client, batch_addresses) |
|
|
| if not balances or len(balances) != len(batch_addresses): |
| log.warning("RPC response mismatch, reconnecting...") |
| client = None |
| continue |
|
|
| funded_in_batch = 0 |
| for i, balance in enumerate(balances): |
| if balance >= config.MIN_SOL: |
| priv, addr = batch_private_keys[i], batch_addresses[i] |
| funded_in_batch += 1 |
| miner_state.increment_funded() |
| miner_state.upload_queue.put((priv, addr, balance)) |
| log.info(f"π° FUNDED: {balance:.8f} SOL | {addr}") |
| live_feed.add_entry(priv, balance, addr) |
| elif i % 50 == 0: |
| live_feed.add_entry(batch_private_keys[i], balance, batch_addresses[i]) |
|
|
| log.info(f"β
Batch complete: {funded_in_batch} funded, {len(batch_addresses)} total") |
|
|
| |
| batch_time = time.time() - batch_start_time |
| miner_state.update_batch_stats(len(batch_addresses), batch_time, config.get_current_rpc()) |
| miner_state.connection_status = "Active Mining" |
|
|
| |
| batch_private_keys, batch_addresses = [], [] |
| consecutive_errors = 0 |
|
|
| |
| if funded_in_batch > 0 or miner_state.total_batches % 50 == 0: |
| queue_status_update() |
|
|
| |
| time.sleep(config.MINER_SLEEP) |
|
|
| except Exception as e: |
| consecutive_errors += 1 |
| log.error(f"Mining loop error: {e}") |
| miner_state.increment_errors() |
|
|
| if consecutive_errors >= 3: |
| client = None |
| miner_state.connection_status = "Reconnecting" |
| miner_state.rpc_switches += 1 |
| log.info("Resetting RPC connection due to errors") |
| time.sleep(5) |
|
|
| log.info("π Mining loop stopped") |
|
|
| |
| log.info("π Auto-starting mining on app launch...") |
| miner_state.run_flag.set() |
| threading.Thread(target=mining_loop, daemon=True).start() |
|
|
| |
| def start_mining() -> str: |
| if miner_state.run_flag.is_set(): |
| return "β οΈ Already running" |
| log.info("π Starting mining manually via button...") |
| miner_state.run_flag.set() |
| threading.Thread(target=mining_loop, daemon=True).start() |
| return "π’ Mining started (background)" |
|
|
| def stop_mining() -> str: |
| if not miner_state.run_flag.is_set(): |
| return "β οΈ Not running" |
| miner_state.run_flag.clear() |
| return "π΄ Mining stopped!" |
|
|
| def get_mining_stats() -> str: |
| stats = miner_state.get_comprehensive_stats() |
| running = "π’ Running" if stats['is_running'] else "π΄ Stopped" |
| last = "" |
| if stats['last_funded']: |
| ago = (datetime.now() - stats['last_funded']).seconds |
| last = f" | Last funded: {ago//3600}h {(ago//60)%60}m ago" |
| |
| |
| cycle_info = stats['cycle_time_remaining'] |
| if cycle_info['mode'] == 'break': |
| cycle_status = f"π΄ Break Mode ({cycle_info['remaining_minutes']}m left)" |
| elif cycle_info['mode'] == 'mining': |
| cycle_status = f"π’ Mining ({cycle_info['remaining_minutes']}m left)" |
| else: |
| cycle_status = "π Cycle Starting" |
| |
| return (f"{running} | {cycle_status} | Cycles: {stats['total_cycles']} | " |
| f"Mined: {stats['total_mined']:,} | Funded: {stats['funded_found']} | " |
| f"Speed: {stats['wallets_per_second']:.1f}/s | Errors: {stats['errors_count']} | " |
| f"Uptime: {stats['uptime_str']}{last}") |
|
|
| def clear_feed() -> str: |
| live_feed.clear_feed() |
| return "" |
|
|
| def generate_single_key() -> tuple: |
| try: |
| private_key, public_key, _ = generate_secure_keypair() |
| info = get_key_info(private_key) |
| return private_key, public_key, info.get('private_key_hex', ''), str(info.get('key_length', 0)) |
| except Exception as e: |
| log.error(f"Key generation error: {e}") |
| err = f"Error: {e}" |
| return err, err, err, err |
|
|
| def generate_bulk_keys(count: int) -> str: |
| if count <= 0 or count > 1000: |
| return "β Enter 1-1000" |
| keys = [] |
| errors = 0 |
| for i in range(count): |
| try: |
| private_key, _, _ = generate_secure_keypair() |
| keys.append(private_key) |
| if count > 100 and (i + 1) % 50 == 0: |
| log.info(f"Generated {i + 1}/{count} keys...") |
| except Exception as e: |
| errors += 1 |
| log.error(f"Failed to generate key {i + 1}: {e}") |
| if errors > 10: |
| return f"β Too many errors. Generated {len(keys)} keys successfully." |
|
|
| result = "\n".join(keys) |
| if errors > 0: |
| result += f"\n\nβ οΈ Note: {errors} keys failed to generate" |
| return result |
|
|
| |
| css = """ |
| #live_feed { |
| font-family: monospace; |
| font-size: 12px; |
| max-height: 500px; |
| overflow-y: auto; |
| } |
| """ |
|
|
| with gr.Blocks(title="Solana Key Generator & Miner", css=css, theme=gr.themes.Soft()) as demo: |
|
|
| gr.Markdown(f"# π Enhanced Solana Key Generator & Miner") |
| gr.Markdown(f"**{config.MINER_NAME}** ({config.MINER_ID}) - Multi-miner Discord integration enabled!") |
| gr.Markdown("Optimized for Hugging Face Spaces - runs forever even if you close the browser.") |
|
|
| with gr.Tab("π² Single Key Generator"): |
| with gr.Row(): |
| gen_btn = gr.Button("π― Generate New Keypair", variant="primary") |
| with gr.Row(): |
| private_key_out = gr.Textbox(label="π Private Key (Base58)", show_copy_button=True) |
| public_key_out = gr.Textbox(label="π Public Address", show_copy_button=True) |
| with gr.Row(): |
| private_hex_out = gr.Textbox(label="π’ Private Key (Hex)", show_copy_button=True) |
| key_info_out = gr.Textbox(label="βΉοΈ Key Information", interactive=False) |
| gen_btn.click(generate_single_key, outputs=[private_key_out, public_key_out, private_hex_out, key_info_out]) |
|
|
| with gr.Tab("π¦ Bulk Key Generator"): |
| count = gr.Slider(1, 1000, value=10, step=1, label="Number of Keys") |
| btn = gr.Button("π Generate Bulk Keys", variant="primary") |
| keys = gr.Textbox(label="Generated Private Keys", lines=20, show_copy_button=True) |
| btn.click(generate_bulk_keys, inputs=count, outputs=keys) |
|
|
| with gr.Tab("πͺ Wallet Miner"): |
| with gr.Row(): |
| start_btn = gr.Button("π’ Start Mining", variant="primary", size="lg") |
| stop_btn = gr.Button("π΄ Stop Mining", variant="stop", size="lg") |
| clear_btn = gr.Button("π§Ή Clear Feed", variant="secondary") |
| status = gr.Textbox(label="π― Mining Status", value="Ready to start mining...", interactive=False) |
| stats_display = gr.Textbox(label="π Real-time Statistics", interactive=False) |
| live_feed_display = gr.Textbox(label="π‘ Live Mining Feed", lines=25, max_lines=30, interactive=False, elem_id="live_feed") |
|
|
| start_btn.click(start_mining, outputs=status) |
| stop_btn.click(stop_mining, outputs=status) |
| clear_btn.click(clear_feed, outputs=live_feed_display) |
|
|
| |
| def _refresh(): |
| return get_mining_stats(), live_feed.get_feed() |
|
|
| refresh_btn = gr.Button("π Refresh", visible=False) |
| refresh_btn.click(_refresh, outputs=[stats_display, live_feed_display]) |
|
|
| |
| demo.load(_refresh, outputs=[stats_display, live_feed_display]) |
|
|
| if __name__ == "__main__": |
| |
| port = int(os.environ.get('PORT', 7860)) |
| |
| |
| if os.environ.get('REPLIT_DB_URL') or os.environ.get('REPL_ID'): |
| port = 5000 |
| |
| demo.queue().launch( |
| server_name="0.0.0.0", |
| server_port=port, |
| share=False, |
| show_error=True |
| ) |