├── 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 | 
4 | [](https://www.python.org/downloads/)
5 | [](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 | 
132 | [](https://www.python.org/downloads/)
133 | [](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 |
--------------------------------------------------------------------------------