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