├── requirements.txt ├── proxies.txt ├── .env ├── README.md └── main.py /requirements.txt: -------------------------------------------------------------------------------- 1 | python-dotenv>=1.0.0 2 | web3>=6.0.0 3 | aiohttp>=3.8.4 4 | eth-account>=0.8.0 5 | asyncio>=3.4.3 6 | aiofiles 7 | -------------------------------------------------------------------------------- /proxies.txt: -------------------------------------------------------------------------------- 1 | # http://user1:pass1@11.22.33.44:8080 2 | # https://44.55.66.77:3128 3 | # http://admin:secure123@88.99.11.22:80 4 | # https://proxy.example.com:8443 5 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # Add your private keys below (without the 0x prefix) 2 | # You can add multiple private keys using PRIVATE_KEY_1, PRIVATE_KEY_2, etc. 3 | # Example: 4 | # PRIVATE_KEY_1=your_private_key_here 5 | # PRIVATE_KEY_2=another_private_key_here 6 | # Or use the default PRIVATE_KEY for a single key: PRIVATE_KEY=0x... 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 0G Storage Scan Bot 2 | 3 | ![Banner](https://img.shields.io/badge/0G%20Storage-Scan%20Bot-blue?style=for-the-badge) 4 | [![Python](https://img.shields.io/badge/Python-3.8+-green.svg)](https://www.python.org/downloads/) 5 | [![License](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) 6 | 7 | **0G Storage Scan Bot** is an automated tool designed to interact with the 0G Storage blockchain network. It automates the process of uploading random images to the 0G Storage network using multiple wallet accounts and manages transactions on the blockchain. 8 | 9 | ## ✨ Features 10 | 11 | - ✅ Support for multiple wallet private keys 12 | - ✅ Proxy rotation for network requests 13 | - ✅ Random image fetching from online sources 14 | - ✅ File hash generation with uniqueness verification 15 | - ✅ Automatic transaction handling 16 | - ✅ Error management with retry mechanism 17 | - ✅ Detailed colored console logging 18 | - ✅ Multi-wallet processing 19 | 20 | ## 📋 Requirements 21 | 22 | - Python 3.8 or higher 23 | - An internet connection 24 | - Valid 0G Storage wallet private keys 25 | - (Optional) List of proxies for rotation 26 | 27 | ## 🚀 Installation 28 | 29 | ### For Windows 30 | 31 | 1. **Install Python**: 32 | - Download and install Python 3.8+ from [python.org](https://www.python.org/downloads/windows/) 33 | - Ensure you check "Add Python to PATH" during installation 34 | 35 | 2. **Clone or download this repository**: 36 | ``` 37 | git clone https://github.com/HexQuant-hub/0g-storage-scan-bot.git 38 | cd 0g-storage-scan-bot 39 | ``` 40 | 41 | 3. **Create a virtual environment**: 42 | ``` 43 | python -m venv venv 44 | venv\Scripts\activate 45 | ``` 46 | 47 | 4. **Install dependencies**: 48 | ``` 49 | pip install -r requirements.txt 50 | ``` 51 | 52 | ### For Linux 53 | 54 | 1. **Install Python and dependencies**: 55 | ```bash 56 | sudo apt update 57 | sudo apt install python3 python3-pip python3-venv 58 | ``` 59 | 60 | 2. **Clone the repository**: 61 | ```bash 62 | git clone https://github.com/HexQuant-hub/0g-storage-scan-bot.git 63 | cd 0g-storage-scan-bot 64 | ``` 65 | 66 | 3. **Create and activate a virtual environment**: 67 | ```bash 68 | python3 -m venv venv 69 | source venv/bin/activate 70 | ``` 71 | 72 | 4. **Install required packages**: 73 | ```bash 74 | pip install -r requirements.txt 75 | ``` 76 | 77 | ## ⚙️ Configuration 78 | 79 | 1. **Create a `.env` file** in the project root directory with your private keys: 80 | ``` 81 | PRIVATE_KEY=0xyourprivatekeyhere 82 | PRIVATE_KEY_1=0xyourfirstprivatekeyhere 83 | PRIVATE_KEY_2=0xyoursecondprivatekeyhere 84 | PRIVATE_KEY_3=0xyourthirdprivatekeyhere 85 | ``` 86 | 87 | 2. **(Optional) Create a `proxies.txt` file** with one proxy per line: 88 | ``` 89 | http://username:password@ip:port 90 | http://ip:port 91 | ``` 92 | 93 | ## 🔧 Usage 94 | 95 | 1. **Activate the virtual environment** (if not already activated): 96 | - Windows: `venv\Scripts\activate` 97 | - Linux: `source venv/bin/activate` 98 | 99 | 2. **Run the bot**: 100 | ``` 101 | python main.py 102 | ``` 103 | 104 | 3. **Follow the on-screen prompts**: 105 | - The bot will display available wallets 106 | - Enter the number of files to upload per wallet when prompted 107 | - The bot will handle the rest automatically 108 | 109 | 4. **Monitor the process**: 110 | - The console will display colorful logs showing progress 111 | - Each wallet will be processed sequentially 112 | - A summary will be shown at the end 113 | 114 | ## 🔍 Troubleshooting 115 | 116 | - **Insufficient Balance Error**: Ensure your wallets have sufficient OG tokens for transactions (minimum 0.0015 OG) 117 | - **Network Connection Issues**: Check your internet connection or try using proxies 118 | - **Transaction Failures**: The bot will automatically retry failed transactions 119 | - **Proxy Errors**: Verify your proxy format in `proxies.txt` 120 | 121 | ## 📄 License 122 | 123 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 124 | 125 | --- 126 | 127 |
128 | 129 | # 0G Storage Scan Bot 130 | 131 | ![بنر](https://img.shields.io/badge/0G%20Storage-Scan%20Bot-blue?style=for-the-badge) 132 | [![پایتون](https://img.shields.io/badge/Python-3.8+-green.svg)](https://www.python.org/downloads/) 133 | [![مجوز](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) 134 | 135 | 136 | ## ✨ ویژگی‌ها 137 | 138 | - ✅ پشتیبانی از چندین کلید خصوصی کیف پول 139 | - ✅ چرخش پروکسی برای درخواست‌های شبکه 140 | - ✅ دریافت تصاویر تصادفی از منابع آنلاین 141 | - ✅ تولید هش فایل با تأیید منحصربه‌فرد بودن 142 | - ✅ مدیریت خودکار تراکنش‌ها 143 | - ✅ مدیریت خطا با مکانیزم تلاش مجدد 144 | - ✅ گزارش‌دهی کنسول رنگی با جزئیات 145 | - ✅ پردازش چند کیف پول 146 | 147 | ## 📋 پیش‌نیازها 148 | 149 | - پایتون ۳.۸ یا بالاتر 150 | - اتصال به اینترنت 151 | - کلیدهای خصوصی معتبر کیف پول 0G Storage 152 | - (اختیاری) فهرستی از پروکسی‌ها برای چرخش 153 | 154 | ## 🚀 نصب و راه‌اندازی 155 | 156 | ### برای ویندوز 157 | 158 | ۱. **نصب پایتون**: 159 | - پایتون ۳.۸ یا بالاتر را از [python.org](https://www.python.org/downloads/windows/) دانلود و نصب کنید 160 | - مطمئن شوید که گزینه "Add Python to PATH" را هنگام نصب انتخاب کرده‌اید 161 | 162 | ۲. **کلون یا دانلود این مخزن**: 163 | ``` 164 | git clone https://github.com/HexQuant-hub/0g-storage-scan-bot.git 165 | cd 0g-storage-scan-bot 166 | ``` 167 | 168 | ۳. **ایجاد محیط مجازی**: 169 | ``` 170 | python -m venv venv 171 | venv\Scripts\activate 172 | ``` 173 | 174 | ۴. **نصب وابستگی‌ها**: 175 | ``` 176 | pip install -r requirements.txt 177 | ``` 178 | 179 | ### برای لینوکس 180 | 181 | ۱. **نصب پایتون و وابستگی‌ها**: 182 | ```bash 183 | sudo apt update 184 | sudo apt install python3 python3-pip python3-venv 185 | ``` 186 | 187 | ۲. **کلون مخزن**: 188 | ```bash 189 | git clone https://github.com/yourusername/0g-storage-scan-bot.git 190 | cd 0g-storage-scan-bot 191 | ``` 192 | 193 | ۳. **ایجاد و فعال‌سازی محیط مجازی**: 194 | ```bash 195 | python3 -m venv venv 196 | source venv/bin/activate 197 | ``` 198 | 199 | ۴. **نصب بسته‌های مورد نیاز**: 200 | ```bash 201 | pip install -r requirements.txt 202 | ``` 203 | 204 | ## ⚙️ پیکربندی 205 | 206 | ۱. **ایجاد فایل `.env`** در دایرکتوری اصلی پروژه با کلیدهای خصوصی خود: 207 | ``` 208 | PRIVATE_KEY=0xyourprivatekeyhere 209 | PRIVATE_KEY_1=0xyourfirstprivatekeyhere 210 | PRIVATE_KEY_2=0xyoursecondprivatekeyhere 211 | PRIVATE_KEY_3=0xyourthirdprivatekeyhere 212 | ``` 213 | 214 | ۲. **(اختیاری) ایجاد فایل `proxies.txt`** با یک پروکسی در هر خط: 215 | ``` 216 | http://username:password@ip:port 217 | http://ip:port 218 | ``` 219 | 220 | ## 🔧 استفاده 221 | 222 | ۱. **فعال‌سازی محیط مجازی** (اگر قبلاً فعال نشده است): 223 | - ویندوز: `venv\Scripts\activate` 224 | - لینوکس: `source venv/bin/activate` 225 | 226 | ۲. **اجرای ربات**: 227 | ``` 228 | python main.py 229 | ``` 230 | 231 | ۳. **دنبال کردن راهنمایی‌های روی صفحه**: 232 | - ربات کیف پول‌های موجود را نمایش می‌دهد 233 | - تعداد فایل‌هایی که برای هر کیف پول آپلود می‌شود را وارد کنید 234 | - ربات به صورت خودکار بقیه فرآیند را انجام می‌دهد 235 | 236 | ۴. **نظارت بر فرآیند**: 237 | - کنسول گزارش‌های رنگی نمایش می‌دهد که پیشرفت را نشان می‌دهد 238 | - هر کیف پول به ترتیب پردازش می‌شود 239 | - در پایان خلاصه‌ای نمایش داده می‌شود 240 | 241 | ## 🔍 عیب‌یابی 242 | 243 | - **خطای موجودی ناکافی**: اطمینان حاصل کنید که کیف پول‌های شما توکن‌های OG کافی برای تراکنش‌ها دارند (حداقل ۰.۰۰۱۵ OG) 244 | - **مشکلات اتصال شبکه**: اتصال اینترنت خود را بررسی کنید یا از پروکسی‌ها استفاده کنید 245 | - **شکست تراکنش‌ها**: ربات به طور خودکار تراکنش‌های ناموفق را دوباره امتحان می‌کند 246 | - **خطاهای پروکسی**: فرمت پروکسی خود را در `proxies.txt` بررسی کنید 247 | 248 | ## 📄 مجوز 249 | 250 | این پروژه تحت مجوز MIT است - برای جزئیات به فایل [LICENSE](LICENSE) مراجعه کنید. 251 | 252 |
253 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import time 4 | import asyncio 5 | import random 6 | import hashlib 7 | import base64 8 | from dotenv import load_dotenv 9 | from web3 import AsyncWeb3, AsyncHTTPProvider 10 | from web3.eth import AsyncEth 11 | from eth_account import Account 12 | import aiohttp 13 | import aiofiles 14 | 15 | load_dotenv() 16 | class Colors: 17 | RESET = "\033[0m" 18 | CYAN = "\033[36m" 19 | GREEN = "\033[32m" 20 | YELLOW = "\033[33m" 21 | RED = "\033[31m" 22 | WHITE = "\033[37m" 23 | GRAY = "\033[90m" 24 | BOLD = "\033[1m" 25 | class Logger: 26 | @staticmethod 27 | def info(msg): print(f"{Colors.GREEN}[✓] {msg}{Colors.RESET}") 28 | @staticmethod 29 | def warn(msg): print(f"{Colors.YELLOW}[⚠] {msg}{Colors.RESET}") 30 | @staticmethod 31 | def error(msg): print(f"{Colors.RED}[✗] {msg}{Colors.RESET}") 32 | @staticmethod 33 | def success(msg): print(f"{Colors.GREEN}[✅] {msg}{Colors.RESET}") 34 | @staticmethod 35 | def loading(msg): print(f"{Colors.CYAN}[⟳] {msg}{Colors.RESET}") 36 | @staticmethod 37 | def process(msg): print(f"\n{Colors.WHITE}[➤] {msg}{Colors.RESET}") 38 | @staticmethod 39 | def debug(msg): print(f"{Colors.GRAY}[…] {msg}{Colors.RESET}") 40 | @staticmethod 41 | def critical(msg): print(f"{Colors.RED}{Colors.BOLD}[❌] {msg}{Colors.RESET}") 42 | @staticmethod 43 | def summary(msg): print(f"{Colors.WHITE}[✓] {msg}{Colors.RESET}") 44 | @staticmethod 45 | def section(msg=None): 46 | line = "=" * 50 47 | print(f"\n{Colors.CYAN}{line}{Colors.RESET}") 48 | if msg: print(f"{Colors.CYAN}{msg}{Colors.RESET}") 49 | print(f"{Colors.CYAN}{line}{Colors.RESET}\n") 50 | @staticmethod 51 | def banner(): 52 | print(f"{Colors.CYAN}{Colors.BOLD}") 53 | print("██╗ ██╗███████╗██╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ███╗ ██╗████████╗") 54 | print("██║ ██║██╔════╝╚██╗██╔╝██╔═══██╗██║ ██║██╔══██╗████╗ ██║╚══██╔══╝") 55 | print("███████║█████╗ ╚███╔╝ ██║ ██║██║ ██║███████║██╔██╗ ██║ ██║ ") 56 | print("██╔══██║██╔══╝ ██╔██╗ ██║▄▄ ██║██║ ██║██╔══██║██║╚██╗██║ ██║ ") 57 | print("██║ ██║███████╗██╔╝ ██╗╚██████╔╝╚██████╔╝██║ ██║██║ ╚████║ ██║ ") 58 | print("╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚══▀▀═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ") 59 | print(" ") 60 | print(" 0G Storage ") 61 | print(f"{Colors.RESET}\n") 62 | 63 | 64 | CHAIN_ID = 80087 65 | RPC_URL = 'https://evmrpc-testnet.0g.ai' 66 | CONTRACT_ADDRESS = '0x56A565685C9992BF5ACafb940ff68922980DBBC5' 67 | METHOD_ID = '0xef3e12dc' 68 | PROXY_FILE = 'proxies.txt' 69 | INDEXER_URL = 'https://indexer-storage-testnet-turbo.0g.ai' 70 | EXPLORER_URL = 'https://chainscan-galileo.0g.ai/tx/' 71 | IMAGE_SOURCES = [ 72 | {'url': 'https://picsum.photos/800/600'}, 73 | {'url': 'https://loremflickr.com/800/600'} 74 | ] 75 | 76 | class OGStorageBot: 77 | def __init__(self): 78 | self.private_keys = [] 79 | self.proxies = [] 80 | self.current_proxy_index = 0 81 | self.web3 = AsyncWeb3(AsyncHTTPProvider(RPC_URL), modules={'eth': (AsyncEth,)}) 82 | 83 | def load_private_keys(self): 84 | """Load private keys from environment variables""" 85 | try: 86 | index = 1 87 | key = os.getenv(f"PRIVATE_KEY_{index}") 88 | if not key and index == 1 and os.getenv("PRIVATE_KEY"): 89 | key = os.getenv("PRIVATE_KEY") 90 | 91 | while key: 92 | if self.is_valid_private_key(key): 93 | self.private_keys.append(key) 94 | else: 95 | Logger.error(f"Invalid private key at PRIVATE_KEY_{index}") 96 | index += 1 97 | key = os.getenv(f"PRIVATE_KEY_{index}") 98 | 99 | if not self.private_keys: 100 | Logger.critical("No valid private keys found in .env file") 101 | exit(1) 102 | 103 | Logger.success(f"Loaded {len(self.private_keys)} private key(s)") 104 | except Exception as e: 105 | Logger.critical(f"Failed to load private keys: {str(e)}") 106 | exit(1) 107 | 108 | def is_valid_private_key(self, key): 109 | """Validate private key format""" 110 | key = key.strip() 111 | if not key.startswith("0x"): 112 | key = "0x" + key 113 | try: 114 | bytes_key = bytes.fromhex(key[2:]) 115 | return len(key) == 66 and len(bytes_key) == 32 116 | except: 117 | return False 118 | 119 | def load_proxies(self): 120 | """Load proxies from file""" 121 | try: 122 | if os.path.exists(PROXY_FILE): 123 | with open(PROXY_FILE, 'r') as f: 124 | self.proxies = [line.strip() for line in f if line.strip() and not line.startswith('#')] 125 | 126 | if self.proxies: 127 | Logger.info(f"Loaded {len(self.proxies)} proxies from {PROXY_FILE}") 128 | else: 129 | Logger.warn(f"No proxies found in {PROXY_FILE}") 130 | else: 131 | Logger.warn(f"Proxy file {PROXY_FILE} not found") 132 | except Exception as e: 133 | Logger.error(f"Failed to load proxies: {str(e)}") 134 | 135 | def get_next_proxy(self): 136 | """Get next proxy in rotation""" 137 | if not self.proxies: 138 | return None 139 | proxy = self.proxies[self.current_proxy_index] 140 | self.current_proxy_index = (self.current_proxy_index + 1) % len(self.proxies) 141 | return proxy 142 | 143 | def get_random_user_agent(self): 144 | """Return a random user agent string""" 145 | user_agents = [ 146 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36', 147 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15', 148 | 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36', 149 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0', 150 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:124.0) Gecko/20100101 Firefox/124.0' 151 | ] 152 | return random.choice(user_agents) 153 | 154 | async def check_network_sync(self): 155 | """Check if network is synced""" 156 | try: 157 | Logger.loading("Checking network sync...") 158 | block_number = await self.web3.eth.block_number 159 | Logger.success(f"Network synced at block {block_number}") 160 | return True 161 | except Exception as e: 162 | Logger.error(f"Network sync check failed: {str(e)}") 163 | return False 164 | 165 | async def fetch_random_image(self): 166 | """Fetch random image from sources""" 167 | try: 168 | Logger.loading("Fetching random image...") 169 | source = random.choice(IMAGE_SOURCES) 170 | headers = {'User-Agent': self.get_random_user_agent()} 171 | proxy = self.get_next_proxy() 172 | 173 | async with aiohttp.ClientSession() as session: 174 | async with session.get(source['url'], headers=headers, proxy=proxy) as response: 175 | if response.status == 200: 176 | image_data = await response.read() 177 | Logger.success("Image fetched successfully") 178 | return image_data 179 | else: 180 | raise Exception(f"HTTP error: {response.status}") 181 | except Exception as e: 182 | Logger.error(f"Error fetching image: {str(e)}") 183 | raise 184 | 185 | async def check_file_exists(self, file_hash): 186 | """Check if file hash already exists in storage""" 187 | try: 188 | Logger.loading(f"Checking file hash {file_hash}...") 189 | headers = {'User-Agent': self.get_random_user_agent()} 190 | proxy = self.get_next_proxy() 191 | 192 | async with aiohttp.ClientSession() as session: 193 | async with session.get( 194 | f"{INDEXER_URL}/file/info/{file_hash}", 195 | headers=headers, 196 | proxy=proxy 197 | ) as response: 198 | if response.status == 200: 199 | data = await response.json() 200 | return data.get('exists', False) 201 | return False 202 | except Exception as e: 203 | Logger.warn(f"Failed to check file hash: {str(e)}") 204 | return False 205 | 206 | async def prepare_image_data(self, image_buffer): 207 | """Prepare image data with unique hash""" 208 | MAX_HASH_ATTEMPTS = 5 209 | attempt = 1 210 | 211 | while attempt <= MAX_HASH_ATTEMPTS: 212 | try: 213 | salt = os.urandom(16).hex() 214 | timestamp = str(int(time.time() * 1000)) 215 | hash_input = image_buffer + salt.encode() + timestamp.encode() 216 | file_hash = "0x" + hashlib.sha256(hash_input).hexdigest() 217 | file_exists = await self.check_file_exists(file_hash) 218 | if file_exists: 219 | Logger.warn(f"Hash {file_hash} already exists, retrying...") 220 | attempt += 1 221 | continue 222 | image_base64 = base64.b64encode(image_buffer).decode('utf-8') 223 | Logger.success(f"Generated unique file hash: {file_hash}") 224 | 225 | return { 226 | 'root': file_hash, 227 | 'data': image_base64 228 | } 229 | except Exception as e: 230 | Logger.error(f"Error generating hash (attempt {attempt}): {str(e)}") 231 | attempt += 1 232 | if attempt > MAX_HASH_ATTEMPTS: 233 | raise Exception(f"Failed to generate unique hash after {MAX_HASH_ATTEMPTS} attempts") 234 | 235 | async def upload_to_storage(self, image_data, wallet_address, private_key, wallet_index): 236 | """Upload file to storage and send transaction""" 237 | MAX_RETRIES = 3 238 | TIMEOUT_SECONDS = 300 239 | attempt = 1 240 | Logger.loading(f"Checking wallet balance for {wallet_address}...") 241 | balance = await self.web3.eth.get_balance(wallet_address) 242 | min_balance = self.web3.to_wei(0.0015, 'ether') 243 | if balance < min_balance: 244 | raise Exception(f"Insufficient balance: {self.web3.from_wei(balance, 'ether')} OG") 245 | 246 | Logger.success(f"Wallet balance: {self.web3.from_wei(balance, 'ether')} OG") 247 | 248 | while attempt <= MAX_RETRIES: 249 | try: 250 | Logger.loading(f"Uploading file for wallet #{wallet_index + 1} [{wallet_address}] (Attempt {attempt}/{MAX_RETRIES})...") 251 | headers = { 252 | 'User-Agent': self.get_random_user_agent(), 253 | 'Content-Type': 'application/json' 254 | } 255 | proxy = self.get_next_proxy() 256 | 257 | payload = { 258 | 'root': image_data['root'], 259 | 'index': 0, 260 | 'data': image_data['data'], 261 | 'proof': { 262 | 'siblings': [image_data['root']], 263 | 'path': [] 264 | } 265 | } 266 | 267 | async with aiohttp.ClientSession() as session: 268 | async with session.post( 269 | f"{INDEXER_URL}/file/segment", 270 | json=payload, 271 | headers=headers, 272 | proxy=proxy 273 | ) as response: 274 | if response.status != 200: 275 | raise Exception(f"Upload failed with status: {response.status}") 276 | 277 | Logger.success("File segment uploaded") 278 | content_hash = os.urandom(32) 279 | tx_data = ( 280 | bytes.fromhex(METHOD_ID[2:]) + 281 | bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000020") + 282 | bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000014") + 283 | bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000060") + 284 | bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000080") + 285 | bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000000") + 286 | bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000001") + 287 | content_hash + 288 | bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000000") 289 | ) 290 | 291 | tx_value = self.web3.to_wei(0.000839233398436224, 'ether') 292 | gas_price = self.web3.to_wei(1.029599997, 'gwei') 293 | Logger.loading("Estimating gas...") 294 | try: 295 | gas_estimate = await self.web3.eth.estimate_gas({ 296 | 'to': CONTRACT_ADDRESS, 297 | 'data': tx_data.hex(), 298 | 'from': wallet_address, 299 | 'value': tx_value 300 | }) 301 | gas_limit = int(gas_estimate * 1.5) 302 | Logger.success(f"Gas limit set: {gas_limit}") 303 | except Exception as e: 304 | gas_limit = 300000 305 | Logger.warn(f"Gas estimation failed, using default: {gas_limit}") 306 | gas_cost = int(gas_price) * gas_limit 307 | required_balance = gas_cost + int(tx_value) 308 | if balance < required_balance: 309 | raise Exception(f"Insufficient balance for transaction: {self.web3.from_wei(balance, 'ether')} OG") 310 | 311 | # Build and sign transaction 312 | Logger.loading("Sending transaction...") 313 | nonce = await self.web3.eth.get_transaction_count(wallet_address, 'latest') 314 | 315 | tx = { 316 | 'to': CONTRACT_ADDRESS, 317 | 'data': tx_data.hex(), 318 | 'value': tx_value, 319 | 'gas': gas_limit, 320 | 'gasPrice': gas_price, 321 | 'nonce': nonce, 322 | 'chainId': CHAIN_ID 323 | } 324 | 325 | signed_tx = self.web3.eth.account.sign_transaction(tx, private_key) 326 | tx_hash = await self.web3.eth.send_raw_transaction(signed_tx.rawTransaction) 327 | tx_hash_hex = tx_hash.hex() 328 | tx_link = f"{EXPLORER_URL}{tx_hash_hex}" 329 | Logger.info(f"Transaction sent: {tx_hash_hex}") 330 | Logger.info(f"Explorer: {tx_link}") 331 | Logger.loading(f"Waiting for confirmation ({TIMEOUT_SECONDS}s)...") 332 | 333 | try: 334 | start_time = time.time() 335 | receipt = None 336 | 337 | while receipt is None and (time.time() - start_time) < TIMEOUT_SECONDS: 338 | try: 339 | receipt = await self.web3.eth.get_transaction_receipt(tx_hash) 340 | await asyncio.sleep(2) 341 | except: 342 | await asyncio.sleep(5) 343 | 344 | if receipt is None: 345 | raise Exception(f"Timeout after {TIMEOUT_SECONDS} seconds") 346 | 347 | except Exception as e: 348 | if "Timeout" in str(e): 349 | Logger.warn(f"Transaction timeout after {TIMEOUT_SECONDS}s") 350 | receipt = await self.web3.eth.get_transaction_receipt(tx_hash) 351 | if receipt and receipt.status == 1: 352 | Logger.success(f"Late confirmation in block {receipt.blockNumber}") 353 | else: 354 | raise Exception(f"Transaction failed or pending: {tx_link}") 355 | else: 356 | raise 357 | 358 | if receipt.status == 1: 359 | Logger.success(f"Transaction confirmed in block {receipt.blockNumber}") 360 | Logger.success(f"File uploaded, root hash: {image_data['root']}") 361 | return receipt 362 | else: 363 | raise Exception(f"Transaction failed: {tx_link}") 364 | 365 | except Exception as e: 366 | Logger.error(f"Upload attempt {attempt} failed: {str(e)}") 367 | if attempt < MAX_RETRIES: 368 | delay = 10 + random.random() * 20 369 | Logger.warn(f"Retrying after {delay:.2f}s...") 370 | await asyncio.sleep(delay) 371 | attempt += 1 372 | continue 373 | raise 374 | 375 | async def process_wallet(self, wallet_index, upload_count, total_uploads): 376 | """Process a single wallet with multiple uploads""" 377 | private_key = self.private_keys[wallet_index] 378 | if not private_key.startswith("0x"): 379 | private_key = "0x" + private_key 380 | 381 | account = Account.from_key(private_key) 382 | wallet_address = account.address 383 | 384 | Logger.section(f"Processing Wallet #{wallet_index + 1} [{wallet_address}]") 385 | 386 | successful = 0 387 | failed = 0 388 | 389 | for i in range(1, upload_count + 1): 390 | upload_number = (wallet_index * upload_count) + i 391 | Logger.process(f"Upload {upload_number}/{total_uploads} (Wallet #{wallet_index + 1}, File #{i})") 392 | 393 | try: 394 | image_buffer = await self.fetch_random_image() 395 | image_data = await self.prepare_image_data(image_buffer) 396 | await self.upload_to_storage(image_data, wallet_address, private_key, wallet_index) 397 | successful += 1 398 | Logger.success(f"Upload {upload_number} completed") 399 | 400 | if upload_number < total_uploads: 401 | Logger.loading("Waiting for next upload...") 402 | await asyncio.sleep(3) 403 | except Exception as e: 404 | failed += 1 405 | Logger.error(f"Upload {upload_number} failed: {str(e)}") 406 | await asyncio.sleep(5) 407 | 408 | return successful, failed 409 | 410 | async def run(self): 411 | """Main execution function""" 412 | try: 413 | Logger.banner() 414 | self.load_private_keys() 415 | self.load_proxies() 416 | 417 | # Check network 418 | Logger.loading("Checking network status...") 419 | chain_id = await self.web3.eth.chain_id 420 | if chain_id != CHAIN_ID: 421 | raise Exception(f"Invalid chainId: expected {CHAIN_ID}, got {chain_id}") 422 | 423 | Logger.success(f"Connected to network: chainId {chain_id}") 424 | 425 | is_network_synced = await self.check_network_sync() 426 | if not is_network_synced: 427 | raise Exception("Network is not synced") 428 | print(f"{Colors.CYAN}Available wallets:{Colors.RESET}") 429 | for idx, key in enumerate(self.private_keys): 430 | account = Account.from_key(key) 431 | print(f"{Colors.GREEN}[{idx + 1}]{Colors.RESET} {account.address}") 432 | print() 433 | upload_count = int(input("How many files to upload per wallet? ")) 434 | if upload_count <= 0: 435 | Logger.error("Invalid number. Please enter a number greater than 0.") 436 | return 437 | 438 | total_uploads = upload_count * len(self.private_keys) 439 | Logger.info(f"Starting {total_uploads} uploads ({upload_count} per wallet)") 440 | total_successful = 0 441 | total_failed = 0 442 | 443 | for wallet_index in range(len(self.private_keys)): 444 | successful, failed = await self.process_wallet(wallet_index, upload_count, total_uploads) 445 | total_successful += successful 446 | total_failed += failed 447 | 448 | if wallet_index < len(self.private_keys) - 1: 449 | Logger.loading("Switching to next wallet...") 450 | await asyncio.sleep(10) 451 | Logger.section("Upload Summary") 452 | Logger.summary(f"Total wallets: {len(self.private_keys)}") 453 | Logger.summary(f"Uploads per wallet: {upload_count}") 454 | Logger.summary(f"Total attempted: {total_uploads}") 455 | if total_successful > 0: 456 | Logger.success(f"Successful: {total_successful}") 457 | if total_failed > 0: 458 | Logger.error(f"Failed: {total_failed}") 459 | Logger.success("All operations completed") 460 | 461 | except Exception as e: 462 | Logger.critical(f"Main process error: {str(e)}") 463 | return 1 464 | 465 | return 0 466 | if __name__ == "__main__": 467 | bot = OGStorageBot() 468 | exit_code = asyncio.run(bot.run()) 469 | print(f"{Colors.YELLOW}Process completed ~ Bye bang !{Colors.RESET}") 470 | exit(exit_code) 471 | --------------------------------------------------------------------------------