├── .gitignore ├── README.md ├── config.py ├── data ├── accounts_data.xlsx ├── bad_wallets.json └── services │ ├── cex_withdraw_list.json │ ├── contact_data.json │ ├── maximum_gwei.json │ ├── orbiter_maker1.json │ ├── orbiter_maker2.json │ ├── orbiter_maker3.json │ ├── orbiter_maker4.json │ ├── orbiter_maker5.json │ └── wallets_progress.json ├── functions.py ├── general_settings.py ├── main.py ├── modules ├── __init__.py ├── blockchains │ ├── __init__.py │ └── evm.py ├── bridges │ ├── __init__.py │ ├── across.py │ ├── bungee.py │ ├── layerswap.py │ ├── nitro.py │ ├── orbiter.py │ ├── owlto.py │ ├── relay.py │ └── rhino.py ├── cexs │ ├── __init__.py │ ├── binance.py │ ├── bingx.py │ ├── bitget.py │ └── okx.py ├── client.py ├── custom_modules.py ├── interfaces.py ├── other │ ├── __init__.py │ └── mintfun.py ├── swaps │ ├── __init__.py │ ├── izumi.py │ ├── odos.py │ ├── oneinch.py │ └── uniswap.py └── txchecker.py ├── requirements.txt ├── settings.py └── utils ├── modules_runner.py ├── networks.py ├── route_generator.py ├── stark_signature ├── eth_coder.py ├── math_utils.py ├── pedersen_params.json └── stark_singature.py └── tools.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/caches/build_file_checksums.ser 2 | *.pyc 3 | *.xlsx 4 | data/services/service_account.json 5 | data/services/cex_withdraw_list.json 6 | *.xml 7 | *.patch 8 | .idea/AttackMachine.iml 9 | *.2046150700048 10 | test.py 11 | data/accounts_data.xlsx 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |  2 | # ⚔️ EthMachine 3 | 4 | ## Общая информация 5 | 6 | Данный софт позволит вам сделать активность в сети ETH. Большинство настроек подготовлены к работе, остальные простые и понятные. 7 | > Путь осилит идущий, а софт осилит любой деген🕵️ 8 | 9 | **Подробная [статья](https://teletype.in/@realaskaer/attackmachine) по работе с этим подобным зверем** 10 | 11 | ## 🧩Модули 12 | 13 | 1. OKX (Депозит / Вывод / Сбор средств с субАккаунтов) 14 | 2. BingX (Депозит / Вывод / Сбор средств с субАккаунтов) 15 | 3. Binance (Депозит / Вывод / Сбор средств с субАккаунтов) 16 | 4. Bitget (Депозит / Вывод / Сбор средств с субАккаунтов) 17 | 5. Across (Bridge по любым направлениям / для любых монет) 18 | 6. Bungee (Bridge по любым направлениям / для любых монет) 19 | 7. Nitro (Bridge по любым направлениям / для любых монет) 20 | 8. Owlto (Bridge по любым направлениям / для любых монет) 21 | 9. Orbiter (Bridge по любым направлениям / для любых монет) 22 | 10. Relay (Bridge по любым направлениям / для любых монет) 23 | 11. Rhino (Bridge по любым направлениям / для любых монет) 24 | 12. Native bridge (офф. мост Bridge / Withdraw) 25 | 13. Uniswap (Свапы между стейблами и ETH) 26 | 14. iZumi (Свапы между стейблами и ETH) 27 | 15. ODOS (Свапы между стейблами и ETH) 28 | 16. 1inch (Свапы между стейблами и ETH) 29 | 17. Mint.fun (Минт любой NFT по контракту) 30 | 18. Bungee (Refuel в/из любой сети) 31 | 19. ETH Sender (Отправка пыли в ETH на свой / рандомный адрес) 32 | 20. Wrap/Unwrap ETH (Делает врапы / анврапы ETH через офф. контракт WETH в сети) 33 | 21. Balancer (Уравнивает баланс токена на аккаунтах) 34 | 22. Random Approve (Делает случайный апрув на контракт DEX) 35 | 36 | ## ♾️Основные функции 37 | 38 | 1. **🚀Запуск прогона всех аккаунтов по подготовленным классическим маршрутам** 39 | 40 | После генерации маршрута (следующая функция), софт запустит выполнение маршрутов для всех аккаунтов. Все варианты работы смотрите в разделе **Настройка софта** 41 | 42 | 2. **📄Генерация классических роутов для каждого аккаунта** 43 | 44 | Классический генератор, работает по дедовской методике. Вам нужно указать списки модулей в настройке `CLASSIC_ROUTES_MODULES_USING` и при запуске этой функции софт соберет вам маршрут по этой настройке. Поддерживается 45 | `None` как один из модулей в списке, при его попадании в маршрут, софт пропустит этот список. 46 | 47 | 3. **💾Создание файла зависимостей ваших и OKX кошельков** 48 | 49 | Создает файл JSON, где привязываются ваши адреса к кошелькам OKX. Сделал для вашей безопасности. Софт сопоставляет 50 | к каждой строке в `CEX address` эту же строку в `Private Key` и если вы ошиблись, то всегда можно проверить это в 51 | файле `cex_withdraw_list.json`, во избежания пересечений кошельков. 52 | 53 | 4. **✅Проверка всех прокси на работоспособность** 54 | 55 | Быстрая проверка прокси(реально быстрая, как с цепи срывается) 56 | 57 | 5. **📊Получение статистики для каждого аккаунта** 58 | 59 | Практически моментальное получение всей статистики по аккаунтам, даже если их больше 100 штук(не забудьте про прокси). Сделаны все необходимые 60 | поля. 61 | 62 | 63 | ## 📄Ввод своих данных 64 | 65 | ### Все нужные данные необходимо указать в таблицу `accounts_data` в папке `/data`. Для каждого проекта необходим свой отдельный в лист. 66 | 1. **Name** - имена ваших аккаунтов, каждое название должно быть уникальным 67 | 2. **Private Key** - приватные ключи от кошельков 68 | 3. **Proxy** - прокси для каждого аккаунта. Если их будет меньше, софт будет брать их по кругу. Если прокси мобильные, то можно указать просто одну проксю. 69 | 4. **CEX address** - адреса пополнения CEX. Для каждого кошелька необходимо указать адрес, иначе вывод не сработает. 70 | 71 | Вы можете установить пароль на вашу таблицу и включить настройку `EXCEL_PASSWORD = True`. При активации пароля, софт будет требовать его ввести для дальнейшей работы. Полезно при работе на сервере. 72 | 73 | ## ⚙️Настройка софта 74 | 75 | >Крайне рекомендую ознакомиться с этой **[статьей](https://teletype.in/@realaskaer/attackmachine)**, с ее помощью вы сможете настроить любую деталь в софте. 76 | 77 | Все настройки вынесены в файл `settings.py`. Заходим в него и видим подробное описание каждого раздела. 78 | Самые важные настройки продублирую здесь. 79 | 80 | 1. Раздел `API KEYS`. Получите все API ключи. В разделе есть ссылки на сайты, где это нужно сделать 81 | 2. Раздел `GENERAL SETTINGS`. Внимательно прочитайте все описания и проставьте необходимые значения 82 | 3. Далее сверху вниз настройте все модули. К каждому модулю есть описание 83 | 84 | ## 🛠️Установка и запуск проекта 85 | 86 | > Устанавливая проект, вы принимаете риски использования софта для добывания денег(потерять жопу, деньги, девственность). 87 | 88 | Как только вы скачаете проект, **убедитесь**, что у вас Python 3.10.11 89 | 90 | Установка проекта 91 | 92 | ```bash 93 | git clone https://github.com/realaskaer/EthMachine.git 94 | ``` 95 | 96 | Для установки необходимых библиотек, пропишите в консоль 97 | 98 | ```bash 99 | pip install -r requirements.txt 100 | ``` 101 | 102 | Запуск проекта 103 | 104 | ```bash 105 | cd ethmachine 106 | python main.py 107 | ``` 108 | 109 | ## 🔗 Ссылки на установку Python и PyCharm 110 | 111 | - [Установка PyCharm](https://www.jetbrains.com/pycharm/download/?section=windows) 112 | - [Установка Python](https://www.python.org/downloads/windows/) (Вам нужна версия 3.10.11) 113 | 114 | ## 🧾FAQ 115 | 116 | #### Есть ли дрейнер в софте? 117 | 118 | > Нет, но перед запуском любого софта, необходимо его проверять 119 | 120 | #### Что делать, если ничего работает? 121 | 122 | > Сначала, прочитать README, если не получилось с первого раза, попытаться еще раз. 123 | 124 | ## ❔Куда писать свой вопрос? 125 | 126 | - [@askaer.foundation](https://t.me/askaer) - мой канал в телеграм 127 | - [@askaer.chat](https://t.me/askaerchat) - ответы на любой вопрос 128 | - [@askaer](https://t.me/realaskaer) - **при обнаружении бомбы в коде** 129 | 130 | ## ❤️‍🔥Donate (Any EVM) 131 | 132 | ### `0x000000a679C2FB345dDEfbaE3c42beE92c0Fb7A5` 133 | > Спасибо за поддержку❤️ 134 | -------------------------------------------------------------------------------- /data/accounts_data.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sealdeck/EthMachine/030c3e1cd97f3319e2f6e85936fb3559dc0d8474/data/accounts_data.xlsx -------------------------------------------------------------------------------- /data/bad_wallets.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sealdeck/EthMachine/030c3e1cd97f3319e2f6e85936fb3559dc0d8474/data/bad_wallets.json -------------------------------------------------------------------------------- /data/services/cex_withdraw_list.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sealdeck/EthMachine/030c3e1cd97f3319e2f6e85936fb3559dc0d8474/data/services/cex_withdraw_list.json -------------------------------------------------------------------------------- /data/services/contact_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "abi": [ 3 | { 4 | "type": "constructor", 5 | "name": "", 6 | "inputs": [], 7 | "outputs": [], 8 | "stateMutability": "nonpayable" 9 | } 10 | ], 11 | "bytecode": "0x60806040526000805461ffff1916905534801561001b57600080fd5b5060fb8061002a6000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c80630c55699c146037578063b49004e914605b575b600080fd5b60005460449061ffff1681565b60405161ffff909116815260200160405180910390f35b60616063565b005b60008054600191908190607a90849061ffff166096565b92506101000a81548161ffff021916908361ffff160217905550565b61ffff81811683821601908082111560be57634e487b7160e01b600052601160045260246000fd5b509291505056fea2646970667358221220666c87ec501268817295a4ca1fc6e3859faf241f38dd688f145135970920009264736f6c63430008120033" 12 | } 13 | -------------------------------------------------------------------------------- /data/services/maximum_gwei.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sealdeck/EthMachine/030c3e1cd97f3319e2f6e85936fb3559dc0d8474/data/services/maximum_gwei.json -------------------------------------------------------------------------------- /data/services/orbiter_maker1.json: -------------------------------------------------------------------------------- 1 | { 2 | "1-2": { 3 | "USDT-USDC": { 4 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 5 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 6 | "maxPrice": 100, 7 | "minPrice": 0.1, 8 | "tradingFee": 2, 9 | "gasFee": 0.35, 10 | "slippage": 50, 11 | "startTime": 0, 12 | "endTime": 99999999999999 13 | }, 14 | "USDC-USDT": { 15 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 16 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 17 | "maxPrice": 100, 18 | "minPrice": 0.1, 19 | "tradingFee": 2, 20 | "gasFee": 0.35, 21 | "slippage": 50, 22 | "startTime": 0, 23 | "endTime": 99999999999999 24 | } 25 | }, 26 | "2-1": { 27 | "USDT-USDC": { 28 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 29 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 30 | "maxPrice": 100, 31 | "minPrice": 0.1, 32 | "tradingFee": 30, 33 | "gasFee": 0.35, 34 | "slippage": 50, 35 | "startTime": 1651842601, 36 | "endTime": 99999999999999 37 | }, 38 | "USDC-USDT": { 39 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 40 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 41 | "maxPrice": 100, 42 | "minPrice": 0.1, 43 | "tradingFee": 30, 44 | "gasFee": 0.35, 45 | "slippage": 50, 46 | "startTime": 1651842601, 47 | "endTime": 99999999999999 48 | } 49 | }, 50 | "1-6": { 51 | "USDT-USDC": { 52 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 53 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 54 | "maxPrice": 100, 55 | "minPrice": 0.1, 56 | "tradingFee": 2, 57 | "gasFee": 0.35, 58 | "slippage": 50, 59 | "startTime": 0, 60 | "endTime": 99999999999999 61 | }, 62 | "USDC-USDT": { 63 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 64 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 65 | "maxPrice": 100, 66 | "minPrice": 0.1, 67 | "tradingFee": 2, 68 | "gasFee": 0.35, 69 | "slippage": 50, 70 | "startTime": 0, 71 | "endTime": 99999999999999 72 | } 73 | }, 74 | "6-1": { 75 | "USDT-USDC": { 76 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 77 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 78 | "maxPrice": 100, 79 | "minPrice": 0.1, 80 | "tradingFee": 30, 81 | "gasFee": 0.35, 82 | "slippage": 50, 83 | "startTime": 1663297201, 84 | "endTime": 99999999999999 85 | }, 86 | "USDC-USDT": { 87 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 88 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 89 | "maxPrice": 100, 90 | "minPrice": 0.1, 91 | "tradingFee": 30, 92 | "gasFee": 0.35, 93 | "slippage": 50, 94 | "startTime": 1663297201, 95 | "endTime": 99999999999999 96 | } 97 | }, 98 | "1-7": { 99 | "USDT-USDC": { 100 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 101 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 102 | "maxPrice": 100, 103 | "minPrice": 0.1, 104 | "tradingFee": 2, 105 | "gasFee": 0.35, 106 | "slippage": 50, 107 | "startTime": 0, 108 | "endTime": 99999999999999 109 | }, 110 | "USDC-USDT": { 111 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 112 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 113 | "maxPrice": 100, 114 | "minPrice": 0.1, 115 | "tradingFee": 2, 116 | "gasFee": 0.35, 117 | "slippage": 50, 118 | "startTime": 0, 119 | "endTime": 99999999999999 120 | } 121 | }, 122 | "7-1": { 123 | "USDT-USDC": { 124 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 125 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 126 | "maxPrice": 100, 127 | "minPrice": 0.1, 128 | "tradingFee": 30, 129 | "gasFee": 0.35, 130 | "slippage": 50, 131 | "startTime": 1636019587, 132 | "endTime": 99999999999999 133 | }, 134 | "USDC-USDT": { 135 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 136 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 137 | "maxPrice": 100, 138 | "minPrice": 0.1, 139 | "tradingFee": 30, 140 | "gasFee": 0.35, 141 | "slippage": 50, 142 | "startTime": 1636019587, 143 | "endTime": 99999999999999 144 | } 145 | }, 146 | "2-6": { 147 | "USDT-USDC": { 148 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 149 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 150 | "maxPrice": 100, 151 | "minPrice": 0.1, 152 | "tradingFee": 2, 153 | "gasFee": 0.35, 154 | "slippage": 50, 155 | "startTime": 1651842601, 156 | "endTime": 99999999999999 157 | }, 158 | "USDC-USDT": { 159 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 160 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 161 | "maxPrice": 100, 162 | "minPrice": 0.1, 163 | "tradingFee": 2, 164 | "gasFee": 0.35, 165 | "slippage": 50, 166 | "startTime": 1651842601, 167 | "endTime": 99999999999999 168 | } 169 | }, 170 | "6-2": { 171 | "USDT-USDC": { 172 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 173 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 174 | "maxPrice": 100, 175 | "minPrice": 0.1, 176 | "tradingFee": 2, 177 | "gasFee": 0.35, 178 | "slippage": 50, 179 | "startTime": 1636019587, 180 | "endTime": 99999999999999 181 | }, 182 | "USDC-USDT": { 183 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 184 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 185 | "maxPrice": 100, 186 | "minPrice": 0.1, 187 | "tradingFee": 2, 188 | "gasFee": 0.35, 189 | "slippage": 50, 190 | "startTime": 1636019587, 191 | "endTime": 99999999999999 192 | } 193 | }, 194 | "2-7": { 195 | "USDT-USDC": { 196 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 197 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 198 | "maxPrice": 100, 199 | "minPrice": 0.1, 200 | "tradingFee": 2, 201 | "gasFee": 0.35, 202 | "slippage": 50, 203 | "startTime": 1651842601, 204 | "endTime": 99999999999999 205 | }, 206 | "USDC-USDT": { 207 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 208 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 209 | "maxPrice": 100, 210 | "minPrice": 0.1, 211 | "tradingFee": 2, 212 | "gasFee": 0.35, 213 | "slippage": 50, 214 | "startTime": 1651842601, 215 | "endTime": 99999999999999 216 | } 217 | }, 218 | "7-2": { 219 | "USDT-USDC": { 220 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 221 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 222 | "maxPrice": 100, 223 | "minPrice": 0.1, 224 | "tradingFee": 2, 225 | "gasFee": 0.35, 226 | "slippage": 50, 227 | "startTime": 1636019587, 228 | "endTime": 99999999999999 229 | }, 230 | "USDC-USDT": { 231 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 232 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 233 | "maxPrice": 100, 234 | "minPrice": 0.1, 235 | "tradingFee": 2, 236 | "gasFee": 0.35, 237 | "slippage": 50, 238 | "startTime": 1636019587, 239 | "endTime": 99999999999999 240 | } 241 | }, 242 | "7-6": { 243 | "USDT-USDC": { 244 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 245 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 246 | "maxPrice": 100, 247 | "minPrice": 0.1, 248 | "tradingFee": 2, 249 | "gasFee": 0.35, 250 | "slippage": 50, 251 | "startTime": 1636019587, 252 | "endTime": 99999999999999 253 | }, 254 | "USDC-USDT": { 255 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 256 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 257 | "maxPrice": 100, 258 | "minPrice": 0.1, 259 | "tradingFee": 2, 260 | "gasFee": 0.35, 261 | "slippage": 50, 262 | "startTime": 1636019587, 263 | "endTime": 99999999999999 264 | } 265 | }, 266 | "6-7": { 267 | "USDT-USDC": { 268 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 269 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 270 | "maxPrice": 100, 271 | "minPrice": 0.1, 272 | "tradingFee": 2, 273 | "gasFee": 0.35, 274 | "slippage": 50, 275 | "startTime": 1636019587, 276 | "endTime": 99999999999999 277 | }, 278 | "USDC-USDT": { 279 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 280 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 281 | "maxPrice": 100, 282 | "minPrice": 0.1, 283 | "tradingFee": 2, 284 | "gasFee": 0.35, 285 | "slippage": 50, 286 | "startTime": 1636019587, 287 | "endTime": 99999999999999 288 | } 289 | } 290 | } -------------------------------------------------------------------------------- /data/services/orbiter_maker2.json: -------------------------------------------------------------------------------- 1 | { 2 | "1-2": { 3 | "USDT-USDC": { 4 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 5 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 6 | "maxPrice": 100, 7 | "minPrice": 0.1, 8 | "tradingFee": 2, 9 | "gasFee": 0.35, 10 | "slippage": 50, 11 | "startTime": 0, 12 | "endTime": 99999999999999 13 | }, 14 | "USDC-USDT": { 15 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 16 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 17 | "maxPrice": 100, 18 | "minPrice": 0.1, 19 | "tradingFee": 2, 20 | "gasFee": 0.35, 21 | "slippage": 50, 22 | "startTime": 0, 23 | "endTime": 99999999999999 24 | } 25 | }, 26 | "2-1": { 27 | "USDT-USDC": { 28 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 29 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 30 | "maxPrice": 100, 31 | "minPrice": 0.1, 32 | "tradingFee": 30, 33 | "gasFee": 0.35, 34 | "slippage": 50, 35 | "startTime": 1651842601, 36 | "endTime": 99999999999999 37 | }, 38 | "USDC-USDT": { 39 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 40 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 41 | "maxPrice": 100, 42 | "minPrice": 0.1, 43 | "tradingFee": 30, 44 | "gasFee": 0.35, 45 | "slippage": 50, 46 | "startTime": 1651842601, 47 | "endTime": 99999999999999 48 | } 49 | }, 50 | "1-6": { 51 | "USDT-USDC": { 52 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 53 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 54 | "maxPrice": 100, 55 | "minPrice": 0.1, 56 | "tradingFee": 2, 57 | "gasFee": 0.35, 58 | "slippage": 50, 59 | "startTime": 0, 60 | "endTime": 99999999999999 61 | }, 62 | "USDC-USDT": { 63 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 64 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 65 | "maxPrice": 100, 66 | "minPrice": 0.1, 67 | "tradingFee": 2, 68 | "gasFee": 0.35, 69 | "slippage": 50, 70 | "startTime": 0, 71 | "endTime": 99999999999999 72 | } 73 | }, 74 | "6-1": { 75 | "USDT-USDC": { 76 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 77 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 78 | "maxPrice": 100, 79 | "minPrice": 0.1, 80 | "tradingFee": 30, 81 | "gasFee": 0.35, 82 | "slippage": 50, 83 | "startTime": 1663297201, 84 | "endTime": 99999999999999 85 | }, 86 | "USDC-USDT": { 87 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 88 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 89 | "maxPrice": 100, 90 | "minPrice": 0.1, 91 | "tradingFee": 30, 92 | "gasFee": 0.35, 93 | "slippage": 50, 94 | "startTime": 1663297201, 95 | "endTime": 99999999999999 96 | } 97 | }, 98 | "1-7": { 99 | "USDT-USDC": { 100 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 101 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 102 | "maxPrice": 100, 103 | "minPrice": 0.1, 104 | "tradingFee": 2, 105 | "gasFee": 0.35, 106 | "slippage": 50, 107 | "startTime": 0, 108 | "endTime": 99999999999999 109 | }, 110 | "USDC-USDT": { 111 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 112 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 113 | "maxPrice": 100, 114 | "minPrice": 0.1, 115 | "tradingFee": 2, 116 | "gasFee": 0.35, 117 | "slippage": 50, 118 | "startTime": 0, 119 | "endTime": 99999999999999 120 | } 121 | }, 122 | "7-1": { 123 | "USDT-USDC": { 124 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 125 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 126 | "maxPrice": 100, 127 | "minPrice": 0.1, 128 | "tradingFee": 30, 129 | "gasFee": 0.35, 130 | "slippage": 50, 131 | "startTime": 1636019587, 132 | "endTime": 99999999999999 133 | }, 134 | "USDC-USDT": { 135 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 136 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 137 | "maxPrice": 100, 138 | "minPrice": 0.1, 139 | "tradingFee": 30, 140 | "gasFee": 0.35, 141 | "slippage": 50, 142 | "startTime": 1636019587, 143 | "endTime": 99999999999999 144 | } 145 | }, 146 | "2-6": { 147 | "USDT-USDC": { 148 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 149 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 150 | "maxPrice": 100, 151 | "minPrice": 0.1, 152 | "tradingFee": 2, 153 | "gasFee": 0.35, 154 | "slippage": 50, 155 | "startTime": 1651842601, 156 | "endTime": 99999999999999 157 | }, 158 | "USDC-USDT": { 159 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 160 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 161 | "maxPrice": 100, 162 | "minPrice": 0.1, 163 | "tradingFee": 2, 164 | "gasFee": 0.35, 165 | "slippage": 50, 166 | "startTime": 1651842601, 167 | "endTime": 99999999999999 168 | } 169 | }, 170 | "6-2": { 171 | "USDT-USDC": { 172 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 173 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 174 | "maxPrice": 100, 175 | "minPrice": 0.1, 176 | "tradingFee": 2, 177 | "gasFee": 0.35, 178 | "slippage": 50, 179 | "startTime": 1636019587, 180 | "endTime": 99999999999999 181 | }, 182 | "USDC-USDT": { 183 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 184 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 185 | "maxPrice": 100, 186 | "minPrice": 0.1, 187 | "tradingFee": 2, 188 | "gasFee": 0.35, 189 | "slippage": 50, 190 | "startTime": 1636019587, 191 | "endTime": 99999999999999 192 | } 193 | }, 194 | "2-7": { 195 | "USDT-USDC": { 196 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 197 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 198 | "maxPrice": 100, 199 | "minPrice": 0.1, 200 | "tradingFee": 2, 201 | "gasFee": 0.35, 202 | "slippage": 50, 203 | "startTime": 1651842601, 204 | "endTime": 99999999999999 205 | }, 206 | "USDC-USDT": { 207 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 208 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 209 | "maxPrice": 100, 210 | "minPrice": 0.1, 211 | "tradingFee": 2, 212 | "gasFee": 0.35, 213 | "slippage": 50, 214 | "startTime": 1651842601, 215 | "endTime": 99999999999999 216 | } 217 | }, 218 | "7-2": { 219 | "USDT-USDC": { 220 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 221 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 222 | "maxPrice": 100, 223 | "minPrice": 0.1, 224 | "tradingFee": 2, 225 | "gasFee": 0.35, 226 | "slippage": 50, 227 | "startTime": 1636019587, 228 | "endTime": 99999999999999 229 | }, 230 | "USDC-USDT": { 231 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 232 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 233 | "maxPrice": 100, 234 | "minPrice": 0.1, 235 | "tradingFee": 2, 236 | "gasFee": 0.35, 237 | "slippage": 50, 238 | "startTime": 1636019587, 239 | "endTime": 99999999999999 240 | } 241 | }, 242 | "7-6": { 243 | "USDT-USDC": { 244 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 245 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 246 | "maxPrice": 100, 247 | "minPrice": 0.1, 248 | "tradingFee": 2, 249 | "gasFee": 0.35, 250 | "slippage": 50, 251 | "startTime": 1636019587, 252 | "endTime": 99999999999999 253 | }, 254 | "USDC-USDT": { 255 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 256 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 257 | "maxPrice": 100, 258 | "minPrice": 0.1, 259 | "tradingFee": 2, 260 | "gasFee": 0.35, 261 | "slippage": 50, 262 | "startTime": 1636019587, 263 | "endTime": 99999999999999 264 | } 265 | }, 266 | "6-7": { 267 | "USDT-USDC": { 268 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 269 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 270 | "maxPrice": 100, 271 | "minPrice": 0.1, 272 | "tradingFee": 2, 273 | "gasFee": 0.35, 274 | "slippage": 50, 275 | "startTime": 1636019587, 276 | "endTime": 99999999999999 277 | }, 278 | "USDC-USDT": { 279 | "makerAddress": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 280 | "sender": "0x1C84DAA159cf68667A54bEb412CDB8B2c193fb32", 281 | "maxPrice": 100, 282 | "minPrice": 0.1, 283 | "tradingFee": 2, 284 | "gasFee": 0.35, 285 | "slippage": 50, 286 | "startTime": 1636019587, 287 | "endTime": 99999999999999 288 | } 289 | } 290 | } -------------------------------------------------------------------------------- /data/services/wallets_progress.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sealdeck/EthMachine/030c3e1cd97f3319e2f6e85936fb3559dc0d8474/data/services/wallets_progress.json -------------------------------------------------------------------------------- /functions.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | from modules import * 4 | from utils.networks import * 5 | from config import LAYERZERO_WRAPED_NETWORKS 6 | from settings import (SRC_CHAIN_BUNGEE, NATIVE_CHAIN_ID_FROM, NATIVE_CHAIN_ID_TO) 7 | 8 | 9 | def get_client(account_name, private_key, network, proxy) -> Client: 10 | return Client(account_name, private_key, network, proxy) 11 | 12 | 13 | def get_interface_by_chain_id(chain_id): 14 | return { 15 | 2: ArbitrumNova, 16 | 3: Base, 17 | 4: Linea, 18 | 8: Scroll, 19 | 11: ZkSync, 20 | 12: Zora, 21 | 13: Ethereum, 22 | }[chain_id] 23 | 24 | 25 | def get_network_by_chain_id(chain_id): 26 | return { 27 | 0: ArbitrumRPC, 28 | 1: ArbitrumRPC, 29 | 2: Arbitrum_novaRPC, 30 | 3: BaseRPC, 31 | 4: LineaRPC, 32 | 5: MantaRPC, 33 | 6: PolygonRPC, 34 | 7: OptimismRPC, 35 | 8: ScrollRPC, 36 | # 9: StarknetRPC, 37 | 10: Polygon_ZKEVM_RPC, 38 | 11: zkSyncEraRPC, 39 | 12: ZoraRPC, 40 | 13: EthereumRPC, 41 | 14: AvalancheRPC, 42 | 15: BSC_RPC, 43 | 16: MoonbeamRPC, 44 | 17: HarmonyRPC, 45 | 18: TelosRPC, 46 | 19: CeloRPC, 47 | 20: GnosisRPC, 48 | 21: CoreRPC, 49 | 22: TomoChainRPC, 50 | 23: ConfluxRPC, 51 | 24: OrderlyRPC, 52 | 25: HorizenRPC, 53 | 26: MetisRPC, 54 | 27: AstarRPC, 55 | 28: OpBNB_RPC, 56 | 29: MantleRPC, 57 | 30: MoonriverRPC, 58 | 31: KlaytnRPC, 59 | 32: KavaRPC, 60 | 33: FantomRPC, 61 | 34: AuroraRPC, 62 | 35: CantoRPC, 63 | 36: DFK_RPC, 64 | 37: FuseRPC, 65 | 38: GoerliRPC, 66 | 39: MeterRPC, 67 | 40: OKX_RPC, 68 | 41: ShimmerRPC, 69 | 42: TenetRPC, 70 | 43: XPLA_RPC, 71 | 44: LootChainRPC, 72 | 45: ZKFairRPC, 73 | 46: BeamRPC, 74 | 47: InEVM_RPC, 75 | 48: RaribleRPC, 76 | 49: BlastRPC, 77 | }[chain_id] 78 | 79 | 80 | async def cex_deposit_util(current_client, dapp_id:int, deposit_data:tuple): 81 | class_name = { 82 | 1: OKX, 83 | 2: BingX, 84 | 3: Binance, 85 | 4: Bitget 86 | }[dapp_id] 87 | 88 | return await class_name(current_client).deposit(deposit_data=deposit_data) 89 | 90 | 91 | async def okx_deposit(account_name, private_key, network, proxy): 92 | worker = Custom(get_client(account_name, private_key, network, proxy)) 93 | return await worker.smart_cex_deposit(dapp_id=1) 94 | 95 | 96 | async def bingx_deposit(account_name, private_key, network, proxy): 97 | worker = Custom(get_client(account_name, private_key, network, proxy)) 98 | return await worker.smart_cex_deposit(dapp_id=2) 99 | 100 | 101 | async def binance_deposit(account_name, private_key, network, proxy): 102 | worker = Custom(get_client(account_name, private_key, network, proxy)) 103 | return await worker.smart_cex_deposit(dapp_id=3) 104 | 105 | 106 | async def bitget_deposit(account_name, private_key, network, proxy): 107 | worker = Custom(get_client(account_name, private_key, network, proxy)) 108 | return await worker.smart_cex_deposit(dapp_id=4) 109 | 110 | 111 | async def bridge_utils(current_client, dapp_id, chain_from_id, bridge_data, need_fee=False): 112 | 113 | class_bridge = { 114 | 1: Across, 115 | 2: Bungee, 116 | 3: Relay, 117 | 4: Nitro, 118 | 5: Orbiter, 119 | 6: Owlto, 120 | 7: Relay, 121 | 8: Rhino, 122 | }[dapp_id] 123 | 124 | return await class_bridge(current_client).bridge(chain_from_id, bridge_data, need_check=need_fee) 125 | 126 | 127 | async def bridge_across(account_name, private_key, network, proxy): 128 | worker = Custom(get_client(account_name, private_key, network, proxy)) 129 | return await worker.smart_bridge(dapp_id=1) 130 | 131 | 132 | async def bridge_bungee(account_name, private_key, network, proxy): 133 | worker = Custom(get_client(account_name, private_key, network, proxy)) 134 | return await worker.smart_bridge(dapp_id=2) 135 | 136 | 137 | async def bridge_relay2(account_name, private_key, network, proxy): 138 | worker = Custom(get_client(account_name, private_key, network, proxy)) 139 | return await worker.smart_bridge(dapp_id=3) 140 | 141 | 142 | async def bridge_nitro(account_name, private_key, network, proxy): 143 | worker = Custom(get_client(account_name, private_key, network, proxy)) 144 | return await worker.smart_bridge(dapp_id=4) 145 | 146 | 147 | async def bridge_orbiter(account_name, private_key, network, proxy): 148 | worker = Custom(get_client(account_name, private_key, network, proxy)) 149 | return await worker.smart_bridge(dapp_id=5) 150 | 151 | 152 | async def bridge_owlto(account_name, private_key, network, proxy): 153 | worker = Custom(get_client(account_name, private_key, network, proxy)) 154 | return await worker.smart_bridge(dapp_id=6) 155 | 156 | 157 | async def bridge_relay(account_name, private_key, network, proxy): 158 | worker = Custom(get_client(account_name, private_key, network, proxy)) 159 | return await worker.smart_bridge(dapp_id=7) 160 | 161 | 162 | async def bridge_rhino(account_name, private_key, network, proxy): 163 | worker = Custom(get_client(account_name, private_key, network, proxy)) 164 | return await worker.smart_bridge(dapp_id=8) 165 | 166 | 167 | async def okx_withdraw(account_name, private_key, network, proxy): 168 | worker = Custom(get_client(account_name, private_key, network, proxy)) 169 | return await worker.smart_cex_withdraw(dapp_id=1) 170 | 171 | 172 | async def bingx_withdraw(account_name, private_key, network, proxy): 173 | worker = Custom(get_client(account_name, private_key, network, proxy)) 174 | return await worker.smart_cex_withdraw(dapp_id=2) 175 | 176 | 177 | async def binance_withdraw(account_name, private_key, network, proxy): 178 | worker = Custom(get_client(account_name, private_key, network, proxy)) 179 | return await worker.smart_cex_withdraw(dapp_id=3) 180 | 181 | 182 | async def bitget_withdraw(account_name, private_key, network, proxy): 183 | worker = Custom(get_client(account_name, private_key, network, proxy)) 184 | return await worker.smart_cex_withdraw(dapp_id=4) 185 | 186 | 187 | async def bridge_native(account_name, private_key, _, proxy, *args, **kwargs): 188 | network = get_network_by_chain_id(13) 189 | blockchain = get_interface_by_chain_id(random.choice(NATIVE_CHAIN_ID_TO)) 190 | 191 | worker = blockchain(get_client(account_name, private_key, network, proxy)) 192 | return await worker.deposit(*args, **kwargs) 193 | 194 | 195 | async def transfer_eth(account_name, private_key, network, proxy): 196 | blockchain = get_interface_by_chain_id(13) 197 | 198 | worker = blockchain(get_client(account_name, private_key, network, proxy)) 199 | return await worker.transfer_eth() 200 | 201 | 202 | async def transfer_eth_to_myself(account_name, private_key, network, proxy): 203 | blockchain = get_interface_by_chain_id(13) 204 | 205 | worker = blockchain(get_client(account_name, private_key, network, proxy)) 206 | return await worker.transfer_eth_to_myself() 207 | 208 | 209 | async def wrap_eth(account_name, private_key, network, proxy, *args): 210 | worker = SimpleEVM(get_client(account_name, private_key, network, proxy)) 211 | return await worker.wrap_eth(*args) 212 | 213 | 214 | async def unwrap_eth(account_name, private_key, network, proxy, *args): 215 | worker = SimpleEVM(get_client(account_name, private_key, network, proxy)) 216 | return await worker.unwrap_eth(*args) 217 | 218 | 219 | # async def mint_deployed_token(account_name, private_key, network, proxy, *args, **kwargs): 220 | # mint = ZkSync(account_name, private_key, network, proxy) 221 | # await mint.mint_token() 222 | 223 | 224 | async def swap_odos(account_name, private_key, network, proxy, **kwargs): 225 | worker = Odos(get_client(account_name, private_key, network, proxy)) 226 | return await worker.swap(**kwargs) 227 | 228 | 229 | async def swap_oneinch(account_name, private_key, network, proxy, **kwargs): 230 | worker = OneInch(get_client(account_name, private_key, network, proxy)) 231 | return await worker.swap(**kwargs) 232 | 233 | 234 | async def swap_izumi(account_name, private_key, network, proxy, **kwargs): 235 | worker = Izumi(get_client(account_name, private_key, network, proxy)) 236 | return await worker.swap(**kwargs) 237 | 238 | 239 | async def refuel_bungee(account_name, private_key, _, proxy): 240 | chain_from_id = LAYERZERO_WRAPED_NETWORKS[random.choice(SRC_CHAIN_BUNGEE)] 241 | network = get_network_by_chain_id(chain_from_id) 242 | 243 | worker = Bungee(get_client(account_name, private_key, network, proxy)) 244 | return await worker.refuel() 245 | 246 | 247 | async def withdraw_txsync(account_name, private_key, _, proxy): 248 | blockchain = get_interface_by_chain_id(11) 249 | network = get_network_by_chain_id(11) 250 | 251 | worker = blockchain(get_client(account_name, private_key, network, proxy)) 252 | return await worker.withdraw() 253 | 254 | 255 | async def swap_uniswap(account_name, private_key, network, proxy, **kwargs): 256 | worker = Uniswap(get_client(account_name, private_key, network, proxy)) 257 | return await worker.swap(**kwargs) 258 | 259 | 260 | async def mint_mintfun(account_name, private_key, network, proxy): 261 | worker = MintFun(get_client(account_name, private_key, network, proxy)) 262 | return await worker.mint() 263 | 264 | 265 | async def random_approve(account_nameaccount_name, private_key, network, proxy): 266 | blockchain = get_interface_by_chain_id(13) 267 | 268 | worker = blockchain(get_client(account_nameaccount_name, private_key, network, proxy)) 269 | return await worker.random_approve() 270 | 271 | 272 | async def make_balance_to_average(account_name, private_key, network, proxy): 273 | 274 | worker = Custom(get_client(account_name, private_key, network, proxy)) 275 | return await worker.balance_average() 276 | 277 | 278 | async def okx_withdraw_util(current_client, **kwargs): 279 | worker = OKX(current_client) 280 | return await worker.withdraw(**kwargs) 281 | 282 | 283 | async def bingx_withdraw_util(current_client, **kwargs): 284 | worker = BingX(current_client) 285 | return await worker.withdraw(**kwargs) 286 | 287 | 288 | async def binance_withdraw_util(current_client, **kwargs): 289 | worker = Binance(current_client) 290 | return await worker.withdraw(**kwargs) 291 | 292 | 293 | async def bitget_withdraw_util(current_client, **kwargs): 294 | worker = Bitget(current_client) 295 | return await worker.withdraw(**kwargs) 296 | 297 | 298 | async def bingx_transfer(account_name, private_key, network, proxy): 299 | worker = BingX(get_client(account_name, private_key, network, proxy)) 300 | return await worker.withdraw(transfer_mode=True) 301 | 302 | 303 | async def bridge_zora(account_name, private_key, _, proxy): 304 | network = get_network_by_chain_id(random.choice(NATIVE_CHAIN_ID_FROM)) 305 | 306 | worker = Zora(get_client(account_name, private_key, network, proxy)) 307 | return await worker.bridge() 308 | -------------------------------------------------------------------------------- /general_settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | ----------------------------------------------AMOUNT CONTROL------------------------------------------------------------ 3 | Здесь вы определяете количество или % токенов для обменов, врапов и трансферов 4 | Софт берет % только для ETH, остальные токены берутся на 100% от баланса 5 | 6 | Можно указать минимальную/максимальную сумму или минимальный/максимальный % от баланса 7 | 8 | Количество - (0.01, 0.02) 9 | Процент - ("55", "60") ⚠️ Значения в скобках 10 | 11 | AMOUNT_PERCENT | Указывать только %, без кавычек. Можно указывать с точностью до 6 цифры (99.123456, 99.654321). 12 | ⚠️Остальные настройки сумм указывать в кавычках(если хотите работать в %)⚠️ 13 | MIN_BALANCE | Минимальный баланс для аккаунта. При меньшем балансе будет ошибка: (Insufficient balance on account!) 14 | """ 15 | AMOUNT_PERCENT = (10, 20) # Применяется для обменов. 16 | WRAP_AMOUNT = (0.0001, 0.0002) # Применяется для врапов 17 | TRANSFER_AMOUNT = (0.0001, 0.0002) # Применяется для трансферов эфира на свой или рандомный адрес 18 | MIN_BALANCE = 0.001 # Количество ETH на аккаунте 19 | 20 | """ 21 | ------------------------------------------------GENERAL SETTINGS-------------------------------------------------------- 22 | 23 | WALLETS_TO_WORK = 0 | Софт будет брать кошельки из таблице по правилам, описанным снизу 24 | 0 = все кошельки подряд 25 | 3 = только кошелек №3 26 | 4, 20 = кошелек №4 и №20 27 | [5, 25] = кошельки с №5 по №25 28 | 29 | ACCOUNTS_IN_STREAM | Количество кошельков в потоке на выполнение. Если всего 100 кошельков, а указать 10, 30 | то софт сделает 10 подходов по 10 кошельков 31 | CONTROL_TIMES_FOR_SLEEP | Количество проверок, после которого для всех аккаунтов будет включен рандомный сон в 32 | моменте, когда газ опуститься до MAXIMUM_GWEI и аккаунты продолжат работать 33 | 34 | EXCEL_PASSWORD | Включает запрос пароля при входе в софт. Сначала установите пароль в таблице 35 | EXCEL_PAGE_NAME | Название листа в таблице. Пример: 'zkSync' 36 | MAIN_PROXY | Прокси для обращения к API бирж. Формат - log:pass@ip:port. По умолчанию - localhost 37 | """ 38 | SOFTWARE_MODE = 0 # 0 - последовательный запуск / 1 - параллельный запуск 39 | ACCOUNTS_IN_STREAM = 1 # Только для SOFTWARE_MODE = 1 (параллельный запуск) 40 | WALLETS_TO_WORK = 0 # 0 / 3 / 3, 20 / [3, 20] 41 | SHUFFLE_WALLETS = False # Перемешивает кошельки перед запуском 42 | SHUFFLE_ROUTE = False # Перемешивает маршрут перед запуском 43 | BREAK_ROUTE = False # Прекращает выполнение маршрута, если произойдет ошибка 44 | STOP_SOFTWARE = False # Прекращает выполнение всего софта, если произойдет критическая ошибка 45 | VOLUME_MODE = False # Приостанавливает выполнение маршрута, при возникновении ошибок с биржами или бриджами 46 | SAVE_PROGRESS = True # Включает сохранение прогресса аккаунта для Classic-routes 47 | TELEGRAM_NOTIFICATIONS = False # Включает уведомления в Telegram 48 | 49 | '------------------------------------------------SLEEP CONTROL---------------------------------------------------------' 50 | SLEEP_MODE = False # Включает сон после каждого модуля и аккаунта 51 | SLEEP_TIME_MODULES = (60, 80) # (минимум, максимум) секунд | Время сна между модулями. 52 | SLEEP_TIME_ACCOUNTS = (40, 60) # (минимум, максимум) секунд | Время сна между аккаунтами. 53 | 54 | '-------------------------------------------------GAS CONTROL----------------------------------------------------------' 55 | GAS_CONTROL = False # Включает контроль газа 56 | MAXIMUM_GWEI = 40 # Максимальный GWEI для работы софта, изменять во время работы софта в maximum_gwei.json 57 | SLEEP_TIME_GAS = 100 # Время очередной проверки газа 58 | CONTROL_TIMES_FOR_SLEEP = 5 # Количество проверок 59 | GAS_LIMIT_MULTIPLIER = 1.5 # Множитель газ лимита для транзакций. Поможет сэкономить на транзакциях 60 | GAS_PRICE_MULTIPLIER = 1.0 # Множитель цены газа для транзакций. Ускоряет выполнение или уменьшает цену транзакции 61 | 62 | '------------------------------------------------RETRY CONTROL---------------------------------------------------------' 63 | MAXIMUM_RETRY = 20 # Количество повторений при ошибках 64 | SLEEP_TIME_RETRY = (5, 10) # (минимум, максимум) секунд | Время сна после очередного повторения 65 | 66 | '------------------------------------------------PROXY CONTROL---------------------------------------------------------' 67 | USE_PROXY = False # Включает использование прокси 68 | MOBILE_PROXY = False # Включает использование мобильных прокси. USE_PROXY должен быть True 69 | MOBILE_PROXY_URL_CHANGER = [ 70 | '', 71 | '', 72 | '' 73 | ] # ['link1', 'link2'..] | Ссылки для смены IP. Софт пройдется по всем ссылкам 74 | 75 | '-----------------------------------------------SLIPPAGE CONTROL-------------------------------------------------------' 76 | SLIPPAGE = 2 # 0.54321 = 0.54321%, 1 = 1% | Slippage, на сколько % вы готовы получить меньше 77 | PRICE_IMPACT = 5 # 0.54321 = 0.54321%, 1 = 1% | Максимальное влияние на цену при обменах токенов 78 | 79 | '-----------------------------------------------APPROVE CONTROL--------------------------------------------------------' 80 | UNLIMITED_APPROVE = False # Включает безлимитный Approve для контракта 81 | 82 | '------------------------------------------------PROXY CONTROL---------------------------------------------------------' 83 | MAIN_PROXY = '' # log:pass@ip:port, прокси для обращения к API бирж. По умолчанию - localhost 84 | 85 | '------------------------------------------------SECURE DATA-----------------------------------------------------------' 86 | # OKX API KEYS https://www.okx.com/ru/account/my-api 87 | OKX_API_KEY = "" 88 | OKX_API_SECRET = "" 89 | OKX_API_PASSPHRAS = "" 90 | 91 | # BITGET API KEYS https://www.bitget.com/ru/account/newapi 92 | BITGET_API_KEY = "" 93 | BITGET_API_SECRET = "" 94 | BITGET_API_PASSPHRAS = "" 95 | 96 | # BINGX API KEYS https://bingx.com/ru-ru/account/api/ 97 | BINGX_API_KEY = "" 98 | BINGX_API_SECRET = "" 99 | 100 | # BINANCE API KEYS https://www.binance.com/ru/my/settings/api-management 101 | BINANCE_API_KEY = "" 102 | BINANCE_API_SECRET = "" 103 | 104 | # EXCEL AND GOOGLE INFO 105 | EXCEL_PASSWORD = False 106 | EXCEL_PAGE_NAME = "EVM" 107 | EXCEL_FILE_PATH = "./data/accounts_data.xlsx" # Можете не изменять, если устраивает дефолтное расположение таблицы 108 | 109 | # TELEGRAM DATA 110 | TG_TOKEN = "" # https://t.me/BotFather 111 | TG_ID = "" # https://t.me/getmyid_bot 112 | 113 | # INCH API KEY https://portal.1inch.dev/dashboard 114 | ONEINCH_API_KEY = "" 115 | 116 | # LAYERSWAP API KEY https://www.layerswap.io/dashboard 117 | LAYERSWAP_API_KEY = "" 118 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import sys 3 | 4 | from config import TITLE 5 | from termcolor import cprint 6 | from modules import txchecker 7 | from questionary import Choice, select 8 | 9 | from modules.interfaces import SoftwareException 10 | from utils.modules_runner import Runner 11 | from utils.route_generator import RouteGenerator 12 | from utils.tools import create_cex_withdrawal_list, check_progress_file 13 | 14 | 15 | def are_you_sure(module=None, gen_route: bool = False): 16 | if gen_route or check_progress_file(): 17 | answer = select( 18 | '\n ⚠️⚠️⚠️ THAT ACTION WILL DELETE ALL PREVIOUS PROGRESS FOR CLASSIC-ROUTES, continue? ⚠️⚠️⚠️ \n', 19 | choices=[ 20 | Choice("❌ NO", 'main'), 21 | Choice("✅ YES", 'module'), 22 | ], 23 | qmark='☢️', 24 | pointer='👉' 25 | ).ask() 26 | print() 27 | if answer == 'main': 28 | main() 29 | else: 30 | if module: 31 | module() 32 | 33 | 34 | def main(): 35 | cprint(TITLE, 'light_red') 36 | cprint(f'\n❤️ My channel for latest updates: https://t.me/askaer\n', 'light_cyan', attrs=["blink"]) 37 | try: 38 | while True: 39 | answer = select( 40 | 'What do you want to do?', 41 | choices=[ 42 | Choice("🚀 Start running classic routes for each wallet", 'classic_routes_run'), 43 | Choice("📄 Generate classic-route for each wallet", 'classic_routes_gen'), 44 | Choice("💾 Create and safe CEX withdrawal file", 'create_cex_list'), 45 | Choice("✅ Check the connection of each proxy", 'check_proxy'), 46 | Choice("📊 Get TX stats for all wallets", 'tx_stat'), 47 | Choice('❌ Exit', "exit") 48 | ], 49 | qmark='🛠️', 50 | pointer='👉' 51 | ).ask() 52 | 53 | runner = Runner() 54 | 55 | if answer == 'check_proxy': 56 | print() 57 | asyncio.run(runner.check_proxies_status()) 58 | print() 59 | elif answer == 'classic_routes_run': 60 | print() 61 | asyncio.run(runner.run_accounts()) 62 | print() 63 | elif answer == 'create_cex_list': 64 | print() 65 | create_cex_withdrawal_list() 66 | print() 67 | elif answer == 'tx_stat': 68 | print() 69 | asyncio.run(txchecker.main()) 70 | print() 71 | elif answer == 'classic_routes_gen': 72 | generator = RouteGenerator() 73 | are_you_sure(generator.classic_routes_json_save, gen_route=True) 74 | elif answer == 'exit': 75 | sys.exit() 76 | elif answer is not None: 77 | print() 78 | answer() 79 | print() 80 | else: 81 | raise KeyboardInterrupt 82 | except KeyboardInterrupt: 83 | cprint(f'\nQuick software shutdown by ', color='light_yellow') 84 | sys.exit() 85 | 86 | except SoftwareException as error: 87 | cprint(f'\n{error}', color='light_red') 88 | sys.exit() 89 | 90 | 91 | if __name__ == "__main__": 92 | main() 93 | -------------------------------------------------------------------------------- /modules/__init__.py: -------------------------------------------------------------------------------- 1 | from .interfaces import DEX, RequestClient, Bridge, Refuel, Messenger, Landing, Minter, Blockchain, Creator, CEX, Logger 2 | from .client import Client 3 | from .custom_modules import Custom 4 | from .blockchains import ZkSync, Base, Scroll, Linea, ArbitrumNova, Zora, Ethereum, Blast, SimpleEVM 5 | from .swaps import Izumi 6 | from .swaps import Odos 7 | from .swaps import OneInch 8 | from .swaps import Uniswap 9 | from .other import MintFun 10 | from .bridges import LayerSwap 11 | from .bridges import Orbiter 12 | from .bridges import Rhino 13 | from .bridges import Across 14 | from .bridges import Relay 15 | from .bridges import Owlto 16 | from .bridges import Bungee 17 | from .bridges import Nitro 18 | from .cexs import OKX 19 | from .cexs import BingX 20 | from .cexs import Binance 21 | from .cexs import Bitget 22 | -------------------------------------------------------------------------------- /modules/blockchains/__init__.py: -------------------------------------------------------------------------------- 1 | from .evm import ZkSync, Scroll, Base, Linea, ArbitrumNova, Zora, Ethereum, Blast, SimpleEVM 2 | -------------------------------------------------------------------------------- /modules/bridges/__init__.py: -------------------------------------------------------------------------------- 1 | from .layerswap import LayerSwap 2 | from .orbiter import Orbiter 3 | from .rhino import Rhino 4 | from .across import Across 5 | from .relay import Relay 6 | from .owlto import Owlto 7 | from .bungee import Bungee 8 | from .nitro import Nitro 9 | -------------------------------------------------------------------------------- /modules/bridges/across.py: -------------------------------------------------------------------------------- 1 | from modules import Bridge, Logger 2 | from modules.interfaces import BridgeExceptionWithoutRetry 3 | from config import TOKENS_PER_CHAIN, ACROSS_ABI, CHAIN_NAME_FROM_ID, ACROSS_CONTRACT 4 | from general_settings import GAS_LIMIT_MULTIPLIER 5 | 6 | 7 | class Across(Bridge, Logger): 8 | def __init__(self, client): 9 | self.client = client 10 | Logger.__init__(self) 11 | Bridge.__init__(self, client) 12 | self.network = self.client.network.name 13 | 14 | async def get_bridge_fee(self, chain_id, amount_in_wei, token_address): 15 | url = 'https://across.to/api/suggested-fees' 16 | 17 | params = { 18 | 'token': token_address, 19 | 'destinationChainId': chain_id, 20 | 'amount': amount_in_wei, 21 | 'originChainId': self.client.chain_id 22 | } 23 | 24 | fees_data = await self.make_request(url=url, params=params) 25 | 26 | return (fees_data['spokePoolAddress'], int(fees_data['relayFeePct']), 27 | int(fees_data['relayGasFeeTotal']), int(fees_data['timestamp'])) 28 | 29 | async def get_bridge_limits(self, chain_id, token_address): 30 | url = 'https://across.to/api/limits' 31 | 32 | params = { 33 | 'token': token_address, 34 | 'destinationChainId': chain_id, 35 | 'originChainId': self.client.chain_id 36 | } 37 | 38 | limits_data = await self.make_request(url=url, params=params) 39 | 40 | return int(limits_data['minDeposit']), int(limits_data['maxDeposit']) 41 | 42 | async def check_available_routes(self, chain_id, token_name): 43 | url = 'https://across.to/api/available-routes' 44 | 45 | params = { 46 | 'originChainId': self.client.chain_id, 47 | 'destinationChainId': chain_id, 48 | 'originToken': TOKENS_PER_CHAIN[self.network][token_name], 49 | 'destinationToken': TOKENS_PER_CHAIN[CHAIN_NAME_FROM_ID[chain_id]][token_name], 50 | } 51 | 52 | return await self.make_request(url=url, params=params) 53 | 54 | async def bridge(self, chain_from_id: int, bridge_data: tuple, need_check: bool = False): 55 | from_chain, to_chain, amount, to_chain_id, token_name, _, from_token_address, to_token_address = bridge_data 56 | 57 | if not need_check: 58 | bridge_info = f'{self.client.network.name} -> {token_name} {CHAIN_NAME_FROM_ID[to_chain]}' 59 | self.logger_msg(*self.client.acc_info, msg=f'Bridge on Across: {amount} {token_name} {bridge_info}') 60 | 61 | decimals = await self.client.get_decimals(token_address=from_token_address) 62 | amount_in_wei = self.client.to_wei(amount, decimals) 63 | 64 | min_limit, max_limit = await self.get_bridge_limits(to_chain, from_token_address) 65 | 66 | if min_limit <= amount_in_wei <= max_limit: 67 | 68 | if await self.check_available_routes(to_chain, token_name): 69 | 70 | pool_adress, relay_fee_pct, relay_gas_fee_total, timestamp = await self.get_bridge_fee( 71 | to_chain, amount_in_wei, from_token_address 72 | ) 73 | 74 | if need_check: 75 | return round(float(relay_gas_fee_total / 10 ** 18), 6) 76 | 77 | data = [ 78 | self.client.address, 79 | from_token_address, 80 | amount_in_wei, 81 | to_chain, 82 | relay_fee_pct, 83 | timestamp, 84 | "0x", 85 | 2**256-1 86 | ] 87 | 88 | if from_chain == 324: 89 | router_contract = self.client.get_contract(contract_address=pool_adress, abi=ACROSS_ABI['pool']) 90 | else: 91 | data.insert(0, pool_adress) 92 | router_contract = self.client.get_contract( 93 | contract_address=ACROSS_CONTRACT[self.network], abi=ACROSS_ABI['router'] 94 | ) 95 | 96 | if token_name != self.client.token: 97 | value = 0 98 | await self.client.check_for_approved( 99 | TOKENS_PER_CHAIN[self.client.network.name][token_name], router_contract.address, amount_in_wei 100 | ) 101 | 102 | else: 103 | value = amount_in_wei 104 | 105 | transaction = await router_contract.functions.deposit( 106 | *data 107 | ).build_transaction(await self.client.prepare_transaction(value=value)) 108 | 109 | transaction['data'] += 'd00dfeeddeadbeef000000a679c2fb345ddefbae3c42bee92c0fb7a5' 110 | transaction['gas'] = int(transaction['gas'] * GAS_LIMIT_MULTIPLIER) 111 | 112 | old_balance_on_dst = await self.client.wait_for_receiving( 113 | token_address=to_token_address, chain_id=to_chain_id, check_balance_on_dst=True 114 | ) 115 | 116 | await self.client.send_transaction(transaction) 117 | 118 | self.logger_msg(*self.client.acc_info, 119 | msg=f"Bridge complete. Note: wait a little for receiving funds", type_msg='success') 120 | 121 | return await self.client.wait_for_receiving( 122 | token_address=to_token_address, old_balance=old_balance_on_dst, chain_id=to_chain_id 123 | ) 124 | 125 | else: 126 | raise BridgeExceptionWithoutRetry(f'Bridge route is not available!') 127 | else: 128 | min_limit, max_limit = min_limit / 10 ** 18, max_limit / 10 ** 18 129 | raise BridgeExceptionWithoutRetry(f'Limit range for bridge: {min_limit:.5f} - {max_limit:.2f} ETH!') 130 | -------------------------------------------------------------------------------- /modules/bridges/bungee.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | from modules import Refuel, Logger, Client 4 | from modules.interfaces import SoftwareException, Bridge, BridgeExceptionWithoutRetry 5 | from settings import DST_CHAIN_BUNGEE_REFUEL, BUNGEE_ROUTE_TYPE 6 | from utils.tools import gas_checker, helper 7 | from config import ( 8 | BUNGEE_CONTRACTS, 9 | BUNGEE_ABI, 10 | BUNGEE_CHAINS_IDS, 11 | LAYERZERO_NETWORKS_DATA, 12 | CHAIN_NAME_FROM_ID, ETH_MASK, COINGECKO_TOKEN_API_NAMES 13 | ) 14 | 15 | 16 | class Bungee(Refuel, Bridge, Logger): 17 | def __init__(self, client: Client): 18 | self.client = client 19 | Logger.__init__(self) 20 | Bridge.__init__(self, client) 21 | 22 | self.network = self.client.network.name 23 | self.refuel_contract = self.client.get_contract( 24 | BUNGEE_CONTRACTS[self.network]['gas_refuel'], BUNGEE_ABI['refuel'] 25 | ) 26 | 27 | async def get_limits_data(self): 28 | url = 'https://refuel.socket.tech/chains' 29 | 30 | async with self.client.session.get(url=url) as response: 31 | if response.status == 200: 32 | data = await response.json() 33 | network_name = self.network if self.network != 'BNB Chain' else 'BSC' 34 | return [chain for chain in data['result'] if chain['name'] == network_name][0] 35 | raise SoftwareException(f'Bad request to Bungee API: {response.status}') 36 | 37 | @helper 38 | @gas_checker 39 | async def refuel(self): 40 | dst_data = random.choice(list(DST_CHAIN_BUNGEE_REFUEL.items())) 41 | dst_chain_name, _, dst_native_name, _ = LAYERZERO_NETWORKS_DATA[dst_data[0]] 42 | dst_amount = await self.client.get_smart_amount(dst_data[1]) 43 | 44 | refuel_info = f'{dst_amount} {self.client.network.token} from {self.client.network.name} to {dst_chain_name}' 45 | self.logger_msg(*self.client.acc_info, msg=f'Refuel on Bungee: {refuel_info}') 46 | 47 | refuel_limits_data = await self.get_limits_data() 48 | 49 | if refuel_limits_data['isSendingEnabled']: 50 | dst_chain_id = BUNGEE_CHAINS_IDS[f'{dst_chain_name}'] 51 | limits_dst_chain_data = {} 52 | 53 | for chain_limits in refuel_limits_data['limits']: 54 | if chain_limits['chainId'] == dst_chain_id: 55 | limits_dst_chain_data = chain_limits 56 | break 57 | 58 | if 'isEnabled' in limits_dst_chain_data and limits_dst_chain_data['isEnabled']: 59 | min_amount_in_wei = int(limits_dst_chain_data['minAmount']) 60 | max_amount_in_wei = int(limits_dst_chain_data['maxAmount']) 61 | 62 | min_amount = round(self.client.w3.from_wei(min_amount_in_wei, 'ether') * 100000) / 100000 63 | max_amount = round(self.client.w3.from_wei(max_amount_in_wei, 'ether') * 100000) / 100000 64 | 65 | amount_in_wei = self.client.to_wei(dst_amount) 66 | 67 | if min_amount_in_wei <= amount_in_wei <= max_amount_in_wei: 68 | 69 | if await self.client.w3.eth.get_balance(self.client.address) >= amount_in_wei: 70 | 71 | tx_params = await self.client.prepare_transaction(value=amount_in_wei) 72 | 73 | transaction = await self.refuel_contract.functions.depositNativeToken( 74 | dst_chain_id, 75 | self.client.address 76 | ).build_transaction(tx_params) 77 | 78 | return await self.client.send_transaction(transaction) 79 | 80 | else: 81 | raise SoftwareException("Insufficient balance!") 82 | else: 83 | raise SoftwareException(f'Limit range for refuel: {min_amount} - {max_amount} ETH!') 84 | else: 85 | raise SoftwareException('Destination chain refuel is not active!') 86 | else: 87 | raise SoftwareException('Source chain refuel is not active!') 88 | 89 | async def get_quote(self, to_chain_id, from_token_address, to_token_address, amount, need_check): 90 | url = 'https://api.socket.tech/v2/quote' 91 | 92 | wanted_route = { 93 | 1: 'across', 94 | 2: 'cctp', 95 | 3: 'celer', 96 | 4: 'connext', 97 | 5: 'stargate', 98 | 6: 'refuel-bridge', 99 | 7: 'synapse', 100 | 8: 'symbiosis', 101 | 9: 'hop', 102 | 10: 'hyphen', 103 | 104 | }.get(BUNGEE_ROUTE_TYPE, False) 105 | 106 | params = { 107 | "fromChainId": self.client.chain_id, 108 | "toChainId": to_chain_id, 109 | "fromTokenAddress": from_token_address, 110 | "toTokenAddress": to_token_address, 111 | "fromAmount": amount, 112 | "userAddress": self.client.address, 113 | "singleTxOnly": "false", 114 | "bridgeWithGas": "false", 115 | "sort": "output", 116 | "defaultSwapSlippage": 0.5, 117 | "bridgeWithInsurance": "true", 118 | "isContractCall": "false", 119 | "showAutoRoutes": "false", 120 | } 121 | 122 | response = await self.make_request(url=url, params=params, headers=self.headers) 123 | final_route = None 124 | if response['success']: 125 | all_routes = response['result']['routes'] 126 | if wanted_route: 127 | for route in all_routes: 128 | if route['usedBridgeNames'][0] == wanted_route and int(route['totalUserTx']) == 1: 129 | final_route = route 130 | break 131 | 132 | if final_route and need_check: 133 | return final_route 134 | elif not final_route and need_check: 135 | return all_routes[0] 136 | elif final_route: 137 | if not need_check: 138 | self.logger_msg( 139 | *self.client.acc_info, 140 | msg=f'Successfully found {wanted_route.capitalize()} route. Initialize bridge...', 141 | type_msg='success' 142 | ) 143 | else: 144 | self.logger_msg( 145 | *self.client.acc_info, 146 | msg=f'Will take {all_routes[0]["usedBridgeNames"][0].capitalize()} route. Initialize bridge...', 147 | ) 148 | final_route = all_routes[0] 149 | 150 | if final_route['extraData']: 151 | rewards = final_route['extraData'].get('rewards') 152 | if rewards: 153 | for reward in rewards: 154 | amount_in_wei = int(reward['amount']) 155 | decimals = int(reward['asset']['decimals']) 156 | amount = round(amount_in_wei / 10 ** decimals, 3) 157 | symbol = reward['asset']['symbol'] 158 | amount_in_usd = round(float(reward['amountInUsd']), 2) 159 | chain_name = f"{CHAIN_NAME_FROM_ID[int(reward['chainId'])]} chain" 160 | 161 | self.logger_msg( 162 | *self.client.acc_info, 163 | msg=f'This TX will be rewarded with {amount} {symbol} ({amount_in_usd}$) in {chain_name}', 164 | type_msg='success' 165 | ) 166 | return final_route 167 | 168 | raise BridgeExceptionWithoutRetry(f'Bad request to Bungee API: {await response.text()}') 169 | 170 | async def build_tx(self, route:dict): 171 | url = 'https://api.socket.tech/v2/build-tx' 172 | 173 | payload = { 174 | 'route': route 175 | } 176 | 177 | response = await self.make_request(method="POST", url=url, json=payload, headers=self.headers) 178 | 179 | if response['success']: 180 | tx_data = response['result']['txData'] 181 | value = int(response['result']['value'], 16) 182 | contract_address = self.client.w3.to_checksum_address(response['result']['txTarget']) 183 | 184 | return tx_data, value, contract_address 185 | raise BridgeExceptionWithoutRetry(f'Bad request to Bungee API: {await response.text()}') 186 | 187 | async def bridge(self, chain_from_id: int, bridge_data: tuple, need_check: bool = False): 188 | (from_chain, to_chain, amount, to_chain_id, from_token_name, 189 | to_token_name, from_token_address, to_token_address) = bridge_data 190 | 191 | if not need_check: 192 | bridge_info = f'{self.client.network.name} -> {to_token_name} {CHAIN_NAME_FROM_ID[to_chain]}' 193 | self.logger_msg(*self.client.acc_info, msg=f'Bridge on Bungee: {amount} {from_token_name} {bridge_info}') 194 | 195 | decimals = await self.client.get_decimals(token_address=from_token_address) 196 | amount_in_wei = self.client.to_wei(amount, decimals=decimals) 197 | 198 | if from_token_name == 'ETH': 199 | from_token_address = ETH_MASK 200 | if to_token_name == 'ETH': 201 | to_token_address = ETH_MASK 202 | 203 | route_data = await self.get_quote(to_chain, from_token_address, to_token_address, amount_in_wei, need_check) 204 | 205 | if need_check: 206 | token_price = await self.client.get_token_price(COINGECKO_TOKEN_API_NAMES[from_token_name]) 207 | return float(route_data['totalGasFeesInUsd']) / token_price 208 | 209 | tx_data, value, to_address = await self.build_tx(route_data) 210 | 211 | if from_token_name != self.client.token: 212 | await self.client.check_for_approved(from_token_address, to_address, amount_in_wei) 213 | 214 | transaction = await self.client.prepare_transaction(value=value) | { 215 | 'to': to_address, 216 | 'data': tx_data 217 | } 218 | 219 | old_balance_on_dst = await self.client.wait_for_receiving( 220 | token_address=to_token_address, token_name=to_token_name, chain_id=to_chain_id, check_balance_on_dst=True 221 | ) 222 | 223 | await self.client.send_transaction(transaction) 224 | 225 | self.logger_msg(*self.client.acc_info, 226 | msg=f"Bridge complete. Note: wait a little for receiving funds", type_msg='success') 227 | 228 | return await self.client.wait_for_receiving( 229 | token_address=to_token_address, token_name=to_token_name, old_balance=old_balance_on_dst, 230 | chain_id=to_chain_id 231 | ) 232 | 233 | @helper 234 | @gas_checker 235 | async def claim_rewards(self): 236 | url = 'https://microservices.socket.tech/loki/rewards/get-claim-data' 237 | 238 | params = { 239 | 'address': self.client.address 240 | } 241 | 242 | claim_contract = self.client.get_contract(BUNGEE_CONTRACTS[self.network]['claim'], BUNGEE_ABI['claim']) 243 | 244 | response = await self.make_request(url=url, params=params) 245 | 246 | if response['success']: 247 | rewards = response['result'] 248 | if rewards: 249 | for reward in rewards: 250 | claimable_amount_in_wei = int(reward['claimableAmount']) 251 | claimed_amount_in_wei = int(reward['claimedAmount']) 252 | pending_amount_in_wei = int(reward['pendingAmount']) 253 | merkle_proof = reward['proof'] 254 | symbol = reward['asset']['symbol'] 255 | decimals = int(reward['asset']['decimals']) 256 | chain_id = int(reward['asset']['chainId']) 257 | 258 | if claimable_amount_in_wei != 0: 259 | 260 | clmbl_amount = claimable_amount_in_wei / 10 ** decimals 261 | clmd_amount = claimed_amount_in_wei / 10 ** decimals 262 | pend_amount = pending_amount_in_wei / 10 ** decimals 263 | 264 | self.logger_msg( 265 | *self.client.acc_info, 266 | msg=f'Claimable: {clmbl_amount:.3f} ${symbol}. Claimed: {clmd_amount:.3f} ${symbol}.' 267 | f' Pending: {pend_amount:.3f} ${symbol}', type_msg='success') 268 | 269 | self.logger_msg( 270 | *self.client.acc_info, 271 | msg=f'Start claiming {clmbl_amount} ${symbol} on {CHAIN_NAME_FROM_ID[chain_id]} chain' 272 | ) 273 | 274 | transaction = await claim_contract.functions.claim( 275 | self.client.address, 276 | claimable_amount_in_wei, 277 | merkle_proof 278 | ).build_transaction(await self.client.prepare_transaction()) 279 | 280 | await self.client.send_transaction(transaction) 281 | else: 282 | self.logger_msg(*self.client.acc_info, msg=f'No tokens are available for claiming') 283 | 284 | return True 285 | 286 | raise BridgeExceptionWithoutRetry(f'Bad request to Bungee API: {await response.text()}') 287 | -------------------------------------------------------------------------------- /modules/bridges/layerswap.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from modules import Bridge, Logger 4 | from modules.interfaces import BridgeExceptionWithoutRetry 5 | 6 | 7 | class LayerSwap(Bridge, Logger): 8 | def __init__(self, client): 9 | self.client = client 10 | Logger.__init__(self) 11 | Bridge.__init__(self, client) 12 | 13 | async def get_networks_data(self): 14 | url = "https://api.layerswap.io/api/v2/networks" 15 | 16 | return (await self.make_request(url=url, headers=self.headers))['data'] 17 | 18 | async def get_swap_limits(self, source_chain, destination_chain, source_asset, destination_asset, _): 19 | url = "https://api.layerswap.io/api/v2/limits" 20 | 21 | params = { 22 | "source_network": source_chain, 23 | "source_token": source_asset, 24 | "destination_network": destination_chain, 25 | "destination_token": destination_asset, 26 | } 27 | 28 | response = (await self.make_request(url=url, headers=self.headers, params=params))['data'] 29 | 30 | min_amount = response['min_amount'] 31 | max_amount = response['max_amount'] 32 | 33 | return min_amount, max_amount 34 | 35 | async def get_swap_fee(self, source_chain, destination_chain, source_asset, destination_asset, amount): 36 | url = "https://api.layerswap.io/api/v2/quote" 37 | 38 | params = { 39 | "source_network": source_chain, 40 | "destination_network": destination_chain, 41 | "source_token": source_asset, 42 | "destination_token": destination_asset, 43 | "amount": amount, 44 | } 45 | 46 | response = (await self.make_request(url=url, headers=self.headers, params=params))['data']['quote'] 47 | 48 | fee_amount = response['total_fee'] 49 | receive_amount = response['min_receive_amount'] 50 | 51 | return fee_amount, receive_amount 52 | 53 | async def get_swap_data( 54 | self, amount, source_chain, destination_chain, source_asset, destination_asset, refuel 55 | ): 56 | 57 | url = "https://api.layerswap.io/api/v2/swaps" 58 | 59 | create_swap_data = { 60 | "source_network": source_chain, 61 | "destination_network": destination_chain, 62 | "source_token": source_asset, 63 | "destination_token": destination_asset, 64 | "amount": amount, 65 | "source_address": self.client.address, 66 | "destination_address": self.client.address, 67 | 68 | } 69 | 70 | return await self.make_request( 71 | method='POST', url=url, headers=self.headers, json=create_swap_data 72 | ) 73 | 74 | async def create_tx(self, swap_data): 75 | 76 | swap_id = swap_data['data']['swap']['id'] 77 | 78 | url = f"https://api.layerswap.io/api/v2/swaps/{swap_id}" 79 | 80 | params = { 81 | 'from_address': f"{self.client.address}" 82 | } 83 | 84 | response = await self.make_request(url=url, headers=self.headers, params=params, json=swap_data) 85 | 86 | call_data = response['data']['deposit_actions'][0]['call_data'] 87 | to_address = response['data']['deposit_actions'][0]['to_address'] 88 | 89 | return call_data, to_address 90 | 91 | async def bridge(self, chain_from_id: int, bridge_data: tuple, need_check: bool = False): 92 | (source_chain, destination_chain, amount, to_chain_id, from_token_name, 93 | to_token_name, from_token_address, to_token_address) = bridge_data 94 | source_asset, destination_asset, refuel = from_token_name, to_token_name, False 95 | 96 | bridge_info = f'{self.client.network.name} -> {destination_asset} {destination_chain}' 97 | if not need_check: 98 | self.logger_msg(*self.client.acc_info, msg=f'Bridge on LayerSwap: {amount} {source_asset} {bridge_info}') 99 | 100 | networks_data = await self.get_networks_data() 101 | 102 | available_for_swap = { 103 | chain['name']: [(asset['symbol'], asset['decimals']) for asset in chain['tokens'] 104 | if asset['symbol'] in [from_token_name, to_token_name]][0] 105 | for chain in networks_data if chain['name'] in [source_chain, destination_chain] 106 | } 107 | 108 | if (len(available_for_swap) == 2 and available_for_swap[source_chain] 109 | and available_for_swap[destination_chain]): 110 | 111 | data = source_chain, destination_chain, source_asset, destination_asset, amount 112 | 113 | min_amount, max_amount = await self.get_swap_limits(*data) 114 | fee_amount, receive_amount = await self.get_swap_fee(*data) 115 | 116 | if need_check: 117 | return round(float(fee_amount), 6) 118 | 119 | if float(min_amount) <= amount <= float(max_amount): 120 | 121 | amount_in_wei = self.client.to_wei(amount, available_for_swap[source_chain][1]) 122 | 123 | swap_data = await self.get_swap_data(amount, *data) 124 | call_data, to_address = await self.create_tx(swap_data) 125 | 126 | if from_token_name != self.client.token: 127 | value = 0 128 | else: 129 | value = amount_in_wei 130 | 131 | transaction = (await self.client.prepare_transaction(value=value)) | { 132 | 'to': self.client.w3.to_checksum_address(to_address), 133 | 'data': call_data 134 | } 135 | 136 | old_balance_on_dst = await self.client.wait_for_receiving( 137 | token_address=to_token_address, token_name=to_token_name, chain_id=to_chain_id, 138 | check_balance_on_dst=True 139 | ) 140 | 141 | await self.client.send_transaction(transaction) 142 | 143 | self.logger_msg(*self.client.acc_info, 144 | msg=f"Bridge complete. Note: wait a little for receiving funds", type_msg='success') 145 | 146 | return await self.client.wait_for_receiving( 147 | token_address=to_token_address, token_name=to_token_name, 148 | old_balance=old_balance_on_dst, chain_id=to_chain_id 149 | ) 150 | 151 | else: 152 | raise BridgeExceptionWithoutRetry(f"Limit range for bridge: {min_amount} - {max_amount} ETH") 153 | else: 154 | raise BridgeExceptionWithoutRetry(f"Bridge {source_asset} {bridge_info} is not active!") 155 | -------------------------------------------------------------------------------- /modules/bridges/nitro.py: -------------------------------------------------------------------------------- 1 | from config import ETH_MASK, CHAIN_NAME_FROM_ID 2 | from modules import Bridge, Logger, Client 3 | 4 | 5 | class Nitro(Bridge, Logger): 6 | def __init__(self, client: Client): 7 | self.client = client 8 | Logger.__init__(self) 9 | Bridge.__init__(self, client) 10 | 11 | self.chain_ids = { 12 | "ethereum": "1", 13 | "arbitrum": "42161", 14 | "optimism": "10", 15 | "zksync": "324", 16 | "scroll": "534352", 17 | "base": "8453", 18 | "linea": "59144", 19 | } 20 | 21 | async def get_quote(self, to_chain_id, from_token_address, to_token_address, amount): 22 | url = "https://api-beta.pathfinder.routerprotocol.com/api/v2/quote" 23 | 24 | params = { 25 | "fromTokenAddress": from_token_address, 26 | "toTokenAddress": to_token_address, 27 | "amount": amount, 28 | "fromTokenChainId": self.client.chain_id, 29 | "toTokenChainId": to_chain_id, 30 | "partnerId": 1 31 | } 32 | 33 | return await self.make_request(url=url, params=params) 34 | 35 | async def build_tx(self, quote: dict): 36 | url = "https://api-beta.pathfinder.routerprotocol.com/api/v2/transaction" 37 | 38 | quote |= { 39 | 'receiverAddress': self.client.address, 40 | 'senderAddress': self.client.address 41 | } 42 | 43 | response = await self.make_request(method="POST", url=url, json=quote) 44 | 45 | return response['txn']['data'], self.client.w3.to_checksum_address(response['txn']['to']) 46 | 47 | async def bridge(self, chain_from_id: int, bridge_data: tuple, need_check: bool = False): 48 | (from_chain, to_chain, amount, to_chain_id, from_token_name, 49 | to_token_name, from_token_address, to_token_address) = bridge_data 50 | 51 | if need_check: 52 | return 0 53 | 54 | bridge_info = f'{self.client.network.name} -> {to_token_name} {CHAIN_NAME_FROM_ID[to_chain]}' 55 | self.logger_msg(*self.client.acc_info, msg=f'Bridge on Nitro: {amount} {from_token_name} {bridge_info}') 56 | 57 | decimals = await self.client.get_decimals(token_address=from_token_address) 58 | amount_in_wei = self.client.to_wei(amount, decimals=decimals) 59 | 60 | if from_token_name == 'ETH': 61 | from_token_address = ETH_MASK 62 | if to_token_name == 'ETH': 63 | to_token_address = ETH_MASK 64 | 65 | route_data = await self.get_quote(to_chain, from_token_address, to_token_address, amount_in_wei) 66 | tx_data, to_address = await self.build_tx(route_data) 67 | 68 | if from_token_name != self.client.token: 69 | await self.client.check_for_approved(from_token_address, to_address, amount_in_wei) 70 | 71 | transaction = await self.client.prepare_transaction(value=amount_in_wei) | { 72 | 'to': to_address, 73 | 'data': tx_data 74 | } 75 | 76 | old_balance_on_dst = await self.client.wait_for_receiving( 77 | token_address=to_token_address, token_name=to_token_name, chain_id=to_chain_id, check_balance_on_dst=True 78 | ) 79 | 80 | await self.client.send_transaction(transaction) 81 | 82 | self.logger_msg(*self.client.acc_info, 83 | msg=f"Bridge complete. Note: wait a little for receiving funds", type_msg='success') 84 | 85 | return await self.client.wait_for_receiving( 86 | token_address=to_token_address, token_name=to_token_name, old_balance=old_balance_on_dst, 87 | chain_id=to_chain_id 88 | ) -------------------------------------------------------------------------------- /modules/bridges/orbiter.py: -------------------------------------------------------------------------------- 1 | import json 2 | from modules import Bridge, Logger 3 | from modules.interfaces import BridgeExceptionWithoutRetry, SoftwareExceptionWithoutRetry 4 | from web3 import AsyncWeb3 5 | 6 | 7 | class Orbiter(Bridge, Logger): 8 | def __init__(self, client): 9 | self.client = client 10 | Logger.__init__(self) 11 | Bridge.__init__(self, client) 12 | 13 | @staticmethod 14 | def get_maker_data(from_id:int, to_id:int, token_name: str): 15 | 16 | paths = ['orbiter_maker1.json', 'orbiter_maker2.json', 'orbiter_maker3.json', 17 | 'orbiter_maker4.json', 'orbiter_maker5.json'] 18 | for path in paths: 19 | try: 20 | with open(f'./data/services/{path}') as file: 21 | data = json.load(file) 22 | 23 | maker_data = data[f"{from_id}-{to_id}"][f"{token_name}-{token_name}"] 24 | 25 | bridge_data = { 26 | 'maker': maker_data['makerAddress'], 27 | 'fee': maker_data['tradingFee'], 28 | 'min_amount': maker_data['minPrice'], 29 | 'max_amount': maker_data['maxPrice'], 30 | } 31 | 32 | if bridge_data: 33 | return bridge_data 34 | except KeyError: 35 | pass 36 | 37 | raise BridgeExceptionWithoutRetry(f'That bridge is not active!') 38 | 39 | async def bridge(self, chain_from_id: int, bridge_data: tuple, need_check: bool = False): 40 | from_chain, to_chain, amount, to_chain_id, token_name, _, from_token_address, to_token_address = bridge_data 41 | 42 | if not need_check: 43 | bridge_info = f'{amount} {token_name} from {from_chain["name"]} to {to_chain["name"]}' 44 | self.logger_msg(*self.client.acc_info, msg=f'Bridge on Orbiter: {bridge_info}') 45 | 46 | bridge_data = self.get_maker_data(from_chain['id'], to_chain['id'], token_name) 47 | destination_code = 9000 + to_chain['id'] 48 | decimals = await self.client.get_decimals(token_address=from_token_address) 49 | fee = int(float(bridge_data['fee']) * 10 ** decimals) 50 | amount_in_wei = self.client.to_wei(amount, decimals) 51 | full_amount = int(round(amount_in_wei + fee, -4) + destination_code) 52 | 53 | if need_check: 54 | return round(float(fee / 10 ** decimals), 6) 55 | 56 | min_price, max_price = bridge_data['min_amount'], bridge_data['max_amount'] 57 | 58 | if token_name != self.client.network.token: 59 | contract = self.client.get_contract(from_token_address) 60 | 61 | transaction = await contract.functions.transfer( 62 | AsyncWeb3.to_checksum_address(bridge_data['maker']), 63 | full_amount 64 | ).build_transaction(await self.client.prepare_transaction()) 65 | else: 66 | transaction = (await self.client.prepare_transaction(value=full_amount)) | { 67 | 'to': self.client.w3.to_checksum_address(bridge_data['maker']) 68 | } 69 | 70 | if min_price <= amount <= max_price: 71 | if int(f"{full_amount}"[-4:]) != destination_code: 72 | raise SoftwareExceptionWithoutRetry('Math problem in Python. Machine will save your money =)') 73 | 74 | old_balance_on_dst = await self.client.wait_for_receiving( 75 | token_address=to_token_address, chain_id=to_chain_id, check_balance_on_dst=True 76 | ) 77 | 78 | await self.client.send_transaction(transaction) 79 | 80 | self.logger_msg(*self.client.acc_info, 81 | msg=f"Bridge complete. Note: wait a little for receiving funds", type_msg='success') 82 | 83 | return await self.client.wait_for_receiving( 84 | token_address=to_token_address, chain_id=to_chain_id, old_balance=old_balance_on_dst 85 | ) 86 | 87 | else: 88 | raise BridgeExceptionWithoutRetry(f"Limit range for bridge: {min_price} – {max_price} {token_name}!") 89 | -------------------------------------------------------------------------------- /modules/bridges/owlto.py: -------------------------------------------------------------------------------- 1 | from modules import Bridge, Logger 2 | from modules.interfaces import BridgeExceptionWithoutRetry, SoftwareExceptionWithoutRetry, RequestClient 3 | from config import CHAIN_NAME_FROM_ID 4 | from web3 import AsyncWeb3 5 | 6 | 7 | class Owlto(Bridge, Logger, RequestClient): 8 | def __init__(self, client): 9 | self.client = client 10 | Logger.__init__(self) 11 | Bridge.__init__(self, client) 12 | RequestClient.__init__(self, client) 13 | 14 | async def get_chains_info(self, from_chain_id:int, to_chain_id:str): 15 | 16 | url = 'https://owlto.finance/api/config/all-chains' 17 | 18 | bridge_config = (await self.make_request(url=url))['msg'] 19 | 20 | to_chain_info = [ 21 | { 22 | 'name': bridge_data['name'], 23 | 'networkCode': bridge_data['networkCode'], 24 | } 25 | for bridge_data in bridge_config 26 | if bridge_data['chainId'] == to_chain_id or bridge_data['aliasName'] == CHAIN_NAME_FROM_ID[to_chain_id]] 27 | 28 | from_chain_info = [ 29 | { 30 | 'name': bridge_data['name'], 31 | } 32 | for bridge_data in bridge_config 33 | if bridge_data['chainId'] == from_chain_id or bridge_data['aliasName'] == self.client.network.name] 34 | 35 | if from_chain_info and to_chain_info: 36 | return from_chain_info[0], to_chain_info[0] 37 | raise BridgeExceptionWithoutRetry(f'That bridge is not active!') 38 | 39 | async def get_lp_config(self, chain_id:int, token_name:str): 40 | 41 | url = 'https://owlto.finance/api/lp-info' 42 | 43 | params = { 44 | 'token': token_name, 45 | 'from_chainid': self.client.network.chain_id, 46 | 'to_chainid': chain_id, 47 | 'user': self.client.address 48 | } 49 | 50 | lp_config = (await self.make_request(url=url, params=params))['msg'] 51 | 52 | from_token_address = lp_config['from_token_address'] 53 | to_token_address = lp_config['to_token_address'] 54 | bridge_address = lp_config['bridge_contract_address'] 55 | decimals = int(lp_config['token_decimal']) 56 | min_amount_in_wei, max_amount_in_wei = int(lp_config['min']), int(lp_config['max']) 57 | min_amount = round(min_amount_in_wei / 10 ** decimals, 6) 58 | max_amount = round(max_amount_in_wei / 10 ** decimals, 6) 59 | 60 | return (lp_config['maker_address'], min_amount, max_amount, decimals, 61 | bridge_address, from_token_address, to_token_address) 62 | 63 | async def get_tx_fee(self, from_chain_name, to_chain_name, amount, token_name): 64 | 65 | url = 'https://owlto.finance/api/dynamic-dtc' 66 | 67 | params = { 68 | 'from': from_chain_name, 69 | 'to': to_chain_name, 70 | 'amount': amount, 71 | 'token': token_name 72 | } 73 | response = await self.make_request(url=url, params=params) 74 | return float(response['dtc']) 75 | 76 | async def bridge(self, chain_from_id: int, bridge_data: tuple, need_check: bool = False): 77 | from_chain, to_chain, amount, to_chain_id, from_token_name, to_token_name, _, _ = bridge_data 78 | if not need_check: 79 | bridge_info = f'{self.client.network.name} -> {from_token_name} {CHAIN_NAME_FROM_ID[to_chain]}' 80 | self.logger_msg(*self.client.acc_info, msg=f'Bridge on Owlto: {amount} {from_token_name} {bridge_info}') 81 | 82 | from_chain_info, to_chain_info = await self.get_chains_info(from_chain, to_chain) 83 | lp_config = await self.get_lp_config(to_chain, from_token_name) 84 | (maker_address, min_amount, max_amount, decimals, 85 | bridge_contract_address, from_token_address, to_token_address) = lp_config 86 | 87 | fee = await self.get_tx_fee(from_chain_info['name'], to_chain_info['name'], amount, from_token_name) 88 | 89 | if need_check: 90 | return round(fee, 6) 91 | 92 | destination_code = int(to_chain_info['networkCode']) 93 | fee_in_wei = int(fee * 10 ** decimals) 94 | amount_in_wei = self.client.to_wei(round(amount, 6), decimals) 95 | full_amount = int(round(amount_in_wei + fee_in_wei, -2) + destination_code) 96 | 97 | if from_token_name != self.client.network.token: 98 | contract = self.client.get_contract(from_token_address) 99 | 100 | transaction = await contract.functions.transfer( 101 | AsyncWeb3.to_checksum_address(maker_address), 102 | full_amount 103 | ).build_transaction(await self.client.prepare_transaction()) 104 | else: 105 | transaction = (await self.client.prepare_transaction(value=full_amount)) | { 106 | 'to': self.client.w3.to_checksum_address(maker_address) 107 | } 108 | 109 | if min_amount <= amount <= max_amount: 110 | if int(f"{full_amount}"[-2:]) != destination_code: 111 | raise SoftwareExceptionWithoutRetry('Math problem in Python. Machine will save your money =)') 112 | 113 | old_balance_on_dst = await self.client.wait_for_receiving( 114 | token_address=to_token_address, token_name=to_token_name, chain_id=to_chain_id, 115 | check_balance_on_dst=True 116 | ) 117 | 118 | await self.client.send_transaction(transaction) 119 | 120 | self.logger_msg(*self.client.acc_info, 121 | msg=f"Bridge complete. Note: wait a little for receiving funds", type_msg='success') 122 | 123 | return await self.client.wait_for_receiving( 124 | token_address=to_token_address, token_name=to_token_name, old_balance=old_balance_on_dst, 125 | chain_id=to_chain_id 126 | ) 127 | 128 | else: 129 | raise BridgeExceptionWithoutRetry(f"Limit range for bridge: {min_amount} – {max_amount} {from_token_name}!") 130 | -------------------------------------------------------------------------------- /modules/bridges/relay.py: -------------------------------------------------------------------------------- 1 | from config import CHAIN_NAME_FROM_ID, ZERO_ADDRESS 2 | from modules import Bridge, Logger 3 | from modules.interfaces import SoftwareException, SoftwareExceptionWithoutRetry 4 | 5 | 6 | class Relay(Bridge, Logger): 7 | def __init__(self, client): 8 | self.client = client 9 | Logger.__init__(self) 10 | Bridge.__init__(self, client) 11 | 12 | async def get_bridge_config(self, dest_chain_id): 13 | url = "https://api.relay.link/config" 14 | 15 | headers = { 16 | "accept": "*/*", 17 | "accept-language": "ru,en;q=0.9,en-GB;q=0.8,en-US;q=0.7", 18 | "sec-ch-ua": "\"Chromium\";v=\"122\", \"Not(A:Brand\";v=\"24\", \"Microsoft Edge\";v=\"122\"", 19 | "sec-ch-ua-mobile": "?0", 20 | "sec-ch-ua-platform": "\"Windows\"", 21 | "sec-fetch-dest": "empty", 22 | "sec-fetch-mode": "cors", 23 | "sec-fetch-site": "same-site", 24 | "referrer": "https://www.relay.link/", 25 | "referrerPolicy": "strict-origin-when-cross-origin", 26 | } 27 | 28 | params = { 29 | 'originChainId': self.client.network.chain_id, 30 | 'destinationChainId': dest_chain_id, 31 | 'user': ZERO_ADDRESS, 32 | 'currency': ZERO_ADDRESS, 33 | } 34 | 35 | return await self.make_request(url=url, headers=headers, params=params) 36 | 37 | async def get_bridge_data(self, dest_chain_id, amount_in_wei): 38 | url = f"https://api.relay.link/execute/call" 39 | 40 | payload = { 41 | "user": self.client.address, 42 | "txs": [ 43 | { 44 | "to": self.client.address, 45 | "value": amount_in_wei, 46 | "data": "0x" 47 | } 48 | ], 49 | "originChainId": self.client.network.chain_id, 50 | "destinationChainId": dest_chain_id, 51 | "source": "relay.link" 52 | } 53 | 54 | return await self.make_request(method='POST', url=url, json=payload) 55 | 56 | async def bridge(self, chain_from_id: int, bridge_data: tuple, need_check: bool = False): 57 | from_chain, to_chain, amount, to_chain_id, token_name, _, from_token_address, to_token_address = bridge_data 58 | 59 | supported_chains = [42161, 42170, 8453, 10, 324, 1, 7777777] 60 | if from_chain not in supported_chains or to_chain not in supported_chains: 61 | raise SoftwareExceptionWithoutRetry( 62 | f'Bridge from {self.client.network.name} to {CHAIN_NAME_FROM_ID[to_chain]} is not exist') 63 | 64 | if not need_check: 65 | bridge_info = f'{self.client.network.name} -> {token_name} {CHAIN_NAME_FROM_ID[to_chain]}' 66 | self.logger_msg(*self.client.acc_info, msg=f'Bridge on Relay: {amount} {token_name} {bridge_info}') 67 | 68 | decimals = 18 if token_name == self.client.token else await self.client.get_decimals( 69 | token_address=from_token_address 70 | ) 71 | 72 | amount_in_wei = self.client.to_wei(amount, decimals) 73 | networks_data = await self.get_bridge_config(to_chain) 74 | tx_data = await self.get_bridge_data(dest_chain_id=to_chain, amount_in_wei=amount_in_wei) 75 | 76 | if need_check: 77 | fee = int(int(tx_data['fees']['gas'])) 78 | return round(float(fee / 10 ** decimals), 6) 79 | 80 | if networks_data['enabled']: 81 | 82 | max_amount = networks_data['solver']['capacityPerRequest'] 83 | 84 | if amount <= float(max_amount): 85 | 86 | transaction = (await self.client.prepare_transaction(value=amount_in_wei)) | { 87 | 'to': self.client.w3.to_checksum_address(tx_data["steps"][0]['items'][0]['data']['to']), 88 | 'data': tx_data["steps"][0]['items'][0]['data']['data'] 89 | } 90 | 91 | old_balance_on_dst = await self.client.wait_for_receiving( 92 | token_address=to_token_address, chain_id=to_chain_id, check_balance_on_dst=True 93 | ) 94 | 95 | await self.client.send_transaction(transaction) 96 | 97 | self.logger_msg(*self.client.acc_info, 98 | msg=f"Bridge complete. Note: wait a little for receiving funds", type_msg='success') 99 | 100 | return await self.client.wait_for_receiving( 101 | token_address=to_token_address, old_balance=old_balance_on_dst, chain_id=to_chain_id 102 | ) 103 | else: 104 | raise SoftwareException(f"Limit range for bridge: 0 - {max_amount} ETH") 105 | else: 106 | raise SoftwareException( 107 | f"Bridge from {self.client.network.name} -> {CHAIN_NAME_FROM_ID[to_chain]} is not active!") 108 | -------------------------------------------------------------------------------- /modules/cexs/__init__.py: -------------------------------------------------------------------------------- 1 | from .binance import Binance 2 | from .bitget import Bitget 3 | from .bingx import BingX 4 | from .okx import OKX 5 | -------------------------------------------------------------------------------- /modules/cexs/bitget.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import base64 3 | import hmac 4 | import json 5 | import time 6 | 7 | from hashlib import sha256 8 | 9 | from general_settings import BITGET_API_PASSPHRAS 10 | from modules import CEX, Logger 11 | from modules.interfaces import SoftwareExceptionWithoutRetry, SoftwareException, InsufficientBalanceException 12 | from utils.tools import get_wallet_for_deposit 13 | from config import CEX_WRAPPED_ID, TOKENS_PER_CHAIN, BITGET_NETWORKS_NAME 14 | 15 | 16 | class Bitget(CEX, Logger): 17 | def __init__(self, client): 18 | self.client = client 19 | Logger.__init__(self) 20 | CEX.__init__(self, client, 'Bitget') 21 | self.api_url = "https://api.bitget.com" 22 | 23 | @staticmethod 24 | def parse_params(params: dict | None = None): 25 | if params: 26 | sorted_keys = sorted(params) 27 | params_str = f'?{"&".join(["%s=%s" % (x, params[x]) for x in sorted_keys])}' 28 | else: 29 | params_str = '' 30 | return params_str 31 | 32 | def get_headers(self, method:str, api_path:str, params:dict = None, payload:dict | str = ""): 33 | try: 34 | timestamp = f"{int(time.time() * 1000)}" 35 | if method == 'GET': 36 | api_path = f"{api_path}{self.parse_params(params)}" 37 | prehash_string = timestamp + method.upper() + api_path 38 | else: 39 | prehash_string = timestamp + method.upper() + api_path + json.dumps(payload) 40 | 41 | secret_key_bytes = self.api_secret.encode('utf8') 42 | signature = hmac.new(secret_key_bytes, prehash_string.encode('utf8'), sha256).digest() 43 | encoded_signature = base64.standard_b64encode(signature).decode('utf8') 44 | 45 | return { 46 | "ACCESS-KEY": self.api_key, 47 | "ACCESS-SIGN": encoded_signature, 48 | "ACCESS-PASSPHRASE": BITGET_API_PASSPHRAS, 49 | "ACCESS-TIMESTAMP": timestamp, 50 | "locale": "en-US", 51 | "Content-Type": "application/json" 52 | } 53 | except Exception as error: 54 | raise SoftwareExceptionWithoutRetry(f'Bad headers for BitGet request: {error}') 55 | 56 | async def get_balance(self, ccy: str): 57 | path = '/api/v2/spot/account/assets' 58 | 59 | params = { 60 | 'coin': ccy 61 | } 62 | 63 | url = f"{self.api_url}{path}" 64 | headers = self.get_headers(method='GET', api_path=path, params=params) 65 | balance = await self.make_request(url=url, params=params, headers=headers, module_name='Balances Data') 66 | if balance: 67 | return float(balance[0]['available']) 68 | raise SoftwareExceptionWithoutRetry(f'Your have not enough {ccy} balance on CEX') 69 | 70 | async def get_currencies(self, ccy): 71 | path = '/api/v2/spot/public/coins' 72 | 73 | params = { 74 | 'coin': ccy 75 | } 76 | 77 | url = f"{self.api_url}{path}" 78 | return await self.make_request(url=url, params=params, module_name='Token info') 79 | 80 | async def get_sub_balances(self): 81 | path = "/api/v2/spot/account/subaccount-assets" 82 | 83 | await asyncio.sleep(2) 84 | url = f"{self.api_url}{path}" 85 | headers = self.get_headers('GET', path) 86 | return await self.make_request(url=url, headers=headers, module_name='Get subAccounts balances') 87 | 88 | async def get_main_info(self): 89 | path = '/api/v2/spot/account/info' 90 | 91 | await asyncio.sleep(2) 92 | url = f"{self.api_url}{path}" 93 | headers = self.get_headers('GET', path) 94 | return await self.make_request(url=url, headers=headers, module_name='Get main account info') 95 | 96 | async def get_main_balance(self, ccy): 97 | path = '/api/v2/spot/account/assets' 98 | 99 | params = { 100 | 'coin': ccy 101 | } 102 | 103 | url = f"{self.api_url}{path}" 104 | headers = self.get_headers('GET', path, params=params) 105 | return await self.make_request(url=url, params=params, headers=headers, module_name='Main account balance') 106 | 107 | async def transfer_from_subaccounts(self, ccy: str = 'ETH', amount: float = None, silent_mode:bool = False): 108 | 109 | if ccy == 'USDC.e': 110 | ccy = 'USDC' 111 | 112 | self.logger_msg(*self.client.acc_info, msg=f'Checking subAccounts balance') 113 | 114 | flag = True 115 | sub_list = await self.get_sub_balances() 116 | main_id = (await self.get_main_info())['userId'] 117 | 118 | for sub_data in sub_list: 119 | sub_id = sub_data['userId'] 120 | sub_balances = sub_data['assetsList'] 121 | ccy_sub_balance = [balance for balance in sub_balances if balance['coin'] == ccy] 122 | 123 | if ccy_sub_balance: 124 | ccy_sub_balance = float(ccy_sub_balance[0]['available']) 125 | 126 | amount = amount if amount else ccy_sub_balance 127 | if ccy_sub_balance == amount and ccy_sub_balance != 0.0: 128 | flag = False 129 | amount = amount if amount else ccy_sub_balance 130 | self.logger_msg( 131 | *self.client.acc_info, msg=f'{sub_id} | subAccount balance : {ccy_sub_balance} {ccy}') 132 | 133 | if ccy_sub_balance < amount: 134 | amount = ccy_sub_balance 135 | 136 | payload = { 137 | "fromType": "spot", 138 | "toType": "spot", 139 | "amount": f"{amount:.10f}", 140 | "coin": f"{ccy}", 141 | "fromUserId": f"{sub_id}", 142 | "toUserId": f"{main_id}", 143 | } 144 | 145 | path = "/api/v2/spot/wallet/subaccount-transfer" 146 | url = f"{self.api_url}{path}" 147 | headers = self.get_headers('POST', path, payload=payload) 148 | await self.make_request( 149 | method="POST", url=url, json=payload, headers=headers, module_name='SubAccount transfer') 150 | 151 | self.logger_msg( 152 | *self.client.acc_info, 153 | msg=f"Transfer {amount} {ccy} to main account complete", type_msg='success' 154 | ) 155 | if not silent_mode: 156 | break 157 | if flag: 158 | self.logger_msg(*self.client.acc_info, msg=f'subAccounts balance: 0 {ccy}', type_msg='warning') 159 | return True 160 | 161 | async def get_cex_balances(self, ccy: str = 'ETH'): 162 | 163 | if ccy == 'USDC.e': 164 | ccy = 'USDC' 165 | 166 | balances = {} 167 | 168 | main_balances = await self.get_main_balance(ccy) 169 | 170 | ccy_balance = [balance for balance in main_balances if balance['coin'] == ccy] 171 | 172 | if ccy_balance: 173 | balances['Main CEX Account'] = float(ccy_balance[0]['available']) 174 | else: 175 | balances['Main CEX Account'] = 0 176 | 177 | sub_list = await self.get_sub_balances() 178 | 179 | for sub_data in sub_list: 180 | sub_name = sub_data['userId'] 181 | sub_balances = sub_data['assetsList'] 182 | ccy_sub_balance = [balance for balance in sub_balances if balance['coin'] == ccy] 183 | 184 | if ccy_sub_balance: 185 | balances[sub_name] = float(ccy_sub_balance[0]['available']) 186 | else: 187 | balances[sub_name] = 0 188 | 189 | await asyncio.sleep(3) 190 | 191 | return balances 192 | 193 | async def wait_deposit_confirmation( 194 | self, amount: float, old_balances: dict, ccy: str = 'ETH', check_time: int = 45 195 | ): 196 | 197 | if ccy == 'USDC.e': 198 | ccy = 'USDC' 199 | 200 | self.logger_msg(*self.client.acc_info, msg=f"Start checking CEX balances") 201 | 202 | await asyncio.sleep(10) 203 | while True: 204 | new_sub_balances = await self.get_cex_balances(ccy=ccy) 205 | for acc_name, acc_balance in new_sub_balances.items(): 206 | if acc_name not in old_balances: 207 | old_balances[acc_name] = 0 208 | if acc_balance > old_balances[acc_name]: 209 | self.logger_msg(*self.client.acc_info, msg=f"Deposit {amount} {ccy} complete", type_msg='success') 210 | return True 211 | else: 212 | continue 213 | else: 214 | self.logger_msg(*self.client.acc_info, msg=f"Deposit still in progress...", type_msg='warning') 215 | await asyncio.sleep(check_time) 216 | 217 | async def withdraw(self, withdraw_data:tuple = None): 218 | path = '/api/v2/spot/wallet/withdrawal' 219 | 220 | network_id, amount = withdraw_data 221 | network_raw_name = BITGET_NETWORKS_NAME[network_id] 222 | split_network_data = network_raw_name.split('-') 223 | ccy, network_name = split_network_data[0], '-'.join(split_network_data[1:]) 224 | dst_chain_id = CEX_WRAPPED_ID[network_id] 225 | if isinstance(amount, str): 226 | amount = round(await self.get_balance(ccy=ccy) * float(amount), 6) 227 | else: 228 | amount = self.client.round_amount(*amount) 229 | 230 | await self.transfer_from_subaccounts(ccy=ccy, silent_mode=True) 231 | 232 | self.logger_msg(*self.client.acc_info, msg=f"Withdraw {amount:.5f} {ccy} to {network_name}") 233 | 234 | while True: 235 | try: 236 | withdraw_raw_data = (await self.get_currencies(ccy))[0]['chains'] 237 | network_data = { 238 | item['chain']: { 239 | 'withdrawEnable': item['withdrawable'], 240 | 'withdrawFee': item['withdrawFee'], 241 | 'withdrawMin': item['minWithdrawAmount'], 242 | } for item in withdraw_raw_data 243 | }[network_name] 244 | 245 | if network_data['withdrawEnable']: 246 | min_wd = float(network_data['withdrawMin']) 247 | 248 | if min_wd <= amount: 249 | 250 | payload = { 251 | "coin": ccy, 252 | "address": self.client.address, 253 | "chain": network_name, 254 | "size": f"{amount}", 255 | "transferType": 'on_chain', 256 | } 257 | 258 | ccy = f"{ccy}.e" if network_id in [29, 30] else ccy 259 | 260 | old_balance_on_dst = await self.client.wait_for_receiving( 261 | dst_chain_id, token_name=ccy, check_balance_on_dst=True 262 | ) 263 | 264 | url = f"{self.api_url}{path}" 265 | headers = self.get_headers('POST', path, payload=payload) 266 | await self.make_request( 267 | method='POST', url=url, headers=headers, json=payload, module_name='Withdraw') 268 | 269 | self.logger_msg(*self.client.acc_info, 270 | msg=f"Withdraw complete. Note: wait a little for receiving funds", 271 | type_msg='success') 272 | 273 | await self.client.wait_for_receiving( 274 | dst_chain_id, old_balance_on_dst, token_name=ccy 275 | ) 276 | 277 | return True 278 | else: 279 | raise SoftwareExceptionWithoutRetry(f"Limit range for withdraw: more than {min_wd:.5f} {ccy}") 280 | else: 281 | self.logger_msg( 282 | *self.client.acc_info, 283 | msg=f"Withdraw from {network_name} is not active now. Will try again in 1 min...", 284 | type_msg='warning' 285 | ) 286 | await asyncio.sleep(60) 287 | except InsufficientBalanceException: 288 | continue 289 | 290 | async def deposit(self, deposit_data:tuple = None): 291 | cex_wallet = get_wallet_for_deposit(self) 292 | info = f"{cex_wallet[:10]}....{cex_wallet[-6:]}" 293 | deposit_network, amount = deposit_data 294 | network_raw_name = BITGET_NETWORKS_NAME[deposit_network] 295 | split_network_data = network_raw_name.split('-') 296 | ccy, network_name = split_network_data[0], '-'.join(split_network_data[1:]) 297 | 298 | await self.transfer_from_subaccounts(ccy=ccy, silent_mode=True) 299 | 300 | if deposit_network in [29, 30]: 301 | ccy = f"{ccy}.e" 302 | 303 | self.logger_msg( 304 | *self.client.acc_info, msg=f"Deposit {amount} {ccy} from {network_name} to Bitget wallet: {info}") 305 | 306 | while True: 307 | try: 308 | withdraw_data = (await self.get_currencies(ccy))[0]['chains'] 309 | network_data = { 310 | item['chain']: { 311 | 'depositEnable': item['rechargeable'] 312 | } for item in withdraw_data 313 | }[network_name] 314 | 315 | if network_data['depositEnable']: 316 | 317 | if ccy != self.client.token: 318 | token_contract = self.client.get_contract(TOKENS_PER_CHAIN[self.client.network.name][ccy]) 319 | decimals = await self.client.get_decimals(ccy) 320 | amount_in_wei = self.client.to_wei(amount, decimals) 321 | 322 | transaction = await token_contract.functions.transfer( 323 | self.client.w3.to_checksum_address(cex_wallet), 324 | amount_in_wei 325 | ).build_transaction(await self.client.prepare_transaction()) 326 | else: 327 | amount_in_wei = self.client.to_wei(amount) 328 | transaction = (await self.client.prepare_transaction(value=int(amount_in_wei))) | { 329 | 'to': self.client.w3.to_checksum_address(cex_wallet), 330 | 'data': '0x' 331 | } 332 | 333 | cex_balances = await self.get_cex_balances(ccy=ccy) 334 | 335 | result_tx = await self.client.send_transaction(transaction) 336 | 337 | if result_tx: 338 | result_confirmation = await self.wait_deposit_confirmation(amount, cex_balances, ccy=ccy) 339 | 340 | result_transfer = await self.transfer_from_subaccounts(ccy=ccy, amount=amount) 341 | 342 | return all([result_tx, result_confirmation, result_transfer]) 343 | else: 344 | raise SoftwareException('Transaction not sent, trying again') 345 | else: 346 | self.logger_msg( 347 | *self.client.acc_info, 348 | msg=f"Deposit to {network_name} is not active now. Will try again in 1 min...", 349 | type_msg='warning' 350 | ) 351 | await asyncio.sleep(60) 352 | except InsufficientBalanceException: 353 | continue 354 | -------------------------------------------------------------------------------- /modules/interfaces.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from aiohttp import ClientSession, TCPConnector 4 | from aiohttp_socks import ProxyConnector 5 | from loguru import logger 6 | from sys import stderr 7 | from datetime import datetime 8 | from abc import ABC, abstractmethod 9 | from random import uniform 10 | from config import CHAIN_NAME 11 | from general_settings import (LAYERSWAP_API_KEY, OKX_API_KEY, OKX_API_PASSPHRAS, 12 | OKX_API_SECRET, BINGX_API_KEY, BINGX_API_SECRET, BINANCE_API_KEY, 13 | BINANCE_API_SECRET, BITGET_API_SECRET, BITGET_API_KEY, MAIN_PROXY) 14 | 15 | 16 | def get_user_agent(): 17 | random_version = f"{uniform(520, 540):.2f}" 18 | return (f'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/{random_version} (KHTML, like Gecko)' 19 | f' Chrome/119.0.0.0 Safari/{random_version} Edg/119.0.0.0') 20 | 21 | 22 | class PriceImpactException(Exception): 23 | pass 24 | 25 | 26 | class BlockchainException(Exception): 27 | pass 28 | 29 | 30 | class BlockchainExceptionWithoutRetry(Exception): 31 | pass 32 | 33 | 34 | class SoftwareException(Exception): 35 | pass 36 | 37 | 38 | class CriticalException(Exception): 39 | pass 40 | 41 | 42 | class SoftwareExceptionWithoutRetry(Exception): 43 | pass 44 | 45 | 46 | class SoftwareExceptionWithRetries(Exception): 47 | pass 48 | 49 | 50 | class InsufficientBalanceException(Exception): 51 | pass 52 | 53 | 54 | class BridgeExceptionWithoutRetry(Exception): 55 | pass 56 | 57 | 58 | class DepositExceptionWithoutRetry(Exception): 59 | pass 60 | 61 | 62 | class Logger(ABC): 63 | def __init__(self): 64 | self.logger = logger 65 | self.logger.remove() 66 | logger_format = "{time:HH:mm:ss} | " "{level: <8} | {message}" 67 | self.logger.add(stderr, format=logger_format) 68 | date = datetime.today().date() 69 | self.logger.add(f"./data/logs/{date}.log", rotation="500 MB", level="INFO", format=logger_format) 70 | 71 | def logger_msg(self, account_name, address, msg, type_msg: str = 'info'): 72 | from config import ACCOUNT_NAMES 73 | class_name = self.__class__.__name__ 74 | software_chain = CHAIN_NAME[13] 75 | acc_index = '1/1' 76 | if account_name: 77 | account_index = ACCOUNT_NAMES.index(account_name) + 1 78 | acc_index = f"{account_index}/{len(ACCOUNT_NAMES)}" 79 | 80 | if account_name is None and address is None: 81 | info = f'[EthMachine] | {software_chain} | {class_name} |' 82 | elif account_name is not None and address is None: 83 | info = f'[{acc_index}] | [{account_name}] | {software_chain} | {class_name} |' 84 | else: 85 | info = f'[{acc_index}] | [{account_name}] | {address} | {software_chain} | {class_name} |' 86 | if type_msg == 'info': 87 | self.logger.info(f"{info} {msg}") 88 | elif type_msg == 'error': 89 | self.logger.error(f"{info} {msg}") 90 | elif type_msg == 'success': 91 | self.logger.success(f"{info} {msg}") 92 | elif type_msg == 'warning': 93 | self.logger.warning(f"{info} {msg}") 94 | 95 | 96 | class DEX(ABC): 97 | @abstractmethod 98 | async def swap(self): 99 | pass 100 | 101 | 102 | class CEX(ABC): 103 | def __init__(self, client, class_name): 104 | self.client = client 105 | self.class_name = class_name 106 | if class_name == 'OKX': 107 | self.api_key = OKX_API_KEY 108 | self.api_secret = OKX_API_SECRET 109 | self.passphras = OKX_API_PASSPHRAS 110 | elif class_name == 'BingX': 111 | self.api_key = BINGX_API_KEY 112 | self.api_secret = BINGX_API_SECRET 113 | elif class_name == 'Binance': 114 | self.api_key = BINANCE_API_KEY 115 | self.api_secret = BINANCE_API_SECRET 116 | elif class_name == 'Bitget': 117 | self.api_key = BITGET_API_KEY 118 | self.api_secret = BITGET_API_SECRET 119 | else: 120 | raise SoftwareException('CEX don`t available now') 121 | 122 | @abstractmethod 123 | async def deposit(self): 124 | pass 125 | 126 | @abstractmethod 127 | async def withdraw(self): 128 | pass 129 | 130 | async def make_request(self, method:str = 'GET', url:str = None, data:str = None, params:dict = None, 131 | headers:dict = None, json:dict = None, module_name:str = 'Request', 132 | content_type:str | None = "application/json"): 133 | 134 | insf_balance_code = { 135 | 'BingX': [100437], 136 | 'Binance': [4026], 137 | 'Bitget': [43012, 13004], 138 | 'OKX': [58350], 139 | }[self.class_name] 140 | 141 | async with ClientSession( 142 | connector=ProxyConnector.from_url(f"http://{MAIN_PROXY}",) if MAIN_PROXY != '' else TCPConnector() 143 | ) as session: 144 | async with session.request( 145 | method=method, url=url, headers=headers, data=data, json=json, params=params 146 | ) as response: 147 | data: dict = await response.json(content_type=content_type) 148 | 149 | if self.class_name == 'Binance' and response.status in [200, 201]: 150 | return data 151 | 152 | if int(data.get('code')) != 0: 153 | message = data.get('msg') or data.get('desc') or 'Unknown error' 154 | code = int(data['code']) 155 | if code in insf_balance_code: 156 | self.client.logger_msg( 157 | *self.client.acc_info, 158 | msg=f"Your CEX balance < your want transfer amount. Will try again in 5 min...", 159 | type_msg='warning' 160 | ) 161 | await asyncio.sleep(300) 162 | raise InsufficientBalanceException('Trying request again...') 163 | 164 | error = f"Error code: {data['code']} Msg: {message}" 165 | raise SoftwareException(f"Bad request to {self.class_name}({module_name}): {error}") 166 | 167 | # self.logger.success(f"{self.info} {module_name}") 168 | return data['data'] 169 | 170 | 171 | class RequestClient(ABC): 172 | def __init__(self, client): 173 | self.client = client 174 | 175 | async def make_request(self, method:str = 'GET', url:str = None, headers:dict = None, params: dict = None, 176 | data:str = None, json:dict = None): 177 | 178 | headers = (headers or {}) | {'User-Agent': get_user_agent()} 179 | async with self.client.session.request(method=method, url=url, headers=headers, data=data, 180 | params=params, json=json) as response: 181 | try: 182 | data = await response.json() 183 | 184 | if response.status == 200: 185 | return data 186 | raise SoftwareException( 187 | f"Bad request to {self.__class__.__name__} API. " 188 | f"Response status: {response.status}. Response: {await response.text()}") 189 | except Exception as error: 190 | raise SoftwareException( 191 | f"Bad request to {self.__class__.__name__} API. " 192 | f"Response status: {response.status}. Response: {await response.text()} Error: {error}") 193 | 194 | 195 | class Bridge(ABC): 196 | def __init__(self, client): 197 | self.client = client 198 | 199 | if self.__class__.__name__ == 'LayerSwap': 200 | self.headers = { 201 | 'X-LS-APIKEY': f'{LAYERSWAP_API_KEY}', 202 | 'Content-Type': 'application/json' 203 | } 204 | elif self.__class__.__name__ == 'Rhino': 205 | self.headers = { 206 | "Accept": "application/json", 207 | "Content-Type": "application/json", 208 | } 209 | elif self.__class__.__name__ == 'Bungee': 210 | self.headers = { 211 | "Api-Key": "1b2fd225-062f-41aa-8c63-d1fef19945e7", 212 | } 213 | 214 | @abstractmethod 215 | async def bridge(self, *args, **kwargs): 216 | pass 217 | 218 | async def make_request(self, method:str = 'GET', url:str = None, headers:dict = None, params: dict = None, 219 | data:str = None, json:dict = None): 220 | 221 | headers = (headers or {}) | {'User-Agent': get_user_agent()} 222 | async with self.client.session.request(method=method, url=url, headers=headers, data=data, json=json, 223 | params=params) as response: 224 | data = await response.json() 225 | if response.status in [200, 201]: 226 | return data 227 | raise SoftwareException( 228 | f"Bad request to {self.__class__.__name__} API. " 229 | f"Response status: {response.status}. Status: {response.status}. Response: {await response.text()}") 230 | 231 | 232 | class Refuel(ABC): 233 | @abstractmethod 234 | async def refuel(self, *args, **kwargs): 235 | pass 236 | 237 | 238 | class Messenger(ABC): 239 | @abstractmethod 240 | async def send_message(self): 241 | pass 242 | 243 | 244 | class Landing(ABC): 245 | @abstractmethod 246 | async def deposit(self): 247 | pass 248 | 249 | @abstractmethod 250 | async def withdraw(self): 251 | pass 252 | 253 | 254 | class Minter(ABC): 255 | @abstractmethod 256 | async def mint(self, *args, **kwargs): 257 | pass 258 | 259 | 260 | class Creator(ABC): 261 | @abstractmethod 262 | async def create(self): 263 | pass 264 | 265 | 266 | class Blockchain(ABC): 267 | def __init__(self, client): 268 | self.client = client 269 | 270 | async def make_request(self, method:str = 'GET', url:str = None, headers:dict = None, params: dict = None, 271 | data:str = None, json:dict = None): 272 | 273 | headers = (headers or {}) | {'User-Agent': get_user_agent()} 274 | async with self.client.session.request(method=method, url=url, headers=headers, data=data, 275 | params=params, json=json) as response: 276 | 277 | data = await response.json() 278 | if response.status == 200: 279 | return data 280 | raise SoftwareException( 281 | f"Bad request to {self.__class__.__name__} API. " 282 | f"Response status: {response.status}. Status: {response.status}. Response: {await response.text()}") -------------------------------------------------------------------------------- /modules/other/__init__.py: -------------------------------------------------------------------------------- 1 | from .mintfun import MintFun 2 | -------------------------------------------------------------------------------- /modules/other/mintfun.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import copy 3 | import random 4 | 5 | from modules import Minter, Logger, RequestClient 6 | from config import MINTFUN_ABI 7 | from modules.interfaces import SoftwareException 8 | from utils.tools import helper, gas_checker 9 | from settings import MINTFUN_CONTRACTS, MINTFUN_MINT_COUNT 10 | 11 | 12 | class MintFun(Minter, Logger, RequestClient): 13 | def __init__(self, client): 14 | self.client = client 15 | Logger.__init__(self) 16 | RequestClient.__init__(self, client) 17 | 18 | async def get_tx_data(self, contract_address): 19 | url = f'https://mint.fun/api/mintfun/contract/{self.client.network.chain_id}:{contract_address}/transactions' 20 | 21 | params = { 22 | 'address': self.client.address 23 | } 24 | 25 | response = await self.make_request(url=url, params=params) 26 | 27 | total_time = 0 28 | timeout = 10 29 | while True: 30 | for tx in response['transactions']: 31 | if int(tx['nftCount']) == 1 and tx['to'].lower() == contract_address.lower() and tx['isValid']: 32 | calldata = tx['callData'].replace( 33 | 'ec45d2d56ec37ffabeb503a27ae21ba806ebe075', self.client.address[2:]) 34 | eth_value = int(tx['ethValue']) 35 | 36 | return calldata, eth_value 37 | 38 | total_time += 10 39 | await asyncio.sleep(10) 40 | 41 | if total_time > timeout: 42 | raise SoftwareException('Mint.fun have not data for this mint!') 43 | 44 | @helper 45 | @gas_checker 46 | async def mint(self): 47 | mint_contracts = copy.deepcopy(MINTFUN_CONTRACTS) 48 | random.shuffle(mint_contracts) 49 | mints_count = random.randint(*copy.deepcopy(MINTFUN_MINT_COUNT)) 50 | 51 | for index, nft_contract in enumerate(mint_contracts[:mints_count]): 52 | try: 53 | nft_contract = self.client.w3.to_checksum_address(nft_contract) 54 | 55 | calldata, eth_value = await self.get_tx_data(nft_contract) 56 | 57 | contract = self.client.get_contract(self.client.w3.to_checksum_address(nft_contract), MINTFUN_ABI[1]) 58 | 59 | try: 60 | nft_name = await contract.functions.name().call() 61 | except: 62 | nft_name = 'Random' 63 | 64 | self.logger_msg(*self.client.acc_info, msg=f"Mint {nft_name} NFT. Price: {eth_value / 10 ** 18:.6f} ETH") 65 | 66 | transaction = await self.client.prepare_transaction(value=eth_value) | { 67 | 'to': nft_contract, 68 | 'data': f"0x{calldata}" 69 | } 70 | 71 | return await self.client.send_transaction(transaction) 72 | 73 | except Exception as error: 74 | self.logger_msg( 75 | *self.client.acc_info, 76 | msg=f"Impossible to mint NFT on contract address '{nft_contract}'. Error: {error}", type_msg='error' 77 | ) 78 | -------------------------------------------------------------------------------- /modules/swaps/__init__.py: -------------------------------------------------------------------------------- 1 | from .izumi import Izumi 2 | from .odos import Odos 3 | from .oneinch import OneInch 4 | from .uniswap import Uniswap 5 | -------------------------------------------------------------------------------- /modules/swaps/izumi.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | from modules import DEX, Logger 3 | from utils.tools import gas_checker, helper 4 | from general_settings import SLIPPAGE 5 | from hexbytes import HexBytes 6 | from config import ( 7 | TOKENS_PER_CHAIN, 8 | IZUMI_QUOTER_ABI, 9 | IZUMI_ROUTER_ABI, 10 | IZUMI_CONTRACTS, 11 | ZERO_ADDRESS 12 | ) 13 | 14 | 15 | class Izumi(DEX, Logger): 16 | def __init__(self, client): 17 | self.client = client 18 | Logger.__init__(self) 19 | self.network = self.client.network.name 20 | self.router_contract = self.client.get_contract(IZUMI_CONTRACTS[self.network]['router'], IZUMI_ROUTER_ABI) 21 | self.quoter_contract = self.client.get_contract(IZUMI_CONTRACTS[self.network]['quoter'], IZUMI_QUOTER_ABI) 22 | 23 | def get_path(self, from_token_address: str, to_token_address: str): 24 | from_token_bytes = HexBytes(from_token_address).rjust(20, b'\0') 25 | to_token_bytes = HexBytes(to_token_address).rjust(20, b'\0') 26 | fee_bytes = (500).to_bytes(3, 'big') 27 | return from_token_bytes + fee_bytes + to_token_bytes 28 | 29 | async def get_min_amount_out(self, path: bytes, amount_in_wei: int): 30 | min_amount_out, _ = await self.quoter_contract.functions.swapAmount( 31 | amount_in_wei, 32 | path 33 | ).call() 34 | 35 | return int(min_amount_out - (min_amount_out / 100 * SLIPPAGE)) 36 | 37 | @helper 38 | @gas_checker 39 | async def swap(self, swapdata: tuple = None, help_deposit: bool = False): 40 | if swapdata: 41 | from_token_name, to_token_name, amount, amount_in_wei = swapdata 42 | else: 43 | from_token_name, to_token_name, amount, amount_in_wei = await self.client.get_auto_amount('Izumi') 44 | 45 | if help_deposit: 46 | to_token_name = 'ETH' 47 | 48 | self.logger_msg(*self.client.acc_info, msg=f'Swap on Izumi: {amount} {from_token_name} -> {to_token_name}') 49 | 50 | from_token_address, to_token_address = (TOKENS_PER_CHAIN[self.network][from_token_name], 51 | TOKENS_PER_CHAIN[self.network][to_token_name]) 52 | 53 | deadline = int(time()) + 1800 54 | path = self.get_path(from_token_address, to_token_address) 55 | min_amount_out = await self.get_min_amount_out(path, amount_in_wei) 56 | 57 | await self.client.price_impact_defender(from_token_name, amount, to_token_name, min_amount_out) 58 | 59 | if from_token_name != 'ETH': 60 | await self.client.check_for_approved( 61 | from_token_address, IZUMI_CONTRACTS[self.network]['router'], amount_in_wei 62 | ) 63 | 64 | tx_data = self.router_contract.encodeABI( 65 | fn_name='swapAmount', 66 | args=[( 67 | path, 68 | self.client.address if to_token_name != 'ETH' else ZERO_ADDRESS, 69 | amount_in_wei, 70 | min_amount_out, 71 | deadline 72 | )] 73 | ) 74 | 75 | full_data = [tx_data] 76 | 77 | if from_token_name == 'ETH' or to_token_name == 'ETH': 78 | tx_additional_data = self.router_contract.encodeABI( 79 | fn_name='unwrapWETH9' if from_token_name != 'ETH' else 'refundETH', 80 | args=[ 81 | min_amount_out, 82 | self.client.address 83 | ] if from_token_name != 'ETH' else None 84 | ) 85 | full_data.append(tx_additional_data) 86 | 87 | tx_params = await self.client.prepare_transaction(value=amount_in_wei if from_token_name == 'ETH' else 0) 88 | transaction = await self.router_contract.functions.multicall( 89 | full_data 90 | ).build_transaction(tx_params) 91 | 92 | return await self.client.send_transaction(transaction) 93 | -------------------------------------------------------------------------------- /modules/swaps/odos.py: -------------------------------------------------------------------------------- 1 | from modules import RequestClient, Logger 2 | from utils.tools import gas_checker, helper 3 | from general_settings import SLIPPAGE 4 | from config import TOKENS_PER_CHAIN, ZERO_ADDRESS, HELP_SOFTWARE 5 | 6 | 7 | class Odos(RequestClient, Logger): 8 | def __init__(self, client): 9 | self.client = client 10 | Logger.__init__(self) 11 | RequestClient.__init__(self, client) 12 | self.network = self.client.network.name 13 | 14 | async def get_quote(self, from_token_address: str, to_token_address: str, amount_in_wei: int): 15 | quote_url = "https://api.odos.xyz/sor/quote/v2" 16 | 17 | quote_request_body = { 18 | "chainId": self.client.chain_id, 19 | "inputTokens": [ 20 | { 21 | "tokenAddress": f"{from_token_address}", 22 | "amount": f"{amount_in_wei}", 23 | } 24 | ], 25 | "outputTokens": [ 26 | { 27 | "tokenAddress": f"{to_token_address}", 28 | "proportion": 1 29 | } 30 | ], 31 | "slippageLimitPercent": SLIPPAGE, 32 | "userAddr": f"{self.client.address}", 33 | "compact": True, 34 | } | ({"referralCode": 2336322279} if HELP_SOFTWARE else {}) 35 | 36 | headers = { 37 | "Content-Type": "application/json" 38 | } 39 | 40 | return await self.make_request(method='POST', url=quote_url, headers=headers, json=quote_request_body) 41 | 42 | async def assemble_transaction(self, path_id): 43 | assemble_url = "https://api.odos.xyz/sor/assemble" 44 | 45 | assemble_request_body = { 46 | "userAddr": f"{self.client.address}", 47 | "pathId": path_id, 48 | "simulate": False, 49 | } 50 | 51 | headers = { 52 | "Content-Type": "application/json" 53 | } 54 | 55 | return await self.make_request(method='POST', url=assemble_url, headers=headers, json=assemble_request_body) 56 | 57 | @helper 58 | @gas_checker 59 | async def swap(self, help_deposit: bool = False, swapdata: tuple = None): 60 | if not swapdata: 61 | from_token_name, to_token_name, amount, amount_in_wei = await self.client.get_auto_amount() 62 | else: 63 | from_token_name, to_token_name, amount, amount_in_wei = swapdata 64 | 65 | if help_deposit: 66 | to_token_name = 'ETH' 67 | 68 | self.logger_msg(*self.client.acc_info, msg=f"Swap on ODOS: {amount} {from_token_name} -> {to_token_name}") 69 | 70 | token_data = TOKENS_PER_CHAIN[self.network] 71 | 72 | from_token_address = ZERO_ADDRESS if from_token_name == "ETH" else token_data[from_token_name] 73 | to_token_address = ZERO_ADDRESS if to_token_name == "ETH" else token_data[to_token_name] 74 | 75 | path_id = (await self.get_quote(from_token_address, to_token_address, amount_in_wei))["pathId"] 76 | transaction_data = (await self.assemble_transaction(path_id))["transaction"] 77 | contract_address = self.client.w3.to_checksum_address(transaction_data["to"]) 78 | 79 | if from_token_name != 'ETH': 80 | await self.client.check_for_approved(from_token_address, contract_address, amount_in_wei) 81 | 82 | tx_params = (await self.client.prepare_transaction()) | { 83 | "to": contract_address, 84 | "data": transaction_data["data"], 85 | "value": int(transaction_data["value"]), 86 | } 87 | 88 | return await self.client.send_transaction(tx_params) 89 | -------------------------------------------------------------------------------- /modules/swaps/oneinch.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from modules import RequestClient, Logger 4 | from utils.tools import gas_checker, helper 5 | from general_settings import SLIPPAGE, ONEINCH_API_KEY 6 | from config import TOKENS_PER_CHAIN, ETH_MASK, HELP_SOFTWARE 7 | 8 | 9 | class OneInch(RequestClient, Logger): 10 | def __init__(self, client): 11 | self.client = client 12 | Logger.__init__(self) 13 | RequestClient.__init__(self, client) 14 | self.network = self.client.network.name 15 | 16 | async def get_contract_address(self): 17 | url = f"https://api.1inch.dev/swap/v5.2/{self.client.chain_id}/approve/spender" 18 | 19 | headers = { 20 | 'Authorization': f'Bearer {ONEINCH_API_KEY}', 21 | 'accept': 'application/json' 22 | } 23 | 24 | return await self.make_request(url=url, headers=headers) 25 | 26 | async def build_swap_transaction(self, from_token_address: str, to_token_address: str, amount: int): 27 | 28 | url = f"https://api.1inch.dev/swap/v5.2/{self.client.chain_id}/swap" 29 | 30 | headers = { 31 | 'Authorization': f'Bearer {ONEINCH_API_KEY}', 32 | } 33 | 34 | params = { 35 | "src": from_token_address, 36 | "dst": to_token_address, 37 | "amount": amount, 38 | "from": self.client.address, 39 | "slippage": SLIPPAGE, 40 | } | ({"fee": 1, "referrer": '0x000000a679C2FB345dDEfbaE3c42beE92c0Fb7A5'} if HELP_SOFTWARE else {}) 41 | 42 | return await self.make_request(url=url, params=params, headers=headers) 43 | 44 | @helper 45 | @gas_checker 46 | async def swap(self, help_deposit: bool = False, swapdata: tuple = None): 47 | if not swapdata: 48 | from_token_name, to_token_name, amount, amount_in_wei = await self.client.get_auto_amount() 49 | else: 50 | from_token_name, to_token_name, amount, amount_in_wei = swapdata 51 | 52 | if help_deposit: 53 | to_token_name = 'ETH' 54 | 55 | self.logger_msg(*self.client.acc_info, msg=f"Swap on 1INCH: {amount} {from_token_name} -> {to_token_name}") 56 | 57 | token_data = TOKENS_PER_CHAIN[self.network] 58 | 59 | chain_token_name = self.client.token 60 | from_token_address = ETH_MASK if from_token_name == chain_token_name else token_data[from_token_name] 61 | to_token_address = ETH_MASK if to_token_name == chain_token_name else token_data[to_token_name] 62 | 63 | contract_address = self.client.w3.to_checksum_address((await self.get_contract_address())['address']) 64 | 65 | if from_token_name != 'ETH': 66 | await self.client.check_for_approved(from_token_address, contract_address, amount_in_wei) 67 | 68 | await asyncio.sleep(5) 69 | 70 | swap_quote_data = await self.build_swap_transaction(from_token_address, to_token_address, amount_in_wei) 71 | 72 | tx_param = (await self.client.prepare_transaction()) | { 73 | "to": contract_address, 74 | "data": swap_quote_data["tx"]["data"], 75 | "value": int(swap_quote_data["tx"]["value"]), 76 | } 77 | 78 | return await self.client.send_transaction(tx_param) 79 | -------------------------------------------------------------------------------- /modules/swaps/uniswap.py: -------------------------------------------------------------------------------- 1 | from modules import DEX, Logger 2 | from utils.tools import gas_checker, helper 3 | from general_settings import SLIPPAGE 4 | from hexbytes import HexBytes 5 | from config import ( 6 | UNISWAP_ABI, 7 | UNISWAP_CONTRACTS, 8 | TOKENS_PER_CHAIN 9 | ) 10 | 11 | 12 | class Uniswap(DEX, Logger): 13 | def __init__(self, client): 14 | self.client = client 15 | Logger.__init__(self) 16 | self.network = self.client.network.name 17 | self.router_contract = self.client.get_contract( 18 | UNISWAP_CONTRACTS[self.network]['router'], 19 | UNISWAP_ABI['router'] 20 | ) 21 | self.quoter_contract = self.client.get_contract( 22 | UNISWAP_CONTRACTS[self.network]['quoter'], 23 | UNISWAP_ABI['quoter'] 24 | ) 25 | 26 | @staticmethod 27 | def get_path(from_token_address: str, to_token_address: str): 28 | from_token_bytes = HexBytes(from_token_address).rjust(20, b'\0') 29 | to_token_bytes = HexBytes(to_token_address).rjust(20, b'\0') 30 | fee_bytes = (500).to_bytes(3, 'big') 31 | return from_token_bytes + fee_bytes + to_token_bytes 32 | 33 | async def get_min_amount_out(self, path: bytes, amount_in_wei: int): 34 | min_amount_out, _, _, _ = await self.quoter_contract.functions.quoteExactInput( 35 | path, 36 | amount_in_wei 37 | ).call() 38 | 39 | return int(min_amount_out - (min_amount_out / 100 * SLIPPAGE)) 40 | 41 | @helper 42 | @gas_checker 43 | async def swap(self, swapdata: tuple = None): 44 | if not swapdata: 45 | from_token_name, to_token_name, amount, amount_in_wei = await self.client.get_auto_amount() 46 | else: 47 | from_token_name, to_token_name, amount, amount_in_wei = swapdata 48 | 49 | self.logger_msg( 50 | *self.client.acc_info, msg=f'Swap on Uniswap: {amount} {from_token_name} -> {to_token_name}') 51 | 52 | from_token_address = TOKENS_PER_CHAIN[self.network][from_token_name] 53 | to_token_address = TOKENS_PER_CHAIN[self.network][to_token_name] 54 | 55 | path = self.get_path(from_token_address, to_token_address) 56 | min_amount_out = await self.get_min_amount_out(path, amount_in_wei) 57 | 58 | await self.client.price_impact_defender(from_token_name, amount, to_token_name, min_amount_out) 59 | 60 | if from_token_name != self.client.token: 61 | await self.client.check_for_approved( 62 | from_token_address, UNISWAP_CONTRACTS[self.network]['router'], amount_in_wei 63 | ) 64 | 65 | tx_data = self.router_contract.encodeABI( 66 | fn_name='exactInput', 67 | args=[( 68 | path, 69 | self.client.address if to_token_name != self.client.token else '0x0000000000000000000000000000000000000002', 70 | amount_in_wei, 71 | min_amount_out 72 | )] 73 | ) 74 | 75 | full_data = [tx_data] 76 | 77 | if from_token_name == self.client.token or to_token_name == self.client.token: 78 | tx_additional_data = self.router_contract.encodeABI( 79 | fn_name='unwrapWETH9' if from_token_name != self.client.token else 'refundETH', 80 | args=[ 81 | min_amount_out, 82 | self.client.address 83 | ] if from_token_name != self.client.token else None 84 | ) 85 | full_data.append(tx_additional_data) 86 | 87 | tx_params = await self.client.prepare_transaction( 88 | value=amount_in_wei if from_token_name == self.client.token else 0 89 | ) 90 | 91 | transaction = await self.router_contract.functions.multicall( 92 | full_data 93 | ).build_transaction(tx_params) 94 | 95 | return await self.client.send_transaction(transaction) 96 | -------------------------------------------------------------------------------- /modules/txchecker.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | import random 4 | 5 | import pandas as pd 6 | from aiohttp import ClientSession 7 | 8 | from termcolor import cprint 9 | from web3 import AsyncWeb3, AsyncHTTPProvider 10 | from prettytable import PrettyTable 11 | from config import PRIVATE_KEYS, PROXIES 12 | from utils.networks import EthereumRPC 13 | 14 | from modules.interfaces import get_user_agent, SoftwareException 15 | 16 | 17 | FILTER_SYMBOLS = ['ETH'] 18 | FIELDS = [ 19 | '#', 'Wallet', 'Balance', 'ETH', 'TX Count' 20 | ] 21 | 22 | table = PrettyTable() 23 | table.field_names = FIELDS 24 | 25 | 26 | class TxChecker: 27 | async def make_request(self, method: str = 'GET', url: str = None, headers: dict = None, params: dict = None, 28 | data: str = None, json: dict = None): 29 | 30 | proxy = random.choice(PROXIES) 31 | headers = (headers or {}) | {'User-Agent': get_user_agent()} 32 | 33 | async with ClientSession() as session: 34 | async with session.request(method=method, url=url, headers=headers, data=data, 35 | params=params, json=json, proxy=f"http://{proxy}" if proxy else "") as response: 36 | data = await response.json() 37 | if response.status == 200: 38 | return data 39 | await self.make_request(method=method, url=url, headers=headers, data=data, params=params, json=json) 40 | 41 | async def get_eth_price(self): 42 | url = 'https://api.coingecko.com/api/v3/simple/price' 43 | 44 | params = { 45 | 'ids': 'ethereum', 46 | 'vs_currencies': 'usd' 47 | } 48 | 49 | return (await self.make_request('GET', url=url, params=params))['ethereum']['usd'] 50 | 51 | @staticmethod 52 | async def get_wallet_balance(wallet): 53 | proxy = random.choice(PROXIES) 54 | request_kwargs = {"proxy": f"http://{proxy}"} if proxy else {} 55 | rpc = random.choice(EthereumRPC.rpc) 56 | w3 = AsyncWeb3(AsyncHTTPProvider(rpc, request_kwargs=request_kwargs)) 57 | balance_in_wei = await w3.eth.get_balance(wallet) 58 | balance = round(balance_in_wei / 10 ** 18, 5) 59 | tx_count = await w3.eth.get_transaction_count(wallet) 60 | 61 | return balance, tx_count 62 | 63 | async def fetch_wallet_data(self, wallet, index, eth_price): 64 | balance, tx_count = await self.get_wallet_balance(wallet) 65 | 66 | return { 67 | '#' : index + 1, 68 | 'Wallet' : f'{wallet}', 69 | 'Balance' : f'{(balance * eth_price):.2f}$', 70 | 'ETH' : f"{balance:.4f}", 71 | 'TX Count' : tx_count, 72 | } 73 | 74 | 75 | async def main(): 76 | try: 77 | wallets = [AsyncWeb3().eth.account.from_key(private_key).address for private_key in PRIVATE_KEYS] 78 | except Exception as error: 79 | cprint('\n⚠️⚠️⚠️Put your wallets into data/accounts_data.xlsx first!⚠️⚠️⚠️\n', color='light_red', attrs=["blink"]) 80 | raise SoftwareException(f"{error}") 81 | 82 | tx_checker = TxChecker() 83 | 84 | eth_price = await tx_checker.get_eth_price() 85 | tasks = [tx_checker.fetch_wallet_data(wallet, index, eth_price) for index, wallet in enumerate(wallets, 0)] 86 | wallet_data = await asyncio.gather(*tasks) 87 | 88 | cprint('✅ Data successfully load to /data/accounts_stats/wallets_stats.xlsx (Excel format)\n', 89 | 'light_yellow', attrs=["blink"]) 90 | await asyncio.sleep(1) 91 | xlsx_data = pd.DataFrame(wallet_data) 92 | directory = './data/accounts_stats/' 93 | if not os.path.exists(directory): 94 | os.makedirs(directory) 95 | xlsx_data.to_excel('./data/accounts_stats/wallets_stats.xlsx', index=False) 96 | 97 | [table.add_row(data.values()) for data in wallet_data] 98 | 99 | print(table) 100 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sealdeck/EthMachine/030c3e1cd97f3319e2f6e85936fb3559dc0d8474/requirements.txt -------------------------------------------------------------------------------- /utils/modules_runner.py: -------------------------------------------------------------------------------- 1 | import re 2 | import json 3 | import random 4 | import asyncio 5 | import traceback 6 | 7 | import aiohttp 8 | import python_socks 9 | import telebot 10 | 11 | from modules import Logger 12 | from aiohttp import ClientSession 13 | from utils.networks import EthereumRPC 14 | from web3 import AsyncWeb3, AsyncHTTPProvider 15 | from functions import get_network_by_chain_id 16 | from modules.interfaces import SoftwareException, CriticalException 17 | from config import ACCOUNT_NAMES, PRIVATE_KEYS, PROXIES, CHAIN_NAME 18 | from utils.route_generator import AVAILABLE_MODULES_INFO, get_func_by_name 19 | from utils.tools import clean_gwei_file 20 | from general_settings import (USE_PROXY, SLEEP_MODE, SLEEP_TIME_MODULES, SOFTWARE_MODE, TG_ID, TG_TOKEN, MOBILE_PROXY, 21 | MOBILE_PROXY_URL_CHANGER, WALLETS_TO_WORK, TELEGRAM_NOTIFICATIONS, 22 | SAVE_PROGRESS, ACCOUNTS_IN_STREAM, SLEEP_TIME_ACCOUNTS, SHUFFLE_WALLETS, BREAK_ROUTE, 23 | STOP_SOFTWARE) 24 | 25 | 26 | class Runner(Logger): 27 | @staticmethod 28 | def get_wallets_batch(account_list: tuple = None): 29 | range_count = range(account_list[0], account_list[1]) 30 | account_names = [ACCOUNT_NAMES[i - 1] for i in range_count] 31 | accounts = [PRIVATE_KEYS[i - 1] for i in range_count] 32 | return zip(account_names, accounts) 33 | 34 | @staticmethod 35 | def get_wallets(): 36 | if WALLETS_TO_WORK == 0: 37 | accounts_data = zip(ACCOUNT_NAMES, PRIVATE_KEYS) 38 | 39 | elif isinstance(WALLETS_TO_WORK, int): 40 | accounts_data = zip([ACCOUNT_NAMES[WALLETS_TO_WORK - 1]], [PRIVATE_KEYS[WALLETS_TO_WORK - 1]]) 41 | 42 | elif isinstance(WALLETS_TO_WORK, tuple): 43 | account_names = [ACCOUNT_NAMES[i - 1] for i in WALLETS_TO_WORK] 44 | accounts = [PRIVATE_KEYS[i - 1] for i in WALLETS_TO_WORK] 45 | accounts_data = zip(account_names, accounts) 46 | 47 | elif isinstance(WALLETS_TO_WORK, list): 48 | range_count = range(WALLETS_TO_WORK[0], WALLETS_TO_WORK[1] + 1) 49 | account_names = [ACCOUNT_NAMES[i - 1] for i in range_count] 50 | accounts = [PRIVATE_KEYS[i - 1] for i in range_count] 51 | accounts_data = zip(account_names, accounts) 52 | else: 53 | accounts_data = [] 54 | 55 | accounts_data = list(accounts_data) 56 | 57 | if SHUFFLE_WALLETS: 58 | random.shuffle(accounts_data) 59 | 60 | return accounts_data 61 | 62 | @staticmethod 63 | async def make_request(method: str = 'GET', url: str = None, headers: dict = None): 64 | 65 | async with ClientSession() as session: 66 | async with session.request(method=method, url=url, headers=headers) as response: 67 | if response.status == 200: 68 | return True 69 | return False 70 | 71 | @staticmethod 72 | def load_routes(): 73 | with open('./data/services/wallets_progress.json', 'r') as f: 74 | return json.load(f) 75 | 76 | async def smart_sleep(self, account_name, account_number, accounts_delay=False): 77 | if SLEEP_MODE and account_number: 78 | if accounts_delay: 79 | duration = random.randint(*tuple(x * account_number for x in SLEEP_TIME_ACCOUNTS)) 80 | else: 81 | duration = random.randint(*SLEEP_TIME_MODULES) 82 | self.logger_msg(account_name, None, f"💤 Sleeping for {duration} seconds\n") 83 | await asyncio.sleep(duration) 84 | 85 | async def send_tg_message(self, account_name, message_to_send, disable_notification=False): 86 | try: 87 | await asyncio.sleep(1) 88 | str_send = '*' + '\n'.join([re.sub(r'([_*\[\]()~`>#+\-=|{}.!])', r'\\\1', message) 89 | for message in message_to_send]) + '*' 90 | bot = telebot.TeleBot(TG_TOKEN) 91 | bot.send_message(TG_ID, str_send, parse_mode='MarkdownV2', disable_notification=disable_notification) 92 | print() 93 | self.logger_msg(account_name, None, f"Telegram message sent", 'success') 94 | except Exception as error: 95 | self.logger_msg(account_name, None, f"Telegram | API Error: {error}", 'error') 96 | 97 | def update_step(self, account_name, step): 98 | wallets = self.load_routes() 99 | wallets[str(account_name)]["current_step"] = step 100 | with open('./data/services/wallets_progress.json', 'w') as f: 101 | json.dump(wallets, f, indent=4) 102 | 103 | @staticmethod 104 | def collect_bad_wallets(account_name, module_name): 105 | try: 106 | with open('./data/bad_wallets.json', 'r') as file: 107 | data = json.load(file) 108 | except (FileNotFoundError, json.JSONDecodeError): 109 | data = {} 110 | 111 | data.setdefault(str(account_name), []).append(module_name) 112 | 113 | with open('./data/bad_wallets.json', 'w') as file: 114 | json.dump(data, file, indent=4) 115 | 116 | async def change_ip_proxy(self): 117 | for index, proxy_url in enumerate(MOBILE_PROXY_URL_CHANGER, 1): 118 | while True: 119 | try: 120 | self.logger_msg(None, None, f'Trying to change IP №{index} address\n', 'info') 121 | 122 | await self.make_request(url=proxy_url) 123 | 124 | self.logger_msg(None, None, f'IP №{index} address changed!\n', 'success') 125 | await asyncio.sleep(5) 126 | break 127 | 128 | except Exception as error: 129 | self.logger_msg(None, None, f'Bad URL for change IP №{index}. Error: {error}', 'error') 130 | await asyncio.sleep(15) 131 | 132 | async def check_proxies_status(self): 133 | tasks = [] 134 | for proxy in PROXIES: 135 | tasks.append(self.check_proxy_status(None, proxy=proxy)) 136 | await asyncio.gather(*tasks) 137 | 138 | async def check_proxy_status(self, account_name: str = None, proxy: str = None, silence: bool = False): 139 | try: 140 | w3 = AsyncWeb3(AsyncHTTPProvider(random.choice(EthereumRPC.rpc), 141 | request_kwargs={"proxy": f"http://{proxy}"})) 142 | if await w3.is_connected(): 143 | if not silence: 144 | info = f'Proxy {proxy[proxy.find("@"):]} successfully connected to Ethereum RPC' 145 | self.logger_msg(account_name, None, info, 'success') 146 | return True 147 | self.logger_msg(account_name, None, f"Proxy: {proxy} can`t connect to Ethereum RPC", 'error') 148 | return False 149 | except Exception as error: 150 | self.logger_msg(account_name, None, f"Bad proxy: {proxy} | Error: {error}", 'error') 151 | return False 152 | 153 | def get_proxy_for_account(self, account_name): 154 | if USE_PROXY: 155 | try: 156 | account_number = ACCOUNT_NAMES.index(account_name) 157 | num_proxies = len(PROXIES) 158 | return PROXIES[account_number % num_proxies] 159 | except Exception as error: 160 | self.logger_msg(account_name, None, f"Bad data in proxy, but you want proxy! Error: {error}", 'error') 161 | raise SoftwareException("Proxy error") 162 | 163 | async def run_account_modules( 164 | self, account_name:str, private_key:str, network, proxy:str | None, index:int, parallel_mode: bool = False 165 | ): 166 | message_list, result_list, used_modules, route_paths, break_flag, module_counter = [], [], [], [], False, 0 167 | try: 168 | route_data = self.load_routes().get(str(account_name), {}).get('route', []) 169 | route_modules = [[i.split()[0], 0] for i in route_data] 170 | 171 | current_step = 0 172 | used_modules.extend(route_modules) 173 | 174 | if SAVE_PROGRESS: 175 | current_step = self.load_routes()[str(account_name)]["current_step"] 176 | 177 | module_info = AVAILABLE_MODULES_INFO 178 | info = CHAIN_NAME[13] 179 | 180 | message_list.append( 181 | f'⚔️ {info} | Account name: "{account_name}"\n \n{len(route_modules)} module(s) in route\n') 182 | 183 | if current_step >= len(route_modules): 184 | self.logger_msg( 185 | account_name, None, f"All modules were completed", type_msg='warning') 186 | return False 187 | 188 | while current_step < len(route_modules): 189 | module_counter += 1 190 | module_name = route_modules[current_step][0] 191 | module_func = get_func_by_name(module_name) 192 | module_name_tg = AVAILABLE_MODULES_INFO[module_func][2] 193 | 194 | if parallel_mode and module_counter == 1: 195 | await self.smart_sleep(account_name, index, accounts_delay=True) 196 | 197 | self.logger_msg(account_name, None, f"🚀 Launch module: {module_info[module_func][2]}\n") 198 | 199 | module_input_data = [account_name, private_key, network, proxy] 200 | 201 | result = False 202 | while True: 203 | try: 204 | result = await module_func(*module_input_data) 205 | break 206 | except KeyError as error: 207 | msg = f"Setting '{error}' for this module is not exist in software!" 208 | self.logger_msg(account_name, None, msg=msg, type_msg='error') 209 | result = False 210 | break 211 | 212 | except (aiohttp.client_exceptions.ClientProxyConnectionError, asyncio.exceptions.TimeoutError, 213 | aiohttp.client_exceptions.ClientHttpProxyError, python_socks.ProxyError): 214 | self.logger_msg( 215 | account_name, None, 216 | msg=f"Connection to RPC is not stable. Will try again in 1 min...", 217 | type_msg='warning' 218 | ) 219 | await asyncio.sleep(60) 220 | continue 221 | 222 | except CriticalException as error: 223 | if not STOP_SOFTWARE: 224 | self.logger_msg(account_name, None, msg=f"{error}", type_msg='error') 225 | result = False 226 | break 227 | raise error 228 | except Exception as error: 229 | info = f"Module name: {module_info[module_func][2]} | Error {error}" 230 | self.logger_msg( 231 | account_name, None, f"Module crashed during the route: {info}", type_msg='error') 232 | traceback.print_exc() 233 | result = False 234 | break 235 | 236 | if result: 237 | self.update_step(account_name, current_step + 1) 238 | if not (current_step + 2) > (len(route_modules)): 239 | await self.smart_sleep(account_name, account_number=1) 240 | else: 241 | self.collect_bad_wallets(account_name, module_name) 242 | result = False 243 | if BREAK_ROUTE: 244 | message_list.extend([f'❌ {module_name_tg}\n', f'💀 The route was stopped!\n']) 245 | account_progress = (False, module_name, account_name) 246 | result_list.append(account_progress) 247 | break 248 | 249 | current_step += 1 250 | message_list.append(f'{"✅" if result else "❌"} {module_name_tg}\n') 251 | account_progress = (result, module_name, account_name) 252 | result_list.append(account_progress) 253 | 254 | success_count = len([1 for i in result_list if i[0]]) 255 | errors_count = len(result_list) - success_count 256 | message_list.append(f'Total result: ✅ — {success_count} | ❌ — {errors_count}') 257 | 258 | if TELEGRAM_NOTIFICATIONS: 259 | if errors_count > 0: 260 | disable_notification = False 261 | else: 262 | disable_notification = True 263 | await self.send_tg_message(account_name, message_to_send=message_list, 264 | disable_notification=disable_notification) 265 | 266 | if not SOFTWARE_MODE: 267 | self.logger_msg(None, None, f"Start running next wallet!\n", 'info') 268 | else: 269 | self.logger_msg(account_name, None, f"Wait for other wallets in stream!\n", 'info') 270 | 271 | return True 272 | except CriticalException as error: 273 | raise error 274 | except Exception as error: 275 | self.logger_msg(account_name, None, f"Error during the route! Error: {error}\n", 'error') 276 | traceback.print_exc() 277 | 278 | async def run_parallel(self): 279 | selected_wallets = list(self.get_wallets()) 280 | num_accounts = len(selected_wallets) 281 | accounts_per_stream = ACCOUNTS_IN_STREAM 282 | num_streams, remainder = divmod(num_accounts, accounts_per_stream) 283 | 284 | for stream_index in range(num_streams + (remainder > 0)): 285 | start_index = stream_index * accounts_per_stream 286 | end_index = (stream_index + 1) * accounts_per_stream if stream_index < num_streams else num_accounts 287 | 288 | accounts = selected_wallets[start_index:end_index] 289 | 290 | tasks = [] 291 | 292 | for index, data in enumerate(accounts): 293 | account_name, private_key = data 294 | tasks.append(asyncio.create_task( 295 | self.run_account_modules( 296 | account_name, private_key, get_network_by_chain_id(13), 297 | self.get_proxy_for_account(account_name), index, parallel_mode=True))) 298 | 299 | result_list = await asyncio.gather(*tasks, return_exceptions=True) 300 | 301 | for result in result_list: 302 | if isinstance(result, Exception): 303 | raise result 304 | 305 | if MOBILE_PROXY: 306 | await self.change_ip_proxy() 307 | 308 | self.logger_msg(None, None, f"Wallets in stream completed their tasks, launching next stream\n", 'success') 309 | 310 | self.logger_msg(None, None, f"All wallets completed their tasks!\n", 'success') 311 | 312 | async def run_consistently(self): 313 | 314 | accounts_data = self.get_wallets() 315 | 316 | for account_name, private_key in accounts_data: 317 | 318 | result = await self.run_account_modules(account_name, private_key, get_network_by_chain_id(13), 319 | self.get_proxy_for_account(account_name), index=1) 320 | 321 | if len(accounts_data) > 1 and result: 322 | await self.smart_sleep(account_name, account_number=1, accounts_delay=True) 323 | 324 | if MOBILE_PROXY: 325 | await self.change_ip_proxy() 326 | 327 | self.logger_msg(None, None, f"All accounts completed their tasks!\n", 328 | 'success') 329 | 330 | async def run_accounts(self): 331 | clean_gwei_file() 332 | 333 | try: 334 | if SOFTWARE_MODE: 335 | await self.run_parallel() 336 | else: 337 | await self.run_consistently() 338 | except SoftwareException as error: 339 | self.logger_msg(None, None, msg=error, type_msg='error') 340 | except Exception as error: 341 | self.logger_msg(None, None, msg=error, type_msg='error') 342 | if not isinstance(error, CriticalException): 343 | traceback.print_exc() 344 | -------------------------------------------------------------------------------- /utils/networks.py: -------------------------------------------------------------------------------- 1 | class Network: 2 | def __init__( 3 | self, 4 | name: str, 5 | rpc: list, 6 | chain_id: int, 7 | eip1559_support: bool, 8 | token: str, 9 | explorer: str, 10 | decimals: int = 18 11 | ): 12 | self.name = name 13 | self.rpc = rpc 14 | self.chain_id = chain_id 15 | self.eip1559_support = eip1559_support 16 | self.token = token 17 | self.explorer = explorer 18 | self.decimals = decimals 19 | 20 | def __repr__(self): 21 | return f'{self.name}' 22 | 23 | 24 | zkSyncEraRPC = Network( 25 | name='zkSync', 26 | rpc=[ 27 | 'https://mainnet.era.zksync.io', 28 | ], 29 | chain_id=324, 30 | eip1559_support=True, 31 | token='ETH', 32 | explorer='https://era.zksync.network/', 33 | ) 34 | 35 | ScrollRPC = Network( 36 | name='Scroll', 37 | rpc=[ 38 | 'https://rpc.scroll.io', 39 | ], 40 | chain_id=534352, 41 | eip1559_support=False, 42 | token='ETH', 43 | explorer='https://scrollscan.com/' 44 | ) 45 | 46 | ArbitrumRPC = Network( 47 | name='Arbitrum', 48 | rpc=[ 49 | 'https://rpc.ankr.com/arbitrum/', 50 | ], 51 | chain_id=42161, 52 | eip1559_support=True, 53 | token='ETH', 54 | explorer='https://arbiscan.io/', 55 | ) 56 | 57 | 58 | OptimismRPC = Network( 59 | name='Optimism', 60 | rpc=[ 61 | 'https://rpc.ankr.com/optimism/', 62 | 'https://optimism.drpc.org', 63 | 'https://1rpc.io/op' 64 | ], 65 | chain_id=10, 66 | eip1559_support=True, 67 | token='ETH', 68 | explorer='https://optimistic.etherscan.io/', 69 | ) 70 | 71 | 72 | PolygonRPC = Network( 73 | name='Polygon', 74 | rpc=[ 75 | 'https://polygon-rpc.com', 76 | ], 77 | chain_id=137, 78 | eip1559_support=False, 79 | token='MATIC', 80 | explorer='https://polygonscan.com/', 81 | ) 82 | 83 | 84 | AvalancheRPC = Network( 85 | name='Avalanche', 86 | rpc=[ 87 | 'https://avalanche.drpc.org' 88 | ], 89 | chain_id=43114, 90 | eip1559_support=False, 91 | token='AVAX', 92 | explorer='https://snowtrace.io/', 93 | ) 94 | 95 | 96 | EthereumRPC = Network( 97 | name='Ethereum', 98 | rpc=[ 99 | 'https://rpc.ankr.com/eth', 100 | 'https://ethereum.publicnode.com', 101 | 'https://rpc.flashbots.net', 102 | 'https://1rpc.io/eth', 103 | 'https://eth.drpc.org' 104 | ], 105 | chain_id=1, 106 | eip1559_support=True, 107 | token='ETH', 108 | explorer='https://etherscan.io/' 109 | ) 110 | 111 | Arbitrum_novaRPC = Network( 112 | name='Arbitrum Nova', 113 | rpc=[ 114 | 'https://rpc.ankr.com/arbitrumnova', 115 | 'https://arbitrum-nova.publicnode.com', 116 | 'https://arbitrum-nova.drpc.org', 117 | 'https://nova.arbitrum.io/rpc' 118 | ], 119 | chain_id=42170, 120 | eip1559_support=True, 121 | token='ETH', 122 | explorer='https://nova.arbiscan.io/' 123 | ) 124 | 125 | BaseRPC = Network( 126 | name='Base', 127 | rpc=[ 128 | 'https://mainnet.base.org', 129 | ], 130 | chain_id=8453, 131 | eip1559_support=False, 132 | token='ETH', 133 | explorer='https://basescan.org/' 134 | ) 135 | 136 | LineaRPC = Network( 137 | name='Linea', 138 | rpc=[ 139 | 'https://linea.drpc.org', 140 | 'https://1rpc.io/linea', 141 | 'https://rpc.linea.build' 142 | ], 143 | chain_id=59144, 144 | eip1559_support=False, 145 | token='ETH', 146 | explorer='https://lineascan.build/' 147 | ) 148 | 149 | ZoraRPC = Network( 150 | name='Zora', 151 | rpc=[ 152 | 'https://rpc.zora.energy' 153 | ], 154 | chain_id=7777777, 155 | eip1559_support=False, 156 | token='ETH', 157 | explorer='https://zora.superscan.network/' 158 | ) 159 | 160 | Polygon_ZKEVM_RPC = Network( 161 | name='Polygon ZKEVM', 162 | rpc=[ 163 | 'https://1rpc.io/polygon/zkevm', 164 | 'https://zkevm-rpc.com', 165 | 'https://rpc.ankr.com/polygon_zkevm' 166 | ], 167 | chain_id=1101, 168 | eip1559_support=False, 169 | token='ETH', 170 | explorer='https://zkevm.polygonscan.com/' 171 | ) 172 | 173 | BSC_RPC = Network( 174 | name='BNB Chain', 175 | rpc=[ 176 | 'https://rpc.ankr.com/bsc', 177 | ], 178 | chain_id=56, 179 | eip1559_support=False, 180 | token='BNB', 181 | explorer='https://bscscan.com/' 182 | ) 183 | 184 | MantaRPC = Network( 185 | name='Manta', 186 | rpc=[ 187 | 'https://pacific-rpc.manta.network/http' 188 | 'https://1rpc.io/manta' 189 | ], 190 | chain_id=169, 191 | eip1559_support=True, 192 | token='ETH', 193 | explorer='https://pacific-explorer.manta.network/' 194 | ) 195 | 196 | MantleRPC = Network( 197 | name='Mantle', 198 | rpc=[ 199 | 'https://mantle.publicnode.com', 200 | 'https://mantle-mainnet.public.blastapi.io', 201 | 'https://mantle.drpc.org', 202 | 'https://rpc.ankr.com/mantle', 203 | 'https://1rpc.io/mantle' 204 | ], 205 | chain_id=5000, 206 | eip1559_support=True, 207 | token='MNT', 208 | explorer='https://explorer.mantle.xyz/' 209 | ) 210 | 211 | OpBNB_RPC = Network( 212 | name='OpBNB', 213 | rpc=[ 214 | 'https://opbnb.publicnode.com', 215 | 'https://1rpc.io/opbnb', 216 | 'https://opbnb-mainnet-rpc.bnbchain.org', 217 | 'https://opbnb-mainnet.nodereal.io/v1/e9a36765eb8a40b9bd12e680a1fd2bc5', 218 | ], 219 | chain_id=204, 220 | eip1559_support=False, 221 | token='BNB', 222 | explorer='https://opbnbscan.com/' 223 | ) 224 | 225 | MoonbeamRPC = Network( 226 | name='Moonbeam', 227 | rpc=[ 228 | 'https://rpc.ankr.com/moonbeam', 229 | 'https://1rpc.io/glmr', 230 | 'https://rpc.api.moonbeam.network', 231 | ], 232 | chain_id=1284, 233 | eip1559_support=False, 234 | token='GLMR', 235 | explorer='https://moonscan.io/' 236 | ) 237 | 238 | MoonriverRPC = Network( 239 | name='Moonriver', 240 | rpc=[ 241 | 'https://moonriver.public.blastapi.io', 242 | 'https://moonriver.publicnode.com', 243 | ], 244 | chain_id=1285, 245 | eip1559_support=False, 246 | token='MOVR', 247 | explorer='https://moonriver.moonscan.io/' 248 | ) 249 | 250 | 251 | HarmonyRPC = Network( 252 | name='Harmony One', 253 | rpc=[ 254 | 'https://api.harmony.one', 255 | 'https://a.api.s0.t.hmny.io', 256 | 'https://endpoints.omniatech.io/v1/harmony/mainnet-0/public', 257 | 'https://1rpc.io/one', 258 | ], 259 | chain_id=1666600000, 260 | eip1559_support=False, 261 | token='ONE', 262 | explorer='https://explorer.harmony.one/' 263 | ) 264 | 265 | TelosRPC = Network( 266 | name='Telos', 267 | rpc=[ 268 | 'https://mainnet.telos.net/evm', 269 | 'https://rpc1.eu.telos.net/evm', 270 | 'https://rpc1.us.telos.net/evm', 271 | 'https://api.kainosbp.com/evm', 272 | ], 273 | chain_id=40, 274 | eip1559_support=False, 275 | token='TLOS', 276 | explorer='https://explorer.telos.net/' 277 | ) 278 | 279 | CeloRPC = Network( 280 | name='Celo', 281 | rpc=[ 282 | 'https://forno.celo.org', 283 | 'https://rpc.ankr.com/celo', 284 | 'https://1rpc.io/celo', 285 | ], 286 | chain_id=42220, 287 | eip1559_support=False, 288 | token='CELO', 289 | explorer='https://explorer.celo.org/mainnet/' 290 | ) 291 | 292 | GnosisRPC = Network( 293 | name='Gnosis', 294 | rpc=[ 295 | 'https://gnosis.drpc.org', 296 | 'https://1rpc.io/gnosis', 297 | ], 298 | chain_id=100, 299 | eip1559_support=False, 300 | token='XDAI', 301 | explorer='https://gnosisscan.io/' 302 | ) 303 | 304 | CoreRPC = Network( 305 | name='CoreDAO', 306 | rpc=[ 307 | 'https://rpc.ankr.com/core', 308 | 'https://1rpc.io/core', 309 | 'https://rpc.coredao.org', 310 | ], 311 | chain_id=1116, 312 | eip1559_support=False, 313 | token='CORE', 314 | explorer='https://scan.coredao.org/' 315 | ) 316 | 317 | TomoChainRPC = Network( 318 | name='TomoChain', 319 | rpc=[ 320 | 'https://rpc.tomochain.com', 321 | 'https://tomo.blockpi.network/v1/rpc/public', 322 | 'https://viction.blockpi.network/v1/rpc/public', 323 | ], 324 | chain_id=88, 325 | eip1559_support=False, 326 | token='TOMO', 327 | explorer='https://tomoscan.io/' 328 | ) 329 | 330 | ConfluxRPC = Network( 331 | name='Conflux', 332 | rpc=[ 333 | 'https://evm.confluxrpc.com', 334 | ], 335 | chain_id=1030, 336 | eip1559_support=False, 337 | token='CFX', 338 | explorer='https://evm.confluxscan.io/' 339 | ) 340 | 341 | OrderlyRPC = Network( 342 | name='Orderly', 343 | rpc=[ 344 | 'https://l2-orderly-mainnet-0.t.conduit.xyz', 345 | 'https://rpc.orderly.network', 346 | ], 347 | chain_id=291, 348 | eip1559_support=False, 349 | token='ETH', 350 | explorer='https://explorer.orderly.network/' 351 | ) 352 | 353 | HorizenRPC = Network( 354 | name='Horizen EON', 355 | rpc=[ 356 | 'https://rpc.ankr.com/horizen_eon', 357 | 'https://eon-rpc.horizenlabs.io/ethv1', 358 | ], 359 | chain_id=7332, 360 | eip1559_support=False, 361 | token='ZEN', 362 | explorer='https://explorer.horizen.io/' 363 | ) 364 | 365 | MetisRPC = Network( 366 | name='Metis', 367 | rpc=[ 368 | 'https://metis-mainnet.public.blastapi.io', 369 | 'https://metis-pokt.nodies.app', 370 | 'https://andromeda.metis.io/?owner=1088' 371 | ], 372 | chain_id=1088, 373 | eip1559_support=False, 374 | token='METIS', 375 | explorer='https://explorer.metis.io/' 376 | ) 377 | 378 | AstarRPC = Network( 379 | name='Astar', 380 | rpc=[ 381 | 'https://evm.astar.network', 382 | 'https://astar.public.blastapi.io', 383 | 'https://1rpc.io/astr', 384 | 'https://astar-rpc.dwellir.com' 385 | ], 386 | chain_id=592, 387 | eip1559_support=False, 388 | token='ASTR', 389 | explorer='https://astar.blockscout.com/' 390 | ) 391 | 392 | KavaRPC = Network( 393 | name='Kava', 394 | rpc=[ 395 | #'https://kava-pokt.nodies.app', 396 | 'https://evm.kava.io', 397 | ], 398 | chain_id=2222, 399 | eip1559_support=False, 400 | token='KAVA', 401 | explorer='https://kavascan.com/' 402 | ) 403 | 404 | KlaytnRPC = Network( 405 | name='Klaytn', 406 | rpc=[ 407 | 'https://rpc.ankr.com/klaytn', 408 | 'https://klaytn.drpc.org', 409 | ], 410 | chain_id=8217, 411 | eip1559_support=False, 412 | token='KLAY', 413 | explorer='https://klaytnscope.com/' 414 | ) 415 | 416 | FantomRPC = Network( 417 | name='Fantom', 418 | rpc=[ 419 | 'https://rpc.ankr.com/fantom', 420 | ], 421 | chain_id=250, 422 | eip1559_support=False, 423 | token='FTM', 424 | explorer='https://ftmscan.com/' 425 | ) 426 | 427 | AuroraRPC = Network( 428 | name='Aurora', 429 | rpc=[ 430 | 'https://mainnet.aurora.dev', 431 | 'https://endpoints.omniatech.io/v1/aurora/mainnet/public', 432 | 'https://1rpc.io/aurora', 433 | 'https://aurora.drpc.org' 434 | ], 435 | chain_id=1313161554, 436 | eip1559_support=False, 437 | token='ETH', 438 | explorer='https://explorer.aurora.dev/' 439 | ) 440 | 441 | CantoRPC = Network( 442 | name='Canto', 443 | rpc=[ 444 | 'https://canto.gravitychain.io', 445 | 'https://jsonrpc.canto.nodestake.top', 446 | 'https://mainnode.plexnode.org:8545', 447 | 'https://canto.slingshot.finance' 448 | ], 449 | chain_id=7700, 450 | eip1559_support=False, 451 | token='CANTO', 452 | explorer='https://cantoscan.com/' 453 | ) 454 | 455 | DFK_RPC = Network( 456 | name='DFK', 457 | rpc=[ 458 | 'https://avax-pokt.nodies.app/ext/bc/q2aTwKuyzgs8pynF7UXBZCU7DejbZbZ6EUyHr3JQzYgwNPUPi/rpc', 459 | 'https://dfkchain.api.onfinality.io/public', 460 | 'https://mainnode.plexnode.org:8545', 461 | 'https://subnets.avax.network/defi-kingdoms/dfk-chain/rpc' 462 | ], 463 | chain_id=53935, 464 | eip1559_support=False, 465 | token='JEWEL', 466 | explorer='https://avascan.info/blockchain/dfk' 467 | ) 468 | 469 | FuseRPC = Network( 470 | name='Fuse', 471 | rpc=[ 472 | 'https://rpc.fuse.io', 473 | 'https://fuse-pokt.nodies.app', 474 | 'https://fuse.liquify.com', 475 | 'https://fuse.api.onfinality.io/public' 476 | ], 477 | chain_id=122, 478 | eip1559_support=False, 479 | token='FUSE', 480 | explorer='https://cantoscan.com/' 481 | ) 482 | 483 | GoerliRPC = Network( 484 | name='Goerli', 485 | rpc=[ 486 | 'https://endpoints.omniatech.io/v1/eth/goerli/public', 487 | 'https://rpc.ankr.com/eth_goerli', 488 | 'https://eth-goerli.public.blastapi.io', 489 | 'https://goerli.blockpi.network/v1/rpc/public' 490 | ], 491 | chain_id=5, 492 | eip1559_support=False, 493 | token='ETH', 494 | explorer='https://goerli.etherscan.io/' 495 | ) 496 | 497 | MeterRPC = Network( 498 | name='Meter', 499 | rpc=[ 500 | 'https://rpc.meter.io', 501 | 'https://rpc-meter.jellypool.xyz', 502 | 'https://meter.blockpi.network/v1/rpc/public', 503 | ], 504 | chain_id=82, 505 | eip1559_support=False, 506 | token='MTR', 507 | explorer='https://scan.meter.io/' 508 | ) 509 | 510 | OKX_RPC = Network( 511 | name='OKX Chain', 512 | rpc=[ 513 | 'https://exchainrpc.okex.org', 514 | 'https://oktc-mainnet.public.blastapi.io', 515 | 'https://1rpc.io/oktc', 516 | 'https://okt-chain.api.onfinality.io/public' 517 | ], 518 | chain_id=66, 519 | eip1559_support=False, 520 | token='OKT', 521 | explorer='https://www.oklink.com/ru/oktc' 522 | ) 523 | 524 | ShimmerRPC = Network( 525 | name='Shimmer', 526 | rpc=[ 527 | 'https://json-rpc.evm.shimmer.network', 528 | ], 529 | chain_id=148, 530 | eip1559_support=False, 531 | token='SMR', 532 | explorer='https://explorer.shimmer.network/' 533 | ) 534 | 535 | TenetRPC = Network( 536 | name='Tenet', 537 | rpc=[ 538 | 'https://rpc.tenet.org', 539 | 'https://tenet-evm.publicnode.com', 540 | ], 541 | chain_id=1559, 542 | eip1559_support=False, 543 | token='TENET', 544 | explorer='https://tenetscan.io/' 545 | ) 546 | 547 | XPLA_RPC = Network( 548 | name='XPLA', 549 | rpc=[ 550 | 'https://dimension-evm-rpc.xpla.dev ', 551 | ], 552 | chain_id=37, 553 | eip1559_support=False, 554 | token='XPLA', 555 | explorer='https://explorer.xpla.io/' 556 | ) 557 | 558 | LootChainRPC = Network( 559 | name='LootChain', 560 | rpc=[ 561 | 'https://rpc.lootchain.com/http', 562 | ], 563 | chain_id=5151706, 564 | eip1559_support=False, 565 | token='AGLD', 566 | explorer='https://explorer.lootchain.com/' 567 | ) 568 | 569 | ZKFairRPC = Network( 570 | name='ZKFair', 571 | rpc=[ 572 | 'https://rpc.zkfair.io', 573 | 'https://zkfair.rpc.thirdweb.com', 574 | ], 575 | chain_id=42766, 576 | eip1559_support=False, 577 | token='USDC', 578 | explorer='https://scan.zkfair.io/' 579 | ) 580 | 581 | BeamRPC = Network( 582 | name='Beam', 583 | rpc=[ 584 | 'https://subnets.avax.network/beam/mainnet/rpc' 585 | ], 586 | chain_id=4337, 587 | eip1559_support=False, 588 | token='Beam', 589 | explorer='https://4337.snowtrace.io/' 590 | ) 591 | 592 | InEVM_RPC = Network( 593 | name='inEVM', 594 | rpc=[ 595 | 'https://inevm.calderachain.xyz/http' 596 | ], 597 | chain_id=2525, 598 | eip1559_support=False, 599 | token='INJ', 600 | explorer='https://inevm.calderaexplorer.xyz/' 601 | ) 602 | 603 | BlastRPC = Network( 604 | name='Blast', 605 | rpc=[ 606 | 'https://rpc.blast.io', 607 | 'https://rpc.ankr.com/blast' 608 | ], 609 | chain_id=81457, 610 | eip1559_support=False, 611 | token='ETH', 612 | explorer='https://blastscan.io/' 613 | ) 614 | 615 | 616 | RaribleRPC = Network( 617 | name='Rarible', 618 | rpc=[ 619 | '' 620 | ], 621 | chain_id=0, 622 | eip1559_support=False, 623 | token='', 624 | explorer='' 625 | ) 626 | 627 | # zkSyncLite = Network( 628 | # name='zksync_lite', 629 | # rpc=[], 630 | # chain_id=0, 631 | # eip1559_support=True, 632 | # token='ETH', 633 | # explorer='https://zkscan.io/' 634 | # ) 635 | -------------------------------------------------------------------------------- /utils/route_generator.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | 4 | from utils.tools import clean_progress_file 5 | from functions import * 6 | from config import ACCOUNT_NAMES 7 | from modules import Logger 8 | from modules.interfaces import SoftwareException 9 | from general_settings import SHUFFLE_ROUTE 10 | from settings import (CLASSIC_ROUTES_MODULES_USING) 11 | 12 | GSHEET_CONFIG = "./data/services/service_account.json" 13 | os.environ["GSPREAD_SILENCE_WARNINGS"] = "1" 14 | 15 | 16 | AVAILABLE_MODULES_INFO = { 17 | # module_name : (module name, priority, tg info, can be help module, supported network) 18 | okx_withdraw : (okx_withdraw, -3, 'OKX withdraw', 0, []), 19 | bingx_withdraw : (bingx_withdraw, -3, 'BingX withdraw', 0, []), 20 | binance_withdraw : (binance_withdraw, -3, 'Binance withdraw', 0, []), 21 | bitget_withdraw : (bitget_withdraw, -3, 'Bitget withdraw', 0, []), 22 | make_balance_to_average : (make_balance_to_average, -2, 'Check and make wanted balance', 0, []), 23 | bridge_rhino : (bridge_rhino, 1, 'Rhino bridge', 0, [2, 3, 4, 8, 9, 11, 12]), 24 | bridge_relay2 : (bridge_relay2, 1, 'Relay2 bridge', 0, [2, 3, 4, 8, 9, 11, 12]), 25 | bridge_orbiter : (bridge_orbiter, 1, 'Orbiter bridge', 0, [2, 3, 4, 8, 9, 11, 12]), 26 | bridge_across : (bridge_across, 1, 'Across bridge', 0, [2, 3, 11, 12]), 27 | bridge_bungee : (bridge_bungee, 1, 'Bungee bridge', 0, [2, 3, 11, 12]), 28 | bridge_owlto : (bridge_owlto, 1, 'Owlto bridge', 0, [2, 3, 11, 12]), 29 | bridge_relay : (bridge_relay, 1, 'Relay bridge', 0, [2, 3, 11, 12]), 30 | bridge_nitro : (bridge_nitro, 1, 'Nitro bridge', 0, [2, 3, 11, 12]), 31 | bridge_native : (bridge_native, 1, 'Native bridge', 0, [2, 3, 4, 8, 9, 11, 12]), 32 | bridge_zora : (bridge_zora, 1, 'Zora instant bridge', 0, [2, 3, 4, 8, 9, 11, 12]), 33 | withdraw_txsync : (withdraw_txsync, 1, 'Withdraw txSync', 0, [2, 3, 4, 8, 9, 11, 12]), 34 | swap_izumi : (swap_izumi, 2, 'iZumi swap', 1, [3, 4, 8, 11]), 35 | swap_odos : (swap_odos, 2, 'ODOS swap', 1, [3, 11]), 36 | swap_oneinch : (swap_oneinch, 2, '1inch swap', 1, [3, 11]), 37 | swap_uniswap : (swap_uniswap, 2, 'Uniswap swap', 1, [3]), 38 | mint_mintfun : (mint_mintfun, 2, 'Mint NFT on mint.fun', 1, [3]), 39 | wrap_eth : (wrap_eth, 2, 'Wrap ETH', 0, []), 40 | unwrap_eth : (unwrap_eth, 2, 'Unwrap ETH', 0, []), 41 | random_approve : (random_approve, 2, 'Random approve', 0, []), 42 | refuel_bungee : (refuel_bungee, 3, 'Bungee refuel', 0, []), 43 | bingx_transfer : (bingx_transfer, 2, 'BingX transfer', 0, []), 44 | transfer_eth : (transfer_eth, 2, 'Transfer ETH', 0, []), 45 | transfer_eth_to_myself : (transfer_eth_to_myself, 2, 'Transfer ETH to myself', 0, []), 46 | okx_deposit : (okx_deposit, 5, 'OKX deposit', 0, []), 47 | bingx_deposit : (bingx_deposit, 5, 'Bingx deposit', 0, []), 48 | binance_deposit : (binance_deposit, 5, 'Binance deposit', 0, []), 49 | bitget_deposit : (bitget_deposit, 5, 'BitGet deposit', 0, []), 50 | } 51 | 52 | 53 | def get_func_by_name(module_name, help_message:bool = False): 54 | for k, v in AVAILABLE_MODULES_INFO.items(): 55 | if k.__name__ == module_name: 56 | if help_message: 57 | return v[2] 58 | return v[0] 59 | 60 | 61 | class RouteGenerator(Logger): 62 | def __init__(self, silent:bool = True): 63 | Logger.__init__(self) 64 | self.modules_names_const = [module.__name__ for module in list(AVAILABLE_MODULES_INFO.keys())] 65 | 66 | @staticmethod 67 | def classic_generate_route(): 68 | route = [] 69 | for i in CLASSIC_ROUTES_MODULES_USING: 70 | module_name = random.choice(i) 71 | if module_name is None: 72 | continue 73 | module = get_func_by_name(module_name) 74 | if module: 75 | route.append(module.__name__) 76 | else: 77 | raise SoftwareException(f'There is no module with the name "{module_name}" in the software.') 78 | return route 79 | 80 | @staticmethod 81 | def sort_classic_route(route): 82 | modules_dependents = { 83 | 'okx_withdraw': 0, 84 | 'bingx_withdraw': 0, 85 | 'binance_withdraw': 0, 86 | 'make_balance_to_average': 1, 87 | 'bridge_rhino': 1, 88 | 'bridge_layerswap': 1, 89 | 'bridge_nitro': 1, 90 | 'bridge_orbiter': 1, 91 | 'bridge_across': 1, 92 | 'bridge_owlto': 1, 93 | 'bridge_relay': 1, 94 | 'bridge_native': 1, 95 | 'bridge_zora': 1, 96 | 'okx_deposit': 4, 97 | 'bingx_deposit': 4, 98 | 'binance_deposit': 4, 99 | } 100 | 101 | new_route = [] 102 | classic_route = [] 103 | for module_name in route: 104 | if module_name in modules_dependents: 105 | classic_route.append((module_name, modules_dependents[module_name])) 106 | else: 107 | new_route.append((module_name, 2)) 108 | 109 | random.shuffle(new_route) 110 | classic_route.extend(new_route) 111 | route_with_priority = [module[0] for module in sorted(classic_route, key=lambda x: x[1])] 112 | 113 | return route_with_priority 114 | 115 | def classic_routes_json_save(self): 116 | clean_progress_file() 117 | with open('./data/services/wallets_progress.json', 'w') as file: 118 | accounts_data = {} 119 | for account_name in ACCOUNT_NAMES: 120 | if isinstance(account_name, (str, int)): 121 | classic_route = self.classic_generate_route() 122 | if SHUFFLE_ROUTE: 123 | classic_route = self.sort_classic_route(route=classic_route) 124 | account_data = { 125 | "current_step": 0, 126 | "route": classic_route 127 | } 128 | accounts_data[str(account_name)] = account_data 129 | json.dump(accounts_data, file, indent=4) 130 | self.logger_msg( 131 | None, None, 132 | f'Successfully generated {len(accounts_data)} classic routes in data/services/wallets_progress.json\n', 133 | 'success') 134 | 135 | def smart_routes_json_save(self, account_name:str, route:list): 136 | progress_file_path = './data/services/wallets_progress.json' 137 | 138 | try: 139 | with open(progress_file_path, 'r+') as file: 140 | data = json.load(file) 141 | except json.JSONDecodeError: 142 | data = {} 143 | 144 | data[account_name] = { 145 | "current_step": 0, 146 | "route": ([" ".join(item) for item in route] if isinstance(route[0], tuple) else route) if route else [] 147 | } 148 | 149 | with open(progress_file_path, 'w') as file: 150 | json.dump(data, file, indent=4) 151 | 152 | self.logger_msg( 153 | None, None, 154 | f'Successfully generated smart routes for {account_name}', 'success') 155 | -------------------------------------------------------------------------------- /utils/stark_signature/eth_coder.py: -------------------------------------------------------------------------------- 1 | import hmac 2 | import ecdsa 3 | 4 | from hashlib import sha256, sha512 5 | from Crypto.Cipher import AES 6 | from Crypto.Util.Padding import unpad 7 | from Crypto.Random import get_random_bytes 8 | from ecdsa import SigningKey, keys, SECP256k1 9 | from ecdsa.ellipticcurve import Point 10 | 11 | 12 | def is_valid_private_key(private_key): 13 | return 1 <= int.from_bytes(private_key, 'big') < SECP256k1.order 14 | 15 | 16 | def get_public_key(private_key, encoding='raw'): 17 | sk = SigningKey.from_string(private_key, curve=SECP256k1) 18 | vk = sk.get_verifying_key() 19 | return vk.to_string(encoding=encoding) 20 | 21 | 22 | def derive(private_key_a, public_key_b): 23 | assert isinstance(private_key_a, bytes), "Неправильный формат закрытого ключа" 24 | assert len(private_key_a) == 32, "Неправильная длина закрытого ключа" 25 | 26 | assert isinstance(public_key_b, bytes), "Неправильный формат открытого ключа" 27 | assert len(public_key_b) in [33, 65], "Неправильная длина открытого ключа" 28 | 29 | if len(public_key_b) == 65: 30 | assert public_key_b[0] == 4, "Неправильный формат открытого ключа" 31 | if len(public_key_b) == 33: 32 | assert public_key_b[0] in [2, 3], "Неправильный формат открытого ключа" 33 | 34 | curve = ecdsa.curves.SECP256k1 35 | 36 | key_a = keys.SigningKey.from_string(private_key_a, curve=curve) 37 | key_b = keys.VerifyingKey.from_string(public_key_b, curve=curve) 38 | 39 | shared_secret = key_a.privkey.secret_multiplier * key_b.pubkey.point 40 | 41 | px = shared_secret.x().to_bytes(32, byteorder='big') 42 | return px 43 | 44 | 45 | def hmac_sha256_sign(key, data): 46 | return hmac.new(key, data, sha256).digest() 47 | 48 | 49 | def hex_to_uint8_array(hex_string): 50 | return bytes.fromhex(hex_string) 51 | 52 | 53 | def uint8_array_to_hex(uint8_array): 54 | return uint8_array.hex() 55 | 56 | 57 | def compress(starts_with04): 58 | test_buffer = bytes.fromhex(starts_with04) 59 | if len(test_buffer) == 64: 60 | starts_with04 = '04' + starts_with04 61 | 62 | return uint8_array_to_hex(public_key_convert(hex_to_uint8_array(starts_with04), True)) 63 | 64 | 65 | def decompress(starts_with_02_or_03): 66 | 67 | test_bytes = bytes.fromhex(starts_with_02_or_03) 68 | if len(test_bytes) == 64: 69 | starts_with_02_or_03 = '04' + starts_with_02_or_03 70 | 71 | decompressed = uint8_array_to_hex(public_key_convert(hex_to_uint8_array(starts_with_02_or_03), False)) 72 | 73 | decompressed = decompressed[2:] 74 | return decompressed 75 | 76 | 77 | def public_key_convert(public_key, compressed=False): 78 | assert len(public_key) == 33 or len(public_key) == 65, "Invalid public key length" 79 | 80 | point = Point.from_bytes(SECP256k1.curve, public_key) 81 | 82 | if compressed: 83 | result = point.to_bytes(encoding='compressed') 84 | else: 85 | result = point.to_bytes(encoding='uncompressed') 86 | 87 | return result 88 | 89 | 90 | def aes_cbc_encrypt(iv, key, data): 91 | cipher = AES.new(key, AES.MODE_CBC, iv) 92 | 93 | padding_length = 16 - (len(data) % 16) 94 | padded_data = data + bytes([padding_length]) * padding_length 95 | 96 | ciphertext = cipher.encrypt(padded_data) 97 | return ciphertext 98 | 99 | 100 | def encrypt(public_key_to:bytes, msg: bytes): 101 | ephem_private_key = get_random_bytes(32) 102 | while not is_valid_private_key(ephem_private_key): 103 | ephem_private_key = get_random_bytes(32) 104 | 105 | ephem_public_key = get_public_key(ephem_private_key, 'uncompressed') 106 | 107 | px = derive(ephem_private_key, public_key_to) 108 | 109 | hash_obj = sha512(px) 110 | 111 | iv = get_random_bytes(16) 112 | 113 | encryption_key = hash_obj.digest()[:32] 114 | 115 | mac_key = hash_obj.digest()[32:] 116 | 117 | ciphertext = aes_cbc_encrypt(iv, encryption_key, msg) 118 | 119 | data_to_mac = iv + ephem_public_key + ciphertext 120 | mac = hmac_sha256_sign(mac_key, data_to_mac) 121 | 122 | ephem_public_key = bytes.fromhex(compress(ephem_public_key.hex())) 123 | 124 | return { 125 | 'iv': iv, 126 | 'ephemPublicKey': ephem_public_key, 127 | 'ciphertext': ciphertext, 128 | 'mac': mac, 129 | } 130 | 131 | 132 | def encrypt_with_public_key(public_key, message): 133 | 134 | public_key = decompress(public_key) 135 | 136 | pub_string = '04' + public_key 137 | 138 | encrypted = encrypt( 139 | bytes.fromhex(pub_string), 140 | message.encode('utf-8') 141 | ) 142 | 143 | return "".join([ 144 | encrypted['iv'].hex(), 145 | encrypted['ephemPublicKey'].hex(), 146 | encrypted['mac'].hex(), 147 | encrypted['ciphertext'].hex() 148 | ]) 149 | 150 | 151 | def parse(data_str:str): 152 | 153 | buf = bytes.fromhex(data_str) 154 | 155 | ret = { 156 | 'iv': buf[0:16].hex(), 157 | 'ephemPublicKey': buf[16:49].hex(), 158 | 'mac': buf[49:81].hex(), 159 | 'ciphertext': buf[81:].hex() 160 | } 161 | 162 | ret['ephemPublicKey'] = '04' + decompress(ret['ephemPublicKey']) 163 | 164 | return ret 165 | 166 | 167 | def aes_cbc_decrypt(iv, encryption_key, ciphertext): 168 | cipher = AES.new(encryption_key, AES.MODE_CBC, iv) 169 | return unpad(cipher.decrypt(ciphertext), AES.block_size) 170 | 171 | 172 | def hmac_sha256_verify(key, msg, sig): 173 | hmac_obj = hmac.new(key.encode('utf-8'), msg.encode('utf-8'), sha256) 174 | 175 | expected_sig = hmac_obj.digest() 176 | 177 | return hmac.compare_digest(expected_sig, sig) 178 | 179 | 180 | def decrypt_with_private_key(private_key, encrypted_data): 181 | 182 | encrypted = parse(encrypted_data) 183 | 184 | stripped_key = private_key[2:] if private_key.startswith('0x') else private_key 185 | 186 | encrypted_buffer = { 187 | 'iv': bytes.fromhex(encrypted['iv']), 188 | 'ephemPublicKey': bytes.fromhex(encrypted['ephemPublicKey']), 189 | 'ciphertext': bytes.fromhex(encrypted['ciphertext']), 190 | 'mac': bytes.fromhex(encrypted['mac']) 191 | } 192 | 193 | derived_key = derive(bytes.fromhex(stripped_key), encrypted_buffer['ephemPublicKey']) 194 | hash_key = sha512(derived_key).digest() 195 | 196 | encryption_key = hash_key[:32] 197 | 198 | decrypted_message = aes_cbc_decrypt(encrypted_buffer['iv'], encryption_key, encrypted_buffer['ciphertext']) 199 | 200 | return decrypted_message.decode() 201 | -------------------------------------------------------------------------------- /utils/stark_signature/math_utils.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright 2019 StarkWare Industries Ltd. # 3 | # # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). # 5 | # You may not use this file except in compliance with the License. # 6 | # You may obtain a copy of the License at # 7 | # # 8 | # https://www.starkware.co/open-source-license/ # 9 | # # 10 | # Unless required by applicable law or agreed to in writing, # 11 | # software distributed under the License is distributed on an "AS IS" BASIS, # 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 13 | # See the License for the specific language governing permissions # 14 | # and limitations under the License. # 15 | ############################################################################### 16 | 17 | 18 | from typing import Tuple 19 | 20 | import mpmath 21 | import sympy 22 | from sympy.core.numbers import igcdex 23 | 24 | # A type that represents a point (x,y) on an elliptic curve. 25 | ECPoint = Tuple[int, int] 26 | 27 | 28 | def pi_as_string(digits: int) -> str: 29 | """ 30 | Returns pi as a string of decimal digits without the decimal point ("314..."). 31 | """ 32 | mpmath.mp.dps = digits # Set number of digits. 33 | return '3' + str(mpmath.mp.pi)[2:] 34 | 35 | 36 | def is_quad_residue(n: int, p: int) -> bool: 37 | """ 38 | Returns True if n is a quadratic residue mod p. 39 | """ 40 | return sympy.is_quad_residue(n, p) 41 | 42 | 43 | def sqrt_mod(n: int, p: int) -> int: 44 | """ 45 | Finds the minimum positive integer m such that (m*m) % p == n 46 | """ 47 | return min(sympy.sqrt_mod(n, p, all_roots=True)) 48 | 49 | 50 | def div_mod(n: int, m: int, p: int) -> int: 51 | """ 52 | Finds a nonnegative integer 0 <= x < p such that (m * x) % p == n 53 | """ 54 | a, b, c = igcdex(m, p) 55 | assert c == 1 56 | return (n * a) % p 57 | 58 | 59 | def ec_add(point1: ECPoint, point2: ECPoint, p: int) -> ECPoint: 60 | """ 61 | Gets two points on an elliptic curve mod p and returns their sum. 62 | Assumes the points are given in affine form (x, y) and have different x coordinates. 63 | """ 64 | assert (point1[0] - point2[0]) % p != 0 65 | m = div_mod(point1[1] - point2[1], point1[0] - point2[0], p) 66 | x = (m * m - point1[0] - point2[0]) % p 67 | y = (m * (point1[0] - x) - point1[1]) % p 68 | return x, y 69 | 70 | 71 | def ec_neg(point: ECPoint, p: int) -> ECPoint: 72 | """ 73 | Given a point (x,y) return (x, -y) 74 | """ 75 | x, y = point 76 | return (x, (-y) % p) 77 | 78 | 79 | def ec_double(point: ECPoint, alpha: int, p: int) -> ECPoint: 80 | """ 81 | Doubles a point on an elliptic curve with the equation y^2 = x^3 + alpha*x + beta mod p. 82 | Assumes the point is given in affine form (x, y) and has y != 0. 83 | """ 84 | assert point[1] % p != 0 85 | m = div_mod(3 * point[0] * point[0] + alpha, 2 * point[1], p) 86 | x = (m * m - 2 * point[0]) % p 87 | y = (m * (point[0] - x) - point[1]) % p 88 | return x, y 89 | 90 | 91 | def ec_mult(m: int, point: ECPoint, alpha: int, p: int) -> ECPoint: 92 | """ 93 | Multiplies by m a point on the elliptic curve with equation y^2 = x^3 + alpha*x + beta mod p. 94 | Assumes the point is given in affine form (x, y) and that 0 < m < order(point). 95 | """ 96 | if m == 1: 97 | return point 98 | if m % 2 == 0: 99 | return ec_mult(m // 2, ec_double(point, alpha, p), alpha, p) 100 | return ec_add(ec_mult(m - 1, point, alpha, p), point, p) -------------------------------------------------------------------------------- /utils/stark_signature/stark_singature.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Copyright 2019 StarkWare Industries Ltd. # 3 | # # 4 | # Licensed under the Apache License, Version 2.0 (the "License"). # 5 | # You may not use this file except in compliance with the License. # 6 | # You may obtain a copy of the License at # 7 | # # 8 | # https://www.starkware.co/open-source-license/ # 9 | # # 10 | # Unless required by applicable law or agreed to in writing, # 11 | # software distributed under the License is distributed on an "AS IS" BASIS, # 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 13 | # See the License for the specific language governing permissions # 14 | # and limitations under the License. # 15 | ############################################################################### 16 | 17 | import hashlib 18 | import json 19 | import math 20 | import os 21 | import random 22 | from typing import Optional, Tuple, Union 23 | 24 | 25 | from ecdsa.rfc6979 import generate_k 26 | 27 | from .math_utils import ECPoint, div_mod, ec_add, ec_double, ec_mult, is_quad_residue, sqrt_mod 28 | 29 | 30 | PEDERSEN_HASH_POINT_FILENAME = os.path.join( 31 | os.path.dirname(__file__), 'pedersen_params.json') 32 | PEDERSEN_PARAMS = json.load(open(PEDERSEN_HASH_POINT_FILENAME)) 33 | 34 | FIELD_PRIME = PEDERSEN_PARAMS['FIELD_PRIME'] 35 | FIELD_GEN = PEDERSEN_PARAMS['FIELD_GEN'] 36 | ALPHA = PEDERSEN_PARAMS['ALPHA'] 37 | BETA = PEDERSEN_PARAMS['BETA'] 38 | EC_ORDER = PEDERSEN_PARAMS['EC_ORDER'] 39 | CONSTANT_POINTS = PEDERSEN_PARAMS['CONSTANT_POINTS'] 40 | 41 | N_ELEMENT_BITS_ECDSA = math.floor(math.log(FIELD_PRIME, 2)) 42 | assert N_ELEMENT_BITS_ECDSA == 251 43 | 44 | N_ELEMENT_BITS_HASH = FIELD_PRIME.bit_length() 45 | assert N_ELEMENT_BITS_HASH == 252 46 | 47 | # Elliptic curve parameters. 48 | assert 2**N_ELEMENT_BITS_ECDSA < EC_ORDER < FIELD_PRIME 49 | 50 | SHIFT_POINT = CONSTANT_POINTS[0] 51 | MINUS_SHIFT_POINT = (SHIFT_POINT[0], FIELD_PRIME - SHIFT_POINT[1]) 52 | EC_GEN = CONSTANT_POINTS[1] 53 | 54 | assert SHIFT_POINT == [0x49ee3eba8c1600700ee1b87eb599f16716b0b1022947733551fde4050ca6804, 55 | 0x3ca0cfe4b3bc6ddf346d49d06ea0ed34e621062c0e056c1d0405d266e10268a] 56 | assert EC_GEN == [0x1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca, 57 | 0x5668060aa49730b7be4801df46ec62de53ecd11abe43a32873000c36e8dc1f] 58 | 59 | 60 | ######### 61 | # ECDSA # 62 | ######### 63 | 64 | # A type for the digital signature. 65 | ECSignature = Tuple[int, int] 66 | 67 | 68 | class InvalidPublicKeyError(Exception): 69 | def __init__(self): 70 | super().__init__('Given x coordinate does not represent any point on the elliptic curve.') 71 | 72 | 73 | def get_y_coordinate(stark_key_x_coordinate: int) -> int: 74 | """ 75 | Given the x coordinate of a stark_key, returns a possible y coordinate such that together the 76 | point (x,y) is on the curve. 77 | Note that the real y coordinate is either y or -y. 78 | If x is invalid stark_key it throws an error. 79 | """ 80 | 81 | x = stark_key_x_coordinate 82 | y_squared = (x * x * x + ALPHA * x + BETA) % FIELD_PRIME 83 | if not is_quad_residue(y_squared, FIELD_PRIME): 84 | raise InvalidPublicKeyError() 85 | return sqrt_mod(y_squared, FIELD_PRIME) 86 | 87 | 88 | def get_random_private_key() -> int: 89 | # NOTE: It is IMPORTANT to use a strong random function here. 90 | return random.randint(1, EC_ORDER - 1) 91 | 92 | 93 | def private_key_to_ec_point_on_stark_curve(priv_key: int) -> ECPoint: 94 | assert 0 < priv_key < EC_ORDER 95 | return ec_mult(priv_key, EC_GEN, ALPHA, FIELD_PRIME) 96 | 97 | 98 | def private_to_stark_key(priv_key: int) -> [int, int]: 99 | return private_key_to_ec_point_on_stark_curve(priv_key)[0] 100 | 101 | 102 | def inv_mod_curve_size(x: int) -> int: 103 | return div_mod(1, x, EC_ORDER) 104 | 105 | 106 | def generate_k_rfc6979(msg_hash: int, priv_key: int, seed: Optional[int] = None) -> int: 107 | # Pad the message hash, for consistency with the elliptic.js library. 108 | if 1 <= msg_hash.bit_length() % 8 <= 4 and msg_hash.bit_length() >= 248: 109 | # Only if we are one-nibble short: 110 | msg_hash *= 16 111 | 112 | if seed is None: 113 | extra_entropy = b'' 114 | else: 115 | extra_entropy = seed.to_bytes(math.ceil(seed.bit_length() / 8), 'big') 116 | 117 | return generate_k(EC_ORDER, priv_key, hashlib.sha256, 118 | msg_hash.to_bytes(math.ceil(msg_hash.bit_length() / 8), 'big'), 119 | extra_entropy=extra_entropy) 120 | 121 | 122 | def sign(msg_hash: int, priv_key: int, seed: Optional[int] = None) -> ECSignature: 123 | # Note: msg_hash must be smaller than 2**N_ELEMENT_BITS_ECDSA. 124 | # Message whose hash is >= 2**N_ELEMENT_BITS_ECDSA cannot be signed. 125 | # This happens with a very small probability. 126 | assert 0 <= msg_hash < 2**N_ELEMENT_BITS_ECDSA, 'Message not signable.' 127 | 128 | # Choose a valid k. In our version of ECDSA not every k value is valid, 129 | # and there is a negligible probability a drawn k cannot be used for signing. 130 | # This is why we have this loop. 131 | while True: 132 | k = generate_k_rfc6979(msg_hash, priv_key, seed) 133 | # Update seed for next iteration in case the value of k is bad. 134 | if seed is None: 135 | seed = 1 136 | else: 137 | seed += 1 138 | 139 | # Cannot fail because 0 < k < EC_ORDER and EC_ORDER is prime. 140 | x = ec_mult(k, EC_GEN, ALPHA, FIELD_PRIME)[0] 141 | 142 | # DIFF: in classic ECDSA, we take int(x) % n. 143 | r = int(x) 144 | if not (1 <= r < 2**N_ELEMENT_BITS_ECDSA): 145 | # Bad value. This fails with negligible probability. 146 | continue 147 | 148 | if (msg_hash + r * priv_key) % EC_ORDER == 0: 149 | # Bad value. This fails with negligible probability. 150 | continue 151 | 152 | w = div_mod(k, msg_hash + r * priv_key, EC_ORDER) 153 | if not (1 <= w < 2**N_ELEMENT_BITS_ECDSA): 154 | # Bad value. This fails with negligible probability. 155 | continue 156 | 157 | s = inv_mod_curve_size(w) 158 | return r, s 159 | 160 | 161 | def mimic_ec_mult_air(m: int, point: ECPoint, shift_point: ECPoint) -> ECPoint: 162 | """ 163 | Computes m * point + shift_point using the same steps like the AIR and throws an exception if 164 | and only if the AIR errors. 165 | """ 166 | assert 0 < m < 2**N_ELEMENT_BITS_ECDSA 167 | partial_sum = shift_point 168 | for _ in range(N_ELEMENT_BITS_ECDSA): 169 | assert partial_sum[0] != point[0] 170 | if m & 1: 171 | partial_sum = ec_add(partial_sum, point, FIELD_PRIME) 172 | point = ec_double(point, ALPHA, FIELD_PRIME) 173 | m >>= 1 174 | assert m == 0 175 | return partial_sum 176 | 177 | 178 | def verify(msg_hash: int, r: int, s: int, public_key: Union[int, ECPoint]) -> bool: 179 | # Compute w = s^-1 (mod EC_ORDER). 180 | assert 1 <= s < EC_ORDER, 's = %s' % s 181 | w = inv_mod_curve_size(s) 182 | 183 | # Preassumptions: 184 | # DIFF: in classic ECDSA, we assert 1 <= r, w <= EC_ORDER-1. 185 | # Since r, w < 2**N_ELEMENT_BITS_ECDSA < EC_ORDER, we only need to verify r, w != 0. 186 | assert 1 <= r < 2**N_ELEMENT_BITS_ECDSA, 'r = %s' % r 187 | assert 1 <= w < 2**N_ELEMENT_BITS_ECDSA, 'w = %s' % w 188 | assert 0 <= msg_hash < 2**N_ELEMENT_BITS_ECDSA, 'msg_hash = %s' % msg_hash 189 | 190 | if isinstance(public_key, int): 191 | # Only the x coordinate of the point is given, check the two possibilities for the y 192 | # coordinate. 193 | try: 194 | y = get_y_coordinate(public_key) 195 | except InvalidPublicKeyError: 196 | return False 197 | assert pow(y, 2, FIELD_PRIME) == ( 198 | pow(public_key, 3, FIELD_PRIME) + ALPHA * public_key + BETA) % FIELD_PRIME 199 | return verify(msg_hash, r, s, (public_key, y)) or \ 200 | verify(msg_hash, r, s, (public_key, (-y) % FIELD_PRIME)) 201 | else: 202 | # The public key is provided as a point. 203 | # Verify it is on the curve. 204 | assert (public_key[1]**2 - (public_key[0]**3 + ALPHA * 205 | public_key[0] + BETA)) % FIELD_PRIME == 0 206 | 207 | # Signature validation. 208 | # DIFF: original formula is: 209 | # x = (w*msg_hash)*EC_GEN + (w*r)*public_key 210 | # While what we implement is: 211 | # x = w*(msg_hash*EC_GEN + r*public_key). 212 | # While both mathematically equivalent, one might error while the other doesn't, 213 | # given the current implementation. 214 | # This formula ensures that if the verification errors in our AIR, it errors here as well. 215 | try: 216 | zG = mimic_ec_mult_air(msg_hash, EC_GEN, MINUS_SHIFT_POINT) 217 | rQ = mimic_ec_mult_air(r, public_key, SHIFT_POINT) 218 | wB = mimic_ec_mult_air(w, ec_add(zG, rQ, FIELD_PRIME), SHIFT_POINT) 219 | x = ec_add(wB, MINUS_SHIFT_POINT, FIELD_PRIME)[0] 220 | except AssertionError: 221 | return False 222 | 223 | # DIFF: Here we drop the mod n from classic ECDSA. 224 | return r == x 225 | 226 | 227 | ################# 228 | # Pedersen hash # 229 | ################# 230 | 231 | def pedersen_hash(*elements: int) -> int: 232 | return pedersen_hash_as_point(*elements)[0] 233 | 234 | 235 | def pedersen_hash_as_point(*elements: int) -> ECPoint: 236 | """ 237 | Similar to pedersen_hash but also returns the y coordinate of the resulting EC point. 238 | This function is used for testing. 239 | """ 240 | point = SHIFT_POINT 241 | for i, x in enumerate(elements): 242 | assert 0 <= x < FIELD_PRIME 243 | point_list = CONSTANT_POINTS[2 + i * N_ELEMENT_BITS_HASH:2 + (i + 1) * N_ELEMENT_BITS_HASH] 244 | assert len(point_list) == N_ELEMENT_BITS_HASH 245 | for pt in point_list: 246 | assert point[0] != pt[0], 'Unhashable input.' 247 | if x & 1: 248 | point = ec_add(point, pt, FIELD_PRIME) 249 | x >>= 1 250 | assert x == 0 251 | return point -------------------------------------------------------------------------------- /utils/tools.py: -------------------------------------------------------------------------------- 1 | import io 2 | import os 3 | import sys 4 | import json 5 | import random 6 | import asyncio 7 | import functools 8 | import traceback 9 | import msoffcrypto 10 | import pandas as pd 11 | 12 | from getpass import getpass 13 | from utils.networks import * 14 | from termcolor import cprint 15 | from python_socks import ProxyError 16 | from asyncio.exceptions import TimeoutError 17 | from web3 import AsyncWeb3, AsyncHTTPProvider 18 | from web3.exceptions import ContractLogicError 19 | from aiohttp import ClientSession, TCPConnector, ClientResponseError 20 | from msoffcrypto.exceptions import DecryptionError, InvalidKeyError 21 | from aiohttp.client_exceptions import ClientProxyConnectionError, ClientHttpProxyError 22 | 23 | from general_settings import ( 24 | SLEEP_TIME_MODULES, 25 | SLEEP_TIME_RETRY, 26 | MAXIMUM_RETRY, 27 | MAXIMUM_GWEI, 28 | GAS_CONTROL, 29 | SLEEP_TIME_GAS, 30 | EXCEL_PASSWORD, 31 | CONTROL_TIMES_FOR_SLEEP, 32 | ACCOUNTS_IN_STREAM, 33 | SOFTWARE_MODE, 34 | EXCEL_PAGE_NAME, SLEEP_TIME_ACCOUNTS, EXCEL_FILE_PATH 35 | ) 36 | 37 | 38 | async def sleep(self, min_time=SLEEP_TIME_MODULES[0], max_time=SLEEP_TIME_MODULES[1]): 39 | duration = random.randint(min_time, max_time) 40 | print() 41 | self.logger_msg(*self.client.acc_info, msg=f"💤 Sleeping for {duration} seconds") 42 | await asyncio.sleep(duration) 43 | 44 | 45 | def get_accounts_data(): 46 | try: 47 | decrypted_data = io.BytesIO() 48 | with open(EXCEL_FILE_PATH, 'rb') as file: 49 | if EXCEL_PASSWORD: 50 | cprint('⚔️ Enter the password degen', color='light_blue') 51 | password = getpass() 52 | office_file = msoffcrypto.OfficeFile(file) 53 | 54 | try: 55 | office_file.load_key(password=password) 56 | except msoffcrypto.exceptions.DecryptionError: 57 | cprint('\n⚠️ Incorrect password to decrypt Excel file! ⚠️', color='light_red', attrs=["blink"]) 58 | raise DecryptionError('Incorrect password') 59 | 60 | try: 61 | office_file.decrypt(decrypted_data) 62 | except msoffcrypto.exceptions.InvalidKeyError: 63 | cprint('\n⚠️ Incorrect password to decrypt Excel file! ⚠️', color='light_red', attrs=["blink"]) 64 | raise InvalidKeyError('Incorrect password') 65 | 66 | except msoffcrypto.exceptions.DecryptionError: 67 | cprint('\n⚠️ Set password on your Excel file first! ⚠️', color='light_red', attrs=["blink"]) 68 | raise DecryptionError('Excel file without password!') 69 | 70 | office_file.decrypt(decrypted_data) 71 | 72 | try: 73 | wb = pd.read_excel(decrypted_data, sheet_name=EXCEL_PAGE_NAME) 74 | except ValueError as error: 75 | cprint('\n⚠️ Wrong page name! Please check EXCEL_PAGE_NAME ⚠️', color='light_red', attrs=["blink"]) 76 | raise ValueError(f"{error}") 77 | else: 78 | try: 79 | wb = pd.read_excel(file, sheet_name=EXCEL_PAGE_NAME) 80 | except ValueError as error: 81 | cprint('\n⚠️ Wrong page name! Please check EXCEL_PAGE_NAME ⚠️', color='light_red', attrs=["blink"]) 82 | raise ValueError(f"{error}") 83 | 84 | accounts_data = {} 85 | for index, row in wb.iterrows(): 86 | account_name = row["Name"] 87 | private_key = row["Private Key"] 88 | proxy = row["Proxy"] 89 | cex_address = row['CEX address'] 90 | accounts_data[int(index) + 1] = { 91 | "account_number": account_name, 92 | "private_key": private_key, 93 | "proxy": proxy, 94 | "cex_wallet": cex_address, 95 | } 96 | 97 | acc_name, priv_key, proxy, cex_wallet = [], [], [], [] 98 | for k, v in accounts_data.items(): 99 | acc_name.append(v['account_number'] if isinstance(v['account_number'], (int, str)) else None) 100 | priv_key.append(v['private_key']) 101 | proxy.append(v['proxy'] if isinstance(v['proxy'], str) else None) 102 | cex_wallet.append(v['cex_wallet'] if isinstance(v['cex_wallet'], str) else None) 103 | 104 | acc_name = [str(item) for item in acc_name if item is not None] 105 | proxy = [item for item in proxy if item is not None] 106 | okx_wallet = [item for item in cex_wallet if item is not None] 107 | 108 | return acc_name, priv_key, proxy, okx_wallet 109 | except (DecryptionError, InvalidKeyError, DecryptionError, ValueError): 110 | sys.exit() 111 | 112 | except ImportError: 113 | cprint(f'\nAre you sure about EXCEL_PASSWORD in general_settings.py?', color='light_red') 114 | sys.exit() 115 | 116 | except Exception as error: 117 | cprint(f'\nError in function! Error: {error}\n', color='light_red') 118 | sys.exit() 119 | 120 | 121 | def clean_progress_file(): 122 | with open('./data/services/wallets_progress.json', 'w') as file: 123 | file.truncate(0) 124 | 125 | 126 | def clean_gwei_file(): 127 | with open('./data/services/maximum_gwei.json', 'w') as file: 128 | file.truncate(0) 129 | 130 | 131 | def check_progress_file(): 132 | file_path = './data/services/wallets_progress.json' 133 | 134 | if os.path.getsize(file_path) > 0: 135 | return True 136 | else: 137 | return False 138 | 139 | 140 | def create_cex_withdrawal_list(): 141 | from config import ACCOUNT_NAMES, CEX_WALLETS 142 | cex_data = {} 143 | 144 | if ACCOUNT_NAMES and CEX_WALLETS: 145 | with open('./data/services/cex_withdraw_list.json', 'w') as file: 146 | for account_name, cex_wallet in zip(ACCOUNT_NAMES, CEX_WALLETS): 147 | cex_data[str(account_name)] = cex_wallet 148 | json.dump(cex_data, file, indent=4) 149 | cprint('✅ Successfully added and saved CEX wallets data', 'light_blue') 150 | cprint('⚠️ Check all CEX deposit wallets by yourself to avoid problems', 'light_yellow', attrs=["blink"]) 151 | else: 152 | cprint('❌ Put your wallets into files, before running this function', 'light_red') 153 | 154 | 155 | def get_wallet_for_deposit(self): 156 | from modules.interfaces import CriticalException 157 | 158 | try: 159 | with open('./data/services/cex_withdraw_list.json') as file: 160 | from json import load 161 | cex_withdraw_list = load(file) 162 | cex_wallet = cex_withdraw_list[self.client.account_name] 163 | return cex_wallet 164 | except json.JSONDecodeError: 165 | from modules.interfaces import CriticalException 166 | raise CriticalException(f"Bad data in cex_wallet_list.json") 167 | except Exception as error: 168 | raise CriticalException(f'There is no wallet listed for deposit to CEX: {error}') 169 | 170 | 171 | def helper(func): 172 | @functools.wraps(func) 173 | async def wrapper(self, *args, **kwargs): 174 | from modules.interfaces import ( 175 | PriceImpactException, BlockchainException, SoftwareException, SoftwareExceptionWithoutRetry, 176 | BlockchainExceptionWithoutRetry, CriticalException 177 | ) 178 | 179 | attempts = 0 180 | stop_flag = False 181 | infinity_flag = False 182 | no_sleep_flag = False 183 | try: 184 | while attempts <= MAXIMUM_RETRY and not infinity_flag: 185 | try: 186 | return await func(self, *args, **kwargs) 187 | except ( 188 | PriceImpactException, BlockchainException, SoftwareException, SoftwareExceptionWithoutRetry, 189 | BlockchainExceptionWithoutRetry, ValueError, ContractLogicError, ClientProxyConnectionError, 190 | TimeoutError, ClientHttpProxyError, ProxyError, ClientResponseError, CriticalException, KeyError 191 | ) as err: 192 | error = err 193 | attempts += 1 194 | 195 | msg = f'{error} | Try[{attempts}/{MAXIMUM_RETRY + 1}]' 196 | 197 | if isinstance(error, KeyError): 198 | stop_flag = True 199 | msg = f"Setting '{error}' for this module is not exist in software!" 200 | 201 | elif 'rate limit' in str(error) or '429' in str(error): 202 | msg = f'Rate limit exceeded. Will try again in 1 min...' 203 | await asyncio.sleep(60) 204 | no_sleep_flag = True 205 | 206 | elif isinstance(error, ( 207 | ClientProxyConnectionError, TimeoutError, ClientHttpProxyError, ProxyError, 208 | ClientResponseError 209 | )): 210 | self.logger_msg( 211 | *self.client.acc_info, 212 | msg=f"Connection to RPC is not stable. Will try again in 1 min...", 213 | type_msg='warning' 214 | ) 215 | await asyncio.sleep(60) 216 | attempts -= 1 217 | no_sleep_flag = True 218 | 219 | elif isinstance(error, CriticalException): 220 | msg_action = f"Software will be stopped. Please, try to fix your settings in files" 221 | self.logger_msg(self.client.account_name, None, msg=msg_action, type_msg='error') 222 | raise error 223 | 224 | elif isinstance(error, asyncio.exceptions.TimeoutError): 225 | error = 'Connection to RPC is not stable' 226 | await self.client.change_rpc() 227 | msg = f'{error} | Try[{attempts}/{MAXIMUM_RETRY + 1}]' 228 | 229 | elif isinstance(error, (SoftwareExceptionWithoutRetry, BlockchainExceptionWithoutRetry)): 230 | stop_flag = True 231 | msg = f'{error}' 232 | 233 | elif isinstance(error, BlockchainException): 234 | if 'insufficient funds' not in str(error): 235 | self.logger_msg( 236 | self.client.account_name, 237 | None, msg=f'Maybe problem with node: {self.client.rpc}', type_msg='warning') 238 | await self.client.change_rpc() 239 | 240 | self.logger_msg(self.client.account_name, None, msg=msg, type_msg='error') 241 | 242 | if stop_flag: 243 | break 244 | 245 | if attempts > MAXIMUM_RETRY and not infinity_flag: 246 | self.logger_msg( 247 | self.client.account_name, None, 248 | msg=f"Tries are over, software will stop module\n", type_msg='error' 249 | ) 250 | else: 251 | if not no_sleep_flag: 252 | await sleep(self, *SLEEP_TIME_RETRY) 253 | 254 | except Exception as error: 255 | msg = f'Unknown Error. Description: {error}' 256 | self.logger_msg(self.client.account_name, None, msg=msg, type_msg='error') 257 | traceback.print_exc() 258 | finally: 259 | await self.client.session.close() 260 | return False 261 | return wrapper 262 | 263 | 264 | def gas_checker(func): 265 | @functools.wraps(func) 266 | async def wrapper(self, *args, **kwargs): 267 | if GAS_CONTROL: 268 | await asyncio.sleep(1) 269 | print() 270 | flag = False 271 | counter = 0 272 | 273 | self.logger_msg(self.client.account_name, None, f"Checking for gas price") 274 | 275 | w3 = AsyncWeb3(AsyncHTTPProvider(random.choice(EthereumRPC.rpc), 276 | request_kwargs=self.client.request_kwargs)) 277 | while True: 278 | gas = round(AsyncWeb3.from_wei(await w3.eth.gas_price, 'gwei'), 3) 279 | 280 | if gas < get_max_gwei_setting(): 281 | 282 | self.logger_msg( 283 | self.client.account_name, None, f"{gas} Gwei | Gas price is good", type_msg='success') 284 | if flag and counter == CONTROL_TIMES_FOR_SLEEP and SOFTWARE_MODE: 285 | account_number = random.randint(1, ACCOUNTS_IN_STREAM) 286 | sleep_duration = tuple(x * account_number for x in SLEEP_TIME_ACCOUNTS) 287 | await sleep(self, *sleep_duration) 288 | return await func(self, *args, **kwargs) 289 | 290 | else: 291 | 292 | flag = True 293 | counter += 1 294 | self.logger_msg( 295 | self.client.account_name, None, 296 | f"{gas} Gwei | Gas is too high. Next check in {SLEEP_TIME_GAS} second", type_msg='warning') 297 | 298 | await asyncio.sleep(SLEEP_TIME_GAS) 299 | return await func(self, *args, **kwargs) 300 | 301 | return wrapper 302 | 303 | 304 | def get_max_gwei_setting(): 305 | file_path = './data/services/maximum_gwei.json' 306 | data = {} 307 | 308 | try: 309 | with open(file_path, 'r') as file: 310 | data = json.load(file) 311 | except (FileNotFoundError, json.JSONDecodeError): 312 | data['maximum_gwei'] = MAXIMUM_GWEI 313 | 314 | with open(file_path, 'w') as file: 315 | json.dump(data, file, indent=4) 316 | 317 | return data['maximum_gwei'] 318 | 319 | 320 | async def get_eth_price(): 321 | try: 322 | url = 'https://api.coingecko.com/api/v3/simple/price' 323 | 324 | params = { 325 | 'ids': 'ethereum', 326 | 'vs_currencies': 'usd' 327 | } 328 | 329 | async with ClientSession(connector=TCPConnector(verify_ssl=False)) as session: 330 | async with session.get(url=url, params=params) as response: 331 | data = await response.json() 332 | if response.status == 200: 333 | return data['ethereum']['usd'] 334 | except Exception as error: 335 | cprint(f'\nError in function! Error: {error}\n', color='light_red') 336 | sys.exit() 337 | --------------------------------------------------------------------------------