├── data ├── wallets.json ├── wallets.txt ├── okx_wallets.txt ├── okx_withdraw_list.json ├── preview.png ├── Annihilator.png ├── GigaMachine.png ├── HyperMachine.png ├── DBRMACHINE_GIT.png ├── GitJumperMachine.png ├── Annihilator_github.png ├── proxies.txt └── contact_data.json ├── .idea └── .gitignore ├── requirements.txt ├── modules ├── __init__.py ├── gnosissafe.py ├── dmail.py ├── omnisea.py ├── merkly.py ├── openocean.py ├── scrollswap.py ├── orbiter.py ├── layerbank.py ├── izumi.py ├── zerius.py ├── modules_runner.py ├── layerswap.py ├── interfaces.py ├── syncswap.py ├── scroll.py ├── okx.py ├── rhino.py └── client.py ├── utils ├── stark_signature │ ├── math_utils.py │ ├── eth_coder.py │ └── stark_singature.py ├── tools.py ├── route_generator.py └── networks.py ├── main.py ├── functions.py ├── README.md └── settings.py /data/wallets.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/wallets.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/okx_wallets.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/okx_withdraw_list.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /data/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realaskaer/Scroll/HEAD/data/preview.png -------------------------------------------------------------------------------- /data/Annihilator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realaskaer/Scroll/HEAD/data/Annihilator.png -------------------------------------------------------------------------------- /data/GigaMachine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realaskaer/Scroll/HEAD/data/GigaMachine.png -------------------------------------------------------------------------------- /data/HyperMachine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realaskaer/Scroll/HEAD/data/HyperMachine.png -------------------------------------------------------------------------------- /data/DBRMACHINE_GIT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realaskaer/Scroll/HEAD/data/DBRMACHINE_GIT.png -------------------------------------------------------------------------------- /data/GitJumperMachine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realaskaer/Scroll/HEAD/data/GitJumperMachine.png -------------------------------------------------------------------------------- /data/Annihilator_github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realaskaer/Scroll/HEAD/data/Annihilator_github.png -------------------------------------------------------------------------------- /data/proxies.txt: -------------------------------------------------------------------------------- 1 | login:password@ip:port 2 | login:password@ip:port 3 | login:password@ip:port 4 | login:password@ip:port 5 | login:password@ip:port -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp==3.9.0b1 2 | ecdsa==0.18.0 3 | Faker==19.11.0 4 | hexbytes==0.3.1 5 | loguru==0.7.2 6 | mnemonic==0.20 7 | questionary==2.0.1 8 | termcolor==2.3.0 9 | web3==6.11.2 10 | pycryptodome==3.19.0 11 | mpmath==1.3.0 12 | sympy==1.11.1 -------------------------------------------------------------------------------- /data/contact_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "abi": [ 3 | { 4 | "type": "constructor", 5 | "name": "", 6 | "inputs": [], 7 | "outputs": [], 8 | "stateMutability": "nonpayable" 9 | } 10 | ], 11 | "bytecode": "0x00040000000000020006000000000002000000000301001900000060033002700000007e04300197000300000041035500020000000103550000007e0030019d000100000000001f0000008001000039000000400010043f00000000010000310000000102200190000000410000c13d0000008402000041000000000202041a000000820220019700000000031" 12 | } 13 | -------------------------------------------------------------------------------- /modules/__init__.py: -------------------------------------------------------------------------------- 1 | from .interfaces import DEX, Aggregator, Bridge, Refuel, Messenger, Landing, Minter, Blockchain, Creator, CEX 2 | from .client import Client 3 | from .syncswap import SyncSwap 4 | from .dmail import Dmail 5 | from .openocean import OpenOcean 6 | from .izumi import Izumi 7 | from .layerbank import LayerBank 8 | from .scrollswap import ScrollSwap 9 | from .layerswap import LayerSwap 10 | from .merkly import Merkly 11 | from .okx import OKX 12 | from .orbiter import Orbiter 13 | from .zerius import Zerius 14 | from .omnisea import Omnisea 15 | from .gnosissafe import GnosisSafe 16 | from .rhino import Rhino 17 | from .scroll import Scroll 18 | -------------------------------------------------------------------------------- /modules/gnosissafe.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | from modules import Creator 3 | from utils.tools import gas_checker, repeater 4 | from config import SAFE_ABI, SAFE_CONTRACTS, ZERO_ADDRESS 5 | 6 | 7 | class GnosisSafe(Creator): 8 | @repeater 9 | @gas_checker 10 | async def create(self): 11 | self.client.logger.info(f'{self.client.info} GnosisSafe | Create safe on chain') 12 | 13 | safe_contract = self.client.get_contract(SAFE_CONTRACTS['proxy_factory'], SAFE_ABI) 14 | tx_params = await self.client.prepare_transaction() 15 | deadline = int(time()) + 1800 16 | 17 | setup_data = safe_contract.encodeABI( 18 | fn_name="setup", 19 | args=[ 20 | [self.client.address], 21 | 1, 22 | ZERO_ADDRESS, 23 | "0x", 24 | SAFE_CONTRACTS['fallback_handler'], 25 | ZERO_ADDRESS, 26 | 0, 27 | ZERO_ADDRESS 28 | ] 29 | ) 30 | 31 | transaction = await safe_contract.functions.createProxyWithNonce( 32 | SAFE_CONTRACTS['gnosis_safe'], 33 | setup_data, 34 | deadline 35 | ).build_transaction(tx_params) 36 | 37 | tx_hash = await self.client.send_transaction(transaction) 38 | 39 | await self.client.verify_transaction(tx_hash) 40 | -------------------------------------------------------------------------------- /modules/dmail.py: -------------------------------------------------------------------------------- 1 | from faker import Faker 2 | from hashlib import sha256 3 | from modules import Messenger 4 | from mnemonic import Mnemonic 5 | from random import choice, randint 6 | from utils.tools import gas_checker, repeater 7 | from config import DMAIL_CONTRACT, DMAIL_ABI 8 | 9 | 10 | class Dmail(Messenger): 11 | def __init__(self, client): 12 | self.client = client 13 | 14 | @staticmethod 15 | def generate_email(): 16 | return f"{Faker().word()}{randint(1, 999999)}@{choice(['gmail.com', 'yahoo.com', 'outlook.com', 'icloud.com'])}" 17 | 18 | @staticmethod 19 | def generate_sentence(): 20 | mnemo = Mnemonic("english") 21 | 22 | return mnemo.generate(128) 23 | 24 | @repeater 25 | @gas_checker 26 | async def send_message(self): 27 | self.client.logger.info(f'{self.client.info} Dmail | Send mail from Dmail') 28 | 29 | email = self.generate_email() 30 | text = self.generate_sentence() 31 | 32 | to_address = sha256(f"{email}".encode()).hexdigest() 33 | message = sha256(f"{text}".encode()).hexdigest() 34 | 35 | self.client.logger.info(f'{self.client.info} Dmail | Generated mail: {email} | Generated text: {text[:25]}...') 36 | 37 | tx_params = await self.client.prepare_transaction() 38 | 39 | dmail_contract = self.client.get_contract(DMAIL_CONTRACT['core'], DMAIL_ABI) 40 | 41 | transaction = await dmail_contract.functions.send_mail( 42 | to_address, 43 | message, 44 | ).build_transaction(tx_params) 45 | 46 | tx_hash = await self.client.send_transaction(transaction) 47 | 48 | await self.client.verify_transaction(tx_hash) 49 | -------------------------------------------------------------------------------- /modules/omnisea.py: -------------------------------------------------------------------------------- 1 | import random 2 | import time 3 | 4 | from modules import Creator 5 | from utils.tools import gas_checker, repeater 6 | from string import ascii_letters 7 | from config import OMNISEA_ABI, OMNISEA_CONTRACT 8 | 9 | 10 | class Omnisea(Creator): 11 | @repeater 12 | @gas_checker 13 | async def create(self): 14 | self.client.logger.info(f"{self.client.info} Omnisea | Create NFT collection on Omnisea") 15 | 16 | contract = self.client.get_contract(OMNISEA_CONTRACT['drop_factory'], OMNISEA_ABI) 17 | 18 | tx_params = await self.client.prepare_transaction() 19 | 20 | name = "".join(random.sample(ascii_letters, random.randint(5, 15))) 21 | symbol = "".join(random.sample(ascii_letters, random.randint(3, 6))) 22 | ipsf = "".join(random.sample(ascii_letters, 20)) 23 | uri = f'QmTuduie9dtu22GG8s{ipsf}ycPkcuCk' 24 | tokens_url = "" 25 | totap_supply = random.randrange(5000, 15000) 26 | is_zero_indexed = random.choice([True, False]) 27 | royalty_amount = random.randrange(1, 5) 28 | end_time = int(time.time()) + random.randrange(1000000, 2000000) 29 | 30 | self.client.logger.info( 31 | f"{self.client.info} Omnisea | Create NFT collection on Omnisea | Name: {name} Symbol: {symbol}") 32 | 33 | transaction = await contract.functions.create([ 34 | name, 35 | symbol, 36 | uri, 37 | tokens_url, 38 | totap_supply, 39 | is_zero_indexed, 40 | royalty_amount, 41 | end_time 42 | ]).build_transaction(tx_params) 43 | 44 | tx_hash = await self.client.send_transaction(transaction) 45 | 46 | await self.client.verify_transaction(tx_hash) 47 | -------------------------------------------------------------------------------- /modules/merkly.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | from modules import Refuel 4 | from eth_abi import abi 5 | from settings import DESTINATION_MERKLY_DATA 6 | from utils.tools import gas_checker, repeater 7 | from config import ( 8 | MERKLY_CONTRACTS_PER_CHAINS, 9 | MERKLY_ROUTER_ABI, 10 | LAYERZERO_NETWORKS_DATA 11 | ) 12 | 13 | 14 | class Merkly(Refuel): 15 | def __init__(self, client): 16 | self.client = client 17 | 18 | @repeater 19 | @gas_checker 20 | async def refuel(self, chain_from_id): 21 | 22 | dst_data = random.choice(list(DESTINATION_MERKLY_DATA.items())) 23 | dst_chain_name, dst_chain_id, dst_native_name, dst_native_api_name = LAYERZERO_NETWORKS_DATA[dst_data[0]] 24 | dst_amount = self.client.round_amount(*dst_data[1]) 25 | 26 | merkly_contracts = MERKLY_CONTRACTS_PER_CHAINS[chain_from_id] 27 | 28 | refuel_contract = self.client.get_contract(merkly_contracts['gas_refuel'], MERKLY_ROUTER_ABI) 29 | router_contract = self.client.get_contract(merkly_contracts['ONFT'], MERKLY_ROUTER_ABI) 30 | 31 | refuel_info = f'{dst_amount} {dst_native_name} to {dst_chain_name}' 32 | self.client.logger.info(f'{self.client.info} Merkly | Refuel on Merkly: {refuel_info}') 33 | 34 | dst_native_gas_amount = int(dst_amount * 10 ** 18) 35 | 36 | gas_limit = 200000 37 | 38 | adapter_params = abi.encode(["uint16", "uint64", "uint256"], 39 | [2, gas_limit, dst_native_gas_amount]) 40 | 41 | adapter_params = self.client.w3.to_hex(adapter_params[30:]) + self.client.address[2:].lower() 42 | 43 | estimate_gas_bridge_fee = (await router_contract.functions.estimateGasBridgeFee( 44 | dst_chain_id, 45 | False, 46 | adapter_params 47 | ).call())[0] 48 | 49 | value = (int(dst_native_gas_amount * (await self.client.get_token_price(dst_native_api_name, 'eth'))) 50 | + estimate_gas_bridge_fee) 51 | 52 | tx_params = await self.client.prepare_transaction(value=value) 53 | 54 | transaction = await refuel_contract.functions.bridgeGas( 55 | dst_chain_id, 56 | self.client.address, 57 | adapter_params 58 | ).build_transaction(tx_params) 59 | 60 | tx_hash = await self.client.send_transaction(transaction) 61 | 62 | await self.client.verify_transaction(tx_hash) 63 | -------------------------------------------------------------------------------- /modules/openocean.py: -------------------------------------------------------------------------------- 1 | from modules import Aggregator 2 | from utils.tools import gas_checker, repeater 3 | from settings import SLIPPAGE_PERCENT 4 | from config import OPENOCEAN_CONTRACT, SCROLL_TOKENS, ETH_MASK, HELP_SOFTWARE 5 | 6 | 7 | class OpenOcean(Aggregator): 8 | async def build_swap_transaction(self, from_token_address: str, to_token_address: str, amount: float): 9 | 10 | url = f'https://open-api.openocean.finance/v3/{self.client.chain_id}/swap_quote' 11 | 12 | params = { 13 | 'chain': self.client.chain_id, 14 | 'inTokenAddress': from_token_address, 15 | 'outTokenAddress': to_token_address, 16 | 'amount': amount, 17 | 'gasPrice': str(self.client.w3.from_wei(await self.client.w3.eth.gas_price, 'gwei')), 18 | 'slippage': SLIPPAGE_PERCENT, 19 | 'account': self.client.address 20 | } | {'referrer': '0x000000a679C2FB345dDEfbaE3c42beE92c0Fb7A5', 'referrerFee': 1} if HELP_SOFTWARE else {} 21 | 22 | return await self.make_request(url=url, params=params) 23 | 24 | @repeater 25 | @gas_checker 26 | async def swap(self, help_add_liquidity:bool = False, amount_to_help:int = 0): 27 | 28 | from_token_name, to_token_name, amount, amount_in_wei = await self.client.get_auto_amount() 29 | 30 | if help_add_liquidity: 31 | to_token_name = 'ETH' 32 | decimals = 6 33 | eth_price = await self.client.get_token_price('ethereum') 34 | 35 | amount = round(amount_to_help * eth_price, 4) 36 | amount_in_wei = int(amount * 10 ** decimals) 37 | 38 | self.client.logger.info( 39 | f'{self.client.info} OpenOcean | Swap on OpenOcean: {amount} {from_token_name} -> {to_token_name}') 40 | 41 | from_token_address = ETH_MASK if from_token_name == "ETH" else SCROLL_TOKENS[from_token_name] 42 | to_token_address = ETH_MASK if to_token_name == "ETH" else SCROLL_TOKENS[to_token_name] 43 | 44 | swap_quote_data = await self.build_swap_transaction(from_token_address, to_token_address, amount) 45 | 46 | if from_token_address != ETH_MASK: 47 | await self.client.check_for_approved(from_token_address, OPENOCEAN_CONTRACT["router"], amount_in_wei) 48 | 49 | tx_params = (await self.client.prepare_transaction()) | { 50 | "to": swap_quote_data["data"]["to"], 51 | "data": swap_quote_data["data"]["data"], 52 | "value": int(swap_quote_data["data"]["value"]) 53 | } 54 | 55 | tx_hash = await self.client.send_transaction(tx_params) 56 | 57 | await self.client.verify_transaction(tx_hash) 58 | -------------------------------------------------------------------------------- /modules/scrollswap.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | from modules import DEX 3 | from utils.tools import gas_checker, repeater 4 | from settings import SLIPPAGE_PERCENT 5 | from config import ( 6 | SCROLLSWAP_ABI, 7 | SCROLLSWAP_CONTRACT, 8 | SCROLL_TOKENS 9 | ) 10 | 11 | 12 | class ScrollSwap(DEX): 13 | def __init__(self, client): 14 | self.client = client 15 | 16 | self.router_contract = self.client.get_contract(SCROLLSWAP_CONTRACT['router'], SCROLLSWAP_ABI) 17 | 18 | async def get_min_amount_out(self, from_token_address: str, to_token_address: str, amount_in_wei: int): 19 | _, min_amount_out = await self.router_contract.functions.getAmountsOut( 20 | amount_in_wei, 21 | [ 22 | from_token_address, 23 | to_token_address 24 | ] 25 | ).call() 26 | 27 | return int(min_amount_out - (min_amount_out / 100 * SLIPPAGE_PERCENT)) 28 | 29 | @repeater 30 | @gas_checker 31 | async def swap(self): 32 | 33 | from_token_name, to_token_name, amount, amount_in_wei = await self.client.get_auto_amount() 34 | 35 | self.client.logger.info( 36 | f'{self.client.info} ScrollSwap | Swap on SpaceFi: {amount} {from_token_name} -> {to_token_name}') 37 | 38 | from_token_address, to_token_address = SCROLL_TOKENS[from_token_name], SCROLL_TOKENS[to_token_name] 39 | 40 | if from_token_name != 'ETH': 41 | await self.client.check_for_approved(from_token_address, SCROLLSWAP_CONTRACT['router'], amount_in_wei) 42 | 43 | tx_params = await self.client.prepare_transaction(value=amount_in_wei if from_token_name == 'ETH' else 0) 44 | deadline = int(time()) + 1800 45 | min_amount_out = await self.get_min_amount_out(from_token_address, to_token_address, amount_in_wei) 46 | 47 | full_data = ( 48 | min_amount_out, 49 | [ 50 | from_token_address, 51 | to_token_address 52 | ], 53 | self.client.address, 54 | deadline 55 | ) 56 | 57 | if from_token_name == 'ETH': 58 | transaction = await self.router_contract.functions.swapExactETHForTokens( 59 | *full_data 60 | ).build_transaction(tx_params) 61 | elif to_token_name == 'ETH': 62 | transaction = await self.router_contract.functions.swapExactTokensForETH( 63 | amount_in_wei, 64 | *full_data 65 | ).build_transaction(tx_params) 66 | else: 67 | transaction = await self.router_contract.functions.swapExactTokensForTokens( 68 | amount_in_wei, 69 | *full_data 70 | ).build_transaction(tx_params) 71 | 72 | tx_hash = await self.client.send_transaction(transaction) 73 | 74 | await self.client.verify_transaction(tx_hash) 75 | -------------------------------------------------------------------------------- /modules/orbiter.py: -------------------------------------------------------------------------------- 1 | from modules import Bridge 2 | from utils.tools import repeater, gas_checker 3 | 4 | 5 | class Orbiter(Bridge): 6 | async def get_bridge_data(self, from_chain: int, to_chain:int, token_name: str): 7 | 8 | url = 'https://openapi.orbiter.finance/explore/v3/yj6toqvwh1177e1sexfy0u1pxx5j8o47' 9 | 10 | request_data = { 11 | "id": 1, 12 | "jsonrpc": "2.0", 13 | "method": "orbiter_getTradingPairs", 14 | "params": [] 15 | } 16 | 17 | response = await self.make_request(method='POST', url=url, json=request_data) 18 | 19 | data = response['result']['ruleList'] 20 | bridge_data = {} 21 | 22 | path = f'{from_chain}-{to_chain}:{token_name}-{token_name}' 23 | 24 | for chain_data in data: 25 | if chain_data['pairId'] == path: 26 | bridge_data = { 27 | 'maker': chain_data['sender'], 28 | 'fee': chain_data['tradingFee'], 29 | 'decimals': chain_data['fromChain']['decimals'], 30 | 'min_amount': chain_data['fromChain']['minPrice'], 31 | 'max_amount': chain_data['fromChain']['maxPrice'], 32 | } 33 | if bridge_data: 34 | return bridge_data 35 | raise RuntimeError(f'That bridge is not active!') 36 | 37 | @repeater 38 | @gas_checker 39 | async def bridge(self, chain_from_id:int, help_okx:bool = False, help_network_id:int = 1): 40 | 41 | from_chain, to_chain, token_name, amount = await self.client.get_bridge_data(chain_from_id, help_okx, 42 | help_network_id, 'Orbiter') 43 | 44 | bridge_info = f'{amount} {token_name} from {from_chain["name"]} to {to_chain["name"]}' 45 | self.client.logger.info(f'{self.client.info} Orbiter | Bridge on Orbiter: {bridge_info}') 46 | 47 | bridge_data = await self.get_bridge_data(from_chain['chainId'], to_chain['chainId'], token_name) 48 | destination_code = 9000 + to_chain['id'] 49 | fee = int(float(bridge_data['fee']) * 10 ** bridge_data['decimals']) 50 | min_price, max_price = bridge_data['min_amount'], bridge_data['max_amount'] 51 | amount_in_wei = int(amount * 10 ** bridge_data['decimals']) 52 | 53 | tx_params = (await self.client.prepare_transaction(value=amount_in_wei + destination_code + fee)) | { 54 | 'to': bridge_data['maker'] 55 | } 56 | 57 | if min_price <= amount <= max_price: 58 | 59 | _, balance_in_wei, _ = await self.client.get_token_balance(token_name) 60 | if balance_in_wei >= tx_params['value']: 61 | 62 | tx_hash = await self.client.send_transaction(tx_params) 63 | 64 | await self.client.verify_transaction(tx_hash) 65 | 66 | else: 67 | raise RuntimeError(f'Insufficient balance!') 68 | else: 69 | raise RuntimeError(f"Limit range for bridge: {min_price} – {max_price} {token_name}!") 70 | -------------------------------------------------------------------------------- /modules/layerbank.py: -------------------------------------------------------------------------------- 1 | from config import LAYERBANK_CONTRACT, LAYERSWAP_ABI 2 | from utils.tools import gas_checker, repeater 3 | from modules import Landing 4 | 5 | 6 | class LayerBank(Landing): 7 | def __init__(self, client): 8 | self.client = client 9 | 10 | self.landing_contract = self.client.get_contract(LAYERBANK_CONTRACT['landing'], LAYERSWAP_ABI) 11 | self.collateral_contract = self.client.get_contract(LAYERBANK_CONTRACT['pool'], LAYERSWAP_ABI) 12 | 13 | @repeater 14 | @gas_checker 15 | async def deposit(self): 16 | 17 | amount, amount_in_wei = await self.client.check_and_get_eth_for_deposit() 18 | 19 | self.client.logger.info(f'{self.client.info} LayerBank | Deposit to LayerBank: {amount} ETH') 20 | 21 | tx_params = await self.client.prepare_transaction(value=amount_in_wei) 22 | 23 | transaction = await self.landing_contract.functions.supply( 24 | LAYERBANK_CONTRACT['pool'], 25 | amount_in_wei 26 | ).build_transaction(tx_params) 27 | 28 | tx_hash = await self.client.send_transaction(transaction) 29 | 30 | await self.client.verify_transaction(tx_hash) 31 | 32 | @repeater 33 | @gas_checker 34 | async def withdraw(self): 35 | self.client.logger.info(f'{self.client.info} LayerBank | Withdraw from LayerBank') 36 | 37 | liquidity_balance = await self.landing_contract.functions.balanceOf(self.client.address).call() 38 | 39 | if liquidity_balance != 0: 40 | 41 | tx_params = await self.client.prepare_transaction() 42 | 43 | transaction = await self.landing_contract.functions.redeemUnderlying( 44 | LAYERBANK_CONTRACT['pool'], 45 | liquidity_balance, 46 | ).build_transaction(tx_params) 47 | 48 | tx_hash = await self.client.send_transaction(transaction) 49 | 50 | await self.client.verify_transaction(tx_hash) 51 | else: 52 | raise RuntimeError("Insufficient balance on LayerBank!") 53 | 54 | @repeater 55 | @gas_checker 56 | async def enable_collateral(self): 57 | self.client.logger.info(f'{self.client.info} LayerBank | Enable collateral on LayerBank') 58 | 59 | tx_params = await self.client.prepare_transaction() 60 | 61 | transaction = await self.landing_contract.functions.enterMarkets( 62 | [LAYERBANK_CONTRACT['pool']] 63 | ).build_transaction(tx_params) 64 | 65 | tx_hash = await self.client.send_transaction(transaction) 66 | 67 | await self.client.verify_transaction(tx_hash) 68 | 69 | @repeater 70 | @gas_checker 71 | async def disable_collateral(self): 72 | self.client.logger.info(f'{self.client.info} LayerBank | Disable collateral on LayerBank') 73 | 74 | tx_params = await self.client.prepare_transaction() 75 | 76 | transaction = await self.collateral_contract.functions.exitMarket( 77 | LAYERBANK_CONTRACT['pool'] 78 | ).build_transaction(tx_params) 79 | 80 | tx_hash = await self.client.send_transaction(transaction) 81 | 82 | await self.client.verify_transaction(tx_hash) 83 | -------------------------------------------------------------------------------- /modules/izumi.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | from modules import DEX 3 | from utils.tools import gas_checker, repeater 4 | from settings import SLIPPAGE_PERCENT 5 | from hexbytes import HexBytes 6 | from config import ( 7 | SCROLL_TOKENS, 8 | IZUMI_QUOTER_ABI, 9 | IZUMI_ROUTER_ABI, 10 | IZUMI_CONTRACTS, 11 | ZERO_ADDRESS 12 | ) 13 | 14 | 15 | class Izumi(DEX): 16 | def __init__(self, client): 17 | self.client = client 18 | 19 | self.router_contract = self.client.get_contract(IZUMI_CONTRACTS['router'], IZUMI_ROUTER_ABI) 20 | self.quoter_contract = self.client.get_contract(IZUMI_CONTRACTS['quoter'], IZUMI_QUOTER_ABI) 21 | 22 | @staticmethod 23 | def get_path(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 = (400).to_bytes(3, 'big') 27 | 28 | return from_token_bytes + fee_bytes + to_token_bytes 29 | 30 | async def get_min_amount_out(self, path: bytes, amount_in_wei: int): 31 | min_amount_out, _ = await self.quoter_contract.functions.swapAmount( 32 | amount_in_wei, 33 | path 34 | ).call() 35 | 36 | return int(min_amount_out - (min_amount_out / 100 * SLIPPAGE_PERCENT)) 37 | 38 | @repeater 39 | @gas_checker 40 | async def swap(self): 41 | 42 | from_token_name, to_token_name, amount, amount_in_wei = await self.client.get_auto_amount() 43 | 44 | self.client.logger.info( 45 | f'{self.client.info} Izumi | Swap on Izumi: {amount} {from_token_name} -> {to_token_name}') 46 | 47 | from_token_address, to_token_address = SCROLL_TOKENS[from_token_name], SCROLL_TOKENS[to_token_name] 48 | 49 | if from_token_name != 'ETH': 50 | await self.client.check_for_approved(from_token_address, IZUMI_CONTRACTS['router'], amount_in_wei) 51 | 52 | tx_params = await self.client.prepare_transaction(value=amount_in_wei if from_token_name == 'ETH' else 0) 53 | deadline = int(time()) + 1800 54 | path = self.get_path(from_token_address, to_token_address) 55 | 56 | min_amount_out = await self.get_min_amount_out(path, amount_in_wei) 57 | 58 | tx_data = self.router_contract.encodeABI( 59 | fn_name='swapAmount', 60 | args=[( 61 | path, 62 | self.client.address if to_token_name != 'ETH' else ZERO_ADDRESS, 63 | amount_in_wei, 64 | min_amount_out, 65 | deadline 66 | )] 67 | ) 68 | 69 | full_data = [tx_data] 70 | 71 | if from_token_name == 'ETH' or to_token_name == 'ETH': 72 | tx_additional_data = self.router_contract.encodeABI( 73 | fn_name='unwrapWETH9' if from_token_name != 'ETH' else 'refundETH', 74 | args=[ 75 | min_amount_out, 76 | self.client.address 77 | ] if from_token_name != 'ETH' else None 78 | ) 79 | full_data.append(tx_additional_data) 80 | 81 | transaction = await self.router_contract.functions.multicall( 82 | full_data 83 | ).build_transaction(tx_params) 84 | 85 | tx_hash = await self.client.send_transaction(transaction) 86 | 87 | await self.client.verify_transaction(tx_hash) 88 | -------------------------------------------------------------------------------- /modules/zerius.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | from settings import DESTINATION_ZERIUS 4 | from config import ZERIUS_CONTRACT_PER_CHAINS, ZERIUS_ABI, ZERO_ADDRESS, LAYERZERO_NETWORKS_DATA 5 | from utils.tools import gas_checker, repeater, sleep 6 | from eth_abi import encode 7 | from modules import Minter 8 | 9 | 10 | class Zerius(Minter): 11 | def __init__(self, client, chain_from_id): 12 | self.client = client 13 | 14 | self.contract = self.client.get_contract(ZERIUS_CONTRACT_PER_CHAINS[chain_from_id]['ONFT'], ZERIUS_ABI) 15 | 16 | async def get_nft_id(self): 17 | balance_nft = self.contract.functions.balanceOf(self.client.address).call() 18 | nft_ids = [] 19 | for i in range(balance_nft): 20 | nft_ids.append(self.contract.tokenOfOwnerByIndex(self.client.address, i).call()) 21 | return nft_ids[-1] 22 | 23 | async def get_estimate_gas_bridge_fee(self, adapter_params, dst_chain_id, nft_id): 24 | 25 | estimate_gas_bridge_fee = (await self.contract.functions.estimateGasBridgeFee( 26 | dst_chain_id, 27 | self.client.address, 28 | nft_id, 29 | False, 30 | adapter_params 31 | ).call())[0] 32 | 33 | return estimate_gas_bridge_fee 34 | 35 | @repeater 36 | @gas_checker 37 | async def mint(self): 38 | 39 | mint_price = await self.contract.functions.mintFee().call() 40 | 41 | self.client.logger.info(f"{self.client.info} Zerius | Mint Zerius NFT. Price: {(mint_price / 10 ** 18):.5f}") 42 | 43 | tx_params = await self.client.get_tx_data(mint_price) 44 | 45 | transaction = await self.contract.functions.mint().build_transaction(tx_params) 46 | 47 | tx_hash = await self.client.send_transaction(transaction) 48 | 49 | await self.client.verify_transaction(tx_hash) 50 | 51 | return tx_hash 52 | 53 | @repeater 54 | @gas_checker 55 | async def bridge(self): 56 | 57 | dst_chain = random.choice(DESTINATION_ZERIUS) 58 | dst_chain_name, dst_chain_id, _, _ = LAYERZERO_NETWORKS_DATA[dst_chain] 59 | 60 | nft_id = await self.get_nft_id() 61 | 62 | if not nft_id: 63 | await self.mint() 64 | 65 | self.client.logger.info(f"{self.client.info} Zerius | Bridge Zerius NFT to {dst_chain_name}. ID: {nft_id}") 66 | 67 | await sleep(5, 10) 68 | 69 | version, gas_limit = 1, await self.contract.functions.minDstGasLookup(dst_chain_id, 1).call() 70 | 71 | adapter_params = encode(["uint16", "uint256"], 72 | [version, gas_limit]) 73 | 74 | adapter_params = self.client.w3.to_hex(adapter_params[30:]) + self.client.address[2:].lower() 75 | 76 | base_bridge_fee = await self.contract.functions.bridgeFee().call() 77 | estimate_gas_bridge_fee = await self.get_estimate_gas_bridge_fee(adapter_params, dst_chain_id, nft_id) 78 | 79 | tx_params = await self.client.prepare_transaction(value=estimate_gas_bridge_fee + base_bridge_fee) 80 | 81 | transaction = await self.contract.functions.sendFrom( 82 | self.client.address, 83 | dst_chain_id, 84 | self.client.address, 85 | nft_id, 86 | ZERO_ADDRESS, 87 | ZERO_ADDRESS, 88 | adapter_params 89 | ).build_transaction(tx_params) 90 | 91 | tx_hash = await self.client.send_transaction(transaction) 92 | 93 | await self.client.verify_transaction(tx_hash) 94 | -------------------------------------------------------------------------------- /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) 101 | -------------------------------------------------------------------------------- /modules/modules_runner.py: -------------------------------------------------------------------------------- 1 | import json 2 | import random 3 | import asyncio 4 | from sys import stderr 5 | from loguru import logger 6 | from web3 import AsyncWeb3 7 | from utils.networks import ScrollRPC 8 | from termcolor import cprint 9 | from functions import MODULES 10 | from config import WALLETS, PROXIES 11 | from settings import USE_PROXY, SLEEP_MODE, MAX_SLEEP, MIN_SLEEP, SOFTWARE_MODE, WALLETS_TO_WORK 12 | 13 | 14 | def get_wallets(): 15 | if WALLETS_TO_WORK == 0: 16 | return WALLETS 17 | elif isinstance(WALLETS_TO_WORK, int): 18 | return [WALLETS[WALLETS_TO_WORK-1]] 19 | elif isinstance(WALLETS_TO_WORK, tuple): 20 | return [WALLETS[i-1] for i in WALLETS_TO_WORK] 21 | elif isinstance(WALLETS_TO_WORK, list): 22 | return [WALLETS[i-1] for i in range(WALLETS_TO_WORK[0], WALLETS_TO_WORK[1])] 23 | 24 | 25 | def load_routes(): 26 | with open('./data/wallets.json', 'r') as f: 27 | return json.load(f) 28 | 29 | 30 | def update_step(wallet, step): 31 | wallets = load_routes() 32 | wallets[wallet]["current_step"] = step 33 | with open('./data/wallets.json', 'w') as f: 34 | json.dump(wallets, f, indent=4) 35 | 36 | 37 | async def get_proxy_for_account(account_number): 38 | if USE_PROXY: 39 | try: 40 | num_proxies = len(PROXIES) 41 | return PROXIES[account_number % num_proxies] 42 | except: 43 | cprint(f"\n❌ Nothing in proxy.txt, but you want proxy!\n", 'light_red') 44 | return None 45 | 46 | 47 | async def sleep(account_number, private_key): 48 | if SLEEP_MODE: 49 | address = AsyncWeb3().eth.account.from_key(private_key).address 50 | address_info = f'{address[:10]}....{address[-6:]}' 51 | logger.remove() 52 | logger.add(stderr, 53 | format="{time:HH:mm:ss} | " "{level: <8} | {message}") 54 | duration = random.randint(MIN_SLEEP, MAX_SLEEP) 55 | logger.info(f"[{account_number}] {address_info} | 💤 Sleeping for {duration} seconds.") 56 | await asyncio.sleep(duration) 57 | 58 | 59 | async def run_module(module): 60 | for account_number, private_key in enumerate(get_wallets(), 1): 61 | proxy = await get_proxy_for_account(account_number) 62 | await MODULES[module](account_number, private_key, ScrollRPC, proxy) 63 | await sleep(account_number, private_key) 64 | 65 | 66 | async def run_account_modules(account_number, private_key, network, proxy): 67 | route = load_routes()[private_key]['route'] 68 | current_step = load_routes()[private_key]["current_step"] 69 | 70 | await sleep(account_number, private_key) 71 | 72 | while current_step < len(route): 73 | module_name = route[current_step] 74 | cprint(f"\n🚀 Running {module_name} for wallet #{account_number}...", 'light_yellow') 75 | await MODULES[module_name](account_number, private_key, network, proxy) 76 | 77 | update_step(private_key, current_step + 1) 78 | current_step += 1 79 | 80 | await sleep(account_number, private_key) 81 | 82 | cprint(f"\n✅ All steps in route completed!", 'light_green') 83 | cprint(f"\n🔁 Started running next wallet!\n", 'light_green') 84 | 85 | 86 | async def run_parallel(): 87 | tasks = [] 88 | 89 | for account_number, private_key in enumerate(get_wallets(), 1): 90 | tasks.append(asyncio.create_task(run_account_modules(account_number, private_key, ScrollRPC, 91 | await get_proxy_for_account(account_number)))) 92 | 93 | await asyncio.gather(*tasks) 94 | 95 | 96 | async def run_consistently(): 97 | for account_number, private_key in enumerate(get_wallets(), 1): 98 | await run_account_modules(account_number, private_key, ScrollRPC, await get_proxy_for_account(account_number)) 99 | cprint(f"\n✅ All accounts completed their tasks!\n", 'light_green') 100 | 101 | 102 | async def run_accounts(): 103 | if SOFTWARE_MODE: 104 | await run_parallel() 105 | else: 106 | await run_consistently() 107 | -------------------------------------------------------------------------------- /utils/tools.py: -------------------------------------------------------------------------------- 1 | import json 2 | import random 3 | import asyncio 4 | import functools 5 | from config import WALLETS, OKX_WALLETS 6 | from utils.networks import * 7 | from web3 import AsyncWeb3, AsyncHTTPProvider 8 | from termcolor import cprint 9 | from settings import ( 10 | MIN_SLEEP, 11 | MAX_SLEEP, 12 | SLEEP_TIME_RETRY, 13 | MAXIMUM_RETRY, 14 | GAS_CONTROL, 15 | MAXIMUM_GWEI, 16 | SLEEP_TIME_GAS 17 | ) 18 | 19 | 20 | async def sleep(self, min_time=MIN_SLEEP, max_time=MAX_SLEEP): 21 | duration = random.randint(min_time, max_time) 22 | print() 23 | self.client.logger.info(f"{self.client.info} {self.__class__.__name__} | 💤 Sleeping for {duration} seconds.") 24 | 25 | await asyncio.sleep(duration) 26 | 27 | 28 | def create_okx_withdrawal_list(): 29 | okx_data = {} 30 | w3 = AsyncWeb3() 31 | if WALLETS and OKX_WALLETS: 32 | with open('./data/okx_withdraw_list.json', 'w') as file: 33 | for private_key, okx_wallet in zip(WALLETS, OKX_WALLETS): 34 | okx_data[w3.eth.account.from_key(private_key).address] = okx_wallet 35 | json.dump(okx_data, file, indent=4) 36 | cprint('✅ Successfully added and saved OKX wallets data', 'light_blue') 37 | cprint('⚠️ Check all OKX deposit wallets by yourself to avoid problems', 'light_yellow', attrs=["blink"]) 38 | else: 39 | cprint('❌ Put your wallets into files, before running this function', 'light_red') 40 | 41 | 42 | async def check_proxies_status(proxies: list): 43 | tasks = [] 44 | for proxy in proxies: 45 | tasks.append(check_proxy_status(proxy)) 46 | await asyncio.gather(*tasks) 47 | 48 | 49 | async def check_proxy_status(proxy:str): 50 | try: 51 | w3 = AsyncWeb3(AsyncHTTPProvider(random.choice(ScrollRPC.rpc), request_kwargs={"proxy": f"http://{proxy}"})) 52 | if await w3.is_connected(): 53 | cprint(f'✅ Proxy {proxy[proxy.find("@"):]} successfully connected to Scroll RPC', 'light_green') 54 | return True 55 | cprint(f"❌ Proxy: {proxy} can`t connect to Ethereum RPC", 'light_red') 56 | return False 57 | except Exception as error: 58 | cprint(f"❌ Bad proxy: {proxy} | Error: {error} ", 'red') 59 | return False 60 | 61 | 62 | def repeater(func): 63 | @functools.wraps(func) 64 | async def wrapper(self, *args, **kwargs): 65 | attempts = 0 66 | class_name = self.__class__.__name__ 67 | while True: 68 | try: 69 | return await func(self, *args, **kwargs) 70 | except Exception as error: 71 | 72 | await asyncio.sleep(1) 73 | self.client.logger.error( 74 | f"{self.client.info} {class_name} | {error} | Try[{attempts + 1}/{MAXIMUM_RETRY + 1}]") 75 | await asyncio.sleep(1) 76 | 77 | attempts += 1 78 | if attempts > MAXIMUM_RETRY: 79 | break 80 | 81 | await sleep(self, SLEEP_TIME_RETRY, SLEEP_TIME_RETRY) 82 | self.client.logger.error(f"{self.client.info} {class_name} | Tries are over, launching next module.") 83 | return wrapper 84 | 85 | 86 | def gas_checker(func): 87 | @functools.wraps(func) 88 | async def wrapper(self, *args, **kwargs): 89 | if GAS_CONTROL: 90 | class_name = self.__class__.__name__ 91 | await asyncio.sleep(1) 92 | print() 93 | self.client.logger.info(f"{self.client.info} {class_name} | Checking for gas price") 94 | w3 = AsyncWeb3(AsyncHTTPProvider(random.choice(Ethereum.rpc), request_kwargs=self.client.request_kwargs)) 95 | while True: 96 | gas = round(AsyncWeb3.from_wei(await w3.eth.gas_price, 'gwei'), 3) 97 | if gas < MAXIMUM_GWEI: 98 | await asyncio.sleep(1) 99 | self.client.logger.success(f"{self.client.info} {class_name} | {gas} Gwei | Gas price is good") 100 | await asyncio.sleep(1) 101 | return await func(self, *args, **kwargs) 102 | else: 103 | await asyncio.sleep(1) 104 | self.client.logger.warning( 105 | f"{self.client.info} {class_name} | {gas} Gwei | Gas is too high." 106 | f" Next check in {SLEEP_TIME_GAS} second") 107 | await asyncio.sleep(SLEEP_TIME_GAS) 108 | return await func(self, *args, **kwargs) 109 | return wrapper 110 | -------------------------------------------------------------------------------- /utils/route_generator.py: -------------------------------------------------------------------------------- 1 | import random 2 | import json 3 | 4 | from termcolor import cprint 5 | from config import WALLETS 6 | from settings import ( 7 | AUTO_ROUTES_MODULES_USING, 8 | CLASSIC_ROUTES_MODULES_USING, 9 | MAX_UNQ_CONTACTS, 10 | WITHDRAW_LP, 11 | TX_COUNT 12 | ) 13 | 14 | 15 | def classic_routes_gen(): 16 | with open('./data/wallets.json', 'w') as file: 17 | accounts_data = {} 18 | for num, private_key in enumerate(WALLETS, 1): 19 | data = classic_generate_route() 20 | account_data = { 21 | "current_step": 0, 22 | "route": data 23 | } 24 | accounts_data[private_key] = account_data 25 | json.dump(accounts_data, file, indent=4) 26 | cprint(f'\n✅ Successfully generated {len(accounts_data)} classic routes in data/wallets.json\n', 'light_blue') 27 | 28 | 29 | def auto_routes_gen(): 30 | with open('./data/wallets.json', 'w') as file: 31 | accounts_data = {} 32 | for num, private_key in enumerate(WALLETS, 1): 33 | data = auto_generate_route() 34 | account_data = { 35 | "current_step": 0, 36 | "route": data 37 | } 38 | accounts_data[private_key] = account_data 39 | json.dump(accounts_data, file, indent=4) 40 | cprint(f'\n✅ Successfully generated {len(accounts_data)} auto routes in data/wallets.json\n', 'light_blue') 41 | 42 | 43 | def auto_generate_route(): 44 | route = [] 45 | bridge_choice = [] 46 | 47 | if AUTO_ROUTES_MODULES_USING['okx_withdraw']: 48 | route.append(('okx_withdraw', 0, 1)) 49 | 50 | if WITHDRAW_LP: 51 | route.extend(withdraw_liquidity_modules) 52 | 53 | while len(route) < random.randint(*TX_COUNT): 54 | mod = random.choice(available_modules) 55 | mod_name, mod_priority, mod_max_count = mod 56 | 57 | if mod_max_count != 0 and route.count(mod) == mod_max_count: 58 | continue 59 | if AUTO_ROUTES_MODULES_USING[mod_name]: 60 | if not MAX_UNQ_CONTACTS: 61 | route.append(mod) 62 | else: 63 | if mod in route and len(route) < sum(list(AUTO_ROUTES_MODULES_USING.values())) - 4: 64 | continue 65 | route.append(mod) 66 | 67 | if AUTO_ROUTES_MODULES_USING['bridge_rhino']: 68 | bridge_choice.append(('bridge_rhino', 1, 1)) 69 | if AUTO_ROUTES_MODULES_USING['bridge_layerswap']: 70 | bridge_choice.append(('bridge_layerswap', 1, 1)) 71 | if AUTO_ROUTES_MODULES_USING['bridge_orbiter']: 72 | bridge_choice.append(('bridge_orbiter', 1, 1)) 73 | if AUTO_ROUTES_MODULES_USING['bridge_scroll']: 74 | bridge_choice.append(('bridge_scroll', 1, 1)) 75 | 76 | route.append(random.choice(bridge_choice)) 77 | 78 | random.shuffle(route) 79 | 80 | data = sorted(route, key=lambda x: x[1]) 81 | route_new = [] 82 | for i in data: 83 | if i[0] in dependencies: 84 | route_new.append(i[0]) 85 | route_new.append(dependencies[i[0]]) 86 | continue 87 | route_new.append(i[0]) 88 | 89 | return route_new 90 | 91 | 92 | def classic_generate_route(): 93 | route = [] 94 | for i in CLASSIC_ROUTES_MODULES_USING: 95 | module = random.choice(i) 96 | if module is None: 97 | continue 98 | route.append(module) 99 | return route 100 | 101 | 102 | available_modules = [ 103 | # module name, priority, max_count 104 | ('okx_withdraw', 0, 1), 105 | ('add_liquidity_syncswap', 2, 1), 106 | ('deposit_layerbank', 2, 0), 107 | ('enable_collateral_layerbank', 2, 0), 108 | ('swap_izumi', 2, 0), 109 | ('swap_openocean', 2, 0), 110 | ('swap_syncswap', 2, 0), 111 | ('swap_scrollswap', 2, 0), 112 | ('wrap_eth', 2, 0), 113 | ('mint_zerius', 2, 0), 114 | ('deploy_contract', 1, 1), 115 | ('bridge_zerius', 3, 0), 116 | ('create_omnisea', 3, 0), 117 | ('create_safe', 3, 1), 118 | # ('mint_and_bridge_l2telegraph', 3, 0), 119 | ('refuel_merkly', 3, 0), 120 | ('send_message_dmail', 3, 0), 121 | # ('send_message_l2telegraph', 3, 0), 122 | ('transfer_eth', 3, 0), 123 | ('transfer_eth_to_myself', 3, 0), 124 | ('withdraw_scroll', 3, 0), 125 | ('okx_deposit', 4, 1), 126 | ('okx_collect_from_sub', 5, 1), 127 | ] 128 | 129 | withdraw_liquidity_modules = [ 130 | # ('withdraw_liquidity_spacefi', 3, 1), 131 | ('withdraw_liquidity_syncswap', 3, 1), 132 | ] 133 | 134 | dependencies = { 135 | 'deposit_layerbank': 'withdraw_layerbank', 136 | 'enable_collateral_layerbank': 'disable_collateral_layerbank', 137 | 'wrap_eth': 'unwrap_eth' 138 | } 139 | -------------------------------------------------------------------------------- /modules/layerswap.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from modules import Bridge 4 | from utils.tools import gas_checker, repeater 5 | 6 | 7 | class LayerSwap(Bridge): 8 | async def get_networks_data(self): 9 | url = "https://api.layerswap.io/api/available_networks" 10 | 11 | headers = { 12 | 'Content-Type': 'application/json' 13 | } 14 | 15 | return (await self.make_request(url=url, headers=headers))['data'] 16 | 17 | async def get_swap_rate(self, source_chain, destination_chain, source_asset, destination_asset, refuel): 18 | url = "https://api.layerswap.io/api/swap_rate" 19 | 20 | swap_rate_data = { 21 | "source": source_chain, 22 | "destination": destination_chain, 23 | "source_asset": source_asset, 24 | "destination_asset": destination_asset, 25 | "refuel": refuel 26 | } 27 | 28 | return (await self.make_request(method='POST', url=url, headers=self.headers, 29 | data=json.dumps(swap_rate_data)))['data'] 30 | 31 | async def get_swap_id(self, amount, source_chain, destination_chain, source_asset, destination_asset, refuel): 32 | url = "https://api.layerswap.io/api/swaps" 33 | 34 | create_swap_data = { 35 | "source": source_chain, 36 | "destination": destination_chain, 37 | "source_asset": source_asset, 38 | "destination_asset": destination_asset, 39 | "amount": amount, 40 | "destination_address": self.client.address, 41 | "refuel": refuel 42 | } 43 | 44 | return (await self.make_request(method='POST', url=url, headers=self.headers, 45 | data=json.dumps(create_swap_data)))['data'] 46 | 47 | async def create_tx(self, swap_id): 48 | url = f"https://api.layerswap.io/api/swaps/{swap_id}/prepare_src_transaction" 49 | 50 | params = { 51 | 'from_address': self.client.address 52 | } 53 | 54 | return (await self.make_request(url=url, headers=self.headers, params=params))['data'] 55 | 56 | @repeater 57 | @gas_checker 58 | async def bridge(self, chain_from_id, help_okx:bool = False, help_network_id:int = 1): 59 | 60 | (source_chain, destination_chain, source_asset, 61 | destination_asset, amount, refuel) = await self.client.get_bridge_data(chain_from_id, help_okx, 62 | help_network_id, 'LayerSwap') 63 | 64 | bridge_info = f'{self.client.network.name} -> {destination_asset} {destination_chain.capitalize()[:-8]}' 65 | self.client.logger.info( 66 | f'{self.client.info} LayerSwap | Bridge on LayerSwap: {amount} {source_asset} {bridge_info}') 67 | 68 | networks_data = await self.get_networks_data() 69 | 70 | available_for_swap = { 71 | chain['name']: (assets['asset'], assets['decimals']) 72 | for chain in networks_data if chain['name'] in [source_chain, destination_chain] 73 | for assets in chain['currencies'] if assets['asset'] in [source_asset, destination_asset] 74 | } 75 | 76 | if (len(available_for_swap) == 2 and source_asset in available_for_swap[source_chain] 77 | and destination_asset in available_for_swap[destination_chain]): 78 | 79 | data = source_chain, destination_chain, source_asset, destination_asset, refuel 80 | 81 | min_amount, max_amount, fee_amount = (await self.get_swap_rate(*data)).values() 82 | 83 | if float(min_amount) <= amount <= float(max_amount): 84 | 85 | _, balance, _ = await self.client.get_token_balance(source_asset) 86 | 87 | if balance >= amount: 88 | 89 | swap_id = await self.get_swap_id(amount, *data) 90 | 91 | tx_data = await self.create_tx(swap_id['swap_id']) 92 | 93 | amount_in_wei = int(amount * 10 ** available_for_swap[source_chain][1]) 94 | 95 | tx_params = (await self.client.prepare_transaction(value=amount_in_wei)) | { 96 | 'to': self.client.w3.to_checksum_address(tx_data['to_address']), 97 | 'data': tx_data['data'] 98 | } 99 | 100 | tx_hash = await self.client.send_transaction(tx_params) 101 | 102 | await self.client.verify_transaction(tx_hash) 103 | 104 | 105 | else: 106 | raise RuntimeError("Insufficient balance!") 107 | else: 108 | raise RuntimeError(f"Limit range for bridge: {min_amount} - {max_amount} ETH") 109 | else: 110 | raise RuntimeError(f"Bridge {source_asset} {bridge_info} is not active!") 111 | -------------------------------------------------------------------------------- /modules/interfaces.py: -------------------------------------------------------------------------------- 1 | from aiohttp import ClientSession 2 | from abc import ABC, abstractmethod 3 | from settings import LAYERSWAP_API_KEY, OKX_API_KEY, OKX_API_PASSPHRAS, OKX_API_SECRET, OKX_DEPOSIT_NETWORK 4 | 5 | 6 | class DEX(ABC): 7 | @abstractmethod 8 | async def swap(self): 9 | pass 10 | 11 | 12 | class CEX(ABC): 13 | def __init__(self, client): 14 | self.client = client 15 | 16 | #self.network_id = OKX_DEPOSIT_NETWORK 17 | self.api_key = OKX_API_KEY 18 | self.api_secret = OKX_API_SECRET 19 | self.passphras = OKX_API_PASSPHRAS 20 | 21 | @abstractmethod 22 | async def deposit(self): 23 | pass 24 | 25 | @abstractmethod 26 | async def withdraw(self): 27 | pass 28 | 29 | async def make_request(self, method:str = 'GET', url:str = None, data:str = None, params:dict = None, 30 | headers:dict = None, module_name:str = 'Request'): 31 | 32 | async with ClientSession() as session: 33 | async with session.request(method=method, url=url, headers=headers, data=data, 34 | params=params, proxy=self.client.proxy) as response: 35 | 36 | data = await response.json() 37 | if data['code'] != 0 and data['msg'] != '': 38 | error = f"Error code: {data['code']} Msg: {data['msg']}" 39 | raise RuntimeError(f"Bad request to OKX({module_name}): {error}") 40 | else: 41 | #self.logger.success(f"{self.info} {module_name}") 42 | return data['data'] 43 | 44 | 45 | class Aggregator(ABC): 46 | def __init__(self, client): 47 | self.client = client 48 | 49 | @abstractmethod 50 | async def swap(self): 51 | pass 52 | 53 | async def make_request(self, method:str = 'GET', url:str = None, headers:dict = None, params: dict = None, 54 | data:str = None, json:dict = None): 55 | 56 | async with ClientSession() as session: 57 | async with session.request(method=method, url=url, headers=headers, data=data, 58 | params=params, json=json, proxy=self.client.proxy) as response: 59 | 60 | data = await response.json() 61 | if response.status == 200: 62 | return data 63 | raise RuntimeError(f"Bad request to {self.__class__.__name__} API: {response.status}") 64 | 65 | 66 | class Bridge(ABC): 67 | def __init__(self, client): 68 | self.client = client 69 | 70 | if self.__class__.__name__ == 'LayerSwap': 71 | self.headers = { 72 | 'X-LS-APIKEY': f'{LAYERSWAP_API_KEY}', 73 | 'Content-Type': 'application/json' 74 | } 75 | elif self.__class__.__name__ == 'Rhino': 76 | self.headers = { 77 | "Accept": "application/json", 78 | "Content-Type": "application/json", 79 | } 80 | 81 | @abstractmethod 82 | async def bridge(self, *args, **kwargs): 83 | pass 84 | 85 | async def make_request(self, method:str = 'GET', url:str = None, headers:dict = None, params: dict = None, 86 | data:str = None, json:dict = None): 87 | 88 | async with ClientSession() as session: 89 | async with session.request(method=method, url=url, headers=headers, data=data, 90 | params=params, json=json, proxy=self.client.proxy) as response: 91 | 92 | data = await response.json() 93 | if response.status in [200, 201]: 94 | return data 95 | raise RuntimeError(f"Bad request to {self.__class__.__name__} API: {response.status}") 96 | 97 | 98 | class Refuel(ABC): 99 | @abstractmethod 100 | async def refuel(self): 101 | pass 102 | 103 | 104 | class Messenger(ABC): 105 | @abstractmethod 106 | async def send_message(self): 107 | pass 108 | 109 | 110 | class Landing(ABC): 111 | @abstractmethod 112 | async def deposit(self): 113 | pass 114 | 115 | @abstractmethod 116 | async def withdraw(self): 117 | pass 118 | 119 | @abstractmethod 120 | async def enable_collateral(self): 121 | pass 122 | 123 | @abstractmethod 124 | async def disable_collateral(self): 125 | pass 126 | 127 | 128 | class Minter(ABC): 129 | @abstractmethod 130 | async def mint(self): 131 | pass 132 | 133 | 134 | class Creator(ABC): 135 | def __init__(self, client): 136 | self.client = client 137 | 138 | @abstractmethod 139 | async def create(self): 140 | pass 141 | 142 | 143 | class Blockchain(ABC): 144 | @abstractmethod 145 | async def deposit(self): 146 | pass 147 | 148 | @abstractmethod 149 | async def withdraw(self): 150 | pass 151 | 152 | @abstractmethod 153 | async def transfer_eth(self): 154 | pass 155 | 156 | @abstractmethod 157 | async def wrap_eth(self): 158 | pass 159 | 160 | @abstractmethod 161 | async def unwrap_eth(self): 162 | pass 163 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import sys 3 | 4 | from questionary import Choice, select 5 | from termcolor import cprint 6 | 7 | from config import PROXIES, TITLE 8 | # from modules import txchecker 9 | from modules.modules_runner import run_module, run_accounts 10 | from utils.route_generator import auto_routes_gen, classic_routes_gen 11 | from utils.tools import check_proxies_status, create_okx_withdrawal_list 12 | 13 | 14 | def exit_from_software(): 15 | sys.exit() 16 | 17 | 18 | def create_okx_list(): 19 | create_okx_withdrawal_list() 20 | 21 | 22 | async def run_one_module(module): 23 | await run_module(module) 24 | 25 | 26 | async def run_modules(): 27 | await run_accounts() 28 | 29 | 30 | async def check_proxy(): 31 | await asyncio.sleep(1) 32 | await check_proxies_status(PROXIES) 33 | 34 | 35 | # async def get_tx_stat(): 36 | # await txchecker.main() 37 | 38 | 39 | def are_you_sure(module): 40 | answer = select( 41 | '\n ⚠️⚠️⚠️ THAT ACTION DELETE ALL PREVIOUS DATA, continue? ⚠️⚠️⚠️ \n', 42 | choices=[ 43 | Choice("❌ NO", 'main'), 44 | Choice("✅ YES", 'module'), 45 | ], 46 | qmark='☢️', 47 | pointer='👉' 48 | ).ask() 49 | 50 | if answer == 'main': 51 | main() 52 | else: 53 | if module == 'auto_routes_gen': 54 | auto_routes_gen() 55 | else: 56 | classic_routes_gen() 57 | 58 | 59 | def get_one_module(): 60 | answer = select( 61 | 'What module do you need?\n', 62 | choices=[ 63 | Choice("⚫ Withdraw OKX", 'okx_withdraw'), 64 | Choice("🔵 Bridge on official bridge", 'bridge_scroll'), 65 | Choice("🔵 Bridge on LayerSwap", 'bridge_layerswap'), 66 | Choice("🔵 Bridge on Orbiter", 'bridge_orbiter'), 67 | Choice("🔵 Bridge on Rhino.fi", 'bridge_rhino'), 68 | Choice("🔴 Refuel on Merkly", 'refuel_merkly'), 69 | Choice("🟢 Swap on Izumi", 'swap_izumi'), 70 | Choice("🟢 Swap on SyncSwap", 'swap_syncswap'), 71 | Choice("🟢 Swap on OpenOcean", 'swap_openocean'), 72 | Choice("🟢 Swap on ScrollSwap", 'swap_scrollswap'), 73 | Choice("🟣 Add liquidity on SyncSwap", 'add_liquidity_syncswap'), 74 | Choice("🟣 Withdraw liquidity from SyncSwap", 'withdraw_liquidity_syncswap'), 75 | Choice("🟡 Deploy contract", 'deploy_contract'), 76 | Choice("🟡 Create safe on chain", 'create_safe'), 77 | Choice("🟡 Create NFT collection on OmniSea", 'create_omnisea'), 78 | Choice("🟡 Mint Zerius NFT", 'mint_zerius'), 79 | Choice("🟡 Bridge Zerius NFT", 'bridge_zerius'), 80 | Choice("⚪ Send message on Dmail", 'send_message_dmail'), 81 | Choice("⚪ Wrap ETH", 'wrap_eth'), 82 | Choice("⚪ Unwrap ETH", 'unwrap_eth'), 83 | Choice("⚪ Transfer ETH to random address", 'transfer_eth'), 84 | Choice("⚪ Transfer ETH to your own address", 'transfer_eth_to_myself'), 85 | Choice("🔵 Withdraw from official bridge", 'withdraw_scroll'), 86 | Choice("⚫ Deposit OKX", 'okx_deposit'), 87 | Choice("⚫ Collect funds on OKX", 'okx_collect_from_sub'), 88 | Choice('Back to menu', 'main') 89 | ], 90 | qmark='🛠️', 91 | pointer='👉' 92 | ).ask() 93 | return answer 94 | 95 | 96 | def main(): 97 | print(TITLE) 98 | cprint(f'\n❤️ Subscribe to my channel: https://t.me/askaer', 'light_cyan', attrs=["blink"]) 99 | cprint(f'\n💵 Donate (Any EVM) --> 0x000000a679C2FB345dDEfbaE3c42beE92c0Fb7A5\n', 'light_cyan') 100 | 101 | while True: 102 | answer = select( 103 | 'What do you want to do?', 104 | choices=[ 105 | Choice("🚀 Start running routes for each wallet", 'run_modules'), 106 | Choice("🤖 Generate auto-route for each wallet", 'auto_routes_gen'), 107 | Choice("📄 Generate classic-route for each wallet", 'classic_routes_gen'), 108 | Choice("💾 Create and safe OKX withdrawal file", create_okx_list), 109 | Choice("✅ Check the connection of each proxy", 'check_proxy'), 110 | Choice("👈 Choose one module to run", 'get_one_module'), 111 | # Choice("📊 Get TX stats for all wallets", 'tx_stat'), 112 | Choice('❌ Exit', sys.exit) 113 | ], 114 | qmark='🛠️', 115 | pointer='👉' 116 | ).ask() 117 | 118 | if answer == 'check_proxy': 119 | print() 120 | asyncio.run(check_proxy()) 121 | print() 122 | elif answer == 'run_modules': 123 | print() 124 | asyncio.run(run_modules()) 125 | print() 126 | elif answer == 'get_one_module': 127 | print() 128 | module_name = get_one_module() 129 | if module_name == 'main': 130 | main() 131 | asyncio.run(run_one_module(module_name)) 132 | print() 133 | # elif answer == 'tx_stat': 134 | # print() 135 | # asyncio.run(get_tx_stat()) 136 | # print() 137 | elif answer in ['auto_routes_gen', 'classic_routes_gen']: 138 | are_you_sure(answer) 139 | else: 140 | print() 141 | answer() 142 | print() 143 | 144 | 145 | if __name__ == "__main__": 146 | main() 147 | -------------------------------------------------------------------------------- /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 | ScrollRPC = Network( 25 | name='Scroll', 26 | rpc=['https://1rpc.io/scroll', 27 | 'https://rpc.scroll.io', 28 | 'https://scroll.blockpi.network/v1/rpc/public'], 29 | chain_id=534352, 30 | eip1559_support=False, 31 | token='ETH', 32 | explorer='https://scrollscan.com/' 33 | ) 34 | 35 | zkSyncEra = Network( 36 | name='zkSync Era', 37 | rpc=['https://rpc.ankr.com/zksync_era', 38 | 'https://zksync.meowrpc.com', 39 | 'https://zksync.drpc.org', 40 | 'https://zksync-era.blockpi.network/v1/rpc/public'], 41 | chain_id=324, 42 | eip1559_support=True, 43 | token='ETH', 44 | explorer='https://explorer.zksync.io/', 45 | ) 46 | 47 | 48 | Arbitrum = Network( 49 | name='Arbitrum', 50 | rpc=['https://rpc.ankr.com/arbitrum/', 51 | 'https://arbitrum.llamarpc.com', 52 | 'https://1rpc.io/arb', 53 | 'https://arb1.arbitrum.io/rpc'], 54 | chain_id=42161, 55 | eip1559_support=True, 56 | token='ETH', 57 | explorer='https://arbiscan.io/', 58 | ) 59 | 60 | 61 | Optimism = Network( 62 | name='Optimism', 63 | rpc=['https://rpc.ankr.com/optimism/', 64 | 'https://optimism.llamarpc.com', 65 | 'https://optimism.drpc.org', 66 | 'https://1rpc.io/op'], 67 | chain_id=10, 68 | eip1559_support=True, 69 | token='ETH', 70 | explorer='https://optimistic.etherscan.io/', 71 | ) 72 | 73 | 74 | Polygon = Network( 75 | name='Polygon', 76 | rpc=['https://rpc.ankr.com/polygon', 77 | 'https://polygon.llamarpc.com', 78 | 'https://1rpc.io/matic', 79 | 'https://polygon-rpc.com'], 80 | chain_id=137, 81 | eip1559_support=True, 82 | token='MATIC', 83 | explorer='https://polygonscan.com/', 84 | ) 85 | 86 | 87 | Avalanche = Network( 88 | name='Avalanche', 89 | rpc=['https://rpc.ankr.com/avalanche/', 90 | 'https://1rpc.io/avax/c', 91 | 'https://avax.meowrpc.com', 92 | 'https://avalanche.drpc.org'], 93 | chain_id=43114, 94 | eip1559_support=True, 95 | token='AVAX', 96 | explorer='https://snowtrace.io/', 97 | ) 98 | 99 | 100 | Ethereum = Network( 101 | name='Ethereum', 102 | rpc=[#'https://eth-mainnet.g.alchemy.com/v2/your_key-CIAX', 103 | #'https://eth.getblock.io/your_key/mainnet/', 104 | 'https://rpc.ankr.com/eth', 105 | 'https://eth.llamarpc.com', 106 | 'https://1rpc.io/eth', 107 | 'https://eth.drpc.org'], 108 | chain_id=1, 109 | eip1559_support=True, 110 | token='ETH', 111 | explorer='https://etherscan.io/' 112 | ) 113 | 114 | Arbitrum_nova = Network( 115 | name='Arbitrum Nova', 116 | rpc=['https://rpc.ankr.com/arbitrumnova', 117 | 'https://arbitrum-nova.publicnode.com', 118 | 'https://arbitrum-nova.drpc.org', 119 | 'https://nova.arbitrum.io/rpc'], 120 | chain_id=42170, 121 | eip1559_support=True, 122 | token='ETH', 123 | explorer='https://nova.arbiscan.io/' 124 | ) 125 | 126 | Base = Network( 127 | name='Base', 128 | rpc=['https://base.llamarpc.com', 129 | 'https://base.publicnode.com', 130 | 'https://base.meowrpc.com', 131 | 'https://1rpc.io/base'], 132 | chain_id=8453, 133 | eip1559_support=True, 134 | token='ETH', 135 | explorer='https://basescan.org/' 136 | ) 137 | 138 | Linea = Network( 139 | name='Linea', 140 | rpc=['https://linea.drpc.org', 141 | 'https://1rpc.io/linea', 142 | 'https://rpc.linea.build'], 143 | chain_id=59144, 144 | eip1559_support=False, 145 | token='ETH', 146 | explorer='https://lineascan.build/' 147 | ) 148 | 149 | Zora = Network( 150 | name='Zora', 151 | rpc=['https://rpc.zora.energy'], 152 | chain_id=7777777, 153 | eip1559_support=False, 154 | token='ETH', 155 | explorer='https://zora.superscan.network/' 156 | ) 157 | 158 | Polygon_ZKEVM = Network( 159 | name='Polygon ZKEVM', 160 | rpc=['https://1rpc.io/polygon/zkevm', 161 | 'https://zkevm-rpc.com', 162 | 'https://rpc.ankr.com/polygon_zkevm'], 163 | chain_id=1101, 164 | eip1559_support=True, 165 | token='ETH', 166 | explorer='https://zkevm.polygonscan.com/' 167 | ) 168 | 169 | BSC = Network( 170 | name='BNB chain', 171 | rpc=['https://binance.llamarpc.com', 172 | 'https://bsc-dataseed.bnbchain.org', 173 | 'https://rpc.ankr.com/bsc', 174 | 'https://1rpc.io/bnb'], 175 | chain_id=56, 176 | eip1559_support=False, 177 | token='BNB', 178 | explorer='https://bscscan.com/' 179 | ) 180 | 181 | Manta = Network( 182 | name='Manta', 183 | rpc=['https://pacific-rpc.manta.network/http'], 184 | chain_id=169, 185 | eip1559_support=True, 186 | token='ETH', 187 | explorer='https://pacific-explorer.manta.network/' 188 | ) 189 | 190 | Mantle = Network( 191 | name='Mantle', 192 | rpc=['https://mantle.publicnode.com', 193 | 'https://mantle-mainnet.public.blastapi.io', 194 | 'https://mantle.drpc.org', 195 | 'https://rpc.ankr.com/mantle', 196 | 'https://1rpc.io/mantle'], 197 | chain_id=5000, 198 | eip1559_support=True, 199 | token='MNT', 200 | explorer='https://explorer.mantle.xyz/' 201 | ) 202 | 203 | OpBNB = Network( 204 | name='OpBNB', 205 | rpc=['https://opbnb.publicnode.com', 206 | 'https://1rpc.io/opbnb', 207 | 'https://opbnb-mainnet-rpc.bnbchain.org', 208 | 'https://opbnb-mainnet.nodereal.io/v1/e9a36765eb8a40b9bd12e680a1fd2bc5', 209 | ], 210 | chain_id=204, 211 | eip1559_support=False, 212 | token='BNB', 213 | explorer='https://opbnbscan.com/' 214 | ) 215 | 216 | # zkSyncLite = Network( 217 | # name='zksync_lite', 218 | # rpc=[], 219 | # chain_id=0, 220 | # eip1559_support=True, 221 | # token='ETH', 222 | # explorer='https://zkscan.io/' 223 | # ) 224 | 225 | -------------------------------------------------------------------------------- /utils/stark_signature/eth_coder.py: -------------------------------------------------------------------------------- 1 | import hmac 2 | import ecdsa 3 | from hashlib import sha256, sha512 4 | from Crypto.Cipher import AES 5 | from Crypto.Util.Padding import unpad 6 | from Crypto.Random import get_random_bytes 7 | from ecdsa.curves import SECP256k1 8 | from ecdsa.keys import SigningKey, VerifyingKey 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 = SigningKey.from_string(private_key_a, curve=curve) 37 | key_b = 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 | -------------------------------------------------------------------------------- /modules/syncswap.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | from eth_abi import abi 3 | from modules import DEX 4 | from utils.tools import gas_checker, repeater 5 | from settings import SLIPPAGE_PERCENT 6 | from config import ( 7 | SYNCSWAP_CONTRACTS, 8 | SYNCSWAP_CLASSIC_POOL_FACTORY_ABI, 9 | SYNCSWAP_CLASSIC_POOL_ABI, 10 | SYNCSWAP_ROUTER_ABI, 11 | ZERO_ADDRESS, 12 | SCROLL_TOKENS 13 | ) 14 | 15 | 16 | class SyncSwap(DEX): 17 | def __init__(self, client): 18 | self.client = client 19 | 20 | self.router_contract = self.client.get_contract(SYNCSWAP_CONTRACTS['router'], SYNCSWAP_ROUTER_ABI) 21 | self.pool_factory_contract = self.client.get_contract(SYNCSWAP_CONTRACTS['classic_pool_factoty'], 22 | SYNCSWAP_CLASSIC_POOL_FACTORY_ABI) 23 | 24 | async def get_min_amount_out(self, pool_address: str, from_token_address: str, amount_in_wei: int): 25 | pool_contract = self.client.get_contract(pool_address, SYNCSWAP_CLASSIC_POOL_ABI) 26 | min_amount_out = await pool_contract.functions.getAmountOut( 27 | from_token_address, 28 | amount_in_wei, 29 | self.client.address 30 | ).call() 31 | 32 | return int(min_amount_out - (min_amount_out / 100 * SLIPPAGE_PERCENT)) 33 | 34 | @repeater 35 | @gas_checker 36 | async def swap(self, help_deposit:bool = False): 37 | 38 | from_token_name, to_token_name, amount, amount_in_wei = await self.client.get_auto_amount() 39 | 40 | if help_deposit: 41 | to_token_name = 'ETH' 42 | 43 | self.client.logger.info( 44 | f'{self.client.info} SyncSwap | Swap on SyncSwap: {amount} {from_token_name} -> {to_token_name}') 45 | 46 | from_token_address, to_token_address = SCROLL_TOKENS[from_token_name], SCROLL_TOKENS[to_token_name] 47 | 48 | if from_token_name != 'ETH': 49 | await self.client.check_for_approved(from_token_address, SYNCSWAP_CONTRACTS['router'], amount_in_wei) 50 | 51 | withdraw_mode = 1 52 | pool_address = await self.pool_factory_contract.functions.getPool(from_token_address, 53 | to_token_address).call() 54 | deadline = int(time()) + 1800 55 | min_amount_out = await self.get_min_amount_out(pool_address, from_token_address, amount_in_wei) 56 | 57 | swap_data = abi.encode(['address', 'address', 'uint8'], 58 | [from_token_address, self.client.address, withdraw_mode]) 59 | 60 | steps = [{ 61 | 'pool': pool_address, 62 | 'data': swap_data, 63 | 'callback': ZERO_ADDRESS, 64 | 'callbackData': '0x' 65 | }] 66 | 67 | paths = [{ 68 | 'steps': steps, 69 | 'tokenIn': from_token_address if from_token_name != 'ETH' else ZERO_ADDRESS, 70 | 'amountIn': amount_in_wei 71 | }] 72 | 73 | tx_params = await self.client.prepare_transaction(value=amount_in_wei if from_token_name == 'ETH' else 0) 74 | 75 | transaction = await self.router_contract.functions.swap( 76 | paths, 77 | min_amount_out, 78 | deadline, 79 | ).build_transaction(tx_params) 80 | 81 | tx_hash = await self.client.send_transaction(transaction) 82 | 83 | await self.client.verify_transaction(tx_hash) 84 | 85 | @repeater 86 | @gas_checker 87 | async def add_liquidity(self): 88 | 89 | amount_from_settings, amount_from_settings_in_wei = await self.client.check_and_get_eth_for_liquidity() 90 | 91 | self.client.logger.info( 92 | f'{self.client.info} SyncSwap | Add liquidity to SyncSwap USDC/ETH pool: {amount_from_settings} ETH') 93 | 94 | token_a_address, token_b_address = SCROLL_TOKENS['ETH'], SCROLL_TOKENS['USDC'] 95 | 96 | pool_address = await self.pool_factory_contract.functions.getPool(token_a_address, token_b_address).call() 97 | pool_contract = self.client.get_contract(pool_address, SYNCSWAP_CLASSIC_POOL_ABI) 98 | 99 | total_supply = await pool_contract.functions.totalSupply().call() 100 | _, reserve_eth = await pool_contract.functions.getReserves().call() 101 | # fee = await pool_contract.functions.getProtocolFee().call() 102 | min_lp_amount_out = int(amount_from_settings_in_wei * total_supply / reserve_eth / 2 * 0.9965) 103 | 104 | inputs = [ 105 | (token_b_address, 0), 106 | (ZERO_ADDRESS, amount_from_settings_in_wei) 107 | ] 108 | 109 | tx_params = await self.client.prepare_transaction(value=amount_from_settings_in_wei) 110 | 111 | transaction = await self.router_contract.functions.addLiquidity2( 112 | pool_address, 113 | inputs, 114 | abi.encode(['address'], [self.client.address]), 115 | min_lp_amount_out, 116 | ZERO_ADDRESS, 117 | '0x' 118 | ).build_transaction(tx_params) 119 | 120 | tx_hash = await self.client.send_transaction(transaction) 121 | 122 | await self.client.verify_transaction(tx_hash) 123 | 124 | @repeater 125 | @gas_checker 126 | async def withdraw_liquidity(self): 127 | self.client.logger.info( 128 | f'{self.client.info} SyncSwap | Withdraw liquidity from SyncSwap') 129 | 130 | token_a_address, token_b_address = SCROLL_TOKENS['ETH'], SCROLL_TOKENS['USDC'] 131 | 132 | pool_address = await self.pool_factory_contract.functions.getPool(token_a_address, token_b_address).call() 133 | pool_contract = self.client.get_contract(pool_address, SYNCSWAP_CLASSIC_POOL_ABI) 134 | 135 | liquidity_balance = await pool_contract.functions.balanceOf(self.client.address).call() 136 | 137 | if liquidity_balance != 0: 138 | 139 | await self.client.check_for_approved(pool_address, SYNCSWAP_CONTRACTS['router'], liquidity_balance) 140 | 141 | tx_params = await self.client.prepare_transaction() 142 | 143 | total_supply = await pool_contract.functions.totalSupply().call() 144 | _, reserve_eth = await pool_contract.functions.getReserves().call() 145 | min_eth_amount_out = int(liquidity_balance * reserve_eth / total_supply * 2 * 0.9965) 146 | 147 | withdraw_mode = 1 148 | data = abi.encode(['address', 'address', 'uint8'], 149 | [token_a_address, self.client.address, withdraw_mode]) 150 | 151 | transaction = await self.router_contract.functions.burnLiquiditySingle( 152 | pool_address, 153 | liquidity_balance, 154 | data, 155 | min_eth_amount_out, 156 | ZERO_ADDRESS, 157 | "0x", 158 | ).build_transaction(tx_params) 159 | 160 | tx_hash = await self.client.send_transaction(transaction) 161 | 162 | await self.client.verify_transaction(tx_hash) 163 | 164 | else: 165 | raise RuntimeError('Insufficient balance on SyncSwap!') 166 | -------------------------------------------------------------------------------- /modules/scroll.py: -------------------------------------------------------------------------------- 1 | from eth_account import Account 2 | from modules import Blockchain 3 | from utils.tools import gas_checker, repeater 4 | from settings import ( 5 | SCROLL_DEP_MAX, 6 | SCROLL_DEP_MIN, 7 | SCROLL_WITHDRAW_MAX, 8 | SCROLL_WITHDRAW_MIN, 9 | TRANSFER_MIN, 10 | TRANSFER_MAX 11 | ) 12 | from config import ( 13 | WETH_ABI, 14 | SCROLL_TOKENS, 15 | SCROLL_CONTRACTS, 16 | SCROLL_DEPOSIT_ABI, 17 | SCROLL_WITHDRAW_ABI, 18 | SCROLL_ORACLE_ABI, 19 | ) 20 | 21 | 22 | class Scroll(Blockchain): 23 | def __init__(self, client): 24 | self.client = client 25 | 26 | self.deposit_contract = self.client.get_contract(SCROLL_CONTRACTS['deposit'], SCROLL_DEPOSIT_ABI) 27 | self.oracle_contract = self.client.get_contract(SCROLL_CONTRACTS["oracle"], SCROLL_ORACLE_ABI) 28 | self.withdraw_contract = self.client.get_contract(SCROLL_CONTRACTS['withdraw'], SCROLL_WITHDRAW_ABI) 29 | self.token_contract = self.client.get_contract(SCROLL_TOKENS['WETH'], WETH_ABI) 30 | 31 | @repeater 32 | @gas_checker 33 | async def deposit(self): 34 | 35 | amount = self.client.round_amount(SCROLL_DEP_MIN, SCROLL_DEP_MAX) 36 | amount_in_wei = int(amount * 10 ** 18) 37 | 38 | self.client.logger.info(f'{self.client.info} Scroll | Bridge {amount} ETH | ERC20 -> Scroll') 39 | 40 | if await self.client.w3.eth.get_balance(self.client.address) > amount_in_wei: 41 | 42 | bridge_fee = await self.oracle_contract.functions.estimateCrossDomainMessageFee(168000).call() 43 | 44 | tx_data = await self.client.prepare_transaction(value=amount_in_wei + bridge_fee) 45 | 46 | transaction = await self.deposit_contract.functions.depositETH( 47 | amount_in_wei, 48 | 168000, 49 | ).build_transaction(tx_data) 50 | 51 | tx_hash = await self.client.send_transaction(transaction) 52 | 53 | await self.client.verify_transaction(tx_hash) 54 | 55 | else: 56 | raise RuntimeError('Insufficient balance!') 57 | 58 | @repeater 59 | @gas_checker 60 | async def withdraw(self): 61 | 62 | amount = self.client.round_amount(SCROLL_WITHDRAW_MIN, SCROLL_WITHDRAW_MAX) 63 | amount_in_wei = int(amount * 10 ** 18) 64 | 65 | self.client.logger.info( 66 | f'{self.client.info} Scroll | Withdraw {amount} ETH Scroll -> ERC20') 67 | 68 | if await self.client.w3.eth.get_balance(self.client.address) > amount_in_wei: 69 | 70 | tx_params = await self.client.prepare_transaction(value=amount_in_wei) 71 | 72 | transaction = await self.withdraw_contract.functions.withdrawETH( 73 | amount_in_wei, 74 | 0 75 | ).build_transaction(tx_params) 76 | 77 | tx_hash = await self.client.send_transaction(transaction) 78 | 79 | await self.client.verify_transaction(tx_hash) 80 | 81 | else: 82 | raise RuntimeError('Insufficient balance!') 83 | 84 | @repeater 85 | @gas_checker 86 | async def transfer_eth_to_myself(self): 87 | 88 | amount, amount_in_wei = await self.client.check_and_get_eth_for_deposit() 89 | 90 | self.client.logger.info( 91 | f"{self.client.info} Scroll | Transfer {amount} ETH to your own address: {self.client.address}") 92 | 93 | tx_params = await self.client.prepare_transaction(value=amount_in_wei) | { 94 | "to": self.client.address, 95 | "data": "0x" 96 | } 97 | 98 | tx_hash = await self.client.send_transaction(tx_params) 99 | 100 | await self.client.verify_transaction(tx_hash) 101 | 102 | @repeater 103 | @gas_checker 104 | async def transfer_eth(self): 105 | 106 | amount = self.client.round_amount(TRANSFER_MIN, TRANSFER_MAX) 107 | amount_in_wei = int(amount * 10 ** 18) 108 | random_address = Account.create().address 109 | 110 | self.client.logger.info(f'{self.client.info} Scroll | Transfer ETH to random zkSync address: {amount} ETH') 111 | 112 | if await self.client.w3.eth.get_balance(self.client.address) > amount_in_wei: 113 | 114 | tx_params = (await self.client.prepare_transaction()) | { 115 | 'to': random_address, 116 | 'value': amount_in_wei, 117 | 'data': "0x" 118 | } 119 | 120 | tx_hash = await self.client.send_transaction(tx_params) 121 | 122 | await self.client.verify_transaction(tx_hash) 123 | 124 | else: 125 | raise RuntimeError('Insufficient balance!') 126 | 127 | @repeater 128 | @gas_checker 129 | async def deploy_contract(self): 130 | 131 | try: 132 | with open('data/contact_data.json') as file: 133 | from json import load 134 | contract_data = load(file) 135 | except: 136 | self.client.logger.info(f"{self.client.info} Scroll | Bad data in contract_json.json") 137 | 138 | self.client.logger.info(f"{self.client.info} Scroll | Deploy contract") 139 | 140 | tx_data = await self.client.prepare_transaction() 141 | 142 | contract = self.client.w3.eth.contract(abi=contract_data['abi'], bytecode=contract_data['bytecode']) 143 | 144 | transaction = await contract.constructor().build_transaction(tx_data) 145 | 146 | tx_hash = await self.client.send_transaction(transaction) 147 | 148 | await self.client.verify_transaction(tx_hash) 149 | 150 | # @repeater 151 | # @gas_checker 152 | # async def transfer_erc20_tokens(self, token_to_sent_name: str, address_to_sent: str, amount: float): 153 | # 154 | # self.logger.info( 155 | # f'{self.info} Transfer {token_to_sent_name} to random address: {amount} {token_to_sent_name}') 156 | # 157 | # amount_in_wei = await self.get_amount_in_wei(token_to_sent_name, amount) 158 | # 159 | # if (await self.get_token_balance(ZKSYNC_TOKENS[token_to_sent_name]))['balance_in_wei'] >= amount_in_wei: 160 | # 161 | # token_contract = self.get_contract(ZKSYNC_TOKENS[token_to_sent_name], ERC20_ABI) 162 | # 163 | # tx_params = await self.prepare_transaction() 164 | # 165 | # transaction = await token_contract.functions.transfer( 166 | # address_to_sent, 167 | # amount 168 | # ).build_transaction(tx_params) 169 | # 170 | # tx_hash = await self.send_transaction(transaction) 171 | # 172 | # await self.verify_transaction(tx_hash) 173 | # 174 | # else: 175 | # self.logger.error(f'{self.info} Insufficient balance!') 176 | 177 | @repeater 178 | @gas_checker 179 | async def wrap_eth(self): 180 | 181 | amount, amount_in_wei = await self.client.check_and_get_eth_for_deposit() 182 | 183 | self.client.logger.info(f'{self.client.info} Scroll | Wrap {amount} ETH') 184 | 185 | if await self.client.w3.eth.get_balance(self.client.address) > amount_in_wei: 186 | 187 | tx_params = await self.client.prepare_transaction(value=amount_in_wei) 188 | transaction = await self.token_contract.functions.deposit().build_transaction(tx_params) 189 | 190 | tx_hash = await self.client.send_transaction(transaction) 191 | 192 | await self.client.verify_transaction(tx_hash) 193 | 194 | else: 195 | raise RuntimeError('Insufficient balance!') 196 | 197 | @repeater 198 | @gas_checker 199 | async def unwrap_eth(self): 200 | 201 | amount_in_wei, amount, _ = await self.client.get_token_balance('WETH', check_symbol=False) 202 | 203 | self.client.logger.info(f'{self.client.info} Scroll | Unwrap {amount:.6f} WETH') 204 | 205 | tx_params = await self.client.prepare_transaction() 206 | 207 | transaction = await self.token_contract.functions.withdraw( 208 | amount_in_wei 209 | ).build_transaction(tx_params) 210 | 211 | tx_hash = await self.client.send_transaction(transaction) 212 | 213 | await self.client.verify_transaction(tx_hash) 214 | -------------------------------------------------------------------------------- /modules/okx.py: -------------------------------------------------------------------------------- 1 | import hmac 2 | import base64 3 | import asyncio 4 | 5 | from hashlib import sha256 6 | from modules import CEX 7 | from datetime import datetime, timezone 8 | from utils.tools import repeater, sleep, gas_checker 9 | from config import OKX_NETWORKS_NAME 10 | from settings import ( 11 | OKX_WITHDRAW_NETWORK, 12 | OKX_AMOUNT_MIN, 13 | OKX_AMOUNT_MAX, 14 | OKX_DEPOSIT_NETWORK, 15 | OKX_BRIDGE_NEED 16 | ) 17 | 18 | 19 | class OKX(CEX): 20 | @staticmethod 21 | def get_network_id(): 22 | return { 23 | 2: 1, 24 | 4: 7, 25 | 6: 10, 26 | 7: 4 27 | }[OKX_DEPOSIT_NETWORK] 28 | 29 | async def get_headers(self, request_path: str, method: str = "GET", body: str = ""): 30 | try: 31 | timestamp = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z' 32 | prehash_string = timestamp + method.upper() + request_path[19:] + body 33 | secret_key_bytes = self.api_secret.encode('utf-8') 34 | signature = hmac.new(secret_key_bytes, prehash_string.encode('utf-8'), sha256).digest() 35 | encoded_signature = base64.b64encode(signature).decode('utf-8') 36 | 37 | return { 38 | "Content-Type": "application/json", 39 | "OK-ACCESS-KEY": self.api_key, 40 | "OK-ACCESS-SIGN": encoded_signature, 41 | "OK-ACCESS-TIMESTAMP": timestamp, 42 | "OK-ACCESS-PASSPHRASE": self.passphras, 43 | "x-simulated-trading": "0" 44 | } 45 | except Exception as error: 46 | raise RuntimeError(f'Bad headers for OKX request: {error}') 47 | 48 | async def get_currencies(self): 49 | url = 'https://www.okx.cab/api/v5/asset/currencies' 50 | 51 | params = {'ccy': 'ETH'} 52 | 53 | headers = await self.get_headers(f'{url}?ccy=ETH') 54 | 55 | return await self.make_request(url=url, headers=headers, params=params, module_name='Token info') 56 | 57 | @repeater 58 | async def withdraw(self): 59 | url = 'https://www.okx.cab/api/v5/asset/withdrawal' 60 | 61 | withdraw_data = await self.get_currencies() 62 | 63 | networks_data = {item['chain']: {'can_withdraw': item['canWd'], 'min_fee': item['minFee']} for item in 64 | withdraw_data} 65 | 66 | network_name = OKX_NETWORKS_NAME[OKX_WITHDRAW_NETWORK] 67 | network_data = networks_data[network_name] 68 | amount = self.client.round_amount(OKX_AMOUNT_MIN, OKX_AMOUNT_MAX) 69 | 70 | self.client.logger.info(f"{self.client.info} OKX | Withdraw {amount} ETH to {network_name[4:]}") 71 | 72 | if network_data['can_withdraw']: 73 | 74 | body = { 75 | "ccy": 'ETH', 76 | "amt": amount - float(network_data['min_fee']), 77 | "dest": "4", 78 | "toAddr": self.client.address, 79 | "fee": network_data['min_fee'], 80 | "chain": f"{network_name}", 81 | } 82 | 83 | headers = await self.get_headers(method="POST", request_path=url, body=str(body)) 84 | 85 | await self.make_request(method='POST', url=url, data=str(body), headers=headers, 86 | module_name='Withdraw') 87 | 88 | self.client.logger.success( 89 | f"{self.client.info} OKX | Withdraw complete. Note: wait 1-2 minute to receive funds") 90 | 91 | await sleep(self, 70, 140) 92 | 93 | else: 94 | raise RuntimeError(f"Withdraw {network_name} is not available") 95 | 96 | @repeater 97 | async def transfer_from_subaccounts(self): 98 | 99 | self.client.logger.info(f'{self.client.info} OKX | Checking subAccounts balance') 100 | 101 | url_sub_list = "https://www.okx.cab/api/v5/users/subaccount/list" 102 | 103 | headers = await self.get_headers(request_path=url_sub_list) 104 | sub_list = await self.make_request(url=url_sub_list, headers=headers, module_name='Get subAccounts list') 105 | await asyncio.sleep(1) 106 | 107 | for sub_data in sub_list: 108 | sub_name = sub_data['subAcct'] 109 | 110 | url_sub_balance = f"https://www.okx.cab/api/v5/asset/subaccount/balances?subAcct={sub_name}&ccy=ETH" 111 | headers = await self.get_headers(request_path=url_sub_balance) 112 | 113 | sub_balance = (await self.make_request( 114 | url=url_sub_balance, 115 | headers=headers, 116 | module_name='Get subAccount balance' 117 | ))[0]['availBal'] 118 | 119 | await asyncio.sleep(1) 120 | 121 | if float(sub_balance) != 0.0: 122 | 123 | self.client.logger.info(f'{self.client.info} {sub_name} | subAccount balance : {sub_balance} ETH') 124 | 125 | body = { 126 | "ccy": "ETH", 127 | "type": "2", 128 | "amt": f"{sub_balance}", 129 | "from": "6", 130 | "to": "6", 131 | "subAcct": sub_name 132 | } 133 | 134 | url_transfer = "https://www.okx.cab/api/v5/asset/transfer" 135 | headers = await self.get_headers(method="POST", request_path=url_transfer, body=str(body)) 136 | await self.make_request(method="POST", url=url_transfer, data=str(body), headers=headers, 137 | module_name='SubAccount transfer') 138 | 139 | self.client.logger.success( 140 | f"{self.client.info} OKX | Transfer {float(sub_balance):.6f} ETH to main account complete") 141 | 142 | @repeater 143 | async def transfer_from_spot_to_funding(self): 144 | 145 | url_balance = "https://www.okx.cab/api/v5/account/balance?ccy=ETH" 146 | headers = await self.get_headers(request_path=url_balance) 147 | balance = (await self.make_request(url=url_balance, headers=headers, 148 | module_name='Trading account'))[0]["details"] 149 | 150 | for ccy in balance: 151 | if ccy['ccy'] == 'ETH' and ccy['availBal'] != '0': 152 | 153 | self.client.logger.info(f"{self.client.info} OKX | Main trading account balance: {ccy['availBal']} ETH") 154 | 155 | body = { 156 | "ccy": 'ETH', 157 | "amt": ccy['availBal'], 158 | "from": "18", 159 | "to": "6" 160 | } 161 | 162 | url_transfer = "https://www.okx.cab/api/v5/asset/transfer" 163 | headers = await self.get_headers(request_path=url_transfer, body=str(body), method="POST") 164 | await self.make_request(url=url_transfer, data=str(body), method="POST", headers=headers, 165 | module_name='Trading account') 166 | self.client.logger.success( 167 | f"{self.client.info} OKX | Transfer {float(ccy['availBal']):.6f} ETH to funding account complete") 168 | break 169 | else: 170 | self.client.logger.info(f"{self.client.info} OKX | Main trading account balance: 0 ETH") 171 | break 172 | 173 | @repeater 174 | @gas_checker 175 | async def deposit_to_okx(self): 176 | 177 | amount_in_wei, amount, _ = await self.client.get_token_balance() 178 | 179 | try: 180 | with open('./data/okx_withdraw_list.json') as file: 181 | from json import load 182 | okx_withdraw_list = load(file) 183 | except: 184 | self.client.logger.info(f"{self.client.info} Scroll | Bad data in okx_wallet_list.json") 185 | 186 | try: 187 | okx_wallet = self.client.w3.to_checksum_address(okx_withdraw_list[self.client.address]) 188 | except Exception as error: 189 | raise RuntimeError(f'There is no wallet listed for deposit to OKX: {error}') 190 | 191 | info = f"{okx_wallet[:10]}....{okx_wallet[-6:]}" 192 | network_name = self.client.network.name 193 | 194 | self.client.logger.info( 195 | f"{self.client.info} OKX | Deposit {amount * 0.98:.6f} ETH from {network_name} to OKX wallet: {info}") 196 | 197 | tx_params = (await self.client.prepare_transaction(value=int(amount_in_wei * 0.98))) | { 198 | 'to': okx_wallet, 199 | 'data': '0x' 200 | } 201 | 202 | tx_hash = await self.client.send_transaction(tx_params) 203 | 204 | await self.client.verify_transaction(tx_hash) 205 | 206 | async def deposit(self): 207 | 208 | # if OKX_DEPOSIT_NETWORK != 6: 209 | 210 | if OKX_BRIDGE_NEED: 211 | await self.client.bridge_from_source(self.get_network_id()) 212 | 213 | await sleep(self, 60, 80) 214 | 215 | await self.deposit_to_okx() 216 | 217 | @repeater 218 | async def collect_from_sub(self): 219 | 220 | await self.transfer_from_subaccounts() 221 | 222 | await sleep(self, 5, 10) 223 | 224 | await self.transfer_from_spot_to_funding() 225 | -------------------------------------------------------------------------------- /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 utils.stark_signature.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 -------------------------------------------------------------------------------- /functions.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | from modules import * 4 | from utils.networks import * 5 | from config import OKX_WRAPED_ID, LAYERZERO_WRAPED_NETWORKS 6 | from settings import (LAYERSWAP_CHAIN_ID_FROM, ORBITER_CHAIN_ID_FROM, RHINO_CHAIN_ID_FROM, 7 | OKX_DEPOSIT_NETWORK, SOURCE_CHAIN_MERKLY, SOURCE_CHAIN_ZERIUS) 8 | 9 | 10 | async def get_client(account_number, private_key, network, proxy): 11 | client_instance = Client(account_number, private_key, network, proxy) 12 | return client_instance 13 | 14 | 15 | def get_network_by_chain_id(chain_id): 16 | return { 17 | 1: Arbitrum, 18 | 2: Arbitrum_nova, 19 | 3: Base, 20 | 4: Linea, 21 | 5: Manta, 22 | 6: Polygon, 23 | 7: Optimism, 24 | 8: ScrollRPC, 25 | 9: Polygon_ZKEVM, 26 | 10: zkSyncEra, 27 | 11: Zora, 28 | 12: zkSyncEra, 29 | 13: Ethereum 30 | }[chain_id] 31 | 32 | 33 | async def swap_syncswap(account_number, private_key, network, proxy, **kwargs): 34 | worker = SyncSwap(await get_client(account_number, private_key, network, proxy)) 35 | await worker.swap(**kwargs) 36 | 37 | 38 | async def add_liquidity_syncswap(account_number, private_key, network, proxy): 39 | worker = SyncSwap(await get_client(account_number, private_key, network, proxy)) 40 | await worker.add_liquidity() 41 | 42 | 43 | async def withdraw_liquidity_syncswap(account_number, private_key, network, proxy): 44 | worker = SyncSwap(await get_client(account_number, private_key, network, proxy)) 45 | await worker.withdraw_liquidity() 46 | 47 | 48 | async def send_message_dmail(account_number, private_key, network, proxy): 49 | worker = Dmail(await get_client(account_number, private_key, network, proxy)) 50 | await worker.send_message() 51 | 52 | 53 | async def bridge_scroll(account_number, private_key, _, proxy): 54 | network = get_network_by_chain_id(13) 55 | 56 | worker = Scroll(await get_client(account_number, private_key, network, proxy)) 57 | await worker.deposit() 58 | 59 | 60 | async def transfer_eth(account_number, private_key, network, proxy): 61 | worker = Scroll(await get_client(account_number, private_key, network, proxy)) 62 | await worker.transfer_eth() 63 | 64 | 65 | async def transfer_eth_to_myself(account_number, private_key, network, proxy): 66 | worker = Scroll(await get_client(account_number, private_key, network, proxy)) 67 | await worker.transfer_eth_to_myself() 68 | 69 | 70 | async def withdraw_scrool(account_number, private_key, network, proxy): 71 | worker = Scroll(await get_client(account_number, private_key, network, proxy)) 72 | await worker.withdraw() 73 | 74 | 75 | async def wrap_eth(account_number, private_key, network, proxy): 76 | worker = Scroll(await get_client(account_number, private_key, network, proxy)) 77 | await worker.wrap_eth() 78 | 79 | 80 | async def unwrap_eth(account_number, private_key, network, proxy): 81 | wrap = Scroll(await get_client(account_number, private_key, network, proxy)) 82 | await wrap.unwrap_eth() 83 | 84 | 85 | async def deploy_contract(account_number, private_key, network, proxy): 86 | wrap = Scroll(await get_client(account_number, private_key, network, proxy)) 87 | await wrap.deploy_contract() 88 | 89 | 90 | # async def deploy_contract(account_number, private_key, network, proxy, *args, **kwargs): 91 | # wrap = ZkSync(account_number, private_key, network, proxy) 92 | # await wrap.deploy_contract(*args, **kwargs) 93 | 94 | 95 | # async def mint_deployed_token(account_number, private_key, network, proxy, *args, **kwargs): 96 | # mint = ZkSync(account_number, private_key, network, proxy) 97 | # await mint.mint_token() 98 | 99 | 100 | async def deposit_layerbank(account_number, private_key, network, proxy): 101 | worker = LayerBank(await get_client(account_number, private_key, network, proxy)) 102 | await worker.deposit() 103 | 104 | 105 | async def withdraw_layerbank(account_number, private_key, network, proxy): 106 | worker = LayerBank(await get_client(account_number, private_key, network, proxy)) 107 | await worker.withdraw() 108 | 109 | 110 | async def enable_collateral_layerbank(account_number, private_key, network, proxy): 111 | worker = LayerBank(await get_client(account_number, private_key, network, proxy)) 112 | await worker.enable_collateral() 113 | 114 | 115 | async def disable_collateral_layerbank(account_number, private_key, network, proxy): 116 | worker = LayerBank(await get_client(account_number, private_key, network, proxy)) 117 | await worker.disable_collateral() 118 | 119 | 120 | async def swap_openocean(account_number, private_key, network, proxy, **kwargs): 121 | worker = OpenOcean(await get_client(account_number, private_key, network, proxy)) 122 | await worker.swap(**kwargs) 123 | 124 | 125 | async def swap_izumi(account_number, private_key, network, proxy): 126 | worker = Izumi(await get_client(account_number, private_key, network, proxy)) 127 | await worker.swap() 128 | 129 | 130 | async def swap_scrollswap(account_number, private_key, network, proxy): 131 | worker = ScrollSwap(await get_client(account_number, private_key, network, proxy)) 132 | await worker.swap() 133 | 134 | 135 | async def bridge_layerswap(account_number, private_key, _, proxy, **kwargs): 136 | if kwargs.get('help_okx') is True: 137 | chain_from_id = 8 138 | else: 139 | chain_from_id = random.choice(LAYERSWAP_CHAIN_ID_FROM) 140 | network = get_network_by_chain_id(chain_from_id) 141 | 142 | worker = LayerSwap(await get_client(account_number, private_key, network, proxy)) 143 | await worker.bridge(chain_from_id, **kwargs) 144 | 145 | 146 | async def bridge_orbiter(account_number, private_key, _, proxy, **kwargs): 147 | if kwargs.get('help_okx') is True: 148 | chain_from_id = 8 149 | else: 150 | chain_from_id = random.choice(ORBITER_CHAIN_ID_FROM) 151 | network = get_network_by_chain_id(chain_from_id) 152 | 153 | worker = Orbiter(await get_client(account_number, private_key, network, proxy)) 154 | await worker.bridge(chain_from_id, **kwargs) 155 | 156 | 157 | async def bridge_rhino(account_number, private_key, _, proxy, **kwargs): 158 | if kwargs.get('help_okx') is True: 159 | chain_from_id = 8 160 | else: 161 | chain_from_id = random.choice(RHINO_CHAIN_ID_FROM) 162 | network = get_network_by_chain_id(chain_from_id) 163 | 164 | worker = Rhino(await get_client(account_number, private_key, network, proxy)) 165 | await worker.bridge(chain_from_id, **kwargs) 166 | 167 | 168 | async def refuel_merkly(account_number, private_key, _, proxy): 169 | chain_from_id = LAYERZERO_WRAPED_NETWORKS[random.choice(SOURCE_CHAIN_MERKLY)] 170 | network = get_network_by_chain_id(chain_from_id) 171 | 172 | worker = Merkly(await get_client(account_number, private_key, network, proxy)) 173 | await worker.refuel(chain_from_id) 174 | 175 | 176 | # async def send_message_l2telegraph(account_number, private_key, network, proxy): 177 | # worker = L2Telegraph(await get_client(account_number, private_key, network, proxy)) 178 | # await worker.send_message() 179 | # 180 | # 181 | # async def mint_and_bridge_l2telegraph(account_number, private_key, network, proxy): 182 | # worker = L2Telegraph(await get_client(account_number, private_key, network, proxy)) 183 | # await worker.mint_and_bridge() 184 | 185 | 186 | async def mint_zerius(account_number, private_key, _, proxy): 187 | chain_from_id = LAYERZERO_WRAPED_NETWORKS[random.choice(SOURCE_CHAIN_ZERIUS)] 188 | network = get_network_by_chain_id(chain_from_id) 189 | 190 | worker = Zerius(await get_client(account_number, private_key, network, proxy), chain_from_id) 191 | await worker.mint() 192 | 193 | 194 | async def bridge_zerius(account_number, private_key, _, proxy): 195 | chain_from_id = LAYERZERO_WRAPED_NETWORKS[random.choice(SOURCE_CHAIN_ZERIUS)] 196 | network = get_network_by_chain_id(chain_from_id) 197 | 198 | worker = Zerius(await get_client(account_number, private_key, network, proxy), chain_from_id) 199 | await worker.bridge() 200 | 201 | 202 | async def create_omnisea(account_number, private_key, network, proxy): 203 | worker = Omnisea(await get_client(account_number, private_key, network, proxy)) 204 | await worker.create() 205 | 206 | 207 | async def create_safe(account_number, private_key, network, proxy): 208 | worker = GnosisSafe(await get_client(account_number, private_key, network, proxy)) 209 | await worker.create() 210 | 211 | 212 | async def okx_withdraw(account_number, private_key, network, proxy): 213 | worker = OKX(await get_client(account_number, private_key, network, proxy)) 214 | await worker.withdraw() 215 | 216 | 217 | async def okx_deposit(account_number, private_key, _, proxy): 218 | network = get_network_by_chain_id(OKX_WRAPED_ID[OKX_DEPOSIT_NETWORK]) 219 | 220 | worker = OKX(await get_client(account_number, private_key, network, proxy)) 221 | await worker.deposit() 222 | 223 | 224 | async def okx_collect_from_sub(account_number, private_key, network, proxy): 225 | worker = OKX(await get_client(account_number, private_key, network, proxy)) 226 | await worker.collect_from_sub() 227 | 228 | 229 | MODULES = { 230 | "okx_withdraw": okx_withdraw, 231 | "bridge_layerswap": bridge_layerswap, 232 | "bridge_orbiter": bridge_orbiter, 233 | "bridge_rhino": bridge_rhino, 234 | "bridge_scroll": bridge_scroll, 235 | "add_liquidity_syncswap": add_liquidity_syncswap, 236 | "swap_izumi": swap_izumi, 237 | "swap_openocean": swap_openocean, 238 | "swap_syncswap": swap_syncswap, 239 | "swap_scrollswap": swap_scrollswap, 240 | "deposit_layerbank": deposit_layerbank, 241 | "enable_collateral_layerbank": enable_collateral_layerbank, 242 | "disable_collateral_layerbank": disable_collateral_layerbank, 243 | "withdraw_layerbank": withdraw_layerbank, 244 | "wrap_eth": wrap_eth, 245 | "create_omnisea": create_omnisea, 246 | # "mint_and_bridge_l2telegraph": mint_and_bridge_l2telegraph, 247 | "create_safe": create_safe, 248 | "refuel_merkly": refuel_merkly, 249 | "send_message_dmail": send_message_dmail, 250 | # "send_message_l2telegraph": send_message_l2telegraph, 251 | "transfer_eth": transfer_eth, 252 | "transfer_eth_to_myself": transfer_eth_to_myself, 253 | "mint_zerius": mint_zerius, 254 | "bridge_zerius": bridge_zerius, 255 | "unwrap_eth": unwrap_eth, 256 | "deploy_contract": deploy_contract, 257 | "withdraw_scroll": withdraw_scrool, 258 | "withdraw_liquidity_syncswap": withdraw_liquidity_syncswap, 259 | "okx_collect_from_sub": okx_collect_from_sub, 260 | "okx_deposit": okx_deposit, 261 | } 262 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # ⚔️ Scroll Machine 3 | 4 | Очень мощный инструмент в умелых руках. Практически полностью автоматизирован. 5 | > Путь осилит идущий, а софт осилит любой деген🕵️ 6 | 7 | ## Основные особенности 8 | 9 | * **Автоматическая работа большинства модулей*** 10 | (не нужно настраивать каждый модуль отдельно) 11 | * **Сохранение процесса выполнения аккаунта** 12 | * **Автоматическая генерация маршрута** 13 | * **Ручная генерация маршрута** 14 | * **Параллельный запуск** (для прогона дешевых аккаунтов) 15 | * **Последовательный запуск** (с депом и выводом через OKX) 16 | * **Одиночный запуск** (1 модуль на все аккаунты) 17 | * **Асинхронный ООП код** (все работает очень быстро) 18 | * **Рандомизация сумм/задержек/количества транзакций** 19 | * **Gas чекер, Повторитель** (при ошибках) 20 | * **Плотнейшее логирование, даже ваш чих залогируется** 21 | 22 | ***❗Благодаря настройкам `AMOUNT_MIN` и `AMOUNT_MAX` софт сам решает, какое количество, какого токена, в какой токен нужно обменять или сделать депозит на лендинг, вывод на ОКХ. 23 | % учитывается для нативного токена `ETH`, остальные токены(включая LP токены) обратно обмениваются на 100% от их баланса. Свапы реализованы во всех направлениях 24 | (`ETH - Токен`, `Токен - Токен`, `Токен - ETH`). 25 | Для оптимальной работы рекомендуется ставить от 60 до 80 %.** 26 | 27 | 28 | ## 🧩Модули и их функционал 29 | 30 | 1. OKX (Депозит / Вывод / Сбор средств с суб-аккаунтов) 31 | 2. Rhino (Bridge по любым направлениям) 32 | 3. LayerSwap (Bridge по любым направлениям) 33 | 4. Orbiter (Bridge по любым направлениям) 34 | 5. Official bridge (Bridge / Withdraw) 35 | 6. Merkly (Refuel все сети из zkSync) 36 | 7. SyncSwap (Свапы между стейблами и ETH + ввод и вывод ликвидности) 37 | 8. OpenOcean (Свапы между стейблами и ETH) 38 | 9. ScrollSwap (Свапы между стейблами и ETH) 39 | 10. iZumi (Свапы между стейблами и ETH) 40 | 11. LayerBank (Ввод и вывод ликвидности + вкл/выкл collateral) 41 | 12. Deployer (Деплой собственного контракта) 42 | 13. Safe (Gnosis) (Создание сейфа) 43 | 14. Zerius (Минт NFT / Бридж NFT) 44 | 15. Omnisea (Создание коллекции) 45 | 16. Dmail (Отправка сообщений) 46 | 17. Send ETH to random addresses (Отправка пыли в ETH на рандомные адресса) 47 | 18. Send ETH to own addresses (Отправка пыли в ETH на свой адресс) 48 | 19. Wrap/Unwrap ETH 49 | 50 | 51 | ## ♾️Основные функции 52 | 53 | 1. **🚀Запуск прогона всех аккаунтов по подготовленным маршрутам.** 54 | 55 | В настройках вы указываете режим работы софта (`SOFTWARE_MODE`), либо параллельный, либо последовательный. Запустив 56 | эту функцию софт будет брать каждый аккаунт поочереди и запускать его на маршрут. Чтобы 57 | понять всю мощность этого аппарата, попробуйте поставить задержку до 5с или вообще выключить (`SLEEP_MODE`). 58 | 59 | 2. **🤖Генерация авто-роутов для каждого аккаунта** 60 | 61 | Мощный генератор маршрутов. Смотрит на настройку `MAX_UNQ_CONTACTS` и `AUTO_ROUTES_MODULES_USING` и генерирует маршрут 62 | по вашим предпочтениям. ВНИМАНИЕ: ПРИ ЗАПУСКЕ ЭТОЙ ФУНКЦИИ, ВСЕ ПРЕДЫДУЩИЕ МАРШРУТЫ БУДУТ УДАЛЕНЫ 63 | 64 | 3. **📄Генерация классических роутов для каждого аккаунта** 65 | 66 | Классический генератор, работает по дедовской методике. Вам нужно в каждом шаге в настройке `CLASSIC_ROUTES_MODULES_USING` 67 | указать один или несколько модулей и при запуске этой функции софт соберет вам маршрут по этой настройке. Поддерживается 68 | `None` как один из модулей в списке, при его попадании в маршрут, софт пропустит этот шаг. ВНИМАНИЕ: ПРИ ЗАПУСКЕ ЭТОЙ 69 | ФУНКЦИИ, ВСЕ ПРЕДЫДУЩИЕ МАРШРУТЫ БУДУТ УДАЛЕНЫ 70 | 71 | 4. **💾Создание файла зависимостей ваших и OKX кошельков** 72 | 73 | Создает файл JSON, где привязываются ваши адреса к кошелькам OKX. Сделал для вашей безопасности. Софт выбирает 74 | к каждой строке в `wallets.txt` эту же строку в `okx_wallets.txt` и если вы ошиблись, то всегда можно проверить это в 75 | файле `okx_withdraw_list.json` 76 | 77 | 5. **✅Проверка всех прокси на работоспособность** 78 | 79 | Быстрая проверка прокси(реально быстрая, как с цепи срывается) 80 | 81 | 6. **👈Запуск одного модуля для всех аккаунтов** 82 | 83 | Запуск одного модуля для всех аккаунтов. Дал себе слово, что будет 3 режима работы, поэтому третьим оказался этот. 84 | 85 | 7. **📊Получение статистики для каждого аккаунта** 86 | 87 | Практически моментальное получение всей статистики по аккаунтам, даже если их больше 100 штук. Сделаны самые необходимые 88 | поля 89 | 90 | ## 📄Ввод своих данных 91 | 92 | ### Все нужные файлы находятся в папке `/data` 93 | 1. wallets.txt - ваши приватные ключи 94 | 2. proxies.txt - ваши прокси в формате login:password@ip:port 95 | 3. okx_wallets.txt - ваши адресса пополнения в OKX 96 | 97 | Каждый ваш `ключ` / `адресс` / `прокси` указывается на новой строке. Если проксей меньше, чем приватников, то 98 | софт работает следующим образом: 99 | 100 | Кошелек 1 -> Прокси 1 101 | Кошелек 2 -> Прокси 2 102 | Кошелек 3 -> Прокси 3 103 | Кошелек 4 -> Прокси 1 (цикличное повторение) 104 | Кошелек 5 -> Прокси 2 (цикличное повторение) 105 | и так далее. 106 | 107 | 108 | ## ⚙️Настройка софта 109 | 110 | Все настройки вынесены в файл `settings.py`. Заходим в него и видим подробное описание каждого раздела. 111 | Самые важные моменты продублирую здесь. 112 | 113 | 1. Раздел `API KEYS`. Получите все API ключи. В разделе есть ссылки на сайты, где это нужно сделать 114 | 2. Раздел `GENERAL SETTINGS`. Внимательно прочитайте все описания и проставьте необходимые значения 115 | 3. Далее сверху вниз настройте все модули. К каждому модулю есть описание на русском английском 116 | 4. Когда вы подойдете к разделу `ROUTES`, нужно собрать всю силу в кулак и перекреститься. `AUTO-ROUTES` это список модулей, которые будут генерироваться самостоятельно для каждого аккаунта, то есть если у модуля значение `1`, то он будет участвовать в генерации маршрута. `CLASSIC-ROUTES` это славянский вариант установки маршрута для аккаунта, вы прописываете в список набор модулей и один из них будет добавлен во время генерации маршрута. 117 | Опять же, в файле есть примеры и подробное описание. 118 | 119 | ### 📚Основные параметры 120 | 121 | * `SOFTWARE_MODE` - определяет режим работы софта (параллельный или последовательный). Параллельный способен одновременно 122 | крутить очень больше количество аккаунтов (необходимы прокси для каждого аккаунта, так как RPC блокчейнов заблокируют вас в 123 | противном случае). Последовательный режим работает как ручной прогон. Условно: депозит на аккаунт -> прохождение маршрута -> 124 | вывод на OKX и так по кругу, для всех аккаунтов 125 | * `WALLETS_TO_WORK` - определяет какие кошельки будут работать. Варианты работы: Одиночный, Выборка, От Х до У, Все сразу. Подробнее в настройках. 126 | * `TX_COUNT` - определяет необходимое количество транзакций(модулей) в маршруте. Это не финальное значение, так как генератор 127 | добавляет к этому числу еще немного, так как существуют зависимости (вывод из EraLend нельзя сделать, если вы еще не депнули туда) 128 | * `MAX_UNQ_CONTRACTS` - при установке в `True`, генератор авто-роутов добавит все включенные модули из списка `AUTO_ROUTES_MODULES_USING` 129 | хотя бы 1 раз, а дальше доделает маршрут случайными модулями 130 | * `RHINO_CHAIN_ID_FROM(TO)`, `LAYERSWAP_CHAIN_ID_FROM(TO)` и `ORBITER_CHAIN_ID_FROM(TO)` - устанавливают исходящие и входящие сети, также возможно 131 | выбрать несколько сетей и софт случайным образом выберет одну из них 132 | * `DESTINATION_MERKLY_DATA` и `DESTINATION_ZERIUS` - определяет входящий блокчейн(куда делаем refuel) и минимальную/максимальную 133 | сумму для refuel. Также можно выбрать несколько сетей, софт выберет одну случайную. 134 | * `AMOUNT_MIN` и `AMOUNT_MAX` - благодаря этим параметрам, софт понимает, сколько % от вашего баланса ему необходимо использовать 135 | в свапах, депозитах на лендинги и выводе на OKX. Более подробно описал [**здесь**](https://github.com/realaskaer/zkSync#%D0%BE%D1%81%D0%BD%D0%BE%D0%B2%D0%BD%D1%8B%D0%B5-%D0%BE%D1%81%D0%BE%D0%B1%D0%B5%D0%BD%D0%BD%D0%BE%D1%81%D1%82%D0%B8) 136 | * `MIN_BALANCE` - устанавливает минимальный баланс для аккаунта, опустившись за который, софт будет выдаться ошибку о 137 | недостаточном балансе на аккаунте `Insufficient balance on account!` 138 | * `GAS_CONTROL` - включает/выключает контроль газа для каждого шага в маршруте 139 | * `GAS_MULTIPLIER` - множитель газа, рекомендуемые значения от 1.3 до 1.5. Этот параметр умножается на газ лимит, чтобы 140 | увеличить шанс успешного завершения транзакции 141 | * `MAXIMUM_RETRY` - количество повторных попыток при ошибках в модулях 142 | * `UNLIMITED_APPROVE` - выбор между бесконечными и точными апрувами 143 | * `SLEEP_MODE` - включает/выключает режим сна после каждого модуля и между аккаунтами. Включив параллельный режим софта и 144 | выключив эту настройку, вы сможете лицезреть скорость данного аппарата 145 | 146 | ## 💻Как софт работает изнутри? 147 | 148 | ### Маршруты и сохранение прогресса его выполнения 149 | 150 | С помощью сгенерированных маршрутов, каждый аккаунт знает по какому пути ему необходимо пройти. Также реализовано сохранение текущего шага при выполнении маршрута движения аккаунтом, то есть если вы выключите софт на 3 модуле, то включив его заново, аккаунт продолжит свою работу ровно с `третьего` модуля. 151 | 152 | ### Контроль газа и выполнения 153 | 154 | Внутри софта есть два работника. Первый будет `проверять газ` перед каждым запуском модуля, и если газ будет выше установленного, аккаунт уйдет в ожидание нормального газа. Второй работник во время выполнения модуля следит, чтобы этот модуль был выполнен, если все же произойдет ошибка, он `повторит выполнение` указанное количество раз. Но если после нескольких повторений (кол-во устанавливаете вы сами) будет выскакивать ошибка, работник пропустит этот модуль и приступит к выполнению следующего. 155 | 156 | ## 🤛🏻Реферальная программа 157 | 158 | Внутри файла `сonfig.py` есть настройка `HELP_SOFTWARE`, если она включена (по умолчанию - включена), то от суммы вашей транзакции на любом агрегаторе (пока что только `OpenOcean`) мне будет идти `1%`. Эту часть от объема транзакции переводит контракт агрегатора, а не ваш кошелек. Поэтому вы не будете иметь дел с моим кошельком. 159 | Чтобы выключить эту функции, укажите значение `False` 160 | 161 | 162 | ## 🛠️Установка проекта 163 | > Устанавливая проект, вы принимаете риски использования софта для добывания денег(потерять жопу, деньги, девственность). 164 | 165 | Как только вы скачаете проект, убедитесь, что у вас Python 3.10.11 и последняя версия PyCharm (2023.2) 166 | 167 | Для установки необходимых библиотек, пропишите в консоль PyCharm 168 | 169 | ```bash 170 | pip install -r requirements.txt 171 | ``` 172 | ## 🔗 Ссылки на программы 173 | 174 | - [Установка PyCharm](https://www.jetbrains.com/pycharm/download/?section=windows) 175 | - [Установка Python](https://www.python.org/downloads/windows/) (Вам нужна версия 3.10.11) 176 | 177 | ## 🧾FAQ 178 | 179 | #### Есть ли дрейнер в софте? 180 | 181 | > Нет, но перед запуском любого софта, необходимо его проверять 182 | 183 | #### Как запустить софт? 184 | 185 | > Сначала, прочитать README, если не получилось с первого раза, попытаться еще раз. 186 | 187 | ## ❔Куда писать свой вопрос? 188 | 189 | - [@foundation](https://t.me/askaer) - мой канал в телеграм 190 | - [@foundation.chat](https://t.me/askaerchat) - ответы на любой вопрос 191 | - [@askaer](https://t.me/realaskaer) - **при обнаружении бомбы в коде** 192 | 193 | ## ❤️‍🔥Donate (Any EVM) 194 | ### `0x000000a679C2FB345dDEfbaE3c42beE92c0Fb7A5` 195 | > Спасибо за поддержку❤️ 196 | -------------------------------------------------------------------------------- /modules/rhino.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import json 4 | import base64 5 | import random 6 | import asyncio 7 | from modules import Bridge 8 | from datetime import datetime, timezone 9 | from utils.tools import gas_checker, sleep 10 | from eth_account.messages import encode_defunct 11 | from utils.stark_signature.stark_singature import sign, pedersen_hash, EC_ORDER, private_to_stark_key 12 | from utils.stark_signature.eth_coder import encrypt_with_public_key, decrypt_with_private_key, get_public_key 13 | 14 | REGISTER_DATA = { 15 | "types": { 16 | "EIP712Domain": [ 17 | {"name": "name", "type": "string"}, 18 | {"name": "version", "type": "string"} 19 | ], 20 | "rhino.fi": [ 21 | {"type": "string", "name": "action"}, 22 | {"type": "string", "name": "onlySignOn"} 23 | ] 24 | }, 25 | "domain": { 26 | "name": "rhino.fi", 27 | "version": "1.0.0" 28 | }, 29 | "primaryType": "rhino.fi", 30 | "message": { 31 | "action": "Access your rhino.fi account", 32 | "onlySignOn": "app.rhino.fi" 33 | } 34 | } 35 | 36 | 37 | class Rhino(Bridge): 38 | def __init__(self, client): 39 | super().__init__(client) 40 | 41 | self.nonce, self.signature = self.get_authentication_data() 42 | 43 | def get_authentication_data(self): 44 | date = datetime.now(timezone.utc).strftime("%a, %d %b %Y %H:%M:%S") 45 | nonce = f"{time.time():.3f}" 46 | 47 | text = (f"To protect your rhino.fi privacy we ask you to sign in with your wallet to see your data.\n" 48 | f"Signing in on {date} GMT. For your safety, only sign this message on rhino.fi!") 49 | 50 | nonse_str = f"v3-{nonce}" 51 | text_hex = "0x" + text.encode('utf-8').hex() 52 | text_encoded = encode_defunct(hexstr=text_hex) 53 | signature = self.client.w3.eth.account.sign_message(text_encoded, private_key=self.client.private_key).signature 54 | 55 | return nonse_str, self.client.w3.to_hex(signature) 56 | 57 | def make_headers(self): 58 | data_to_headers = f'{{"signature":"{self.signature}","nonce":"{self.nonce}"}}' 59 | 60 | headers = { 61 | "Accept":"application/json", 62 | "Accept-Encoding":"utf-8", 63 | "Accept-Language": "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7", 64 | "Content-Type":"application/json", 65 | "Origin":"https://app.rhino.fi", 66 | "Referer":"https://app.rhino.fi/", 67 | "Sec-Ch-Ua":'"Chromium";v="118", "Google Chrome";v="118", "Not=A?Brand";v="99"', 68 | "Sec-Ch-Ua-Mobile":"?0", 69 | "Sec-Ch-Ua-Platform":'"Windows"', 70 | "Sec-Fetch-Dest":"empty", 71 | "Sec-Fetch-Mode":"cors", 72 | "Sec-Fetch-Site":"same-site", 73 | "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)" 74 | " Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0", 75 | "Authorization": f"EcRecover {base64.b64encode(data_to_headers.encode('utf-8')).decode('utf-8')}" 76 | } 77 | 78 | return headers 79 | 80 | @staticmethod 81 | def create_stark_key(dtk_private_key): 82 | stark_key = private_to_stark_key(int(f"0x{dtk_private_key}", 16) % EC_ORDER) 83 | 84 | return f"0{hex(stark_key)[2:]}" 85 | 86 | def create_dtk(self): 87 | dtk = os.urandom(32).hex() 88 | 89 | sing_data = self.client.w3.eth.account.sign_typed_data(self.client.private_key, 90 | full_message=REGISTER_DATA).signature 91 | 92 | encryption_key = self.client.w3.keccak(f"{sing_data.hex()}".encode('utf-8')) 93 | 94 | public_key = get_public_key(encryption_key).hex() 95 | 96 | encrypted_message = encrypt_with_public_key(public_key, json.dumps({"data": dtk})) 97 | 98 | return dtk, encrypted_message 99 | 100 | async def get_user_config(self): 101 | 102 | url = "https://api.rhino.fi/v1/trading/r/getUserConf" 103 | 104 | data = { 105 | 'nonce': self.nonce, 106 | 'signature': self.signature 107 | } 108 | 109 | while True: 110 | try: 111 | data = await self.make_request(method='POST', url=url, headers=self.headers, json=data) 112 | return data 113 | except: 114 | self.client.logger.warning(f"{self.client.info} Rhino | Get bad API data") 115 | await asyncio.sleep(5) 116 | 117 | async def get_vault_id(self): 118 | 119 | url = "https://api.rhino.fi/v1/trading/r/getVaultId" 120 | 121 | data = { 122 | 'nonce': self.nonce, 123 | 'signature': self.signature, 124 | 'token': 'ETH' 125 | } 126 | 127 | return await self.make_request(method='POST', url=url, headers=self.headers, json=data) 128 | 129 | async def reg_new_acc(self): 130 | 131 | url = 'https://api.rhino.fi/v1/trading/w/register' 132 | 133 | dtk, encrypted_trading_key = self.create_dtk() 134 | stark_public_key_x = self.create_stark_key(dtk) 135 | 136 | data = { 137 | "encryptedTradingKey": { 138 | "dtk": encrypted_trading_key, 139 | "dtkVersion": "v3" 140 | }, 141 | "meta": 142 | { 143 | "walletType": "metamask", 144 | "campaign": None, 145 | "referer": None, 146 | "platform": "DESKTOP", 147 | }, 148 | "nonce": self.nonce, 149 | "signature": self.signature, 150 | "starkKey": stark_public_key_x, 151 | } 152 | 153 | return await self.make_request(method='POST', url=url, headers=self.headers, json=data) 154 | 155 | async def recover_trading_key(self): 156 | 157 | url = 'https://api.rhino.fi/v1/trading/r/recoverTradingKey' 158 | 159 | data = { 160 | "nonce": self.nonce, 161 | "signature": self.signature, 162 | "meta": { 163 | "ethAddress": self.client.address, 164 | } 165 | } 166 | 167 | return await self.make_request(method='POST', url=url, headers=self.headers, json=data) 168 | 169 | async def recover_dtk(self): 170 | encrypted_trading_key = (await self.recover_trading_key())['encryptedTradingKey'] 171 | sing_data = self.client.w3.eth.account.sign_typed_data(self.client.private_key, 172 | full_message=REGISTER_DATA).signature 173 | encryption_private_key = self.client.w3.keccak(f"{sing_data.hex()}".encode('utf-8')).hex() 174 | 175 | dtk = decrypt_with_private_key(encryption_private_key, encrypted_trading_key) 176 | 177 | return json.loads(dtk)['data'] 178 | 179 | async def get_vault_id_and_stark_key(self, deversifi_address): 180 | 181 | url = "https://api.rhino.fi/v1/trading/r/vaultIdAndStarkKey" 182 | 183 | headers = self.make_headers() 184 | 185 | params = { 186 | "token": 'ETH', 187 | "targetEthAddress": deversifi_address, 188 | } 189 | 190 | return await self.make_request(method="GET", url=url, headers=headers, params=params) 191 | 192 | async def get_user_balance(self): 193 | 194 | data = { 195 | "nonce": self.nonce, 196 | "signature": self.signature, 197 | "token": "ETH", 198 | "fields": [ 199 | "balance", 200 | "available", 201 | "updatedAt" 202 | ] 203 | } 204 | 205 | url = "https://api.rhino.fi/v1/trading/r/getBalance" 206 | 207 | response = await self.make_request(method="POST", url=url, headers=self.headers, json=data) 208 | if response: 209 | return response[0]['available'] 210 | return 0 211 | 212 | async def get_stark_signature(self, amount_in_wei, expiration_timestamp, tx_nonce, receiver_public_key, 213 | receiver_vault_id, sender_vault_id, token_address): 214 | 215 | packed_message = 1 # instruction_type 216 | packed_message = (packed_message << 31) + int(sender_vault_id) 217 | packed_message = (packed_message << 31) + int(receiver_vault_id) 218 | packed_message = (packed_message << 63) + int(amount_in_wei) 219 | packed_message = (packed_message << 63) + 0 220 | packed_message = (packed_message << 31) + int(tx_nonce) 221 | packed_message = (packed_message << 22) + int(expiration_timestamp) 222 | 223 | msg_hash = pedersen_hash(pedersen_hash(int(token_address, 16), int(receiver_public_key, 16)), 224 | int(packed_message)) 225 | 226 | stark_dtk_private_key = int(await self.recover_dtk(), 16) % EC_ORDER 227 | 228 | tx_signature = sign(msg_hash=msg_hash, priv_key=stark_dtk_private_key) 229 | return hex(tx_signature[0]), hex(tx_signature[1]) 230 | 231 | @gas_checker 232 | async def deposit_to_rhino(self, amount, source_chain_info): 233 | logger_info = f"{self.client.info} Rhino | Deposit {amount} ETH from {self.client.network.name} to Rhino.fi" 234 | self.client.logger.info(logger_info) 235 | 236 | if source_chain_info['enabled']: 237 | source_chain_address = source_chain_info['contractAddress'] 238 | 239 | tx_params = await self.client.prepare_transaction(value=int(amount * 10 ** 18)) | { 240 | 'data': "0xdb6b5246", 241 | 'to': self.client.w3.to_checksum_address(source_chain_address) 242 | } 243 | 244 | tx_hash = await self.client.send_transaction(tx_params) 245 | 246 | await self.client.verify_transaction(tx_hash) 247 | 248 | async def withdraw_from_rhino(self, rhino_user_config, amount, chain_name): 249 | 250 | while True: 251 | await asyncio.sleep(4) 252 | if int(amount * 10 ** 8) <= int(await self.get_user_balance()): 253 | self.client.logger.success(f"{self.client.info} Rhino | Funds have been received") 254 | break 255 | self.client.logger.warning(f"{self.client.info} Rhino | Wait a little, while the funds come into Rhino.fi") 256 | await asyncio.sleep(1) 257 | await sleep(self, 90, 120) 258 | 259 | logger_info = f"{self.client.info} Rhino | Withdraw {amount} ETH from Rhino.fi to {chain_name.capitalize()}" 260 | self.client.logger.info(logger_info) 261 | 262 | url = "https://api.rhino.fi/v1/trading/bridgedWithdrawals" 263 | 264 | deversifi_address = rhino_user_config["DVF"]['deversifiAddress'] 265 | receiver_vault_id, receiver_public_key = (await self.get_vault_id_and_stark_key(deversifi_address)).values() 266 | 267 | sender_public_key = rhino_user_config['starkKeyHex'] 268 | sender_vault_id = await self.get_vault_id() 269 | token_address = rhino_user_config['tokenRegistry']['ETH']['starkTokenId'] 270 | 271 | expiration_timestamp = int(time.time() / 3600) + 4320 272 | payload_nonce = random.randint(1, 2**53 - 1) 273 | tx_nonce = random.randint(1, 2**31 - 1) 274 | amount_in_wei = int(amount * 10 ** 8) 275 | 276 | r_signature, s_signature = await self.get_stark_signature(amount_in_wei, expiration_timestamp, tx_nonce, 277 | receiver_public_key,receiver_vault_id, 278 | sender_vault_id, token_address) 279 | 280 | headers = self.make_headers() 281 | 282 | payload = { 283 | "chain": chain_name, 284 | "token": "ETH", 285 | "amount": f"{amount_in_wei}", 286 | "tx": { 287 | "amount": amount_in_wei, 288 | "senderPublicKey": sender_public_key, 289 | "receiverPublicKey": receiver_public_key, 290 | "receiverVaultId": receiver_vault_id, 291 | "senderVaultId": sender_vault_id, 292 | "signature": { 293 | "r": r_signature, 294 | "s": s_signature 295 | }, 296 | "token": token_address, 297 | "type": "TransferRequest", 298 | "nonce": tx_nonce, 299 | "expirationTimestamp": expiration_timestamp 300 | }, 301 | "nonce": payload_nonce, 302 | "recipientEthAddress": self.client.address, 303 | "isBridge": False, 304 | } 305 | 306 | await self.make_request(method='POST', url=url, headers=headers, json=payload) 307 | 308 | self.client.logger.success(f"{self.client.info} Rhino | Withdraw compete") 309 | 310 | async def bridge(self, chain_from_id:int, help_okx:bool = False, help_network_id:int = 1): 311 | try: 312 | self.client.logger.info(f"{self.client.info} Rhino | Check previous registration") 313 | 314 | rhino_user_config = await self.get_user_config() 315 | 316 | if not rhino_user_config['isRegistered']: 317 | await asyncio.sleep(1) 318 | 319 | self.client.logger.info(f"{self.client.info} Rhino | New user on Rhino, make registration") 320 | await self.reg_new_acc() 321 | 322 | await asyncio.sleep(1) 323 | 324 | self.client.logger.success(f"{self.client.info} Rhino | Successfully registered") 325 | rhino_user_config = await self.get_user_config() 326 | else: 327 | await asyncio.sleep(1) 328 | 329 | self.client.logger.success(f"{self.client.info} Rhino | Already registered") 330 | 331 | await asyncio.sleep(1) 332 | 333 | chain_from_name, chain_to_name, amount = await self.client.get_bridge_data(chain_from_id, help_okx, 334 | help_network_id, 'Rhino') 335 | 336 | _, balance, _ = await self.client.get_token_balance() 337 | 338 | if amount < balance: 339 | 340 | source_chain_info = rhino_user_config['DVF']['bridgeConfigPerChain'][chain_from_name] 341 | 342 | await asyncio.sleep(1) 343 | 344 | await self.deposit_to_rhino(amount=amount, source_chain_info=source_chain_info) 345 | 346 | await asyncio.sleep(1) 347 | 348 | await self.withdraw_from_rhino(rhino_user_config=rhino_user_config, 349 | amount=amount, chain_name=chain_to_name) 350 | else: 351 | self.client.logger.error( 352 | f"{self.client.info} Rhino | Insufficient balance in {self.client.network.name}") 353 | except Exception as error: 354 | self.client.logger.error(f"{self.client.info} Rhino | Error: {error}") 355 | 356 | -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | -------------------------------------------------OKX WITHDRAW----------------------------------------------------------- 3 | Choose withdrawal | deposit network. The software works only with ETH withdraw | deposit 4 | Don't forget to insert your API_KEYs 5 | 6 | *1 - ETH-ERC20 7 | 2 - ETH-Arbitrum One 8 | *3 - ETH-zkSync Lite 9 | 4 - ETH-Optimism 10 | *5 - ETH-Starknet 11 | 6 - ETH-zkSync Era 12 | 7 - ETH-Linea 13 | 14 | * - software cant deposit to these chains (see OKX_DEPOSIT_NETWORK) 15 | 16 | OKX_WITHDRAW_NETWORK = 6 17 | OKX_AMOUNT_MIN = 0.009 ( ETH ) 18 | OKX_AMOUNT_MAX = 0.012 ( ETH ) 19 | 20 | OKX_BRIDGE_NEED = True | Here you can set the need for a bridge to withdraw money. 21 | Maybe you already have money in the withdrawal network? (IF YES = SET FALSE) 22 | OKX_DEPOSIT_NETWORK = 2 23 | OKX_DEPOSIT_AMOUNT = 90 | % of MAX TOKEN BALANCE. Will withdraw this amount from the largest token balance in SCROLL 24 | OKX_BRIDGE_MODE = [1, 2, 3] | 1 - Rhino.fi, 2 - Orbiter, 3 - LayerSwap. Select the bridges you need 25 | to transfer your funds to the network, which you choose in OKX_DEPOSIT_NETWORK. One bridge in list will be chosen. 26 | 27 | ------------------------------------------------------------------------------------------------------------------------ 28 | """ 29 | OKX_WITHDRAW_NETWORK = 2 # NETWORK ID 30 | OKX_AMOUNT_MIN = 0.014 # ETH 31 | OKX_AMOUNT_MAX = 0.014 # ETH 32 | 33 | OKX_BRIDGE_NEED = False # False or True 34 | OKX_DEPOSIT_NETWORK = 2 # NETWORK ID 35 | OKX_DEPOSIT_AMOUNT = 90 # % of MAX TOKEN BALANCE to withdraw from Scroll 36 | OKX_BRIDGE_MODE = [1] # BRIDGES 37 | 38 | """ 39 | --------------------------------------------------SCROLL BRIDGE--------------------------------------------------------- 40 | Official bridge on Scroll. Specify the minimum and maximum of deposit/withdraw in ETH 41 | You can specify the percentage in quotes and the software will use this setting as % of balance 42 | 43 | SCROLL_DEP_MIN = 0.01 ( ETH ) or "10" ( % ) 44 | SCROLL_DEP_MAX = 0.02 ( ETH ) or "20" ( % ) 45 | SCROLL_WITHDRAW_MIN = 0.02 ( ETH ) 46 | SCROLL_WITHDRAW_MAX = 0.02 ( ETH ) 47 | """ 48 | SCROLL_DEP_PERCENT = 70 # % 49 | SCROLL_DEP_MIN = 0.01 # ETH or % 50 | SCROLL_DEP_MAX = 0.02 # ETH or % 51 | SCROLL_WITHDRAW_MIN = 0.001 # ETH 52 | SCROLL_WITHDRAW_MAX = 0.002 # ETH 53 | 54 | """ 55 | ------------------------------------------------LayerSwap BRIDGE-------------------------------------------------------- 56 | Check available tokens and networks for bridge before setting values. Works only with ETH 57 | You can specify the percentage in quotes and the software will use this setting as % of balance 58 | Don't forget to insert your API_KEY 59 | 60 | Arbitrum = 1 Optimism = 7 61 | Arbitrum Nova = 2 Scroll = 8 62 | Base = 3 Polygon ZKEVM = 9 63 | Linea = 4 zkSync Era = 10 64 | Manta = 5 Zora = 11 65 | Polygon = 6 zkSync Lite = 12 66 | 67 | LAYERSWAP_CHAIN_ID_FROM(TO) = [2, 4, 16] | One network in list will be chosen 68 | LAYERSWAP_REFUEL means to add a little amount of native tokens to destination chain 69 | """ 70 | LAYERSWAP_CHAIN_ID_FROM = [2, 10] # BRIDGE FROM 71 | LAYERSWAP_CHAIN_ID_TO = [11] # BRIDGE TO 72 | LAYERSWAP_AMOUNT_MIN = 0.018 # ETH or % 73 | LAYERSWAP_AMOUNT_MAX = 0.019 # ETH or % 74 | LAYERSWAP_REFUEL = False # True or False 75 | 76 | """ 77 | ------------------------------------------------ORBITER BRIDGE---------------------------------------------------------- 78 | Check available tokens and networks for bridge before setting values. Works only with ETH 79 | You can specify the percentage in quotes and the software will use this setting as % of balance 80 | 81 | Arbitrum = 1 Optimism = 7 82 | Arbitrum Nova = 2 Scroll = 8 83 | Base = 3 Polygon ZKEVM = 9 84 | Linea = 4 zkSync Era = 10 85 | Manta = 5 Zora = 11 86 | Polygon = 6 zkSync Lite = 12 87 | 88 | ORBITER_CHAIN_ID_FROM(TO) = [2, 4, 11] | One network in list will be chosen 89 | """ 90 | ORBITER_CHAIN_ID_FROM = [2, 10, 11] # BRIDGE FROM 91 | ORBITER_CHAIN_ID_TO = [5] # BRIDGE TO 92 | ORBITER_AMOUNT_MIN = 0.009 # ETH or % 93 | ORBITER_AMOUNT_MAX = 0.012 # ETH or % 94 | 95 | """ 96 | ------------------------------------------------RHINO BRIDGE---------------------------------------------------------- 97 | Check networks for bridge before setting values. Works only with ETH. 98 | You can specify the percentage in quotes and the software will use this setting as % of balance 99 | 100 | Arbitrum = 1 *Polygon = 6 101 | Arbitrum Nova = 2 Optimism = 7 102 | Base = 3 Scroll = 8 103 | Linea = 4 Polygon ZKEVM = 9 104 | Manta = 5 zkSync Era = 10 105 | 106 | * - Not support in RHINO_CHAIN_ID_FROM 107 | RHINO_CHAIN_ID_FROM(TO) = [2, 3, 10] | One network in list will be chosen 108 | """ 109 | RHINO_CHAIN_ID_FROM = [8] # BRIDGE FROM 110 | RHINO_CHAIN_ID_TO = [1] # BRIDGE TO 111 | RHINO_AMOUNT_MIN = "10" # ETH or % 112 | RHINO_AMOUNT_MAX = "20" # ETH or % 113 | 114 | 115 | """ 116 | ----------------------------------------------AMOUNT CONTROL------------------------------------------------------------ 117 | Exchange of all tokens(include LP tokens), except ETH, is 100% of the balance 118 | You specify how much ETH in % will be exchanged for the tokens. 119 | OKX_DEPOSIT USE THIS AMOUNT. 120 | 121 | MIN_BALANCE set the amount of ETH on the account balance, enabling the software working. 122 | 123 | AMOUNT_MIN = 70 ( % ) of token balance 124 | AMOUNT_MAX = 80 ( % ) of token balance 125 | MIN_BALANCE = 0.001 ( ETH ) 126 | """ 127 | AMOUNT_MIN = 50 # % 128 | AMOUNT_MAX = 60 # % 129 | MIN_BALANCE = 0.001 # ETH 130 | 131 | """ 132 | --------------------------------------------ADD LIQUIDITY TO DEX-------------------------------------------------------- 133 | DEX include SyncSwap 134 | Liquidity is added only to interact with the new contract 135 | 136 | DEX_LP_MIN = 0.0005 ( ETH ) 137 | DEX_LP_MAX = 0.0015 ( ETH ) 138 | WITHDRAW_LP = Fasle # Perform withdrawal after deposit or not? 139 | """ 140 | DEX_LP_MIN = 0.0005 # ETH 141 | DEX_LP_MAX = 0.001 # ETH 142 | WITHDRAW_LP = False # True or False 143 | 144 | """ 145 | ---------------------------------------------LayerZero CONTROL---------------------------------------------------------- 146 | Check website for working destination networks and min/max amount before setting values 147 | One network in list will be chosen 148 | 149 | *Arbitrum = 1 Kava = 15 150 | Astar = 2 Klaytn = 16 151 | Aurora = 3 *Linea = 17 152 | Avalanche = 4 Meter = 18 153 | *Base = 5 Metis = 19 154 | BNB chain = 6 Moonbeam = 20 155 | Canto = 7 Moonriver = 21 156 | Celo = 8 *Arbitrum Nova = 22 157 | Core = 9 OpBNB = 23 158 | Ethereum = 10 *Optimism = 24 159 | Fantom = 11 *Polygon = 25 160 | Fuse = 12 *Polygon ZKEVM = 26 161 | Gnosis = 13 *Scroll = 27 162 | Harmony = 14 Tenet = 28 163 | *zkSync Era = 29 164 | 165 | SOURCE_CHAIN_ZERIUS = [27, 29] | One network in list will be chosen (BRIDGE NFT) 166 | SOURCE_CHAIN_MERKLY = [27, 29] | One network in list will be chosen (REFUEL) 167 | DESTINATION_MERKLY_AMOUNT = { 168 | 1: (0.0016, 0.002), # Chain ID: (min amount, max amount) in destination native** 169 | 2: (0.0002, 0.0005) # Chain ID: (min amount, max amount) in destination native** 170 | } 171 | 172 | * - Сan be used as a source network 173 | ** - Amount for merkly needs to be given in the native token of destination network. And also decrease the maximum 174 | amount by 5-10% to avoid errors. You can see maximum amount to refuel on https://minter.merkly.com/gas 175 | """ 176 | SOURCE_CHAIN_ZERIUS = [27, 29] # BRIDGE FROM 177 | DESTINATION_ZERIUS = [1, 4, 8] 178 | 179 | SOURCE_CHAIN_MERKLY = [1] # REFUEL FROM 180 | DESTINATION_MERKLY_DATA = { 181 | 27: (0.0003, 0.0004), # Chain ID: (min amount , max amount) in destination native 182 | 28: (0.04, 0.05) # Chain ID: (min amount, max amount) in destination native 183 | } 184 | 185 | """ 186 | ------------------------------------------------TRANSFERS CONTROL------------------------------------------------------- 187 | Specify min/max ETH amount to send for random wallets 188 | 189 | TRANSFER_MIN = 0.00001 ( ETH ) 190 | TRANSFER_MAX = 0.00005 ( ETH ) 191 | """ 192 | TRANSFER_MIN = 0.00001 # ETH 193 | TRANSFER_MAX = 0.00005 # ETH 194 | 195 | """ 196 | ------------------------------------------------GENERAL SETTINGS-------------------------------------------------------- 197 | 198 | TX_COUNT = [70, 80] | [min, max] number of transactions needed 199 | 200 | MAX_UNQ_CONTACT = False | enable/disable maximum number of unique contracts. 201 | (The software will run each module at least once, if the number of needed transactions allows it) 202 | 203 | SOFTWARE_MODE = 0 | this setting is used to set the mode of the software. 204 | 1 - Parallel Mode (can`t deposit to OKX). A group of wallets works at the same time. 205 | 0 - Consistently Mode (good with deposit and withdraw on OKX). Only 1 account work at the same time. 206 | 207 | WALLETS_TO_WORK = 0 | if the value differs from zero, the software works only with the specified wallets. 208 | 0 = all wallets will work 209 | 3 = only wallet #3 will work 210 | 4, 20 = wallets #4 and #20 will work 211 | [5, 25] = wallets from 5 to 25 will work 212 | 213 | GAS_MULTIPLIER = 1.1 | multiply gas limit by this value 214 | UNLIMITED_APPROVE = False | unlimited approve for spender contract (2**256-1 of needed tokens) 215 | """ 216 | SOFTWARE_MODE = 1 # 0 / 1 217 | WALLETS_TO_WORK = 0 # 0 / 4, 20 / [5, 25] 218 | TX_COUNT = [70, 80] # [min, max] will be chosen at random between 219 | MAX_UNQ_CONTACTS = True # True or False 220 | 221 | '-------------------------------------------------GAS CONTROL----------------------------------------------------------' 222 | GAS_CONTROL = False # True or False 223 | MAXIMUM_GWEI = 100 # Gwei 224 | SLEEP_TIME_GAS = 120 # Second 225 | GAS_MULTIPLIER = 1.1 # Coefficient 226 | 227 | '------------------------------------------------RETRY CONTROL---------------------------------------------------------' 228 | MAXIMUM_RETRY = 1 # Times 229 | SLEEP_TIME_RETRY = 5 # Second 230 | 231 | '------------------------------------------------PROXY CONTROL---------------------------------------------------------' 232 | USE_PROXY = False # True or False 233 | 234 | '-----------------------------------------------APPROVE CONTROL--------------------------------------------------------' 235 | UNLIMITED_APPROVE = False # True or False 236 | 237 | '-----------------------------------------------SLIPPAGE CONTROL-------------------------------------------------------' 238 | SLIPPAGE_PERCENT = 0.987 # 0.5 = 0.5%, 1 = 1%, 2 = 2% 239 | 240 | '------------------------------------------------SLEEP CONTROL---------------------------------------------------------' 241 | SLEEP_MODE = False # True or False 242 | MIN_SLEEP = 60 # Second 243 | MAX_SLEEP = 120 # Second 244 | 245 | '--------------------------------------------------API KEYS------------------------------------------------------------' 246 | # OKX API KEYS https://www.okx.com/ru/account/my-api 247 | OKX_API_KEY = "" 248 | OKX_API_SECRET = "" 249 | OKX_API_PASSPHRAS = "" 250 | 251 | # LAYERSWAP API KEY https://www.layerswap.io/dashboard 252 | LAYERSWAP_API_KEY = "" 253 | 254 | """ 255 | ----------------------------------------------AUTO-ROUTES CONTROL------------------------------------------------------- 256 | Select the required modules to interact with 257 | These settings will be used to generate a random auto-route if you enabled it 258 | 259 | AUTO_ROUTES_MODULES_USING = { 260 | okx_withdraw : 1, | ( module name ) : ( 1 - enable, 0 - disable ) 261 | bridge_layerswap : 0, | ( module name ) : ( 1 - enable, 0 - disable ) 262 | bridge_rhino : 1, | ( module name ) : ( 1 - enable, 0 - disable ) 263 | etc... 264 | 265 | """ 266 | 267 | AUTO_ROUTES_MODULES_USING = { 268 | 'okx_withdraw' : 1, # check OKX settings 269 | 'bridge_layerswap' : 1, # check LayerSwap settings 270 | 'bridge_rhino' : 1, # check Rhino settings 271 | 'bridge_scroll' : 1, # check Scroll settings 272 | 'bridge_orbiter' : 1, # check Orbiter settings 273 | 'add_liquidity_syncswap' : 1, # USDC/WETH LP 274 | 'deposit_layerbank' : 1, # including withdraw 275 | 'enable_collateral_layerbank' : 1, # including disable 276 | 'swap_izumi' : 1, 277 | 'swap_openocean' : 1, 278 | 'swap_syncswap' : 1, 279 | 'swap_scrollswap' : 1, 280 | 'deploy_contract' : 1, # deploy your own contract 281 | 'wrap_eth' : 1, # including unwrap 282 | 'create_omnisea' : 1, # create new NFT collection 283 | 'create_safe' : 1, # create safe on chain 284 | 'mint_zerius' : 1, # mint non-free NFT 285 | 'bridge_zerius' : 1, # bridge last NFT 286 | 'refuel_merkly' : 1, # see LayerZero settings 287 | 'send_message_dmail' : 1, 288 | 'transfer_eth' : 1, # transfer ETH to random address 289 | 'transfer_eth_to_myself' : 1, 290 | 'withdraw_scroll' : 1, # check Scroll settings 291 | 'okx_deposit' : 1, # check OKX settings 292 | 'okx_collect_from_sub' : 1 # collect your funds from subAccounts to main 293 | } 294 | 295 | """ 296 | --------------------------------------------CLASSIC-ROUTES CONTROL------------------------------------------------------ 297 | Select the required modules to interact with 298 | Here you can create your own route. For each step the software will select one module from the list. 299 | You can set None to skip a module during the route. 300 | See the AUTO-ROUTES settings for list of current modules. 301 | 302 | CLASSIC_ROUTES_MODULES_USING = [ 303 | ['okx_withdraw'], 304 | ['bridge_layerswap', 'bridge_scroll', None], 305 | ['swap_scrollswap', 'swap_izumi'], 306 | ... 307 | ] 308 | """ 309 | CLASSIC_ROUTES_MODULES_USING = [ 310 | ['refuel_merkly'], 311 | ['deploy_contract'], 312 | ['bridge_layerswap', 'bridge_scroll', 'bridge_orbiter'], 313 | ['enable_collateral_layerbank', 'deposit_layerbank', None], 314 | ['swap_izumi', 'swap_openocean'], 315 | ['refuel_merkly', 'swap_syncswap'], 316 | ['wrap_eth', 'swap_izumi'], 317 | ['swap_scrollswap'], 318 | ['withdraw_scroll'], 319 | ['okx_deposit'] 320 | ] 321 | 322 | 323 | -------------------------------------------------------------------------------- /modules/client.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import random 3 | import aiohttp 4 | 5 | from sys import stderr 6 | from loguru import logger 7 | from asyncio import sleep 8 | from hexbytes import HexBytes 9 | from utils.networks import Network 10 | from config import ERC20_ABI, SCROLL_TOKENS 11 | from web3 import AsyncHTTPProvider, AsyncWeb3 12 | from config import RHINO_CHAIN_INFO, ORBITER_CHAINS_INFO, LAYERSWAP_CHAIN_NAME 13 | from settings import ( 14 | GAS_MULTIPLIER, 15 | UNLIMITED_APPROVE, 16 | AMOUNT_MIN, 17 | AMOUNT_MAX, 18 | MIN_BALANCE, 19 | DEX_LP_MAX, 20 | DEX_LP_MIN, 21 | OKX_BRIDGE_MODE, 22 | OKX_DEPOSIT_AMOUNT, 23 | LAYERSWAP_AMOUNT_MIN, 24 | LAYERSWAP_AMOUNT_MAX, 25 | LAYERSWAP_CHAIN_ID_TO, 26 | LAYERSWAP_REFUEL, 27 | ORBITER_AMOUNT_MIN, 28 | ORBITER_AMOUNT_MAX, 29 | ORBITER_CHAIN_ID_TO, 30 | RHINO_AMOUNT_MAX, 31 | RHINO_AMOUNT_MIN, 32 | RHINO_CHAIN_ID_TO 33 | ) 34 | 35 | 36 | class Client: 37 | def __init__(self, account_number: int, private_key: str, network: Network, proxy: None | str = None): 38 | self.network = network 39 | self.eip1559_support = network.eip1559_support 40 | self.token = network.token 41 | self.explorer = network.explorer 42 | self.chain_id = network.chain_id 43 | 44 | self.proxy = f"http://{proxy}" if proxy else "" 45 | self.proxy_init = proxy 46 | self.request_kwargs = {"proxy": f"http://{proxy}"} if proxy else {} 47 | self.w3 = AsyncWeb3(AsyncHTTPProvider(random.choice(network.rpc), request_kwargs=self.request_kwargs)) 48 | self.account_number = account_number 49 | self.private_key = private_key 50 | self.address = AsyncWeb3.to_checksum_address(self.w3.eth.account.from_key(private_key).address) 51 | 52 | self.min_amount_eth_on_balance = MIN_BALANCE 53 | self.logger = logger 54 | self.info = f'[{self.account_number}] {self.address[:10]}....{self.address[-6:]} |' 55 | self.logger.remove() 56 | logger_format = "{time:HH:mm:ss} | " "{level: <8} | {message}" 57 | self.logger.add(stderr, format=logger_format) 58 | 59 | @staticmethod 60 | def round_amount(min_amount: float, max_amount:float) -> float: 61 | decimals = max(len(str(min_amount)) - 1, len(str(max_amount)) - 1) 62 | return round(random.uniform(min_amount, max_amount), decimals) 63 | 64 | async def bridge_from_source(self, network_to_id) -> None: 65 | from functions import bridge_layerswap, bridge_rhino, bridge_orbiter 66 | 67 | self.logger.info(f"{self.info} {self.__class__.__name__} | Bridge balance from {self.network.name}") 68 | 69 | id_of_bridge = { 70 | 1: bridge_rhino, 71 | 2: bridge_orbiter, 72 | 3: bridge_layerswap 73 | } 74 | 75 | bridge_id = random.choice(OKX_BRIDGE_MODE) 76 | 77 | func = id_of_bridge[bridge_id] 78 | 79 | await asyncio.sleep(1) 80 | await func(self.account_number, self.private_key, self.network, self.proxy_init, 81 | help_okx=True, help_network_id=network_to_id) 82 | 83 | async def get_bridge_data(self, chain_from_id:int, help_okx:bool, help_network_id: int, module_name:str): 84 | if module_name == 'Rhino': 85 | chain_from_name = RHINO_CHAIN_INFO[chain_from_id] 86 | chain_to_name = RHINO_CHAIN_INFO[random.choice(RHINO_CHAIN_ID_TO)] 87 | 88 | if isinstance(RHINO_AMOUNT_MIN, str): 89 | _, amount, _ = await self.get_token_balance() 90 | 91 | percent = round(random.uniform(int(RHINO_AMOUNT_MIN), int(RHINO_AMOUNT_MAX))) / 100 92 | amount = round(amount * percent, 6) 93 | else: 94 | amount = self.round_amount(RHINO_AMOUNT_MIN, RHINO_AMOUNT_MAX) 95 | 96 | if help_okx: 97 | chain_from_name = RHINO_CHAIN_INFO[8] 98 | chain_to_name = RHINO_CHAIN_INFO[help_network_id] 99 | amount, _ = await self.check_and_get_eth_for_deposit(OKX_DEPOSIT_AMOUNT) 100 | 101 | return chain_from_name, chain_to_name, amount 102 | 103 | elif module_name == 'LayerSwap': 104 | source_chain = LAYERSWAP_CHAIN_NAME[chain_from_id] 105 | destination_chain = LAYERSWAP_CHAIN_NAME[random.choice(LAYERSWAP_CHAIN_ID_TO)] 106 | source_asset, destination_asset = 'ETH', 'ETH' 107 | refuel = LAYERSWAP_REFUEL 108 | 109 | if isinstance(LAYERSWAP_AMOUNT_MIN, str): 110 | _, amount, _ = await self.get_token_balance() 111 | percent = round(random.uniform(int(LAYERSWAP_AMOUNT_MIN), int(LAYERSWAP_AMOUNT_MAX))) / 100 112 | amount = round(amount * percent, 6) 113 | else: 114 | amount = self.round_amount(LAYERSWAP_AMOUNT_MIN, LAYERSWAP_AMOUNT_MAX) 115 | 116 | if help_okx: 117 | source_chain = LAYERSWAP_CHAIN_NAME[8] 118 | destination_chain = LAYERSWAP_CHAIN_NAME[help_network_id] 119 | amount, _ = await self.check_and_get_eth_for_deposit(OKX_DEPOSIT_AMOUNT) 120 | 121 | return source_chain, destination_chain, source_asset, destination_asset, amount, refuel 122 | 123 | elif module_name == 'Orbiter': 124 | from_chain = ORBITER_CHAINS_INFO[chain_from_id] 125 | to_chain = ORBITER_CHAINS_INFO[random.choice(ORBITER_CHAIN_ID_TO)] 126 | token_name = 'ETH' 127 | 128 | if isinstance(ORBITER_AMOUNT_MIN, str): 129 | _, amount, _ = await self.get_token_balance() 130 | percent = round(random.uniform(int(ORBITER_AMOUNT_MIN), int(ORBITER_AMOUNT_MAX))) / 100 131 | amount = round(amount * percent, 6) 132 | else: 133 | amount = self.round_amount(ORBITER_AMOUNT_MIN, ORBITER_AMOUNT_MAX) 134 | 135 | if help_okx: 136 | from_chain = ORBITER_CHAINS_INFO[8] 137 | to_chain = ORBITER_CHAINS_INFO[help_network_id] 138 | amount, _ = await self.check_and_get_eth_for_deposit(OKX_DEPOSIT_AMOUNT) 139 | 140 | return from_chain, to_chain, token_name, amount 141 | 142 | async def check_and_get_eth_for_deposit(self, okx_percent: int = 0) -> [float, int]: 143 | from functions import swap_syncswap 144 | data = await self.get_auto_amount(token_name_search='ETH') 145 | 146 | if data is False: 147 | self.logger.warning(f'{self.info} {self.__class__.__name__} | Not enough ETH! Launching swap module') 148 | 149 | await asyncio.sleep(1) 150 | await swap_syncswap(self.account_number, self.private_key, self.network, self.proxy_init, help_deposit=True) 151 | 152 | percent = round(random.uniform(AMOUNT_MIN, AMOUNT_MAX)) / 100 153 | balance_in_wei, balance, _ = await self.get_token_balance() 154 | if okx_percent: 155 | percent = okx_percent 156 | amount = round(balance * percent, 7) 157 | amount_in_wei = int(balance_in_wei * percent) 158 | else: 159 | _, _, amount, amount_in_wei = data 160 | 161 | return amount, amount_in_wei 162 | 163 | async def check_and_get_eth_for_liquidity(self) -> [float, int]: 164 | from functions import swap_openocean 165 | class_name = self.__class__.__name__ 166 | 167 | eth_balance_in_wei, eth_balance, _ = await self.get_token_balance('ETH') 168 | amount_from_settings = self.round_amount(DEX_LP_MIN, DEX_LP_MAX) 169 | amount_from_settings_in_wei = int(amount_from_settings * 10 ** 18) 170 | 171 | await asyncio.sleep(1) 172 | if eth_balance < amount_from_settings: 173 | self.logger.warning(f'{self.info} {class_name} | Not enough ETH to add liquidity! Launching swap module') 174 | await asyncio.sleep(1) 175 | await swap_openocean(self.account_number, self.private_key, self.network, self.proxy_init, 176 | help_add_liquidity=True, amount_to_help=amount_from_settings) 177 | 178 | return amount_from_settings, amount_from_settings_in_wei 179 | 180 | async def get_auto_amount(self, token_name_search:str = None) -> [str, float, int]: 181 | 182 | wallet_balance = {k: await self.get_token_balance(k, False) for k, v in SCROLL_TOKENS.items()} 183 | valid_wallet_balance = {k: v[1] for k, v in wallet_balance.items() if v[0] != 0} 184 | eth_price = await self.get_token_price('ethereum') 185 | 186 | if 'ETH' in valid_wallet_balance: 187 | valid_wallet_balance['ETH'] = valid_wallet_balance['ETH'] * eth_price 188 | 189 | if sum(valid_wallet_balance.values()) > self.min_amount_eth_on_balance * eth_price: 190 | 191 | valid_wallet_balance = {k: round(v, 7) for k, v in valid_wallet_balance.items()} 192 | 193 | from_token_name = max(valid_wallet_balance, key=lambda x: valid_wallet_balance[x]) 194 | 195 | if token_name_search == 'ETH' and from_token_name != 'ETH': 196 | return False 197 | 198 | amount_from_token_on_balance_in_wei = wallet_balance[from_token_name][0] 199 | amount_from_token_on_balance = wallet_balance[from_token_name][1] 200 | 201 | token_names_list = list(filter(lambda token_name: token_name != from_token_name, SCROLL_TOKENS.keys())) 202 | token_names_list.remove('WETH') 203 | 204 | # if self.__class__.__name__ in ['OpenOcean']: 205 | # if 'BUSD' in token_names_list: 206 | # token_names_list.remove('BUSD') 207 | 208 | to_token_name = random.choice(token_names_list) 209 | 210 | if from_token_name == 'ETH': 211 | percent = round(random.uniform(AMOUNT_MIN, AMOUNT_MAX)) / 100 212 | else: 213 | percent = 1 214 | 215 | amount = round(amount_from_token_on_balance * percent, 7) 216 | amount_in_wei = round(amount_from_token_on_balance_in_wei * percent) 217 | 218 | return from_token_name, to_token_name, amount, amount_in_wei 219 | 220 | else: 221 | raise RuntimeError('Insufficient balance on account!') 222 | 223 | async def get_token_balance(self, token_name: str = 'ETH', check_symbol: bool = True) -> [float, int, str]: 224 | if token_name != 'ETH': 225 | contract = self.get_contract(SCROLL_TOKENS[token_name]) 226 | 227 | amount_in_wei = await contract.functions.balanceOf(self.address).call() 228 | decimals = await contract.functions.decimals().call() 229 | 230 | if check_symbol: 231 | symbol = await contract.functions.symbol().call() 232 | return amount_in_wei, amount_in_wei / 10 ** decimals, symbol 233 | return amount_in_wei, amount_in_wei / 10 ** decimals, '' 234 | 235 | amount_in_wei = await self.w3.eth.get_balance(self.address) 236 | return amount_in_wei, amount_in_wei / 10 ** 18, 'ETH' 237 | 238 | def get_contract(self, contract_address: str, abi=ERC20_ABI): 239 | return self.w3.eth.contract( 240 | address=AsyncWeb3.to_checksum_address(contract_address), 241 | abi=abi 242 | ) 243 | 244 | async def get_allowance(self, token_address: str, spender_address: str) -> int: 245 | contract = self.get_contract(token_address) 246 | return await contract.functions.allowance( 247 | self.address, 248 | spender_address 249 | ).call() 250 | 251 | async def prepare_transaction(self, value: int = 0): 252 | try: 253 | tx_params = { 254 | 'from': self.w3.to_checksum_address(self.address), 255 | 'nonce': await self.w3.eth.get_transaction_count(self.address), 256 | 'value': value, 257 | 'chainId': self.network.chain_id 258 | } 259 | 260 | if self.network.eip1559_support: 261 | 262 | max_priority_fee_per_gas = 0 263 | base_fee = await self.w3.eth.gas_price 264 | max_fee_per_gas = base_fee + max_priority_fee_per_gas 265 | 266 | tx_params['maxPriorityFeePerGas'] = max_priority_fee_per_gas 267 | tx_params['maxFeePerGas'] = max_fee_per_gas 268 | else: 269 | tx_params['gasPrice'] = await self.w3.eth.gas_price 270 | 271 | return tx_params 272 | except Exception as error: 273 | raise RuntimeError(f'Prepare transaction | Error: {error}') 274 | 275 | async def make_approve(self, token_address: str, spender_address: str, amount_in_wei: int): 276 | transaction = await self.get_contract(token_address).functions.approve( 277 | spender_address, 278 | amount=2 ** 256 - 1 if UNLIMITED_APPROVE else amount_in_wei 279 | ).build_transaction(await self.prepare_transaction()) 280 | 281 | return await self.send_transaction(transaction) 282 | 283 | async def check_for_approved(self, token_address: str, spender_address: str, amount_in_wei: int) -> bool: 284 | try: 285 | class_name = self.__class__.__name__ 286 | contract = self.get_contract(token_address) 287 | 288 | balance_in_wei = await contract.functions.balanceOf(self.address).call() 289 | symbol = await contract.functions.symbol().call() 290 | 291 | await asyncio.sleep(1) 292 | 293 | self.logger.info(f'{self.info} {class_name} Check approval {symbol} for spending by {class_name}') 294 | 295 | await asyncio.sleep(1) 296 | 297 | if balance_in_wei <= 0: 298 | self.logger.info(f'{self.info} {class_name} | Zero balance') 299 | return False 300 | 301 | approved_amount_in_wei = await self.get_allowance( 302 | token_address=token_address, 303 | spender_address=spender_address 304 | ) 305 | await asyncio.sleep(1) 306 | 307 | if amount_in_wei <= approved_amount_in_wei: 308 | self.logger.info(f'{self.info} Already approved') 309 | return False 310 | 311 | tx_hash = await self.make_approve( 312 | token_address, 313 | spender_address, 314 | amount_in_wei, 315 | ) 316 | 317 | await self.verify_transaction(tx_hash) 318 | 319 | await sleep(random.randint(5, 9)) 320 | except Exception as error: 321 | raise RuntimeError(f'Check for approve | {error}') 322 | 323 | async def send_transaction(self, transaction): 324 | try: 325 | transaction['gas'] = int((await self.w3.eth.estimate_gas(transaction)) * GAS_MULTIPLIER) 326 | except Exception as error: 327 | if 'message' in error.args[0]: 328 | error = error.args[0]['message'] 329 | raise RuntimeError(f'Gas calculating | {error}') 330 | 331 | try: 332 | singed_tx = self.w3.eth.account.sign_transaction(transaction, self.private_key) 333 | 334 | return await self.w3.eth.send_raw_transaction(singed_tx.rawTransaction) 335 | except Exception as error: 336 | raise RuntimeError(f'Send transaction | {error.args[0]["message"]}') 337 | 338 | async def verify_transaction(self, tx_hash: HexBytes): 339 | try: 340 | data = await self.w3.eth.wait_for_transaction_receipt(tx_hash, timeout=360) 341 | if 'status' in data and data['status'] == 1: 342 | self.logger.success(f'{self.info} {self.__class__.__name__} |' 343 | f' Transaction was successful: {self.explorer}tx/{tx_hash.hex()}') 344 | else: 345 | raise RuntimeError(f'Transaction failed: {self.explorer}tx/{data["transactionHash"].hex()}') 346 | except Exception as error: 347 | raise RuntimeError(f'Verify transaction | {error}') 348 | 349 | async def get_token_price(self, token_name: str, vs_currency:str = 'usd') -> float: 350 | 351 | url = 'https://api.coingecko.com/api/v3/simple/price' 352 | 353 | params = {'ids': f'{token_name}', 'vs_currencies': f'{vs_currency}'} 354 | 355 | async with aiohttp.ClientSession() as session: 356 | async with session.get(url, params=params, proxy=self.proxy) as response: 357 | if response.status == 200: 358 | data = await response.json() 359 | return float(data[token_name][vs_currency]) 360 | raise RuntimeError(f'Bad request to CoinGecko API: {response.status}') 361 | --------------------------------------------------------------------------------