diff --git a/README.md b/README.md index d318c0e..991721e 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ cd scroll pip install -r requirements.txt -~/scroll$ python run_swaps.py --wallets WALLET_KEY1 WALLET_KEY2 WALLET_KEYN +~/scroll$ python run_swaps.py --wallet_file json file that contains keys ``` ---

🚨 Features

diff --git a/README_refactor.md b/README_refactor.md new file mode 100644 index 0000000..dd97738 --- /dev/null +++ b/README_refactor.md @@ -0,0 +1,37 @@ +## Description + +This pull request introduces significant enhancements to the existing functionality, enabling automated trades across multiple websites using a single Python command. The key features include: + +Unified Command: Users can now execute automated trades across multiple websites with a single Python command. + +Support for Multiple Wallets: The system now accepts one or multiple wallet private keys for performing swaps, offering enhanced flexibility to users. + +Dynamic Randomization Schedule: Trades are executed using a dynamically randomizing schedule, contributing to improved security and efficiency. + +Configurability: Command-line arguments have been exposed to facilitate easy configuration, allowing users to customize randomization schedules, swap amounts, and tokens. + +Cycle Functionality: The system automatically cycles between swapping from ETH to USDC and back, optimizing trading processes. + +## Changes Made +Implemented run_swaps.py to enable executing automated trades across multiple websites. + +Added support for accepting one or multiple wallet private keys for performing swaps. + +Integrated dynamic randomization schedule for executing trades and recycling wallets. + +Exposed command-line arguments for configuring randomization schedules, swap amounts, and tokens. + +Implemented automatic cycling between swapping from ETH to USDC and back for optimized trading. + +## Usage +python run_swaps.py --wallets "WALLET_KEY1 WALLET_KEY2 ... WALLET_KEYN" +or +python run_swaps.py --wallet_file + +## Testing +Manual testing has been conducted to validate the behavior across different machines all using one wallet. + +## Additional Notes +Documentation has been updated to reflect the changes and provide guidance on usage. + +All new code adheres to best practices. diff --git a/domain/config.py b/domain/config.py index 9ee6aa1..7687931 100644 --- a/domain/config.py +++ b/domain/config.py @@ -1,72 +1,74 @@ import json +import os +base_dir = os.path.dirname(os.path.abspath(__file__)) -with open('domain/data/rpc.json') as file: +with open(os.path.join(base_dir, 'data/rpc.json')) as file: RPC = json.load(file) -with open('domain/data/abi/erc20_abi.json') as file: +with open(os.path.join(base_dir, 'data/abi/erc20_abi.json')) as file: ERC20_ABI = json.load(file) -with open('domain/data/abi/bridge/deposit.json') as file: +with open(os.path.join(base_dir, 'data/abi/bridge/deposit.json')) as file: DEPOSIT_ABI = json.load(file) -with open('domain/data/abi/bridge/withdraw.json') as file: +with open(os.path.join(base_dir, 'data/abi/bridge/withdraw.json')) as file: WITHDRAW_ABI = json.load(file) -with open('domain/data/abi/bridge/oracle.json') as file: +with open(os.path.join(base_dir, 'data/abi/bridge/oracle.json')) as file: ORACLE_ABI = json.load(file) -with open('domain/data/abi/scroll/weth.json') as file: +with open(os.path.join(base_dir, 'data/abi/scroll/weth.json')) as file: WETH_ABI = json.load(file) -with open("domain/data/abi/syncswap/router.json", "r") as file: +with open(os.path.join(base_dir, 'data/abi/syncswap/router.json'), "r") as file: SYNCSWAP_ROUTER_ABI = json.load(file) -with open('domain/data/abi/syncswap/classic_pool.json') as file: +with open(os.path.join(base_dir, 'data/abi/syncswap/classic_pool.json')) as file: SYNCSWAP_CLASSIC_POOL_ABI = json.load(file) -with open('domain/data/abi/syncswap/classic_pool_data.json') as file: +with open(os.path.join(base_dir, 'data/abi/syncswap/classic_pool_data.json')) as file: SYNCSWAP_CLASSIC_POOL_DATA_ABI = json.load(file) -with open("domain/data/abi/skydrome/abi.json", "r") as file: +with open(os.path.join(base_dir, 'data/abi/skydrome/abi.json'), "r") as file: SKYDROME_ROUTER_ABI = json.load(file) -with open("domain/data/abi/zebra/abi.json", "r") as file: +with open(os.path.join(base_dir, 'data/abi/zebra/abi.json'), "r") as file: ZEBRA_ROUTER_ABI = json.load(file) -with open("domain/data/abi/aave/abi.json", "r") as file: +with open(os.path.join(base_dir, 'data/abi/aave/abi.json'), "r") as file: AAVE_ABI = json.load(file) -with open("domain/data/abi/layerbank/abi.json", "r") as file: +with open(os.path.join(base_dir, 'data/abi/layerbank/abi.json'), "r") as file: LAYERBANK_ABI = json.load(file) -with open("domain/data/abi/zerius/abi.json", "r") as file: +with open(os.path.join(base_dir, 'data/abi/zerius/abi.json'), "r") as file: ZERIUS_ABI = json.load(file) -with open("domain/data/abi/l2pass/abi.json", "r") as file: +with open(os.path.join(base_dir, 'data/abi/l2pass/abi.json'), "r") as file: L2PASS_ABI = json.load(file) -with open("domain/data/abi/dmail/abi.json", "r") as file: +with open(os.path.join(base_dir, 'data/abi/dmail/abi.json'), "r") as file: DMAIL_ABI = json.load(file) -with open("domain/data/abi/omnisea/abi.json", "r") as file: +with open(os.path.join(base_dir, 'data/abi/omnisea/abi.json'), "r") as file: OMNISEA_ABI = json.load(file) -with open("domain/data/abi/nft2me/abi.json", "r") as file: +with open(os.path.join(base_dir, 'data/abi/nft2me/abi.json'), "r") as file: NFTS2ME_ABI = json.load(file) -with open("domain/data/abi/gnosis/abi.json", "r") as file: +with open(os.path.join(base_dir, 'data/abi/gnosis/abi.json'), "r") as file: SAFE_ABI = json.load(file) -with open("domain/data/deploy/abi.json", "r") as file: +with open(os.path.join(base_dir, 'data/deploy/abi.json'), "r") as file: DEPLOYER_ABI = json.load(file) -with open("domain/data/deploy/bytecode.txt", "r") as file: +with open(os.path.join(base_dir, 'data/deploy/bytecode.txt'), "r") as file: DEPLOYER_BYTECODE = file.read() -with open("domain/data/abi/zkstars/abi.json", "r") as file: +with open(os.path.join(base_dir, 'data/abi/zkstars/abi.json'), "r") as file: ZKSTARS_ABI = json.load(file) -with open("domain/data/abi/rubyscore/abi.json", "r") as file: +with open(os.path.join(base_dir, 'data/abi/rubyscore/abi.json'), "r") as file: RUBYSCORE_VOTE_ABI = json.load(file) ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" diff --git a/domain/main.py b/domain/main.py index 7a31708..2bfed96 100644 --- a/domain/main.py +++ b/domain/main.py @@ -2,16 +2,17 @@ import time from typing import Union import asyncio - +from eth_account import Account as EthereumAccount from loguru import logger - +from .modules_settings import handle_app_expiration +from .settings import managingEnvironment async def run_module(module, wallet_number, key, recipient: Union[str, None] = None, settings: dict = {}): try: await module( - wallet_number, - key, - recipient, + wallet_number, + key, + recipient, from_token=settings.get('from_token'), to_token=settings.get('to_token'), min_amount=settings.get('min_amount'), @@ -30,51 +31,122 @@ def _async_run_module(module, wallet_number, key, recipient, settings): asyncio.run(run_module(module, wallet_number, key, recipient, settings)) + def main( websites, wallets, - website_settings, + website_settings, wait_between_wallets_max=30, wait_between_wallets_min=20, wait_between_websites_max=20, wait_between_websites_min=5, wait_between_cycles_max=((12*60*60)+90), - wait_between_cycles_min=((12*60*60)+5), + wait_between_cycles_min=((12*60*60)+5), ): - + is_expired = handle_app_expiration() + if is_expired: + return + + + while True: + + logger.info(f"Selected wallets: {len(wallets)}") # iterate through the wallets for _, wallet_key in enumerate(wallets, start=1): # website transactions to perform at each website - # iterate through websites - for tuple in zip(websites, website_settings): - logger.info(f"Running module {tuple[0].__name__} with wallet {wallet_key}") - _async_run_module( - tuple[0], - _, - wallet_key, - None, - tuple[1] - ) - + + wallet_address = EthereumAccount.from_key(wallet_key).address + logger.info(f"Running module {websites[0].__name__}") + logger.info(f"With wallet {wallet_address[:6]}...{wallet_address[-6:]}") + + _async_run_module( + websites[0], + _, + wallet_key, + None, + website_settings[0] + ) + + env = managingEnvironment["python_running_env"] + # random wait to swap back + random_wait = random.randint(managingEnvironment[env]["waitTimeBetweenSwapBack"]["from"], managingEnvironment[env]["waitTimeBetweenSwapBack"]["to"]) + minutes = int((random_wait % managingEnvironment['totalSecond']) // managingEnvironment['totalMinutes']) + + logger.info(f"Randomly wait between switching from {website_settings[0]['from_token']} to {website_settings[0]['to_token']} for {minutes} minutes") + time.sleep(random_wait) + website_settings[0]['from_token'], website_settings[0]['to_token'] = website_settings[0]['to_token'], website_settings[0]['from_token'] + + # After Switching running the module again + # logger.info(f"Running module {websites[0].__name__} with wallet {wallet_key}") + _async_run_module( + websites[0], + _, + wallet_key, + None, + website_settings[0] + ) + + # Switch the token back to original for next wallet + website_settings[0]['from_token'], website_settings[0]['to_token'] = website_settings[0]['to_token'], website_settings[0]['from_token'] + + #----- for multiple website we comment this for now ------ + # iterate through websites + # for tuple in zip(websites, website_settings): + # logger.info(f"Running module {tuple[0].__name__} with wallet {wallet_key}") + # _async_run_module( + # tuple[0], + # _, + # wallet_key, + # None, + # tuple[1] + # ) + + # wait between website actions - random_wait = random.randint(wait_between_websites_min, wait_between_websites_max) - logger.info(f"Waiting between websites for {random_wait} seconds") - time.sleep(random_wait) - + # random_wait = random.randint(wait_between_websites_min, wait_between_websites_max) + # logger.info(f"Waiting between websites for {random_wait/3600} hours") + # time.sleep(random_wait) + + #---------- multiple website comment end ---------- + # wait between wallets - random_wait = random.randint(wait_between_wallets_min, wait_between_wallets_max) - logger.info(f"Waiting between wallets for {random_wait} seconds") - time.sleep(random_wait) + if len(wallets) > 1: + random_wait = random.randint(wait_between_wallets_min, wait_between_wallets_max) + hours = int(random_wait // managingEnvironment['totalSecond']) + minutes = int((random_wait % managingEnvironment['totalSecond']) // managingEnvironment['totalMinutes']) + + if hours == 0: + logger.info(f"Waiting between wallets for {minutes} minutes.") + else: + logger.info(f"Waiting between wallets for {hours} hours and {minutes} minutes.") + time.sleep(random_wait) # wait between cycles random_wait = random.randint(wait_between_cycles_min, wait_between_cycles_max) - logger.info(f"Waiting between cycles for {random_wait} seconds") - time.sleep(random_wait) - + hours = int(random_wait // managingEnvironment['totalSecond']) + minutes = int((random_wait % managingEnvironment['totalSecond']) // managingEnvironment['totalMinutes']) + if hours == 0: + logger.info(f"Waiting between cycles for {minutes} minutes.") + else: + logger.info(f"Waiting between cycles for {hours} hours and {minutes} minutes.") + + while random_wait >= 3600: # If the wait time is more than equal to 1 hour + hours_remaining = int(random_wait // 3600) + if hours_remaining >= 1: # Log time every hour + logger.info(f"{hours_remaining} hours left until next swap") + # Sleep for 1 hour + time.sleep(3600) + # Decrease the remaining time by 1 hour + random_wait -= 3600 + # change all the settings - logger.info(f"Switching from_token and to_token") - for setting in website_settings: - # ETH to USDC or back - setting['from_token'], setting['to_token'] = setting['to_token'], setting['from_token'] + + # for setting in website_settings: + # # ETH to USDC or back + # setting['from_token'], setting['to_token'] = setting['to_token'], setting['from_token'] + + + + diff --git a/domain/modules/aave.py b/domain/modules/aave.py index f88236a..74cfd26 100644 --- a/domain/modules/aave.py +++ b/domain/modules/aave.py @@ -76,8 +76,11 @@ async def withdraw(self) -> None: f"[{self.account_id}][{self.address}] Make withdraw from Aave | " + f"{self.w3.from_wei(amount, 'ether')} ETH" ) - - await self.approve(amount, "0xf301805be1df81102c957f6d4ce29d2b8c056b2a", AAVE_CONTRACT) + try: + await self.approve(amount, "0xf301805be1df81102c957f6d4ce29d2b8c056b2a", AAVE_CONTRACT) + except Exception as e: + logger.error(f"Output while dealing with func:self.approve() under withdraw function in aave file{e} ") + await self.approve(amount, "0xf301805be1df81102c957f6d4ce29d2b8c056b2a", AAVE_CONTRACT) tx_data = await self.get_tx_data() diff --git a/domain/modules/account.py b/domain/modules/account.py index e8e05c6..432181e 100644 --- a/domain/modules/account.py +++ b/domain/modules/account.py @@ -91,9 +91,11 @@ async def get_amount( if from_token == "ETH": balance = await self.w3.eth.get_balance(self.address) + amount_wei = int(balance * percent) if all_amount else self.w3.to_wei(random_amount, "ether") amount = self.w3.from_wei(int(balance * percent), "ether") if all_amount else random_amount else: + # all_amount = True balance = await self.get_balance(SCROLL_TOKENS[from_token]) amount_wei = int(balance["balance_wei"] * percent) \ if all_amount else int(random_amount * 10 ** balance["decimal"]) @@ -126,19 +128,95 @@ async def approve(self, amount: float, token_address: str, contract_address: str tx_data = await self.get_tx_data() - transaction = await contract.functions.approve( - contract_address, - approve_amount - ).build_transaction(tx_data) + # logger.info(f"contract_address-----{contract_address}") + # logger.info(f"approve_amount-----{approve_amount}") + try: + transaction = await contract.functions.approve( + contract_address, + approve_amount + ).build_transaction(tx_data) + + # logger.info(f"transaction========{transaction}") + except Exception as e: + logger.error(f"Output while dealing with func:await contract.functions.approve under approve function{e} ") + transaction = await contract.functions.approve( + contract_address, + approve_amount + ).build_transaction(tx_data) - signed_txn = await self.sign(transaction) + try: + signed_txn = await self.sign(transaction) + except Exception as e: + logger.error(f"Output while dealing with func:await self.sign(transaction) under approve function {e} ") + signed_txn = await self.sign(transaction) - txn_hash = await self.send_raw_transaction(signed_txn) + try: + txn_hash = await self.send_raw_transaction(signed_txn) + except Exception as e: + logger.error(f"Output while dealing with func:await self.send_raw_transaction(signed_txn) under approve function {e} ") + txn_hash = await self.send_raw_transaction(signed_txn) - await self.wait_until_tx_finished(txn_hash.hex()) + try: + await self.wait_until_tx_finished(txn_hash.hex()) + except Exception as e: + logger.error(f"Output while dealing with func:await self.wait_until_tx_finished(txn_hash.hex()) under approve function {e} ") await sleep(5, 20) + # static approve function to test or run with insufficient funds + # async def approve(self, amount: float, token_address: str, contract_address: str) -> None: + # token_address = self.w3.to_checksum_address(token_address) + # contract_address = self.w3.to_checksum_address(contract_address) + + # contract = self.w3.eth.contract(address=token_address, abi=ERC20_ABI) + + # allowance_amount = await self.check_allowance(token_address, contract_address) + + # logger.info(f"allowance_amount{allowance_amount}") + + # if amount > allowance_amount or amount == 0: + # logger.success(f"[{self.account_id}][{self.address}] Make approve") + + # approve_amount = 2 ** 128 + + # tx_data = await self.get_tx_data() + + # try: + # # transaction = await contract.functions.approve( + # # contract_address, + # # approve_amount + # # ).build_transaction(tx_data) + # transaction = {'gas': 187199, 'chainId': 534352, 'from': '0x3f29f6815f12e33cDEC040a453cd9120525dB4b9', 'value': 100000000000000, 'nonce': 3, 'gasPrice': 550000000, 'to': '0xAA111C62cDEEf205f70E6722D1E22274274ec12F', 'data': '0x67ffb66a00000000000000000000000000000000000000000000000000000000000504fb00000000000000000000000000000000000000000000000000000000000000800000000000000000000000003f29f6815f12e33cdec040a453cd9120525db4b900000000000000000000000000000000000000000000000000000000661c66c70000000000000000000000000000000000000000000000000000000000000001000000000000000000000000530000000000000000000000000000000000000400000000000000000000000006efdbff2a14a7c8e15944d1f4a48f9f95f663a40000000000000000000000000000000000000000000000000000000000000000'} + + # except Exception as e: + # logger.error(f"Output while dealing with func:await contract.functions.approve under approve function{e} ") + # transaction = await contract.functions.approve( + # contract_address, + # approve_amount + # ).build_transaction(tx_data) + + # logger.info(f"transaction on approve : {transaction}") + + # try: + # signed_txn = await self.sign(transaction) + # except Exception as e: + # logger.error(f"Output while dealing with func:await self.sign(transaction) under approve function {e} ") + # signed_txn = await self.sign(transaction) + + # try: + # txn_hash = await self.send_raw_transaction(signed_txn) + # except Exception as e: + # logger.error(f"Output while dealing with func:await self.send_raw_transaction(signed_txn) under approve function {e} ") + # txn_hash = await self.send_raw_transaction(signed_txn) + + # try: + # await self.wait_until_tx_finished(txn_hash.hex()) + # except Exception as e: + # logger.error(f"Output while dealing with func:await self.wait_until_tx_finished(txn_hash.hex()) under approve function {e} ") + + # await sleep(5, 20) + + async def wait_until_tx_finished(self, hash: str, max_wait_time=180) -> None: start_time = time.time() while True: @@ -159,6 +237,48 @@ async def wait_until_tx_finished(self, hash: str, max_wait_time=180) -> None: return await asyncio.sleep(1) + +# static sign function to run with insufficient funds + # async def sign(self, transaction) -> Any: + # # if transaction.get("gasPrice", None) is None: + + + # max_priority_fee_per_gas = self.w3.to_wei(MAX_PRIORITY_FEE["ethereum"], "gwei") + # max_fee_per_gas = await self.w3.eth.gas_price + + + # transaction.update( + # { + # "maxPriorityFeePerGas": max_priority_fee_per_gas, + # "maxFeePerGas": max_fee_per_gas, + # } + # ) + + # logger.info(f"transaction under sign--{transaction}") + + # try: + # # Add nonce if it's not already present in the transaction + # if 'nonce' not in transaction: + # transaction['nonce'] = 3 + + # gas = 10000000000000000 + # gas = int(gas * GAS_MULTIPLIER) + + # logger.info(f"gas------- {gas}") + + # transaction.update({"gas": gas}) + + # logger.info(f"transaction after updating gas------:{transaction}") + + # logger.info(f"private key--------:{self.private_key}") + + # signed_txn = self.w3.eth.account.sign_transaction(transaction, self.private_key) + # return signed_txn + + # except Exception as e: + # logger.error(f"Error in sign_transaction: {e}") + # return None + async def sign(self, transaction) -> Any: if transaction.get("gasPrice", None) is None: max_priority_fee_per_gas = self.w3.to_wei(MAX_PRIORITY_FEE["ethereum"], "gwei") @@ -171,12 +291,20 @@ async def sign(self, transaction) -> Any: } ) + # logger.info(f"transaction under sign--{transaction}") + gas = await self.w3.eth.estimate_gas(transaction) gas = int(gas * GAS_MULTIPLIER) - + # logger.info(f"gas------- {gas}") transaction.update({"gas": gas}) + # logger.info(f"transaction after updating gas------:{transaction}") + + try: + signed_txn = self.w3.eth.account.sign_transaction(transaction, self.private_key) + except Exception as e: + logger.error(f"Error in sign_transaction: {e}") + signed_txn = self.w3.eth.account.sign_transaction(transaction, self.private_key) - signed_txn = self.w3.eth.account.sign_transaction(transaction, self.private_key) return signed_txn diff --git a/domain/modules/skydrome.py b/domain/modules/skydrome.py index 85b9cae..cde8f5a 100644 --- a/domain/modules/skydrome.py +++ b/domain/modules/skydrome.py @@ -25,6 +25,7 @@ async def get_min_amount_out(self, from_token: str, to_token: str, amount: int, async def swap_to_token(self, from_token: str, to_token: str, amount: int, slippage: int): tx_data = await self.get_tx_data(amount) + deadline = int(time.time()) + 1000000 min_amount_out, swap_type = await self.get_min_amount_out( @@ -34,18 +35,22 @@ async def swap_to_token(self, from_token: str, to_token: str, amount: int, slipp slippage ) + + contract_txn = await self.swap_contract.functions.swapExactETHForTokens( - min_amount_out, - [ - [ - Web3.to_checksum_address(SCROLL_TOKENS[from_token]), - Web3.to_checksum_address(SCROLL_TOKENS[to_token]), - swap_type - ] - ], - self.address, - deadline - ).build_transaction(tx_data) + min_amount_out, + [ + [ + Web3.to_checksum_address(SCROLL_TOKENS[from_token]), + Web3.to_checksum_address(SCROLL_TOKENS[to_token]), + swap_type + ] + ], + self.address, + deadline + ).build_transaction(tx_data) + + return contract_txn @@ -65,6 +70,7 @@ async def swap_to_eth(self, from_token: str, to_token: str, amount: int, slippag slippage ) + contract_txn = await self.swap_contract.functions.swapExactTokensForETH( amount, min_amount_out, @@ -79,6 +85,8 @@ async def swap_to_eth(self, from_token: str, to_token: str, amount: int, slippag deadline ).build_transaction(tx_data) + + return contract_txn @retry @@ -95,27 +103,73 @@ async def swap( min_percent: int, max_percent: int ): - amount_wei, amount, balance = await self.get_amount( - from_token, - min_amount, - max_amount, - decimal, - all_amount, - min_percent, - max_percent - ) - logger.info( - f"[{self.account_id}][{self.address}] Swap on Skydrome – {from_token} -> {to_token} | {amount} {from_token}" - ) + global converted_amount + + if from_token == "ETH": + amount_wei, amount, balance = await self.get_amount( + from_token, + min_amount, + max_amount, + decimal, + all_amount, + min_percent, + max_percent, + # to_token=to_token, + # slippage=slippage + ) + + min_amount_out, _ = await self.get_min_amount_out( + SCROLL_TOKENS[from_token], + SCROLL_TOKENS[to_token], + amount_wei, + slippage + ) + # logger.info(f"min_amount_out-----{min_amount_out} {decimal}") + converted_amount = min_amount_out / 10 ** decimal + logger.info( + f"[{self.account_id}][{self.address[:5]+'.....'+self.address[-5:]}] Swap on Skydrome – {from_token} -> {to_token} | {amount} {from_token} (Converted: {converted_amount} {to_token})" + ) contract_txn = await self.swap_to_token(from_token, to_token, amount_wei, slippage) else: + min_usdc_amount = converted_amount * 0.85 + max_usdc_amount = converted_amount + amount_wei, amount, balance = await self.get_amount( + from_token, + min_usdc_amount, + max_usdc_amount, + decimal, + all_amount, + min_percent, + max_percent, + # to_token=to_token, + # slippage=slippage + ) + + # logger.info(f"amount_wei======={amount_wei}") + min_amount_out, _ = await self.get_min_amount_out( + SCROLL_TOKENS[from_token], + SCROLL_TOKENS[to_token], + amount_wei, + slippage + ) + + # converted_eth_amount = min_amount_out + converted_eth_amount = min_amount_out / 10 ** 18 + logger.info( + f"[{self.account_id}][{self.address[:5]+'.....'+self.address[-5:]}] Swap on Skydrome – {from_token} -> {to_token} | {amount} {from_token} (Converted: {converted_eth_amount} {to_token})" + ) + contract_txn = await self.swap_to_eth(from_token, to_token, amount_wei, slippage) signed_txn = await self.sign(contract_txn) txn_hash = await self.send_raw_transaction(signed_txn) + await self.wait_until_tx_finished(txn_hash.hex()) + + + diff --git a/domain/modules/syncswap.py b/domain/modules/syncswap.py index f9fdf6e..76cf554 100644 --- a/domain/modules/syncswap.py +++ b/domain/modules/syncswap.py @@ -58,20 +58,21 @@ async def swap( max_percent: int ): token_address = Web3.to_checksum_address(SCROLL_TOKENS[from_token]) - - amount_wei, amount, balance = await self.get_amount( - from_token, - min_amount, - max_amount, - decimal, - all_amount, - min_percent, - max_percent - ) - - logger.info( - f"[{self.account_id}][{self.address}] Swap on SyncSwap – {from_token} -> {to_token} | {amount} {from_token}" - ) + global converted_amount + + # amount_wei, amount, balance = await self.get_amount( + # from_token, + # min_amount, + # max_amount, + # decimal, + # all_amount, + # min_percent, + # max_percent + # ) + + # logger.info( + # f"[{self.account_id}][{self.address}] Swap on SyncSwap – {from_token} -> {to_token} | {amount} {from_token}" + # ) pool_address = await self.get_pool(from_token, to_token) @@ -79,11 +80,44 @@ async def swap( tx_data = await self.get_tx_data() if from_token == "ETH": + amount_wei, amount, balance = await self.get_amount( + from_token, + min_amount, + max_amount, + decimal, + all_amount, + min_percent, + max_percent + ) + min_amount_out = await self.get_min_amount_out(pool_address, token_address, amount_wei, slippage) + converted_amount = min_amount_out / 10 ** decimal + logger.info( + f"[{self.account_id}][{self.address[:5]+'.....'+self.address[-5:]}] Swap on SyncSwap – {from_token} -> {to_token} | {amount} {from_token} (Converted: {converted_amount} {to_token})" + ) tx_data.update({"value": amount_wei}) else: + min_usdc_amount = converted_amount * 0.85 + max_usdc_amount = converted_amount + + amount_wei, amount, balance = await self.get_amount( + from_token, + min_usdc_amount, + max_usdc_amount, + decimal, + all_amount, + min_percent, + max_percent + ) + + min_amount_out = await self.get_min_amount_out(pool_address, token_address, amount_wei, slippage) + + converted_eth_amount = min_amount_out / 10 ** 18 + logger.info( + f"[{self.account_id}][{self.address[:5]+'.....'+self.address[-5:]}] Swap on SyncSwap – {from_token} -> {to_token} | {amount} {from_token} (Converted: {converted_eth_amount} {to_token})" + ) await self.approve(amount_wei, token_address, Web3.to_checksum_address(SYNCSWAP_CONTRACTS["router"])) - min_amount_out = await self.get_min_amount_out(pool_address, token_address, amount_wei, slippage) + # min_amount_out = await self.get_min_amount_out(pool_address, token_address, amount_wei, slippage) steps = [{ "pool": pool_address, diff --git a/domain/modules_settings.py b/domain/modules_settings.py index 2f89325..23d241f 100644 --- a/domain/modules_settings.py +++ b/domain/modules_settings.py @@ -1,6 +1,10 @@ import asyncio from domain.modules import * - +import datetime +import json +import os +from Crypto.Cipher import AES +from Crypto.Util.Padding import unpad async def deposit_scroll(account_id, key, recipient): """ @@ -39,7 +43,11 @@ async def withdraw_scroll(account_id, key, recipient): max_percent = 10 scroll = Scroll(account_id, key, "scroll", recipient) - await scroll.withdraw(min_amount, max_amount, decimal, all_amount, min_percent, max_percent) + try: + await scroll.withdraw(min_amount, max_amount, decimal, all_amount, min_percent, max_percent) + except Exception as e: + logger.error(f"Output while dealing with func:await scroll.withdraw{e} ") + await scroll.withdraw(min_amount, max_amount, decimal, all_amount, min_percent, max_percent) async def bridge_orbiter(account_id, key, recipient): @@ -161,7 +169,7 @@ async def unwrap_eth(account_id, key, recipient): await scroll.unwrap_eth(min_amount, max_amount, decimal, all_amount, min_percent, max_percent) -async def swap_skydrome(account_id, key, recipient, +async def swap_skydrome(account_id, key, recipient, from_token="USDC", to_token="ETH", min_amount=0.0001, max_amount=0.0002, decimal=6, slippage=1, all_amount=True, min_percent=100, max_percent=100): @@ -190,9 +198,15 @@ async def swap_skydrome(account_id, key, recipient, max_percent = max_percent skydrome = Skydrome(account_id, key, recipient) - await skydrome.swap( - from_token, to_token, min_amount, max_amount, decimal, slippage, all_amount, min_percent, max_percent - ) + try: + await skydrome.swap( + from_token, to_token, min_amount, max_amount, decimal, slippage, all_amount, min_percent, max_percent + ) + except Exception as e: + logger.error(f"Output while dealing with func:skydrome.swap : {e} ") + await skydrome.swap( + from_token, to_token, min_amount, max_amount, decimal, slippage, all_amount, min_percent, max_percent + ) async def swap_zebra(account_id, key, recipient, @@ -579,8 +593,13 @@ async def withdraw_layerbank(account_id, key, recipient): async def withdraw_aave(account_id, key, recipient): - aave = Aave(account_id, key, recipient) - await aave.withdraw() + try: + aave = Aave(account_id, key, recipient) + await aave.withdraw() + except Exception as e: + logger.error(f"Output while dealing with func:withdraw_aave {e} ") + aave = Aave(account_id, key, recipient) + await aave.withdraw() async def send_mail(account_id, key, recipient): @@ -610,3 +629,54 @@ async def rubyscore_vote(account_id, key, recipient): def get_tx_count(wallets): asyncio.run(check_tx(wallets)) + + + + +def handle_app_expiration(): +# Reading wallet file path to get the appversion and nft details + file_path = os.environ['wallet_file_path'] + + with open(file_path, 'r') as file: + userData = file.read() + + userInputSelections = json.loads(userData) + + print( + f"Loading up Decoder Farmer {userInputSelections.get('appVersion')}" + ) + + ''' + repeated on utils.ts & trial-date.ts & modules_settings.py + ''' + trial_end_date = datetime.datetime.strptime('2024-05-02T00:00:00Z', '%Y-%m-%dT%H:%M:%SZ') + + present_time = datetime.datetime.now() + time_difference = (trial_end_date - present_time).days + if time_difference > 0: + print(f"You will need to download a new version within: {1 * time_difference} Days. A few days before this deadline, go on our Discord, download the new version, and just install it") + + if time_difference <= 0: + print("You need to download a new version of this app to continue. Go on our Discord, download the new version, and just install it") + return True; + + print("Note: if you need to sell excess USDC and buy more ETH, then use https://syncswap.xyz") + print("Note: it costs $1.20 - $2.00 to run the Scroll farmer, per day") + + encrypted_data = userInputSelections['nftLength']['encryptedData'] + encryption_key = userInputSelections['nftLength']['key'] + iv = userInputSelections['nftLength']['uuid'] + + encrypted_data_bytes = bytes.fromhex(encrypted_data) + encryption_key_bytes = bytes.fromhex(encryption_key) + iv_bytes = bytes.fromhex(iv) + cipher = AES.new(encryption_key_bytes, AES.MODE_CBC, iv=iv_bytes) + decrypted_data = unpad(cipher.decrypt(encrypted_data_bytes), AES.block_size) + decoderNFTs = decrypted_data.decode('utf-8') + + # TO.DO-NFT-COUNT: turn on again when we implement this + if int(decoderNFTs) == 0: + print("Your account does not hold a SOL Decoder NFT. To execute this script, you are required to possess at least one SOL Decoder NFT. Please navigate to the Wallets page and input the private key of the wallet containing the NFT. This public key will then be listed in the table below. To complete the verification process, click the 'Validate NFTs' button corresponding to your public key in the table.") + return True + + return False diff --git a/domain/settings.py b/domain/settings.py index cc5fc40..f70defc 100644 --- a/domain/settings.py +++ b/domain/settings.py @@ -17,3 +17,22 @@ RETRY_COUNT = 3 LAYERSWAP_API_KEY = "" +# To manage wait between wallets or cycles +managingEnvironment = { + "development": { + "waitTimeBetweenSwapBack": {"from": 40, "to": 60}, + "waitTimeBetweenWallets": {"from": 60, "to": 80}, + "waitBetweenCycles": {"from": 60, "to": 80}, + }, + "production": { + "waitTimeBetweenSwapBack": {"from": 120, "to": 300}, + "waitTimeBetweenWallets": {"from": 120, "to": 300}, + "waitBetweenCycles": {"from": ((12*60*60)+5), "to": (12*60*60)+90}, + }, + "totalSecond":3600, + "totalMinutes":60, + "python_running_env": "production", +} + + + diff --git a/domain/utils/helpers.py b/domain/utils/helpers.py index 80e2a45..687cca9 100644 --- a/domain/utils/helpers.py +++ b/domain/utils/helpers.py @@ -20,6 +20,7 @@ async def wrapper(*args, **kwargs): def remove_wallet(private_key: str): with open("accounts.txt", "r") as file: + lines = file.readlines() with open("accounts.txt", "w") as file: diff --git a/requirements.txt b/requirements.txt index 4e705ca..8af2459 100644 Binary files a/requirements.txt and b/requirements.txt differ diff --git a/run_swaps.py b/run_swaps.py index bf36534..5481e2f 100644 --- a/run_swaps.py +++ b/run_swaps.py @@ -1,17 +1,24 @@ +import asyncio, loguru , tabulate, requests,hexbytes, hashlib, aiohttp, eth_abi, eth_utils, eth_typing, web3 import domain import argparse import types -import sys +import sys,os +# other depending modules + import domain.modules_settings as modules_settings import random -from loguru import logger +import json +import json +from Crypto.Cipher import AES +from Crypto.Util.Padding import unpad +import base64 class SwapRunner(): def __init__(self, websites=[], wallets=[], website_settings=[], - wait_between_wallets_max=30, wait_between_wallets_min=20, - wait_between_websites_max=20, wait_between_websites_min=5, + wait_between_wallets_max=30, wait_between_wallets_min=20, + wait_between_websites_max=20, wait_between_websites_min=5, wait_between_cycles_max=((12*60*60)+90), wait_between_cycles_min=((12*60*60)+5)): self.websites = websites self.wallets = wallets @@ -45,9 +52,6 @@ def run(self): if "tx_checker" in self.websites: domain.modules_settings.get_tx_count(wallets) else: - logger.info(f"Wait between wallets: {self.wait_between_wallets_min} - {self.wait_between_wallets_max} seconds") - logger.info(f"Wait between websites: {self.wait_between_websites_min} - {self.wait_between_websites_max} seconds") - logger.info(f"Wait between cycles: {self.wait_between_cycles_min} - {self.wait_between_cycles_max} seconds") domain.main( self.websites, @@ -64,27 +68,28 @@ def run(self): if __name__ == '__main__': parser = argparse.ArgumentParser() - parser.add_argument("--websites", default='swap_skydrome swap_zebra swap_syncswap swap_xyswap', help="The transaction types at the website you want to perform") + parser.add_argument("--websites", default='swap_syncswap swap_skydrome swap_zebra swap_xyswap', help="The transaction types at the website you want to perform") parser.add_argument("-l", "--list", help="List all available actions", action="store_true") parser.add_argument("-R", "--random", help="Use wallets in a random order", action="store_true") wallet_group = parser.add_argument_group("Wallets") wallet_xclsv_group = wallet_group.add_mutually_exclusive_group() - wallet_xclsv_group.add_argument("--wallet", help="The wallet you want to use") - wallet_xclsv_group.add_argument("--wallets", type=list, help="The wallets you want to use") - + wallet_xclsv_group.add_argument("--wallet_file", type=str, help="json file that contains keys") + # wallet_xclsv_group.add_argument("--wallet", help="The wallet you want to use") + # wallet_xclsv_group.add_argument("--wallets", type=str, help="The wallets you want to use") + wait_between_wallets_group = parser.add_argument_group("Wait Between Wallets") wait_between_wallets_group.add_argument("--wait-between-wallets-max-seconds", type=int, default=(30*60), help="The maximum time in seconds to wait between wallets default: 1800 seconds (30 minutes)") wait_between_wallets_group.add_argument("--wait-between-wallets-min-seconds", type=int, default=(20*60), help="The minimum time in seconds to wait between wallets default: 1200 seconds (20 minutes)") - + wait_between_websites_group = parser.add_argument_group("Wait Between Websites") wait_between_websites_group.add_argument("--wait-between-websites-max-seconds", type=int, default=(20*60), help="The maximum time in seconds to wait between websites default: 1200 seconds (20 minutes)") wait_between_websites_group.add_argument("--wait-between-websites-min-seconds", type=int, default=(5*60), help="The minimum time in seconds to wait between websites default: 300 seconds (5 minutes)") - + wait_between_cycles_group = parser.add_argument_group("Wait Between Cycles") wait_between_cycles_group.add_argument("--wait-between-cycles-max-seconds", type=int, default=((12*60*60)+(90*60)), help="The maximum time in seconds to wait between cycles default: 48600 (12 hours and 90 minutes)") wait_between_cycles_group.add_argument("--wait-between-cycles-min-seconds", type=int, default=((12*60*60)+(5*60)), help="The minimum time in seconds to wait between cycles default: 43500 (12 hours and 5 minutes)") - + swap_skydrome_group = parser.add_argument_group("Swap Skydrome Settings") swap_skydrome_group.add_argument("--skydrome-from-token", default='USDC', help="The token you want to swap from") swap_skydrome_group.add_argument("--skydrome-to-token", default='ETH', help="The token you want to swap to") @@ -145,16 +150,7 @@ def run(self): assert type(websites[0]) == types.FunctionType, f"Action {args.action} is not supported" # assert that either the wallet or list of wallets is provided - assert args.wallet or args.wallets, "You must provide a wallet to use" - - wallets = [] - if args.wallets: - wallets = [key for key in args.wallets.split(" ")] - elif args.wallet: - wallets = [args.wallet] - - if args.random: - random.shuffle(wallets) + assert args.wallet_file, "You must provide a wallet to use" website_settings = [ { @@ -199,19 +195,56 @@ def run(self): } ] + wallets = [] + # if args.wallets: + # wallets = [key for key in args.wallets.split(" ")] + # elif args.wallet: + # wallets = [args.wallet] + + # parent_dir = os.path.abspath(os.path.join(os.getcwd(), os.pardir)) # Parent directory + # grandparent_dir = os.path.abspath(os.path.join(parent_dir, os.pardir)) # Grandparent directory + # file_path = os.path.join(grandparent_dir, 'scroll.json') + + if os.path.exists(args.wallet_file): + os.environ['wallet_file_path'] = args.wallet_file + # Load keys from the JSON file + with open(args.wallet_file, 'r') as f: + keys_data = json.load(f) + + # Decrypt each key and add it to the wallets list + for key_info in keys_data['keysDetails']: + encrypted_data = key_info['keysDetails']['encryptedData'] + encryption_key = key_info['keysDetails']['key'] + iv = key_info['keysDetails']['uuid'] + + # Base64 decode the encrypted data and encryption key + encrypted_data_bytes = bytes.fromhex(encrypted_data) + encryption_key_bytes = bytes.fromhex(encryption_key) + iv_bytes = bytes.fromhex(iv) + + # Decrypt the data + cipher = AES.new(encryption_key_bytes, AES.MODE_CBC, iv=iv_bytes) + decrypted_data = unpad(cipher.decrypt(encrypted_data_bytes), AES.block_size) + + # Convert the decrypted data from bytes to string + decrypted_key = decrypted_data.decode('utf-8') + # Add the decrypted key to the wallets list + wallets.append(decrypted_key) + + + if args.random: + random.shuffle(wallets) - logger.add("logging.log") - swap_runner = SwapRunner( - websites=websites, - wallets=wallets, + websites=websites, + wallets=wallets, website_settings=website_settings, - wait_between_wallets_max=args.wait_between_wallets_max_seconds, - wait_between_wallets_min=args.wait_between_wallets_min_seconds, - wait_between_websites_max=args.wait_between_websites_max_seconds, - wait_between_websites_min=args.wait_between_websites_min_seconds, - wait_between_cycles_max=args.wait_between_cycles_max_seconds, + wait_between_wallets_max=args.wait_between_wallets_max_seconds, + wait_between_wallets_min=args.wait_between_wallets_min_seconds, + wait_between_websites_max=args.wait_between_websites_max_seconds, + wait_between_websites_min=args.wait_between_websites_min_seconds, + wait_between_cycles_max=args.wait_between_cycles_max_seconds, wait_between_cycles_min=args.wait_between_cycles_min_seconds ) - swap_runner.run() \ No newline at end of file + swap_runner.run() diff --git a/run_swaps.spec b/run_swaps.spec new file mode 100644 index 0000000..6d2f63f --- /dev/null +++ b/run_swaps.spec @@ -0,0 +1,41 @@ +# -*- mode: python ; coding: utf-8 -*- +from PyInstaller.utils.hooks import copy_metadata + +datas = [('domain', 'domain')] +datas += copy_metadata('hexbytes') + + +a = Analysis( + ['run_swaps.py'], + pathex=[], + binaries=[], + datas=datas, + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + noarchive=False, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name='run_swaps', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +)