From ca66cdcc7e2cd012deea6690f2c31db83f510b8f Mon Sep 17 00:00:00 2001 From: maked0n1an Date: Thu, 7 Nov 2024 18:28:11 +0200 Subject: [PATCH 01/18] checking permissions. --- lesson_03_dz/main.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 lesson_03_dz/main.py diff --git a/lesson_03_dz/main.py b/lesson_03_dz/main.py new file mode 100644 index 0000000..e69de29 From 73b3afdb87d17773f2a77759e0f39cc4966d43a8 Mon Sep 17 00:00:00 2001 From: maked0n1an Date: Fri, 8 Nov 2024 12:33:15 +0200 Subject: [PATCH 02/18] Added first practics for lesson_3_part_1 --- lesson_03_dz/main.py | 0 .../client.py | 136 +++ .../config.py | 2 + .../data/abis/token.json | 772 ++++++++++++++++++ .../data/models.py | 254 ++++++ .../info.txt | 29 + .../main.py | 36 + .../requirements.txt | 3 + .../spacefi_bytes.py | 109 +++ .../spacefi_params.py | 81 ++ .../tasks/spacefi.py | 455 +++++++++++ .../tasks/spacefi_hard.py | 190 +++++ .../utils.py | 8 + 13 files changed, 2075 insertions(+) delete mode 100644 lesson_03_dz/main.py create mode 100644 lesson_03_unverified_contracts_part_1_dz/client.py create mode 100644 lesson_03_unverified_contracts_part_1_dz/config.py create mode 100644 lesson_03_unverified_contracts_part_1_dz/data/abis/token.json create mode 100644 lesson_03_unverified_contracts_part_1_dz/data/models.py create mode 100644 lesson_03_unverified_contracts_part_1_dz/info.txt create mode 100644 lesson_03_unverified_contracts_part_1_dz/main.py create mode 100644 lesson_03_unverified_contracts_part_1_dz/requirements.txt create mode 100644 lesson_03_unverified_contracts_part_1_dz/spacefi_bytes.py create mode 100644 lesson_03_unverified_contracts_part_1_dz/spacefi_params.py create mode 100644 lesson_03_unverified_contracts_part_1_dz/tasks/spacefi.py create mode 100644 lesson_03_unverified_contracts_part_1_dz/tasks/spacefi_hard.py create mode 100644 lesson_03_unverified_contracts_part_1_dz/utils.py diff --git a/lesson_03_dz/main.py b/lesson_03_dz/main.py deleted file mode 100644 index e69de29..0000000 diff --git a/lesson_03_unverified_contracts_part_1_dz/client.py b/lesson_03_unverified_contracts_part_1_dz/client.py new file mode 100644 index 0000000..2412b19 --- /dev/null +++ b/lesson_03_unverified_contracts_part_1_dz/client.py @@ -0,0 +1,136 @@ +import asyncio +from hexbytes import HexBytes + +from curl_cffi.requests import AsyncSession +from fake_useragent import UserAgent +from eth_typing import ChecksumAddress, HexStr +from eth_account.signers.local import LocalAccount +from web3.exceptions import Web3Exception +from web3 import AsyncWeb3, Web3 +from web3.middleware import geth_poa_middleware + + +class Client: + private_key: str + rpc: str + proxy: str | None + w3: AsyncWeb3 + account: LocalAccount + + def __init__(self, private_key: str, rpc: str, proxy: str | None = None): + self.private_key = private_key + self.rpc = rpc + self.proxy = proxy + + if self.proxy: + if '://' not in self.proxy: + self.proxy = f'http://{self.proxy}' + + self.headers = { + 'accept': '*/*', + 'accept-language': 'en-US,en;q=0.9', + 'content-type': 'application/json', + 'user-agent': UserAgent().chrome + } + + self.w3 = AsyncWeb3(AsyncWeb3.AsyncHTTPProvider( + endpoint_uri=rpc, + request_kwargs={'proxy': self.proxy, 'headers': self.headers} + )) + self.account = self.w3.eth.account.from_key(private_key) + + def max_priority_fee(self, block: dict | None = None) -> int: + w3 = Web3(provider=AsyncWeb3.HTTPProvider(endpoint_uri=self.rpc)) + w3.middleware_onion.inject(geth_poa_middleware, layer=0) + + if not block: + block = w3.eth.get_block('latest') + + block_number = block['number'] + latest_block_transaction_count = w3.eth.get_block_transaction_count(block_number) + max_priority_fee_per_gas_lst = [] + for i in range(latest_block_transaction_count): + try: + transaction = w3.eth.get_transaction_by_block(block_number, i) + if 'maxPriorityFeePerGas' in transaction: + max_priority_fee_per_gas_lst.append(transaction['maxPriorityFeePerGas']) + except Exception: + continue + + if not max_priority_fee_per_gas_lst: + max_priority_fee_per_gas = 0 + else: + max_priority_fee_per_gas_lst.sort() + max_priority_fee_per_gas = max_priority_fee_per_gas_lst[len(max_priority_fee_per_gas_lst) // 2] + return max_priority_fee_per_gas + + async def send_transaction( + self, + to: str | ChecksumAddress, + data: HexStr | None = None, + from_: str | ChecksumAddress | None = None, + increase_gas: float = 1, + value: int | None = None, + eip1559: bool = True, + max_priority_fee_per_gas: int | None = None + ) -> HexBytes | None: + if not from_: + from_ = self.account.address + + tx_params = { + 'chainId': await self.w3.eth.chain_id, + 'nonce': await self.w3.eth.get_transaction_count(self.account.address), + 'from': AsyncWeb3.to_checksum_address(from_), + 'to': AsyncWeb3.to_checksum_address(to), + } + + if eip1559: + if max_priority_fee_per_gas is None: + max_priority_fee_per_gas = await self.w3.eth.max_priority_fee + base_fee = (await self.w3.eth.get_block('latest'))['baseFeePerGas'] + max_fee_per_gas = base_fee + max_priority_fee_per_gas + tx_params['maxFeePerGas'] = max_fee_per_gas # максимальная общая комиссия + tx_params['maxPriorityFeePerGas'] = max_priority_fee_per_gas # приоритетная комиссия майнеру + else: + tx_params['gasPrice'] = await self.w3.eth.gas_price + + if data: + tx_params['data'] = data + if value: + tx_params['value'] = value + + gas = await self.w3.eth.estimate_gas(tx_params) + tx_params['gas'] = int(gas * increase_gas) + + sign = self.w3.eth.account.sign_transaction(tx_params, self.private_key) + return await self.w3.eth.send_raw_transaction(sign.rawTransaction) + + async def verif_tx(self, tx_hash: HexBytes, timeout: int = 200) -> str: + data = await self.w3.eth.wait_for_transaction_receipt(tx_hash, timeout=timeout) + if data.get('status') == 1: + return tx_hash.hex() + raise Web3Exception(f'transaction failed {data["transactionHash"].hex()}') + + @staticmethod + async def get_token_price(token_symbol='ETH') -> float | None: + token_symbol = token_symbol.upper() + + if token_symbol in ('USDC', 'USDT', 'DAI', 'CEBUSD', 'BUSD', 'USDC.E'): + return 1 + if token_symbol == 'WETH': + token_symbol = 'ETH' + if token_symbol == 'WBTC': + token_symbol = 'BTC' + + for _ in range(5): + try: + async with AsyncSession() as session: + response = await session.get( + f'https://api.binance.com/api/v3/depth?limit=1&symbol={token_symbol}USDT') + result_dict = response.json() + if 'asks' not in result_dict: + return + return float(result_dict['asks'][0][0]) + except Exception: + await asyncio.sleep(5) + raise ValueError(f'Can not get {token_symbol} price from Binance API') diff --git a/lesson_03_unverified_contracts_part_1_dz/config.py b/lesson_03_unverified_contracts_part_1_dz/config.py new file mode 100644 index 0000000..51b85c3 --- /dev/null +++ b/lesson_03_unverified_contracts_part_1_dz/config.py @@ -0,0 +1,2 @@ +PRIVATE_KEY = '' +PROXY = '' \ No newline at end of file diff --git a/lesson_03_unverified_contracts_part_1_dz/data/abis/token.json b/lesson_03_unverified_contracts_part_1_dz/data/abis/token.json new file mode 100644 index 0000000..3869640 --- /dev/null +++ b/lesson_03_unverified_contracts_part_1_dz/data/abis/token.json @@ -0,0 +1,772 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_user", + "type": "address" + } + ], + "name": "BlockPlaced", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_user", + "type": "address" + } + ], + "name": "BlockReleased", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_blockedUser", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_balance", + "type": "uint256" + } + ], + "name": "DestroyedBlockedFunds", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_destination", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "Mint", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_contract", + "type": "address" + } + ], + "name": "NewPrivilegedContract", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "Redeem", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_contract", + "type": "address" + } + ], + "name": "RemovedPrivilegedContract", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_trustedDeFiContract", + "type": "address" + } + ], + "name": "addPrivilegedContract", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_user", + "type": "address" + } + ], + "name": "addToBlockedList", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_owner", + "type": "address" + }, + { + "internalType": "address", + "name": "_spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "bridgeBurn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "bridgeMint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_blockedUser", + "type": "address" + } + ], + "name": "destroyBlockedFunds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_name", + "type": "string" + }, + { + "internalType": "string", + "name": "_symbol", + "type": "string" + }, + { + "internalType": "uint8", + "name": "_decimals", + "type": "uint8" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_name", + "type": "string" + }, + { + "internalType": "string", + "name": "_symbol", + "type": "string" + }, + { + "internalType": "uint8", + "name": "_decimals", + "type": "uint8" + }, + { + "internalType": "address", + "name": "_l2Gateway", + "type": "address" + }, + { + "internalType": "address", + "name": "_l1Counterpart", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isBlocked", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isTrusted", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "l1Address", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "l2Gateway", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_destination", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "_recipients", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "_values", + "type": "uint256[]" + } + ], + "name": "multiTransfer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "redeem", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_user", + "type": "address" + } + ], + "name": "removeFromBlockedList", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_trustedDeFiContract", + "type": "address" + } + ], + "name": "removePrivilegedContract", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_sender", + "type": "address" + }, + { + "internalType": "address", + "name": "_recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/lesson_03_unverified_contracts_part_1_dz/data/models.py b/lesson_03_unverified_contracts_part_1_dz/data/models.py new file mode 100644 index 0000000..ab3bf56 --- /dev/null +++ b/lesson_03_unverified_contracts_part_1_dz/data/models.py @@ -0,0 +1,254 @@ +from decimal import Decimal + + +class TokenAmount: + Wei: int + Ether: Decimal + decimals: int + + def __init__(self, amount: int | float | str | Decimal, decimals: int = 18, wei: bool = False) -> None: + if wei: + self.Wei: int = int(amount) + self.Ether: Decimal = Decimal(str(amount)) / 10 ** decimals + + else: + self.Wei: int = int(Decimal(str(amount)) * 10 ** decimals) + self.Ether: Decimal = Decimal(str(amount)) + + self.decimals = decimals + + def __str__(self): + return f'{self.Wei}' + + +class ABIs: + TokenABI = [ + { + 'constant': True, + 'inputs': [], + 'name': 'name', + 'outputs': [{'name': '', 'type': 'string'}], + 'payable': False, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': True, + 'inputs': [], + 'name': 'symbol', + 'outputs': [{'name': '', 'type': 'string'}], + 'payable': False, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': True, + 'inputs': [], + 'name': 'decimals', + 'outputs': [{'name': '', 'type': 'uint256'}], + 'payable': False, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': True, + 'inputs': [{'name': 'who', 'type': 'address'}], + 'name': 'balanceOf', + 'outputs': [{'name': '', 'type': 'uint256'}], + 'payable': False, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': True, + 'inputs': [ + {'name': 'spender', 'type': 'address'}, + {'name': 'amount', 'type': 'uint256'} + ], + 'name': 'approve', + 'outputs': [], + 'payable': False, + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'constant': True, + 'inputs': [ + {'name': 'owner', 'type': 'address'}, + {'name': 'spender', 'type': 'address'}, + ], + 'name': 'allowance', + 'outputs': [ + {'name': '', 'type': 'uint256'}, + ], + 'payable': False, + 'stateMutability': 'view', + 'type': 'function' + }, + ] + + SpaceFiABI = [ + { + "constant": False, + "inputs": [ + { + "name": "amountOutMin", + "type": "uint256" + }, + { + "name": "path", + "type": "address[]" + }, + { + "name": "to", + "type": "address" + }, + { + "name": "deadline", + "type": "uint256" + } + ], + "name": "swapExactETHForTokens", + "outputs": [ + { + "name": "", + "type": "uint256[]" + } + ], + "payable": True, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": False, + "inputs": [ + { + "name": "amountOutMin", + "type": "uint256" + }, + { + "name": "amountOutMin", + "type": "uint256" + }, + { + "name": "path", + "type": "address[]" + }, + { + "name": "to", + "type": "address" + }, + { + "name": "deadline", + "type": "uint256" + } + ], + "name": "swapExactTokensForETH", + "outputs": [ + { + "name": "", + "type": "uint256[]" + } + ], + "payable": True, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": False, + "inputs": [ + { + "name": "amountIn", + "type": "uint256" + }, + { + "name": "amountOutMin", + "type": "uint256" + }, + { + "name": "path", + "type": "address[]" + }, + { + "name": "to", + "type": "address" + }, + { + "name": "deadline", + "type": "uint256" + } + ], + "name": "swapExactTokensForTokens", + "outputs": [ + { + "name": "", + "type": "uint256[]" + } + ], + "payable": True, + "stateMutability": "payable", + "type": "function" + } + ] + + SpaceFiABIBytes = [ + { + "constant": False, + "inputs": [ + { + "name": "", + "type": "uint256" + }, + { + "name": "", + "type": "address[]" + }, + { + "name": "", + "type": "address" + }, + { + "name": "", + "type": "uint256" + } + ], + "name": "swap", + "outputs": [], + "payable": True, + "stateMutability": "payable", + "type": "function" + } + ] + + SpaceFiABIBytesTokenToToken = [ + { + "constant": False, + "inputs": [ + { + "name": "", + "type": "uint256" + }, + { + "name": "", + "type": "uint256" + }, + { + "name": "", + "type": "address[]" + }, + { + "name": "", + "type": "address" + }, + { + "name": "", + "type": "uint256" + } + ], + "name": "swap", + "outputs": [], + "payable": True, + "stateMutability": "payable", + "type": "function" + } + ] diff --git a/lesson_03_unverified_contracts_part_1_dz/info.txt b/lesson_03_unverified_contracts_part_1_dz/info.txt new file mode 100644 index 0000000..1bb3a39 --- /dev/null +++ b/lesson_03_unverified_contracts_part_1_dz/info.txt @@ -0,0 +1,29 @@ +Урок 3, домашнее задание 1 - Неверифицированные контракты на примере zkSync + +В этом уроке мы разберёмся как работать с неверифицированными контрактами, где находить их аби и что +делать, если не знаем имя функции для обмена. + +Наши задания: +- задание №1: сделать скрипт на следующие cвапы в сети zkSync на площадке SpaceFi (https://swap-zksync.spacefi.io/#/swap): +swap_eth_to_usdt +swap_eth_to_wbtc +swap_usdc_to_eth +Примеры: +(0.001 ETH -> 2.87 USDT): https://era.zksync.network/tx/0x358ab333050193e02623c0b81aad6acea73f358eabd35e6c7526a5e7f52b98db +(0.0008 ETH -> 0.000029 WBTC): https://era.zksync.network/tx/0x669310c1ec16ed385e8d0778cc96c05e2bc3d8b2e6d3490f4363b370bc6d2446 +(0.001 ETH -> 2.28988 USDC): https://era.zksync.network/tx/0x5e97aaaa972dc2aca2bdb8b6241fe6dd5bb9eaeb238d0dcd941c31c46198b51e + +- задание №2: сделать скрипт на cвапы в сети zkSync на площадке SpaceFi (https://swap-zksync.spacefi.io/#/swap): +swap_usdt_to_eth +swap_usdt_to_usdc_e +Функции должны иметь флаг is_all_balance, который будет использовать весь баланс на счету для обмена. Реализацию data +делать через самописный ABI функции swap. +Примеры: +(0.001 ETH -> 2.87 USDT): https://era.zksync.network/tx/0x0161e7cb528408427fce8eda171a251632d0b28cb89bf8dfd9616189964ae08b +(1.734763 USDT -> 1.665372 USDC.e): https://era.zksync.network/tx/0x179df85ee97094190b17433b68c0a87f382a39188a44255917ed0afed9a386b2 +Подсказка: сигнатура функции +async def swap_usdt_to_eth(self, token_amount: TokenAmount | None = None, slippage: float = 0.5, is_all_balance: bool = False) -> str. + +- задание №3*: реализовать скрипт для универсальных свапов from_token -> to_token +Подсказка: сигнатура функции +async def swap(self, from_token_symbol, to_token_symbol, amount, slippage) -> str. \ No newline at end of file diff --git a/lesson_03_unverified_contracts_part_1_dz/main.py b/lesson_03_unverified_contracts_part_1_dz/main.py new file mode 100644 index 0000000..4a66c85 --- /dev/null +++ b/lesson_03_unverified_contracts_part_1_dz/main.py @@ -0,0 +1,36 @@ +import asyncio +from client import Client +import config +from data.models import TokenAmount +from tasks.spacefi import SpaceFi +from tasks.spacefi_hard import SpaceFiHard + + +async def main(): + client = Client( + private_key=config.PRIVATE_KEY, + rpc='https://mainnet.era.zksync.io', + proxy=config.PROXY + ) + + spacefi = SpaceFi(client) + # await spacefi.swap_eth_to_usdt( + # token_amount=TokenAmount(0.0006), + # slippage=5 + # ) + # await spacefi.swap_usdt_to_usdc_e( + # slippage=4, + # is_all_balance=True + # ) + + spacefi = SpaceFiHard(client) + await spacefi.swap( + from_token_symbol='WBTC', + to_token_symbol='ETH', + amount=0.000023, + slippage=5 + ) + +if __name__ == "__main__": + asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) + asyncio.run(main()) diff --git a/lesson_03_unverified_contracts_part_1_dz/requirements.txt b/lesson_03_unverified_contracts_part_1_dz/requirements.txt new file mode 100644 index 0000000..6a56bdb --- /dev/null +++ b/lesson_03_unverified_contracts_part_1_dz/requirements.txt @@ -0,0 +1,3 @@ +web3==6.14.0 +curl_cffi==0.7.1 +fake-useragent==1.4.0 diff --git a/lesson_03_unverified_contracts_part_1_dz/spacefi_bytes.py b/lesson_03_unverified_contracts_part_1_dz/spacefi_bytes.py new file mode 100644 index 0000000..e552407 --- /dev/null +++ b/lesson_03_unverified_contracts_part_1_dz/spacefi_bytes.py @@ -0,0 +1,109 @@ +import asyncio +import time + +from web3 import AsyncWeb3 + +import config +from client import Client +from data.models import ABIs, TokenAmount + + +''' +0x7ff36ab5 +000 000000000000000000000000000000000000000000000000000000000021626d # 2187885 (amount out min) +020 0000000000000000000000000000000000000000000000000000000000000080 # cсылка на байт 80 (16 СС) +040 00000000000000000000000036f302d18dcede1ab1174f47726e62212d1ccead # address +060 0000000000000000000000000000000000000000000000000000000066e84acc # 1726499532 deadline +080 0000000000000000000000000000000000000000000000000000000000000004 # длина массива +0A0 0000000000000000000000005aea5775959fbc2557cc8789bc1bf90a239d9a91 # WETH +0C0 000000000000000000000000838a66f841dd5148475a8918db0732c239499a03 # STAR +0E0 0000000000000000000000003355df6d4c9c3035724fd0e3914de96a5a83aaf4 # USDC.e +100 0000000000000000000000001d17cbcf0d6d143135ae902365d2e5e2a16538d4 # USDC + +0xc671639d +0000000000000000000000000000000000000000000000000000000000005cc0 +0000000000000000000000000000000000000000000000000000000000000080 +00000000000000000000000036f302d18dcede1ab1174f47726e62212d1ccead +0000000000000000000000000000000000000000000000000000000067193976 +0000000000000000000000000000000000000000000000000000000000000004 +0000000000000000000000005aea5775959fbc2557cc8789bc1bf90a239d9a91 +000000000000000000000000838a66f841dd5148475a8918db0732c239499a03 +0000000000000000000000003355df6d4c9c3035724fd0e3914de96a5a83aaf4 +0000000000000000000000001d17cbcf0d6d143135ae902365d2e5e2a16538d4 +''' + + +async def main(from_token_amount: TokenAmount, slippage: float = 5): + client = Client( + private_key=config.PRIVATE_KEY, + rpc='https://mainnet.era.zksync.io', + proxy=config.PROXY + ) + + path = [ + AsyncWeb3.to_checksum_address('0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91'), # WETH + AsyncWeb3.to_checksum_address('0x838A66F841DD5148475a8918db0732c239499a03'), # STAR + AsyncWeb3.to_checksum_address('0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4'), # USDC.e + AsyncWeb3.to_checksum_address('0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4'), # USDC + ] + + from_token_address = path[0] + from_token_contract = client.w3.eth.contract( + address=from_token_address, + abi=ABIs.TokenABI + ) + from_token_symbol = await from_token_contract.functions.symbol().call() + + to_token_address = path[-1] + to_token_contract = client.w3.eth.contract( + address=to_token_address, + abi=ABIs.TokenABI + ) + to_token_symbol = await to_token_contract.functions.symbol().call() + + # адрес свапалки + router_address = AsyncWeb3.to_checksum_address('0xbE7D1FD1f6748bbDefC4fbaCafBb11C6Fc506d1d') + router_contract = client.w3.eth.contract( + address=router_address, + abi=ABIs.SpaceFiABIBytes + ) + + from_token_price_dollar = await client.get_token_price(token_symbol=from_token_symbol) + to_token_price_dollar = await client.get_token_price(token_symbol=to_token_symbol) + amount_out_min = TokenAmount( + amount=float(from_token_amount.Ether) * from_token_price_dollar / to_token_price_dollar * ( + 100 - slippage) / 100, + decimals=await to_token_contract.functions.decimals().call() + ) + + data = router_contract.encodeABI('swap', + args=( + amount_out_min.Wei, + path, + client.account.address, + int(time.time() + 1200) + )) + data = '0x7ff36ab5' + data[10:] + + tx_hash = await client.send_transaction( + to=router_address, + data=data, + value=from_token_amount.Wei, + max_priority_fee_per_gas=0 + ) + + if tx_hash: + try: + await client.verif_tx(tx_hash=tx_hash) + print(f'Transaction success ({from_token_amount.Ether} ETH -> {amount_out_min.Ether} {to_token_symbol})!! ' + f'tx_hash: {tx_hash.hex()}') + # Transaction success (0.001 ETH -> 2.28988 USDC)!! tx_hash: 0x5e97aaaa972dc2aca2bdb8b6241fe6dd5bb9eaeb238d0dcd941c31c46198b51e + except Exception as err: + print(f'Transaction error!! tx_hash: {tx_hash.hex()}; error: {err}') + else: + print(f'Transaction error!!') + + +if __name__ == '__main__': + amount = TokenAmount(0.00001) + asyncio.run(main(from_token_amount=amount)) diff --git a/lesson_03_unverified_contracts_part_1_dz/spacefi_params.py b/lesson_03_unverified_contracts_part_1_dz/spacefi_params.py new file mode 100644 index 0000000..43345e8 --- /dev/null +++ b/lesson_03_unverified_contracts_part_1_dz/spacefi_params.py @@ -0,0 +1,81 @@ +import asyncio +import time + +from web3 import AsyncWeb3 + +import config +from client import Client +from data.models import ABIs, TokenAmount + + +async def main(from_token_amount: TokenAmount, slippage: float = 5): + client = Client( + private_key=config.PRIVATE_KEY, + rpc='https://mainnet.era.zksync.io', + proxy=config.PROXY + ) + + path = [ + AsyncWeb3.to_checksum_address('0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91'), # WETH + AsyncWeb3.to_checksum_address('0x838A66F841DD5148475a8918db0732c239499a03'), # STAR + AsyncWeb3.to_checksum_address('0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4'), # USDC.e + AsyncWeb3.to_checksum_address('0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4'), # USDC + ] + + from_token_address = path[0] + from_token_contract = client.w3.eth.contract( + address=from_token_address, + abi=ABIs.TokenABI + ) + from_token_symbol = await from_token_contract.functions.symbol().call() + + to_token_address = path[-1] + to_token_contract = client.w3.eth.contract( + address=to_token_address, + abi=ABIs.TokenABI + ) + to_token_symbol = await to_token_contract.functions.symbol().call() + + # адрес свапалки + router_address = AsyncWeb3.to_checksum_address('0xbE7D1FD1f6748bbDefC4fbaCafBb11C6Fc506d1d') + router_contract = client.w3.eth.contract( + address=router_address, + abi=ABIs.SpaceFiABI + ) + + from_token_price_dollar = await client.get_token_price(token_symbol=from_token_symbol) + to_token_price_dollar = await client.get_token_price(token_symbol=to_token_symbol) + amount_out_min = TokenAmount( + amount=float(from_token_amount.Ether) * from_token_price_dollar / to_token_price_dollar * ( + 100 - slippage) / 100, + decimals=await to_token_contract.functions.decimals().call() + ) + + tx_hash = await client.send_transaction( + to=router_address, + data=router_contract.encodeABI('swapExactETHForTokens', + args=( + amount_out_min.Wei, + path, + client.account.address, + int(time.time() + 1200) + )), + value=from_token_amount.Wei, + max_priority_fee_per_gas=0 + ) + + if tx_hash: + try: + await client.verif_tx(tx_hash=tx_hash) + print(f'Transaction success ({from_token_amount.Ether} ETH -> {amount_out_min.Ether} {to_token_symbol})!! ' + f'tx_hash: {tx_hash.hex()}') + # Transaction success (0.001 ETH -> 2.28988 USDC)!! tx_hash: 0x5e97aaaa972dc2aca2bdb8b6241fe6dd5bb9eaeb238d0dcd941c31c46198b51e + except Exception as err: + print(f'Transaction error!! tx_hash: {tx_hash.hex()}; error: {err}') + else: + print(f'Transaction error!!') + + +if __name__ == '__main__': + amount = TokenAmount(0.00001) + asyncio.run(main(from_token_amount=amount)) diff --git a/lesson_03_unverified_contracts_part_1_dz/tasks/spacefi.py b/lesson_03_unverified_contracts_part_1_dz/tasks/spacefi.py new file mode 100644 index 0000000..9519cf4 --- /dev/null +++ b/lesson_03_unverified_contracts_part_1_dz/tasks/spacefi.py @@ -0,0 +1,455 @@ +import asyncio +import time +from web3 import AsyncWeb3 +from client import Client +from data.models import ABIs, TokenAmount + + +class SpaceFi: + def __init__(self, client: Client): + self.client = client + + async def get_raw_tx_params(self, wei_value: float = 0) -> dict: + return { + "chainId": await self.client.w3.eth.chain_id, + "from": self.client.account.address, + "value": wei_value, + "gasPrice": await self.client.w3.eth.gas_price, + "nonce": await self.client.w3.eth.get_transaction_count(self.client.account.address), + } + + async def swap_eth_to_usdt( + self, + token_amount: TokenAmount, + slippage: float = 0.5 + ): + path = [ + AsyncWeb3.to_checksum_address('0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91'), # WETH + AsyncWeb3.to_checksum_address('0x493257fD37EDB34451f62EDf8D2a0C418852bA4C') # USDT + ] + + from_token_address = path[0] + from_token_contract = self.client.w3.eth.contract( + address=from_token_address, + abi=ABIs.TokenABI + ) + to_token_address = path[-1] + to_token_contract = self.client.w3.eth.contract( + address=to_token_address, + abi=ABIs.TokenABI + ) + from_token_symbol, to_token_symbol = await asyncio.gather( + from_token_contract.functions.symbol().call(), + to_token_contract.functions.symbol().call(), + ) + + router_address = AsyncWeb3.to_checksum_address('0xbE7D1FD1f6748bbDefC4fbaCafBb11C6Fc506d1d') + router_contract = self.client.w3.eth.contract( + address=router_address, + abi=ABIs.SpaceFiABI + ) + from_token_price_dollar, to_token_price_dollar = await asyncio.gather( + self.client.get_token_price(token_symbol=from_token_symbol), + self.client.get_token_price(token_symbol=to_token_symbol) + ) + + amount_out_min = TokenAmount( + amount=float(token_amount.Ether) + * from_token_price_dollar + / to_token_price_dollar + * (100 - slippage) / 100, + decimals=await to_token_contract.functions.decimals().call() + ) + + tx_params = await router_contract.functions.swapExactETHForTokens( + amount_out_min.Wei, + path, + self.client.account.address, + int(time.time() + 1200) + ).build_transaction( + await self.get_raw_tx_params(wei_value=token_amount.Wei) + ) + + signed_tx = self.client.w3.eth.account.sign_transaction( + tx_params, + self.client.private_key + ) + tx_hash_bytes = await self.client.w3.eth.send_raw_transaction(signed_tx.rawTransaction) + receipt = await self.client.w3.eth.wait_for_transaction_receipt(tx_hash_bytes) + + if receipt['status']: + try: + await self.client.verif_tx(tx_hash=tx_hash_bytes) + print(f'Transaction success ({token_amount.Ether} {from_token_symbol} -> {amount_out_min.Ether} {to_token_symbol})!! ' + f'tx_hash: {tx_hash_bytes.hex()}') + # Transaction success (0.001 ETH -> 2.87 USDT)!! tx_hash: 0x358ab333050193e02623c0b81aad6acea73f358eabd35e6c7526a5e7f52b98db + except Exception as err: + print(f'Transaction error!! tx_hash: {tx_hash_bytes.hex()}; error: {err}') + else: + print(f'Transaction error!!') + + async def swap_eth_to_wbtc( + self, + token_amount: TokenAmount, + slippage: float = 0.5 + ): + path = { + 'ETH': AsyncWeb3.to_checksum_address('0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91'), # WETH + 'WBTC': AsyncWeb3.to_checksum_address('0xBBeB516fb02a01611cBBE0453Fe3c580D7281011') # WBTC + } + + to_token_address = path['WBTC'] + to_token_contract = self.client.w3.eth.contract( + address=to_token_address, + abi=ABIs.TokenABI + ) + + router_address = AsyncWeb3.to_checksum_address('0xbE7D1FD1f6748bbDefC4fbaCafBb11C6Fc506d1d') + router_contract = self.client.w3.eth.contract( + address=router_address, + abi=ABIs.SpaceFiABI + ) + + from_token_price_dollar = await self.client.get_token_price(token_symbol='ETH') + to_token_price_dollar = await self.client.get_token_price(token_symbol='WBTC') + + amount_out_min = TokenAmount( + amount=float(token_amount.Ether) + * from_token_price_dollar + / to_token_price_dollar + * (100 - slippage) / 100, + decimals=await to_token_contract.functions.decimals().call() + ) + + tx_hash = await self.client.send_transaction( + to=router_address, + data = router_contract.encodeABI( + 'swapExactTokensForETH', + args=( + token_amount.Wei, + amount_out_min.Wei, + path, + self.client.account.address, + int(time.time() + 1200) + ) + ), + value=token_amount.Wei, + max_priority_fee_per_gas=0 + ) + + if tx_hash: + try: + await self.client.verif_tx(tx_hash=tx_hash) + print(f'Transaction success ({token_amount.Ether} ETH -> {amount_out_min.Ether} WBTC)!! ' + f'tx_hash: {tx_hash.hex()}') + # Transaction success (0.0008 ETH -> 0.000029105322888639857 WBTC)!! tx_hash: 0x669310c1ec16ed385e8d0778cc96c05e2bc3d8b2e6d3490f4363b370bc6d2446 + except Exception as err: + print(f'Transaction error!! tx_hash: {tx_hash.hex()}; error: {err}') + else: + print(f'Transaction error!!') + + async def swap_usdc_e_to_eth( + self, + token_amount: TokenAmount, + slippage: float = 0.5 + ): + path = [ + AsyncWeb3.to_checksum_address('0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4'), # USDC.e + AsyncWeb3.to_checksum_address('0x47260090cE5e83454d5f05A0AbbB2C953835f777'), # SPACE + AsyncWeb3.to_checksum_address('0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91') # WETH + ] + + from_token_address = path[0] + from_token_contract = self.client.w3.eth.contract( + address=from_token_address, + abi=ABIs.TokenABI + ) + from_token_symbol = await from_token_contract.functions.symbol().call() + + to_token_address = path[-1] + to_token_contract = self.client.w3.eth.contract( + address=to_token_address, + abi=ABIs.TokenABI + ) + to_token_symbol = await to_token_contract.functions.symbol().call() + + router_address = AsyncWeb3.to_checksum_address('0xbE7D1FD1f6748bbDefC4fbaCafBb11C6Fc506d1d') + router_contract = self.client.w3.eth.contract( + address=router_address, + abi=ABIs.SpaceFiABI + ) + + from_token_price_dollar = await self.client.get_token_price(token_symbol=from_token_symbol) + to_token_price_dollar = await self.client.get_token_price(token_symbol=to_token_symbol) + + amount_out_min = TokenAmount( + amount=float(token_amount.Ether) + * from_token_price_dollar + / to_token_price_dollar + * (100 - slippage) / 100, + decimals=await to_token_contract.functions.decimals().call() + ) + + tx_hash_bytes = await self.client.send_transaction( + to=from_token_address, + data = from_token_contract.encodeABI( + 'approve', + args=( + router_address, + token_amount.Wei + ) + ), + max_priority_fee_per_gas=0 + ) + + tx_hash = await self.client.verif_tx(tx_hash_bytes) + + if tx_hash: + waiting_time = 15 + print( + f'Approved {token_amount.Ether} {from_token_symbol} to swap on SpaceFi, ' + f'sleeping {waiting_time} secs...' + ) + await asyncio.sleep(waiting_time) + else: + print('Failed') + + tx_hash_bytes = await self.client.send_transaction( + to=router_address, + data = router_contract.encodeABI( + 'swapExactTokensForETH', + args=( + token_amount.Wei, + amount_out_min.Wei, + path, + self.client.account.address, + int(time.time() + 1200) + ) + ), + max_priority_fee_per_gas=0 + ) + + if tx_hash_bytes: + try: + tx_hash = await self.client.verif_tx(tx_hash=tx_hash_bytes) + print(f'Transaction success ({token_amount.Ether} {from_token_symbol} -> {amount_out_min.Ether} {to_token_symbol})!! ' + f'tx_hash: {tx_hash}') + # Transaction success (1.961663 USDC.e -> 0.0006465444482972901 WETH)!! tx_hash: 0x0161e7cb528408427fce8eda171a251632d0b28cb89bf8dfd9616189964ae08b + except Exception as err: + print(f'Transaction error!! tx_hash: {tx_hash}; error: {err}') + else: + print(f'Transaction error!!') + + async def swap_usdt_to_eth( + self, + token_amount: TokenAmount | None = None, + slippage: float = 0.5, + is_all_balance: bool = False + ): + path = [ + AsyncWeb3.to_checksum_address('0x493257fD37EDB34451f62EDf8D2a0C418852bA4C'), # USDT + AsyncWeb3.to_checksum_address('0x47260090cE5e83454d5f05A0AbbB2C953835f777'), # SPACE + AsyncWeb3.to_checksum_address('0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91') # WETH + ] + + from_token_address = path[0] + from_token_contract = self.client.w3.eth.contract( + address=from_token_address, + abi=ABIs.TokenABI + ) + to_token_address = path[-1] + to_token_contract = self.client.w3.eth.contract( + address=to_token_address, + abi=ABIs.TokenABI + ) + from_token_symbol, to_token_symbol = await asyncio.gather( + from_token_contract.functions.symbol().call(), + to_token_contract.functions.symbol().call(), + ) + + if is_all_balance: + from_token_balance, from_token_decimals = await asyncio.gather( + from_token_contract.functions.balanceOf(self.client.account.address).call(), + from_token_contract.functions.decimals().call() + ) + token_amount = TokenAmount( + amount=from_token_balance, + decimals=from_token_decimals, + wei=True + ) + + router_address = AsyncWeb3.to_checksum_address('0xbE7D1FD1f6748bbDefC4fbaCafBb11C6Fc506d1d') + router_contract = self.client.w3.eth.contract( + address=router_address, + abi=ABIs.SpaceFiABIBytesTokenToToken + ) + from_token_price_dollar, to_token_price_dollar = await asyncio.gather( + self.client.get_token_price(token_symbol=from_token_symbol), + self.client.get_token_price(token_symbol=to_token_symbol) + ) + + amount_out_min = TokenAmount( + amount=float(token_amount.Ether) + * from_token_price_dollar + / to_token_price_dollar + * (100 - slippage) / 100, + decimals=await to_token_contract.functions.decimals().call() + ) + + tx_params = await from_token_contract.functions.approve( + router_address, + token_amount.Wei + ).build_transaction(await self.get_raw_tx_params()) + + signed_tx = self.client.w3.eth.account.sign_transaction(tx_params, self.client.private_key) + tx_hash_bytes = await self.client.w3.eth.send_raw_transaction(signed_tx.rawTransaction) + receipt = await self.client.w3.eth.wait_for_transaction_receipt(tx_hash_bytes) + + if receipt['status']: + waiting_time = 15 + print( + f'Approved {token_amount.Ether} {from_token_symbol} to swap on SpaceFi, ' + f'sleeping {waiting_time} secs...' + ) + await asyncio.sleep(waiting_time) + else: + print('Failed') + + data = router_contract.encodeABI( + 'swap', + args=( + token_amount.Wei, + amount_out_min.Wei, + path, + self.client.account.address, + int(time.time() + 1200) + ) + ) + data = '0x18cbafe5' + data[10:] + + tx_hash_bytes = await self.client.send_transaction( + to=router_address, + data=data, + max_priority_fee_per_gas=0 + ) + + if tx_hash_bytes: + try: + tx_hash = await self.client.verif_tx(tx_hash=tx_hash_bytes) + print(f'Transaction success ({token_amount.Ether} {from_token_symbol} -> {amount_out_min.Ether} {to_token_symbol})!! ' + f'tx_hash: {tx_hash}') + # Transaction success (0.0004 ETH -> 1.13988 USDT)!! tx_hash: 0x16ed6ce885e1f65a4a068b5e9253a5ebe2251ae93ed878ab583830515c627fb0 + except Exception as err: + print(f'Transaction error!! tx_hash: {tx_hash}; error: {err}') + else: + print(f'Transaction error!!') + + async def swap_usdt_to_usdc_e( + self, + token_amount: TokenAmount | None = None, + slippage: float = 0.5, + is_all_balance: bool = False + ): + path = [ + AsyncWeb3.to_checksum_address('0x493257fD37EDB34451f62EDf8D2a0C418852bA4C'), # USDT + AsyncWeb3.to_checksum_address('0x47260090cE5e83454d5f05A0AbbB2C953835f777'), # SPACE + AsyncWeb3.to_checksum_address('0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4'), # USDC.E + ] + + from_token_address = path[0] + from_token_contract = self.client.w3.eth.contract( + address=from_token_address, + abi=ABIs.TokenABI + ) + to_token_address = path[-1] + to_token_contract = self.client.w3.eth.contract( + address=to_token_address, + abi=ABIs.TokenABI + ) + from_token_symbol, to_token_symbol = await asyncio.gather( + from_token_contract.functions.symbol().call(), + to_token_contract.functions.symbol().call(), + ) + + if is_all_balance: + from_token_balance, from_token_decimals = await asyncio.gather( + from_token_contract.functions.balanceOf(self.client.account.address).call(), + from_token_contract.functions.decimals().call() + ) + token_amount = TokenAmount( + amount=from_token_balance, + decimals=from_token_decimals, + wei=True + ) + + router_address = AsyncWeb3.to_checksum_address('0xbE7D1FD1f6748bbDefC4fbaCafBb11C6Fc506d1d') + router_contract = self.client.w3.eth.contract( + address=router_address, + abi=ABIs.SpaceFiABIBytesTokenToToken + ) + + from_token_price_dollar, to_token_price_dollar = await asyncio.gather( + self.client.get_token_price(token_symbol=from_token_symbol), + self.client.get_token_price(token_symbol=to_token_symbol) + ) + + amount_out_min = TokenAmount( + amount=float(token_amount.Ether) + * from_token_price_dollar + / to_token_price_dollar + * (100 - slippage) / 100, + decimals=await to_token_contract.functions.decimals().call() + ) + + tx_hash_bytes = await self.client.send_transaction( + to=from_token_address, + data = from_token_contract.encodeABI( + 'approve', + args=( + router_address, + token_amount.Wei + ) + ), + max_priority_fee_per_gas=0 + ) + + tx_hash = await self.client.verif_tx(tx_hash_bytes) + + if tx_hash: + waiting_time = 15 + print( + f'Approved {token_amount.Ether} {from_token_symbol} to swap on SpaceFi, ' + f'sleeping {waiting_time} secs...' + ) + await asyncio.sleep(waiting_time) + else: + print('Failed') + + data = router_contract.encodeABI( + 'swap', + args=( + token_amount.Wei, + amount_out_min.Wei, + path, + self.client.account.address, + int(time.time() + 1200) + ) + ) + data = '0x38ed1739' + data[10:] + + tx_hash_bytes = await self.client.send_transaction( + to=router_address, + data=data, + max_priority_fee_per_gas=0 + ) + + if tx_hash_bytes: + try: + tx_hash = await self.client.verif_tx(tx_hash=tx_hash_bytes) + print(f'Transaction success ({token_amount.Ether} {from_token_symbol} -> {amount_out_min.Ether} {to_token_symbol})!! ' + f'tx_hash: {tx_hash}') + # Transaction success (2.027439 USDT -> 1.946341 USDC.e)!! tx_hash: 0xbd678a795c66238f067a0df7f49c759d7e3bc422a60c8fd7baadd1532566c98c + except Exception as err: + print(f'Transaction error!! tx_hash: {tx_hash}; error: {err}') + else: + print(f'Transaction error!!') \ No newline at end of file diff --git a/lesson_03_unverified_contracts_part_1_dz/tasks/spacefi_hard.py b/lesson_03_unverified_contracts_part_1_dz/tasks/spacefi_hard.py new file mode 100644 index 0000000..eebaa7d --- /dev/null +++ b/lesson_03_unverified_contracts_part_1_dz/tasks/spacefi_hard.py @@ -0,0 +1,190 @@ +import asyncio +import time +from web3 import AsyncWeb3 +from client import Client +from data.models import ABIs, TokenAmount + +ZKSYNC_TOKENS_DICT = { + # 'WETH': '0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91', + 'WBTC': '0xBBeB516fb02a01611cBBE0453Fe3c580D7281011', + 'USDT': '0x493257fD37EDB34451f62EDf8D2a0C418852bA4C', + 'USDC': '0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4', + 'USDC.E': '0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4', +} + +SPACEFI_PATHS = { + ('ETH', 'USDT'): [ + '0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91', # WETH + '0x47260090cE5e83454d5f05A0AbbB2C953835f777', # SPACE + '0x493257fD37EDB34451f62EDf8D2a0C418852bA4C' # USDT + ], + ('ETH', 'USDC'): [ + '0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91', # WETH + '0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4' # USDC + ], + ('ETH', 'WBTC'): [ + '0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91', # WETH + '0xBBeB516fb02a01611cBBE0453Fe3c580D7281011' # WBTC + ], + ('USDT', 'ETH'): [ + '0x493257fD37EDB34451f62EDf8D2a0C418852bA4C', # USDT + '0x47260090cE5e83454d5f05A0AbbB2C953835f777', # SPACE + '0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91' # WETH + ], + ('USDC.E', 'ETH'): [ + '0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4', # USDC.E + '0x47260090cE5e83454d5f05A0AbbB2C953835f777', # SPACE + '0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91' # WETH + ], + ('USDC', 'USDT'): [ + '0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4', # USDC + '0x47260090cE5e83454d5f05A0AbbB2C953835f777', # SPACE + '0x493257fD37EDB34451f62EDf8D2a0C418852bA4C', # USDT + ], + ('USDT', 'USDC.E'): [ + '0x493257fD37EDB34451f62EDf8D2a0C418852bA4C', # USDT + '0x47260090cE5e83454d5f05A0AbbB2C953835f777', # SPACE + '0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4', # USDC + ], + ('WBTC', 'ETH'): [ + '0xBBeB516fb02a01611cBBE0453Fe3c580D7281011', # WBTC + '0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4', # USDC + '0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91' # WETH + ] +} + +class SpaceFiHard: + def __init__(self, client: Client): + self.client = client + + async def get_raw_tx_params(self, wei_value: float = 0) -> dict: + return { + "chainId": await self.client.w3.eth.chain_id, + "from": self.client.account.address, + "value": wei_value, + "gasPrice": await self.client.w3.eth.gas_price, + "nonce": await self.client.w3.eth.get_transaction_count(self.client.account.address), + } + + async def swap( + self, + from_token_symbol: str, + to_token_symbol: str, + amount: float, + slippage: float = 0.5 + ): + from_token_symbol = from_token_symbol.upper() + to_token_symbol = to_token_symbol.upper() + + if from_token_symbol == to_token_symbol: + print(f'Invalid input params: {from_token_symbol} -> {to_token_symbol}') + return + + router_address = AsyncWeb3.to_checksum_address('0xbE7D1FD1f6748bbDefC4fbaCafBb11C6Fc506d1d') + router_contract = self.client.w3.eth.contract( + address=router_address, + abi=ABIs.SpaceFiABI + ) + + from_token_price_dollar = await self.client.get_token_price(token_symbol=from_token_symbol) + to_token_price_dollar = await self.client.get_token_price(token_symbol=to_token_symbol) + + if from_token_symbol != 'ETH': + from_token_contract = self.client.w3.eth.contract( + address=AsyncWeb3.to_checksum_address(ZKSYNC_TOKENS_DICT[from_token_symbol]), + abi=ABIs.TokenABI + ) + from_token_decimals = await from_token_contract.functions.decimals().call() + else: + from_token_decimals = 18 + + if to_token_symbol != 'ETH': + to_token_contract = self.client.w3.eth.contract( + address=AsyncWeb3.to_checksum_address(ZKSYNC_TOKENS_DICT[to_token_symbol]), + abi=ABIs.TokenABI + ) + to_token_decimals = await to_token_contract.functions.decimals().call() + else: + to_token_decimals = 18 + + amount_in = TokenAmount( + amount=amount, + decimals=from_token_decimals + ) + amount_out_min = TokenAmount( + amount=float(amount_in.Ether) + * from_token_price_dollar + / to_token_price_dollar + * (1 - slippage / 100), + decimals=to_token_decimals + ) + + checksum_path = [ + AsyncWeb3.to_checksum_address(path_part) + for path_part in SPACEFI_PATHS[(from_token_symbol, to_token_symbol)] + ] + args_list = [ + amount_out_min.Wei, + checksum_path, + self.client.account.address, + int(time.time() + 1200) + ] + + value = 0 + if from_token_symbol != 'ETH': + args_list.insert(0, amount_in.Wei) + + approve = await self.client.send_transaction( + to=from_token_contract.address, + data=from_token_contract.encodeABI( + 'approve', + args=(router_address, amount_in.Wei) + ), + max_priority_fee_per_gas=0 + ) + + approve_tx_hash = await self.client.verif_tx(approve) + + if approve_tx_hash: + waiting_time = 15 + print( + f'Approved {amount_in.Ether} {from_token_symbol} to swap on SpaceFi, ' + f'sleeping {waiting_time} secs...' + ) + await asyncio.sleep(waiting_time) + else: + print('Failed') + else: + value = amount_in.Wei + + method_name = ( + 'swapExactTokensForETH' + if from_token_symbol != 'ETH' + else 'swapExactETHForTokens' + ) + + data=router_contract.encodeABI( + method_name, + args=tuple(args_list) + ) + tx_hash_bytes = await self.client.send_transaction( + to=router_address, + data=data, + value=value, + max_priority_fee_per_gas=0 + ) + + if tx_hash_bytes: + try: + tx_hash = await self.client.verif_tx(tx_hash=tx_hash_bytes) + print(f'Transaction success ({amount_in.Ether} {from_token_symbol} -> {amount_out_min.Ether} {to_token_symbol})!! ' + f'tx_hash: {tx_hash}') + # Transaction success (0.001 ETH -> 2.7496135 USDT)!! tx_hash: 0x4a932d2340742a921ab9d2c52d65ab38671cd8f707a2497439d6ec5b5cec602f + # Transaction success (0.87 USDT -> 0.0002914421881475342 ETH)!! tx_hash: 0x8e505d335bfe54f961848bbb961f15aa84b51ccbcc283156476c01b470a42d1b + # Transaction success (2.0 USDT -> 0.000621587742289722 ETH)!! tx_hash: 0xdaae5af02bfa7c2a0922d88840a535d027f56b16b48e5a7cdebb9f4616ef3d2c + # Transaction success (0.0008 ETH -> 0.000029105322888639857 WBTC)!! tx_hash: 0x669310c1ec16ed385e8d0778cc96c05e2bc3d8b2e6d3490f4363b370bc6d2446 + # Transaction success (0.00003 WBTC -> 0.0007439477656330048 ETH)!! tx_hash: 0x5879c726265b08d2c62424401ca4b03b89e4eef249f0d40983f05b3a24a872af + except Exception as err: + print(f'Transaction error!! tx_hash: {tx_hash}; error: {err}') + else: + print(f'Transaction error!!') \ No newline at end of file diff --git a/lesson_03_unverified_contracts_part_1_dz/utils.py b/lesson_03_unverified_contracts_part_1_dz/utils.py new file mode 100644 index 0000000..f8f4c36 --- /dev/null +++ b/lesson_03_unverified_contracts_part_1_dz/utils.py @@ -0,0 +1,8 @@ +import json +import os + + +def get_json(path: str | list[str] | tuple[str]): + if isinstance(path, (list, tuple)): + path = os.path.join(*path) + return json.load(open(path)) From bc42d3453ca4e6f92b9850b4ad42458f676eaf10 Mon Sep 17 00:00:00 2001 From: maked0n1an Date: Fri, 8 Nov 2024 12:37:45 +0200 Subject: [PATCH 03/18] updated main.py --- lesson_03_unverified_contracts_part_1_dz/main.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lesson_03_unverified_contracts_part_1_dz/main.py b/lesson_03_unverified_contracts_part_1_dz/main.py index 4a66c85..3f75812 100644 --- a/lesson_03_unverified_contracts_part_1_dz/main.py +++ b/lesson_03_unverified_contracts_part_1_dz/main.py @@ -24,12 +24,12 @@ async def main(): # ) spacefi = SpaceFiHard(client) - await spacefi.swap( - from_token_symbol='WBTC', - to_token_symbol='ETH', - amount=0.000023, - slippage=5 - ) + # await spacefi.swap( + # from_token_symbol='WBTC', + # to_token_symbol='ETH', + # amount=0.000023, + # slippage=5 + # ) if __name__ == "__main__": asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) From 5731748e8cc861abf774bb06e7c063f29188cdb8 Mon Sep 17 00:00:00 2001 From: maked0n1an Date: Fri, 8 Nov 2024 21:16:41 +0200 Subject: [PATCH 04/18] Updated. --- .../info.txt | 9 +++-- .../main.py | 37 ++++++++++++------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/lesson_03_unverified_contracts_part_1_dz/info.txt b/lesson_03_unverified_contracts_part_1_dz/info.txt index 1bb3a39..b0266ea 100644 --- a/lesson_03_unverified_contracts_part_1_dz/info.txt +++ b/lesson_03_unverified_contracts_part_1_dz/info.txt @@ -7,7 +7,7 @@ - задание №1: сделать скрипт на следующие cвапы в сети zkSync на площадке SpaceFi (https://swap-zksync.spacefi.io/#/swap): swap_eth_to_usdt swap_eth_to_wbtc -swap_usdc_to_eth +swap_usdc_e_to_eth Примеры: (0.001 ETH -> 2.87 USDT): https://era.zksync.network/tx/0x358ab333050193e02623c0b81aad6acea73f358eabd35e6c7526a5e7f52b98db (0.0008 ETH -> 0.000029 WBTC): https://era.zksync.network/tx/0x669310c1ec16ed385e8d0778cc96c05e2bc3d8b2e6d3490f4363b370bc6d2446 @@ -21,9 +21,10 @@ swap_usdt_to_usdc_e Примеры: (0.001 ETH -> 2.87 USDT): https://era.zksync.network/tx/0x0161e7cb528408427fce8eda171a251632d0b28cb89bf8dfd9616189964ae08b (1.734763 USDT -> 1.665372 USDC.e): https://era.zksync.network/tx/0x179df85ee97094190b17433b68c0a87f382a39188a44255917ed0afed9a386b2 -Подсказка: сигнатура функции -async def swap_usdt_to_eth(self, token_amount: TokenAmount | None = None, slippage: float = 0.5, is_all_balance: bool = False) -> str. - задание №3*: реализовать скрипт для универсальных свапов from_token -> to_token -Подсказка: сигнатура функции + +Подсказка к заданию №2: сигнатура функции +async def swap_usdt_to_eth(self, token_amount: TokenAmount | None = None, slippage: float = 0.5, is_all_balance: bool = False) -> str. +Подсказка к заданию №3: сигнатура функции async def swap(self, from_token_symbol, to_token_symbol, amount, slippage) -> str. \ No newline at end of file diff --git a/lesson_03_unverified_contracts_part_1_dz/main.py b/lesson_03_unverified_contracts_part_1_dz/main.py index 3f75812..f646a85 100644 --- a/lesson_03_unverified_contracts_part_1_dz/main.py +++ b/lesson_03_unverified_contracts_part_1_dz/main.py @@ -14,22 +14,31 @@ async def main(): ) spacefi = SpaceFi(client) - # await spacefi.swap_eth_to_usdt( - # token_amount=TokenAmount(0.0006), - # slippage=5 - # ) - # await spacefi.swap_usdt_to_usdc_e( - # slippage=4, - # is_all_balance=True - # ) + await spacefi.swap_eth_to_usdt( + token_amount=TokenAmount(0.0006), + slippage=5 + ) + await spacefi.swap_eth_to_wbtc( + token_amount=TokenAmount(0.001), + slippage=5 + ) + await spacefi.swap_usdt_to_usdc_e( + slippage=5, + is_all_balance=True + ) + await spacefi.swap_usdc_e_to_eth( + token_amount=TokenAmount(2, decimals=6), + slippage=5, + is_all_balance=True + ) spacefi = SpaceFiHard(client) - # await spacefi.swap( - # from_token_symbol='WBTC', - # to_token_symbol='ETH', - # amount=0.000023, - # slippage=5 - # ) + await spacefi.swap( + from_token_symbol='WBTC', + to_token_symbol='ETH', + amount=0.00004, + slippage=5 + ) if __name__ == "__main__": asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) From aee8932ef19771fdf79afa47c764caa1461f6fba Mon Sep 17 00:00:00 2001 From: maked0n1an Date: Sun, 10 Nov 2024 22:07:47 +0200 Subject: [PATCH 05/18] inited. --- .../client.py | 135 +++++++ .../config.py | 2 + .../data/models.py | 330 ++++++++++++++++++ .../info.txt | 26 ++ .../koi_finance.py | 120 +++++++ .../maveric.py | 105 ++++++ .../requirements.txt | 3 + .../spacefi_bytes.py | 109 ++++++ .../spacefi_params.py | 81 +++++ .../syncswap.py | 180 ++++++++++ .../utils.py | 27 ++ 11 files changed, 1118 insertions(+) create mode 100644 lesson_03_unverified_contracts_part_2_dz/client.py create mode 100644 lesson_03_unverified_contracts_part_2_dz/config.py create mode 100644 lesson_03_unverified_contracts_part_2_dz/data/models.py create mode 100644 lesson_03_unverified_contracts_part_2_dz/info.txt create mode 100644 lesson_03_unverified_contracts_part_2_dz/koi_finance.py create mode 100644 lesson_03_unverified_contracts_part_2_dz/maveric.py create mode 100644 lesson_03_unverified_contracts_part_2_dz/requirements.txt create mode 100644 lesson_03_unverified_contracts_part_2_dz/spacefi_bytes.py create mode 100644 lesson_03_unverified_contracts_part_2_dz/spacefi_params.py create mode 100644 lesson_03_unverified_contracts_part_2_dz/syncswap.py create mode 100644 lesson_03_unverified_contracts_part_2_dz/utils.py diff --git a/lesson_03_unverified_contracts_part_2_dz/client.py b/lesson_03_unverified_contracts_part_2_dz/client.py new file mode 100644 index 0000000..090fcba --- /dev/null +++ b/lesson_03_unverified_contracts_part_2_dz/client.py @@ -0,0 +1,135 @@ +import asyncio + +from curl_cffi.requests import AsyncSession +from eth_account.signers.local import LocalAccount +from eth_typing import ChecksumAddress, HexStr +from fake_useragent import UserAgent +from hexbytes import HexBytes +from web3 import AsyncWeb3, Web3 +from web3.exceptions import Web3Exception +from web3.middleware import geth_poa_middleware + +class Client: + private_key: str + rpc: str + proxy: str | None + w3: AsyncWeb3 + account: LocalAccount + + def __init__(self, private_key: str, rpc: str, proxy: str | None = None): + self.private_key = private_key + self.rpc = rpc + self.proxy = proxy + + if self.proxy: + if '://' not in self.proxy: + self.proxy = f'http://{self.proxy}' + + self.headers = { + 'accept': '*/*', + 'accept-language': 'en-US,en;q=0.9', + 'content-type': 'application/json', + 'user-agent': UserAgent().chrome + } + + self.w3 = AsyncWeb3(AsyncWeb3.AsyncHTTPProvider( + endpoint_uri=rpc, + request_kwargs={'proxy': self.proxy, 'headers': self.headers} + )) + self.account = self.w3.eth.account.from_key(private_key) + + def max_priority_fee(self, block: dict | None = None) -> int: + w3 = Web3(provider=AsyncWeb3.HTTPProvider(endpoint_uri=self.rpc)) + w3.middleware_onion.inject(geth_poa_middleware, layer=0) + + if not block: + block = w3.eth.get_block('latest') + + block_number = block['number'] + latest_block_transaction_count = w3.eth.get_block_transaction_count(block_number) + max_priority_fee_per_gas_lst = [] + for i in range(latest_block_transaction_count): + try: + transaction = w3.eth.get_transaction_by_block(block_number, i) + if 'maxPriorityFeePerGas' in transaction: + max_priority_fee_per_gas_lst.append(transaction['maxPriorityFeePerGas']) + except Exception: + continue + + if not max_priority_fee_per_gas_lst: + max_priority_fee_per_gas = 0 + else: + max_priority_fee_per_gas_lst.sort() + max_priority_fee_per_gas = max_priority_fee_per_gas_lst[len(max_priority_fee_per_gas_lst) // 2] + return max_priority_fee_per_gas + + async def send_transaction( + self, + to: str | ChecksumAddress, + data: HexStr | None = None, + from_: str | ChecksumAddress | None = None, + increase_gas: float = 1, + value: int | None = None, + eip1559: bool = True, + max_priority_fee_per_gas: int | None = None + ) -> HexBytes | None: + if not from_: + from_ = self.account.address + + tx_params = { + 'chainId': await self.w3.eth.chain_id, + 'nonce': await self.w3.eth.get_transaction_count(self.account.address), + 'from': AsyncWeb3.to_checksum_address(from_), + 'to': AsyncWeb3.to_checksum_address(to), + } + + if eip1559: + if max_priority_fee_per_gas is None: + max_priority_fee_per_gas = await self.w3.eth.max_priority_fee + base_fee = (await self.w3.eth.get_block('latest'))['baseFeePerGas'] + max_fee_per_gas = base_fee + max_priority_fee_per_gas + tx_params['maxFeePerGas'] = max_fee_per_gas # максимальная общая комиссия + tx_params['maxPriorityFeePerGas'] = max_priority_fee_per_gas # приоритетная комиссия майнеру + else: + tx_params['gasPrice'] = await self.w3.eth.gas_price + + if data: + tx_params['data'] = data + if value: + tx_params['value'] = value + + gas = await self.w3.eth.estimate_gas(tx_params) + tx_params['gas'] = int(gas * increase_gas) + + sign = self.w3.eth.account.sign_transaction(tx_params, self.private_key) + return await self.w3.eth.send_raw_transaction(sign.rawTransaction) + + async def verif_tx(self, tx_hash: HexBytes, timeout: int = 200) -> str: + data = await self.w3.eth.wait_for_transaction_receipt(tx_hash, timeout=timeout) + if data.get('status') == 1: + return tx_hash.hex() + raise Web3Exception(f'transaction failed {data["transactionHash"].hex()}') + + @staticmethod + async def get_token_price(token_symbol='ETH') -> float | None: + token_symbol = token_symbol.upper() + + if token_symbol in ('USDC', 'USDT', 'DAI', 'CEBUSD', 'BUSD', 'USDC.E'): + return 1 + if token_symbol == 'WETH': + token_symbol = 'ETH' + if token_symbol == 'WBTC': + token_symbol = 'BTC' + + for _ in range(5): + try: + async with AsyncSession() as session: + response = await session.get( + f'https://api.binance.com/api/v3/depth?limit=1&symbol={token_symbol}USDT') + result_dict = response.json() + if 'asks' not in result_dict: + return + return float(result_dict['asks'][0][0]) + except Exception: + await asyncio.sleep(5) + raise ValueError(f'Can not get {token_symbol} price from Binance API') diff --git a/lesson_03_unverified_contracts_part_2_dz/config.py b/lesson_03_unverified_contracts_part_2_dz/config.py new file mode 100644 index 0000000..c4c851c --- /dev/null +++ b/lesson_03_unverified_contracts_part_2_dz/config.py @@ -0,0 +1,2 @@ +PRIVATE_KEY = '...' +PROXY = '...' diff --git a/lesson_03_unverified_contracts_part_2_dz/data/models.py b/lesson_03_unverified_contracts_part_2_dz/data/models.py new file mode 100644 index 0000000..7620ff8 --- /dev/null +++ b/lesson_03_unverified_contracts_part_2_dz/data/models.py @@ -0,0 +1,330 @@ +from decimal import Decimal + +from web3 import AsyncWeb3 + + +class TokenAmount: + Wei: int + Ether: Decimal + decimals: int + + def __init__(self, amount: int | float | str | Decimal, decimals: int = 18, wei: bool = False) -> None: + if wei: + self.Wei: int = int(amount) + self.Ether: Decimal = Decimal(str(amount)) / 10 ** decimals + + else: + self.Wei: int = int(Decimal(str(amount)) * 10 ** decimals) + self.Ether: Decimal = Decimal(str(amount)) + + self.decimals = decimals + + def __str__(self): + return f'{self.Wei}' + + +class Tokens: + WETH = AsyncWeb3.to_checksum_address('0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91') + USDC = AsyncWeb3.to_checksum_address('0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4') + USDC_E = AsyncWeb3.to_checksum_address('0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4') + USDT = AsyncWeb3.to_checksum_address('0x493257fd37edb34451f62edf8d2a0c418852ba4c') + STAR = AsyncWeb3.to_checksum_address('0x838A66F841DD5148475a8918db0732c239499a03') + ETH = AsyncWeb3.to_checksum_address(f'0x{"".zfill(40)}') + + USDC_E_ZK_LP = AsyncWeb3.to_checksum_address('0x40b768de8b2e4ed83d982804cb2fcc53d2529be9') + ZK_WETH_LP = AsyncWeb3.to_checksum_address('0x1a32a715b4ebef211bbf4baa414f563b25cc50c9') + ZK = AsyncWeb3.to_checksum_address('0x5a7d6b2f92c77fad6ccabd7ee0624e64907eaf3e') + ZERO = AsyncWeb3.to_checksum_address('0x0000000000000000000000000000000000000000') + + +class TxArgs: + def __init__(self, **kwargs) -> None: + self.__dict__.update(kwargs) + + def list(self) -> list[...]: + return list(self.__dict__.values()) + + def tuple(self) -> tuple[str, ...]: + return tuple(self.__dict__.values()) + + +class ABIs: + TokenABI = [ + { + 'constant': True, + 'inputs': [], + 'name': 'name', + 'outputs': [{'name': '', 'type': 'string'}], + 'payable': False, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': True, + 'inputs': [], + 'name': 'symbol', + 'outputs': [{'name': '', 'type': 'string'}], + 'payable': False, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': True, + 'inputs': [], + 'name': 'decimals', + 'outputs': [{'name': '', 'type': 'uint256'}], + 'payable': False, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': True, + 'inputs': [{'name': 'who', 'type': 'address'}], + 'name': 'balanceOf', + 'outputs': [{'name': '', 'type': 'uint256'}], + 'payable': False, + 'stateMutability': 'view', + 'type': 'function' + }, + { + 'constant': True, + 'inputs': [ + {'name': 'spender', 'type': 'address'}, + {'name': 'amount', 'type': 'uint256'} + ], + 'name': 'approve', + 'outputs': [], + 'payable': False, + 'stateMutability': 'nonpayable', + 'type': 'function' + }, + { + 'constant': True, + 'inputs': [ + {'name': 'owner', 'type': 'address'}, + {'name': 'spender', 'type': 'address'}, + ], + 'name': 'allowance', + 'outputs': [ + {'name': '', 'type': 'uint256'}, + ], + 'payable': False, + 'stateMutability': 'view', + 'type': 'function' + }, + ] + + SpaceFiABI = [ + { + "constant": False, + "inputs": [ + { + "name": "amountOutMin", + "type": "uint256" + }, + { + "name": "path", + "type": "address[]" + }, + { + "name": "to", + "type": "address" + }, + { + "name": "deadline", + "type": "uint256" + } + ], + "name": "swapExactETHForTokens", + "outputs": [ + { + "name": "", + "type": "uint256[]" + } + ], + "payable": True, + "stateMutability": "payable", + "type": "function" + } + ] + + SpaceFiABIBytes = [ + { + "constant": False, + "inputs": [ + { + "name": "", + "type": "uint256" + }, + { + "name": "", + "type": "address[]" + }, + { + "name": "", + "type": "address" + }, + { + "name": "", + "type": "uint256" + } + ], + "name": "swap", + "outputs": [], + "payable": True, + "stateMutability": "payable", + "type": "function" + } + ] + + KoiFinance = [ + { + "constant": False, + "inputs": [ + { + "name": "commands", + "type": "bytes" + }, + { + "name": "inputs", + "type": "bytes[]" + }, + { + "name": "deadline", + "type": "uint256" + } + ], + "name": "execute", + "outputs": [], + "payable": True, + "stateMutability": "payable", + "type": "function" + } + ] + + MavericABI = [ + { + "constant": False, + "inputs": [ + { + "components": [ + { + "name": "path", + "type": "bytes" + }, + { + "name": "wallet", + "type": "address" + }, + { + "name": "deadline", + "type": "uint256" + }, + { + "name": "amountIn", + "type": "uint256" + }, + { + "name": "amountOut", + "type": "uint256" + }, + ], + "name": "params", + "type": "tuple" + } + ], + "name": "exactInput", + "outputs": [], + "payable": True, + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "refundETH", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "name": "data", + "type": "bytes[]" + } + ], + "name": "multicall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + } + ] + + SyncSwapABI = [ + { + "inputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "address", + "name": "callback", + "type": "address" + }, + { + "internalType": "bytes", + "name": "callbackData", + "type": "bytes" + }, + { + "name": "flag", + "type": "bool" + } + ], + "internalType": "struct IRouter.SwapStep[]", + "name": "steps", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + } + ], + "internalType": "struct IRouter.SwapPath[]", + "name": "paths", + "type": "tuple[]" + }, + { + "internalType": "uint256", + "name": "amountOutMin", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "swap", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + ] diff --git a/lesson_03_unverified_contracts_part_2_dz/info.txt b/lesson_03_unverified_contracts_part_2_dz/info.txt new file mode 100644 index 0000000..15846df --- /dev/null +++ b/lesson_03_unverified_contracts_part_2_dz/info.txt @@ -0,0 +1,26 @@ +Урок 3, домашнее задание 2 - Неверифицированные контракты на примере zkSync (part 2) + +В этом уроке мы разберёмся как работать с неверифицированными контрактами, поработаем с площадкой Koi Finance, SyncSwap и Maverick. + +Наши задания: +- задание №1: сделать скрипт на следующие cвапы в сети zkSync на площадке Maverick (https://app.mav.xyz/?chain=324): +swap_eth_to_busd +swap_usdc_to_eth +swap_busd_to_eth + + +- задание №2: сделать скрипт на cвапы в сети zkSync на площадке KoiFinance (https://dapp.koi.finance/swap): +swap_usdt_to_eth +swap_usdt_to_usdc_e +Функции должны иметь флаг is_all_balance, который будет использовать весь баланс на счету для обмена. Реализацию data +делать через самописный ABI функции swap. +Примеры: +(2.83 USDT -> 0.00092 ETH): https://explorer.zksync.io/tx/0x226e9a7c741618f3e867b8f156fae43d0c4dd4bf773fc00c17e24c7e61d4696b +(1.734763 USDT -> 1.665372 USDC.e): https://explorer.zksync.io/tx/0x179df85ee97094190b17433b68c0a87f382a39188a44255917ed0afed9a386b2 + +- задание №3*: реализовать скрипт для универсальных свапов from_token -> to_token + +Подсказка к заданию №2: сигнатура функции +async def swap_usdt_to_eth(self, token_amount: TokenAmount | None = None, slippage: float = 0.5, is_all_balance: bool = False) -> str. +Подсказка к заданию №3: сигнатура функции +async def swap(self, from_token_symbol, to_token_symbol, amount, slippage) -> str. \ No newline at end of file diff --git a/lesson_03_unverified_contracts_part_2_dz/koi_finance.py b/lesson_03_unverified_contracts_part_2_dz/koi_finance.py new file mode 100644 index 0000000..b833a22 --- /dev/null +++ b/lesson_03_unverified_contracts_part_2_dz/koi_finance.py @@ -0,0 +1,120 @@ +import asyncio +import time + +from web3 import AsyncWeb3 + +import config +from client import Client +from data.models import ABIs, TokenAmount, Tokens + + +async def main(from_token_amount: TokenAmount, slippage: float = 5): + client = Client( + private_key=config.PRIVATE_KEY, + rpc='https://mainnet.era.zksync.io', + proxy=config.PROXY + ) + + from_token_address = Tokens.WETH + from_token_contract = client.w3.eth.contract( + address=from_token_address, + abi=ABIs.TokenABI + ) + from_token_symbol = await from_token_contract.functions.symbol().call() + + to_token_address = Tokens.USDC + to_token_contract = client.w3.eth.contract( + address=to_token_address, + abi=ABIs.TokenABI + ) + to_token_symbol = await to_token_contract.functions.symbol().call() + + # адрес свапалки + router_address = AsyncWeb3.to_checksum_address('0x3388530FbaF0C916fA7C0390413DFB178Cb33CBb') + router_contract = client.w3.eth.contract( + address=router_address, + abi=ABIs.KoiFinance + ) + + from_token_price_dollar = await client.get_token_price(token_symbol=from_token_symbol) + to_token_price_dollar = await client.get_token_price(token_symbol=to_token_symbol) + amount_out_min = TokenAmount( + amount=float(from_token_amount.Ether) * from_token_price_dollar / to_token_price_dollar * ( + 100 - slippage) / 100, + decimals=await to_token_contract.functions.decimals().call() + ) + + ''' + 0x + 0000000000000000000000000000000000000000000000000000000000000002 – какая-то константа + 00000000000000000000000000000000000000000000000000038d7ea4c68000 – from_token_amount + + 0x + 0000000000000000000000000000000000000000000000000000000000000001 – какая-то константа + 00000000000000000000000000000000000000000000000000038d7ea4c68000 – from_token_amount + 0000000000000000000000000000000000000000000000000000000000224cd1 – amount_out_min + 00000000000000000000000000000000000000000000000000000000000000a0 – какая-то константа + 0000000000000000000000000000000000000000000000000000000000000000 – какая-то константа + 0000000000000000000000000000000000000000000000000000000000000003 – какая-то константа + 0000000000000000000000005aea5775959fbc2557cc8789bc1bf90a239d9a91 – WETH + 000000000000000000000000493257fd37edb34451f62edf8d2a0c418852ba4c – USDT + 0000000000000000000000000000000000000000000000000000000000000000 – какая-то константа + 000000000000000000000000493257fd37edb34451f62edf8d2a0c418852ba4c – USDT + 0000000000000000000000003355df6d4c9c3035724fd0e3914de96a5a83aaf4 – USDC.e + 0000000000000000000000000000000000000000000000000000000000000001 – какая-то константа + 0000000000000000000000003355df6d4c9c3035724fd0e3914de96a5a83aaf4 – USDC.e + 0000000000000000000000001d17cbcf0d6d143135ae902365d2e5e2a16538d4 – USDC + 0000000000000000000000000000000000000000000000000000000000000001 – какая-то константа + ''' + + inputs = [ + f'0x' # 0x + f'{"2".zfill(64)}' # 0000000000000000000000000000000000000000000000000000000000000002 + f'{hex(from_token_amount.Wei)[2:].zfill(64)}', # 00000000000000000000000000000000000000000000000000038d7ea4c68000 + + f'0x' # 0x + f'{"1".zfill(64)}' # 0000000000000000000000000000000000000000000000000000000000000001 + f'{hex(from_token_amount.Wei)[2:].zfill(64)}' # 00000000000000000000000000000000000000000000000000038d7ea4c68000 + f'{hex(amount_out_min.Wei)[2:].zfill(64)}' # 0000000000000000000000000000000000000000000000000000000000224cd1 + f'{"a0".zfill(64)}' # 00000000000000000000000000000000000000000000000000000000000000a0 + f'{"".zfill(64)}' # 0000000000000000000000000000000000000000000000000000000000000000 + f'{"3".zfill(64)}' # 0000000000000000000000000000000000000000000000000000000000000003 + f'{Tokens.WETH[2:].zfill(64)}' # 0000000000000000000000005aea5775959fbc2557cc8789bc1bf90a239d9a91 + f'{Tokens.USDT[2:].zfill(64)}' # 000000000000000000000000493257fd37edb34451f62edf8d2a0c418852ba4c + f'{"".zfill(64)}' # 0000000000000000000000000000000000000000000000000000000000000000 + f'{Tokens.USDT[2:].zfill(64)}' # 000000000000000000000000493257fd37edb34451f62edf8d2a0c418852ba4c + f'{Tokens.USDC_E[2:].zfill(64)}' # 0000000000000000000000003355df6d4c9c3035724fd0e3914de96a5a83aaf4 + f'{"1".zfill(64)}' # 0000000000000000000000000000000000000000000000000000000000000001 + f'{Tokens.USDC_E[2:].zfill(64)}' # 0000000000000000000000003355df6d4c9c3035724fd0e3914de96a5a83aaf4 + f'{Tokens.USDC[2:].zfill(64)}' # 0000000000000000000000001d17cbcf0d6d143135ae902365d2e5e2a16538d4 + f'{"1".zfill(64)}' # 0000000000000000000000000000000000000000000000000000000000000001 + ] + + tx_hash = await client.send_transaction( + to=router_address, + data=router_contract.encodeABI('execute', + args=( + '0x0b08', + inputs, + int(time.time() + 1200) + )), + value=from_token_amount.Wei, + max_priority_fee_per_gas=client.max_priority_fee() + ) + + if tx_hash: + try: + await client.verif_tx(tx_hash=tx_hash) + print(f'Transaction success ({from_token_amount.Ether} ETH -> {amount_out_min.Ether} {to_token_symbol})!! ' + f'tx_hash: {tx_hash.hex()}') + # Transaction success (0.001 ETH -> 2.28988 USDC)!! tx_hash: 0x5e97aaaa972dc2aca2bdb8b6241fe6dd5bb9eaeb238d0dcd941c31c46198b51e + except Exception as err: + print(f'Transaction error!! tx_hash: {tx_hash.hex()}; error: {err}') + else: + print(f'Transaction error!!') + + +if __name__ == '__main__': + amount = TokenAmount(0.00001) + asyncio.run(main(from_token_amount=amount)) + diff --git a/lesson_03_unverified_contracts_part_2_dz/maveric.py b/lesson_03_unverified_contracts_part_2_dz/maveric.py new file mode 100644 index 0000000..a8e8993 --- /dev/null +++ b/lesson_03_unverified_contracts_part_2_dz/maveric.py @@ -0,0 +1,105 @@ +''' +5aea5775959fbc2557cc8789bc1bf90a239d9a91 WETH +66aed71a8d53b5f4f02764284d4b0745e7ef288a - +3355df6d4c9c3035724fd0e3914de96a5a83aaf4 USDC_E +ef1cada686c12f7a0008cd5b78eefaad35ad6f01 - +1d17cbcf0d6d143135ae902365d2e5e2a16538d4 USDC +''' + +import asyncio +import time + +from web3 import AsyncWeb3 + +import config +from client import Client +from data.models import ABIs, TokenAmount, Tokens + + +async def main(from_token_amount: TokenAmount, slippage: float = 5): + client = Client( + private_key=config.PRIVATE_KEY, + rpc='https://mainnet.era.zksync.io', + proxy=config.PROXY + ) + + path = [ + Tokens.WETH, + AsyncWeb3.to_checksum_address('66aed71a8d53b5f4f02764284d4b0745e7ef288a'), # pool1 + Tokens.USDC_E, # USDC.e + AsyncWeb3.to_checksum_address('ef1cada686c12f7a0008cd5b78eefaad35ad6f01'), # pool2 + Tokens.USDC, # USDC + ] + + from_token_address = path[0] + from_token_contract = client.w3.eth.contract( + address=from_token_address, + abi=ABIs.TokenABI + ) + from_token_symbol = await from_token_contract.functions.symbol().call() + + to_token_address = path[-1] + to_token_contract = client.w3.eth.contract( + address=to_token_address, + abi=ABIs.TokenABI + ) + to_token_symbol = await to_token_contract.functions.symbol().call() + + # адрес свапалки + router_address = AsyncWeb3.to_checksum_address('0x39E098A153Ad69834a9Dac32f0FCa92066aD03f4') + router_contract = client.w3.eth.contract( + address=router_address, + abi=ABIs.MavericABI + ) + + from_token_price_dollar = await client.get_token_price(token_symbol=from_token_symbol) + to_token_price_dollar = await client.get_token_price(token_symbol=to_token_symbol) + amount_out_min = TokenAmount( + amount=float(from_token_amount.Ether) * from_token_price_dollar / to_token_price_dollar * ( + 100 - slippage) / 100, + decimals=await to_token_contract.functions.decimals().call() + ) + + b_path = [] + for address in path: + b_path.append( + # переводим адрес из строки в байты и добавляем в список + AsyncWeb3.to_bytes(hexstr=address) + ) + joined_path = b''.join(b_path) + + args = ( + joined_path, + client.account.address, + int(time.time()) + 1200, + from_token_amount.Wei, + amount_out_min.Wei, + ) + + data = router_contract.encodeABI('exactInput', args=[args]) + second_item = router_contract.encodeABI('refundETH', args=[]) + + tx_hash = await client.send_transaction( + to=router_address, + data=router_contract.encodeABI('multicall', args=[ + [data, second_item] + ]), + value=from_token_amount.Wei, + max_priority_fee_per_gas=client.max_priority_fee() + ) + + if tx_hash: + try: + await client.verif_tx(tx_hash=tx_hash) + print(f'Transaction success ({from_token_amount.Ether} ETH -> {amount_out_min.Ether} {to_token_symbol})!! ' + f'tx_hash: {tx_hash.hex()}') + # Transaction success (0.001 ETH -> 2.28988 USDC)!! tx_hash: 0x5e97aaaa972dc2aca2bdb8b6241fe6dd5bb9eaeb238d0dcd941c31c46198b51e + except Exception as err: + print(f'Transaction error!! tx_hash: {tx_hash.hex()}; error: {err}') + else: + print(f'Transaction error!!') + + +if __name__ == '__main__': + amount = TokenAmount(0.00001) + asyncio.run(main(from_token_amount=amount)) diff --git a/lesson_03_unverified_contracts_part_2_dz/requirements.txt b/lesson_03_unverified_contracts_part_2_dz/requirements.txt new file mode 100644 index 0000000..6a56bdb --- /dev/null +++ b/lesson_03_unverified_contracts_part_2_dz/requirements.txt @@ -0,0 +1,3 @@ +web3==6.14.0 +curl_cffi==0.7.1 +fake-useragent==1.4.0 diff --git a/lesson_03_unverified_contracts_part_2_dz/spacefi_bytes.py b/lesson_03_unverified_contracts_part_2_dz/spacefi_bytes.py new file mode 100644 index 0000000..e552407 --- /dev/null +++ b/lesson_03_unverified_contracts_part_2_dz/spacefi_bytes.py @@ -0,0 +1,109 @@ +import asyncio +import time + +from web3 import AsyncWeb3 + +import config +from client import Client +from data.models import ABIs, TokenAmount + + +''' +0x7ff36ab5 +000 000000000000000000000000000000000000000000000000000000000021626d # 2187885 (amount out min) +020 0000000000000000000000000000000000000000000000000000000000000080 # cсылка на байт 80 (16 СС) +040 00000000000000000000000036f302d18dcede1ab1174f47726e62212d1ccead # address +060 0000000000000000000000000000000000000000000000000000000066e84acc # 1726499532 deadline +080 0000000000000000000000000000000000000000000000000000000000000004 # длина массива +0A0 0000000000000000000000005aea5775959fbc2557cc8789bc1bf90a239d9a91 # WETH +0C0 000000000000000000000000838a66f841dd5148475a8918db0732c239499a03 # STAR +0E0 0000000000000000000000003355df6d4c9c3035724fd0e3914de96a5a83aaf4 # USDC.e +100 0000000000000000000000001d17cbcf0d6d143135ae902365d2e5e2a16538d4 # USDC + +0xc671639d +0000000000000000000000000000000000000000000000000000000000005cc0 +0000000000000000000000000000000000000000000000000000000000000080 +00000000000000000000000036f302d18dcede1ab1174f47726e62212d1ccead +0000000000000000000000000000000000000000000000000000000067193976 +0000000000000000000000000000000000000000000000000000000000000004 +0000000000000000000000005aea5775959fbc2557cc8789bc1bf90a239d9a91 +000000000000000000000000838a66f841dd5148475a8918db0732c239499a03 +0000000000000000000000003355df6d4c9c3035724fd0e3914de96a5a83aaf4 +0000000000000000000000001d17cbcf0d6d143135ae902365d2e5e2a16538d4 +''' + + +async def main(from_token_amount: TokenAmount, slippage: float = 5): + client = Client( + private_key=config.PRIVATE_KEY, + rpc='https://mainnet.era.zksync.io', + proxy=config.PROXY + ) + + path = [ + AsyncWeb3.to_checksum_address('0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91'), # WETH + AsyncWeb3.to_checksum_address('0x838A66F841DD5148475a8918db0732c239499a03'), # STAR + AsyncWeb3.to_checksum_address('0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4'), # USDC.e + AsyncWeb3.to_checksum_address('0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4'), # USDC + ] + + from_token_address = path[0] + from_token_contract = client.w3.eth.contract( + address=from_token_address, + abi=ABIs.TokenABI + ) + from_token_symbol = await from_token_contract.functions.symbol().call() + + to_token_address = path[-1] + to_token_contract = client.w3.eth.contract( + address=to_token_address, + abi=ABIs.TokenABI + ) + to_token_symbol = await to_token_contract.functions.symbol().call() + + # адрес свапалки + router_address = AsyncWeb3.to_checksum_address('0xbE7D1FD1f6748bbDefC4fbaCafBb11C6Fc506d1d') + router_contract = client.w3.eth.contract( + address=router_address, + abi=ABIs.SpaceFiABIBytes + ) + + from_token_price_dollar = await client.get_token_price(token_symbol=from_token_symbol) + to_token_price_dollar = await client.get_token_price(token_symbol=to_token_symbol) + amount_out_min = TokenAmount( + amount=float(from_token_amount.Ether) * from_token_price_dollar / to_token_price_dollar * ( + 100 - slippage) / 100, + decimals=await to_token_contract.functions.decimals().call() + ) + + data = router_contract.encodeABI('swap', + args=( + amount_out_min.Wei, + path, + client.account.address, + int(time.time() + 1200) + )) + data = '0x7ff36ab5' + data[10:] + + tx_hash = await client.send_transaction( + to=router_address, + data=data, + value=from_token_amount.Wei, + max_priority_fee_per_gas=0 + ) + + if tx_hash: + try: + await client.verif_tx(tx_hash=tx_hash) + print(f'Transaction success ({from_token_amount.Ether} ETH -> {amount_out_min.Ether} {to_token_symbol})!! ' + f'tx_hash: {tx_hash.hex()}') + # Transaction success (0.001 ETH -> 2.28988 USDC)!! tx_hash: 0x5e97aaaa972dc2aca2bdb8b6241fe6dd5bb9eaeb238d0dcd941c31c46198b51e + except Exception as err: + print(f'Transaction error!! tx_hash: {tx_hash.hex()}; error: {err}') + else: + print(f'Transaction error!!') + + +if __name__ == '__main__': + amount = TokenAmount(0.00001) + asyncio.run(main(from_token_amount=amount)) diff --git a/lesson_03_unverified_contracts_part_2_dz/spacefi_params.py b/lesson_03_unverified_contracts_part_2_dz/spacefi_params.py new file mode 100644 index 0000000..43345e8 --- /dev/null +++ b/lesson_03_unverified_contracts_part_2_dz/spacefi_params.py @@ -0,0 +1,81 @@ +import asyncio +import time + +from web3 import AsyncWeb3 + +import config +from client import Client +from data.models import ABIs, TokenAmount + + +async def main(from_token_amount: TokenAmount, slippage: float = 5): + client = Client( + private_key=config.PRIVATE_KEY, + rpc='https://mainnet.era.zksync.io', + proxy=config.PROXY + ) + + path = [ + AsyncWeb3.to_checksum_address('0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91'), # WETH + AsyncWeb3.to_checksum_address('0x838A66F841DD5148475a8918db0732c239499a03'), # STAR + AsyncWeb3.to_checksum_address('0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4'), # USDC.e + AsyncWeb3.to_checksum_address('0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4'), # USDC + ] + + from_token_address = path[0] + from_token_contract = client.w3.eth.contract( + address=from_token_address, + abi=ABIs.TokenABI + ) + from_token_symbol = await from_token_contract.functions.symbol().call() + + to_token_address = path[-1] + to_token_contract = client.w3.eth.contract( + address=to_token_address, + abi=ABIs.TokenABI + ) + to_token_symbol = await to_token_contract.functions.symbol().call() + + # адрес свапалки + router_address = AsyncWeb3.to_checksum_address('0xbE7D1FD1f6748bbDefC4fbaCafBb11C6Fc506d1d') + router_contract = client.w3.eth.contract( + address=router_address, + abi=ABIs.SpaceFiABI + ) + + from_token_price_dollar = await client.get_token_price(token_symbol=from_token_symbol) + to_token_price_dollar = await client.get_token_price(token_symbol=to_token_symbol) + amount_out_min = TokenAmount( + amount=float(from_token_amount.Ether) * from_token_price_dollar / to_token_price_dollar * ( + 100 - slippage) / 100, + decimals=await to_token_contract.functions.decimals().call() + ) + + tx_hash = await client.send_transaction( + to=router_address, + data=router_contract.encodeABI('swapExactETHForTokens', + args=( + amount_out_min.Wei, + path, + client.account.address, + int(time.time() + 1200) + )), + value=from_token_amount.Wei, + max_priority_fee_per_gas=0 + ) + + if tx_hash: + try: + await client.verif_tx(tx_hash=tx_hash) + print(f'Transaction success ({from_token_amount.Ether} ETH -> {amount_out_min.Ether} {to_token_symbol})!! ' + f'tx_hash: {tx_hash.hex()}') + # Transaction success (0.001 ETH -> 2.28988 USDC)!! tx_hash: 0x5e97aaaa972dc2aca2bdb8b6241fe6dd5bb9eaeb238d0dcd941c31c46198b51e + except Exception as err: + print(f'Transaction error!! tx_hash: {tx_hash.hex()}; error: {err}') + else: + print(f'Transaction error!!') + + +if __name__ == '__main__': + amount = TokenAmount(0.00001) + asyncio.run(main(from_token_amount=amount)) diff --git a/lesson_03_unverified_contracts_part_2_dz/syncswap.py b/lesson_03_unverified_contracts_part_2_dz/syncswap.py new file mode 100644 index 0000000..034c15f --- /dev/null +++ b/lesson_03_unverified_contracts_part_2_dz/syncswap.py @@ -0,0 +1,180 @@ +import asyncio +import time + +from web3 import AsyncWeb3 + +import config +from client import Client +from data.models import ABIs, TokenAmount, Tokens, TxArgs + + +async def main(from_token_amount: TokenAmount, slippage: float = 5): + client = Client( + private_key=config.PRIVATE_KEY, + rpc='https://mainnet.era.zksync.io', + proxy=config.PROXY + ) + + from_token_address = Tokens.WETH + from_token_contract = client.w3.eth.contract( + address=from_token_address, + abi=ABIs.TokenABI + ) + from_token_symbol = await from_token_contract.functions.symbol().call() + + to_token_address = Tokens.USDC + to_token_contract = client.w3.eth.contract( + address=to_token_address, + abi=ABIs.TokenABI + ) + to_token_symbol = await to_token_contract.functions.symbol().call() + + # адрес свапалки + router_address = AsyncWeb3.to_checksum_address('0x9B5def958d0f3b6955cBEa4D5B7809b2fb26b059') + router_contract = client.w3.eth.contract( + address=router_address, + abi=ABIs.SyncSwapABI + ) + + from_token_price_dollar = await client.get_token_price(token_symbol=from_token_symbol) + to_token_price_dollar = await client.get_token_price(token_symbol=to_token_symbol) + amount_out_min = TokenAmount( + amount=float(from_token_amount.Ether) * from_token_price_dollar / to_token_price_dollar * ( + 100 - slippage) / 100, + decimals=await to_token_contract.functions.decimals().call() + ) + + ''' + +[0]: 0000000000000000000000000000000000000000000000000000000000000060 +[1]: 000000000000000000000000000000000000000000000000000000000027d651 – amount out min +[2]: 0000000000000000000000000000000000000000000000000000000066f2df39 – deadline +[3]: 0000000000000000000000000000000000000000000000000000000000000001 +[4]: 0000000000000000000000000000000000000000000000000000000000000020 +[5]: 0000000000000000000000000000000000000000000000000000000000000060 +[6]: 0000000000000000000000000000000000000000000000000000000000000000 +[7]: 00000000000000000000000000000000000000000000000000038d7ea4c68000 – amount in +[8]: 0000000000000000000000000000000000000000000000000000000000000002 - количество элементов steps +[9]: 0000000000000000000000000000000000000000000000000000000000000040 +[10]: 0000000000000000000000000000000000000000000000000000000000000180 +[11]: 0000000000000000000000001a32a715b4ebef211bbf4baa414f563b25cc50c9 – ZK/WETH-A (SyncSwap Aqua LP) +[12]: 00000000000000000000000000000000000000000000000000000000000000a0 +[13]: 0000000000000000000000000000000000000000000000000000000000000000 +[14]: 0000000000000000000000000000000000000000000000000000000000000120 +[15]: 0000000000000000000000000000000000000000000000000000000000000000 +[16]: 0000000000000000000000000000000000000000000000000000000000000060 +[17]: 0000000000000000000000005aea5775959fbc2557cc8789bc1bf90a239d9a91 – WETH +[18]: 00000000000000000000000040b768de8b2e4ed83d982804cb2fcc53d2529be9 – USDC.e/ZK-A (SyncSwap Aqua LP) +[19]: 0000000000000000000000000000000000000000000000000000000000000002 +[20]: 0000000000000000000000000000000000000000000000000000000000000000 +[21]: 00000000000000000000000040b768de8b2e4ed83d982804cb2fcc53d2529be9 – USDC.e/ZK-A (SyncSwap Aqua LP) +[22]: 00000000000000000000000000000000000000000000000000000000000000a0 +[23]: 0000000000000000000000000000000000000000000000000000000000000000 +[24]: 0000000000000000000000000000000000000000000000000000000000000120 +[25]: 0000000000000000000000000000000000000000000000000000000000000000 +[26]: 0000000000000000000000000000000000000000000000000000000000000060 +[27]: 0000000000000000000000005a7d6b2f92c77fad6ccabd7ee0624e64907eaf3e – ZK +[28]: 00000000000000000000000036f302d18dcede1ab1174f47726e62212d1ccead – wallet address +[29]: 0000000000000000000000000000000000000000000000000000000000000002 +[30]: 0000000000000000000000000000000000000000000000000000000000000000 + +[00] 0000000000000000000000000000000000000000000000000000000000000060 + +[01] 0000000000000000000000000000000000000000000000000000000000005c36 + +[02] 00000000000000000000000000000000000000000000000000000000671c0f3a + +[03] 0000000000000000000000000000000000000000000000000000000000000001 + +[04] 0000000000000000000000000000000000000000000000000000000000000020 + +[05] 0000000000000000000000000000000000000000000000000000000000000060 + +[06] 0000000000000000000000000000000000000000000000000000000000000000 + +[07] 000000000000000000000000000000000000000000000000000009184e72a000 + +[08] 0000000000000000000000000000000000000000000000000000000000000002 + +[09] 0000000000000000000000000000000000000000000000000000000000000040 + +[10] 0000000000000000000000000000000000000000000000000000000000000140 ? +[11] 0000000000000000000000001a32a715b4ebef211bbf4baa414f563b25cc50c9 + +[12] 0000000000000000000000000000000000000000000000000000000000000080 ? +[13] 0000000000000000000000000000000000000000000000000000000000000000 + +[14] 0000000000000000000000000000000000000000000000000000000000000120 + +[15] 0000000000000000000000000000000000000000000000000000000000000080 +[16] 0000000000000000000000000000000000000000000000000000000000011111 +[17] 0000000000000000000000000000000000000000000000000000000000000123 +[18] 0000000000000000000000000000000000000000000000000000000000000124 +[19] 0000000000000000000000000000000000000000000000000000000000000126 +[20] 0000000000000000000000000000000000000000000000000000000000000040 +[21] 0000000000000000000000000000000000000000000000000000000000000000 +[22] 0000000000000000000000000000000000000000000000000002222222222222 +[23] 0000000000000000000000001a32a715b4ebef211bbf4baa414f563b25cc50c9 +[24] 0000000000000000000000000000000000000000000000000000000000000080 +[25] 00000000000000000000000040b768de8b2e4ed83d982804cb2fcc53d2529be9 +[26] 00000000000000000000000000000000000000000000000000000000000000c0 +[27] 0000000000000000000000000000000000000000000000000000000000000020 +[28] 0000000000000000000000000000000000000000000000000000000000033333 +[29] 0000000000000000000000000000000000000000000000000000000000000020 +[30] 0000000000000000000000000000000000000000000000000000000000044444 + + ''' + + tx_args = TxArgs( + paths=[ + TxArgs( + steps=[ + TxArgs( + pool=Tokens.ZK_WETH_LP, + data=f'0x' + f'{Tokens.WETH[2:].zfill(64)}' + f'{Tokens.USDC_E_ZK_LP[2:].zfill(64)}' + f'{"2".zfill(64)}', + callback=Tokens.ZERO, + callbackData='0x', + useVault=False + ).tuple(), + TxArgs( + pool=Tokens.USDC_E_ZK_LP, + data=f'0x' + f'{Tokens.ZK[2:].zfill(64)}' + f'{client.account.address[2:].zfill(64)}' + f'{"2".zfill(64)}', + callback=Tokens.ZERO, + callbackData='0x', + useVault=True + ).tuple(), + ], + tokenIn=Tokens.ZERO, + amountIn=from_token_amount.Wei + ).tuple() + ], + amountOutMin=amount_out_min.Wei, + deadline=int(time.time()) + 1200 + ) + + data = router_contract.encodeABI('swap', args=tx_args.tuple()) + print(data) + + print(data[:10]) + data = data[10:] + i = 0 + while data: + print(f'[{str(i).zfill(2)}] {data[:64]}') + data = data[64:] + i += 1 + + tx_hash = await client.send_transaction( + to=router_address, + data=router_contract.encodeABI('swap', args=tx_args.tuple()), + value=from_token_amount.Wei, + max_priority_fee_per_gas=client.max_priority_fee() + ) + + if tx_hash: + try: + await client.verif_tx(tx_hash=tx_hash) + print(f'Transaction success ({from_token_amount.Ether} ETH -> {amount_out_min.Ether} {to_token_symbol})!! ' + f'tx_hash: {tx_hash.hex()}') + # Transaction success (0.001 ETH -> 2.28988 USDC)!! tx_hash: 0x5e97aaaa972dc2aca2bdb8b6241fe6dd5bb9eaeb238d0dcd941c31c46198b51e + except Exception as err: + print(f'Transaction error!! tx_hash: {tx_hash.hex()}; error: {err}') + else: + print(f'Transaction error!!') + + +if __name__ == '__main__': + amount = TokenAmount(0.00001) + asyncio.run(main(from_token_amount=amount)) diff --git a/lesson_03_unverified_contracts_part_2_dz/utils.py b/lesson_03_unverified_contracts_part_2_dz/utils.py new file mode 100644 index 0000000..c038013 --- /dev/null +++ b/lesson_03_unverified_contracts_part_2_dz/utils.py @@ -0,0 +1,27 @@ +import json +import os + + +def get_json(path: str | list[str] | tuple[str]): + if isinstance(path, (list, tuple)): + path = os.path.join(*path) + return json.load(open(path)) + + +class TxUtils: + @staticmethod + def to_cut_hex_prefix_and_zfill(hex_data: str, length: int = 64): + """ + Convert the hex string to lowercase, remove the '0x' prefix, and fill it with zeros to the specified length. + + Args: + hex_data (str): The original hex string. + length (int): The desired length of the string after filling. The default is 64. + + Returns: + str: The modified string with '0x' prefix removed and zero-filled to the specified length. + """ + if hex_data.startswith('0x'): + hex_data = hex_data[2:] + + return hex_data.zfill(length) From 2a4fa9cf2689112018bad3ab254bd8f4599c5366 Mon Sep 17 00:00:00 2001 From: maked0n1an Date: Mon, 11 Nov 2024 18:43:39 +0200 Subject: [PATCH 06/18] lesson_03_unverified_contracts_part_1: some fixes. --- .../tasks/spacefi.py | 40 +++++++++---------- .../tasks/spacefi_hard.py | 18 +++++---- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/lesson_03_unverified_contracts_part_1_dz/tasks/spacefi.py b/lesson_03_unverified_contracts_part_1_dz/tasks/spacefi.py index b75ac8a..a80d2a4 100644 --- a/lesson_03_unverified_contracts_part_1_dz/tasks/spacefi.py +++ b/lesson_03_unverified_contracts_part_1_dz/tasks/spacefi.py @@ -54,10 +54,10 @@ async def swap_eth_to_usdt( ) amount_out_min = TokenAmount( - amount=float(token_amount.Ether) + amount=(float(token_amount.Ether) * from_token_price_dollar / to_token_price_dollar - * (100 - slippage) / 100, + * (100 - slippage) / 100), decimals=await to_token_contract.functions.decimals().call() ) @@ -79,9 +79,9 @@ async def swap_eth_to_usdt( if receipt['status']: try: - await self.client.verif_tx(tx_hash=tx_hash_bytes) + tx_hash = await self.client.verif_tx(tx_hash=tx_hash_bytes) print(f'Transaction success ({token_amount.Ether} {from_token_symbol} -> {amount_out_min.Ether} {to_token_symbol})!! ' - f'tx_hash: {tx_hash_bytes.hex()}') + f'tx_hash: {tx_hash}') # Transaction success (0.001 ETH -> 2.87 USDT)!! tx_hash: 0x358ab333050193e02623c0b81aad6acea73f358eabd35e6c7526a5e7f52b98db except Exception as err: print(f'Transaction error!! tx_hash: {tx_hash_bytes.hex()}; error: {err}') @@ -114,14 +114,14 @@ async def swap_eth_to_wbtc( to_token_price_dollar = await self.client.get_token_price(token_symbol='WBTC') amount_out_min = TokenAmount( - amount=float(token_amount.Ether) + amount=(float(token_amount.Ether) * from_token_price_dollar / to_token_price_dollar - * (100 - slippage) / 100, + * (100 - slippage) / 100), decimals=await to_token_contract.functions.decimals().call() ) - tx_hash = await self.client.send_transaction( + tx_hash_bytes = await self.client.send_transaction( to=router_address, data = router_contract.encodeABI( 'swapExactTokensForETH', @@ -137,14 +137,14 @@ async def swap_eth_to_wbtc( max_priority_fee_per_gas=0 ) - if tx_hash: + if tx_hash_bytes: try: - await self.client.verif_tx(tx_hash=tx_hash) + tx_hash = await self.client.verif_tx(tx_hash=tx_hash_bytes) print(f'Transaction success ({token_amount.Ether} ETH -> {amount_out_min.Ether} WBTC)!! ' - f'tx_hash: {tx_hash.hex()}') + f'tx_hash: {tx_hash}') # Transaction success (0.0008 ETH -> 0.000029105322888639857 WBTC)!! tx_hash: 0x669310c1ec16ed385e8d0778cc96c05e2bc3d8b2e6d3490f4363b370bc6d2446 except Exception as err: - print(f'Transaction error!! tx_hash: {tx_hash.hex()}; error: {err}') + print(f'Transaction error!! tx_hash: {tx_hash_bytes.hex()}; error: {err}') else: print(f'Transaction error!!') @@ -183,10 +183,10 @@ async def swap_usdc_e_to_eth( to_token_price_dollar = await self.client.get_token_price(token_symbol=to_token_symbol) amount_out_min = TokenAmount( - amount=float(token_amount.Ether) + amount=(float(token_amount.Ether) * from_token_price_dollar / to_token_price_dollar - * (100 - slippage) / 100, + * (100 - slippage) / 100), decimals=await to_token_contract.functions.decimals().call() ) @@ -236,7 +236,7 @@ async def swap_usdc_e_to_eth( f'tx_hash: {tx_hash}') # Transaction success (1.961663 USDC.e -> 0.0006465444482972901 WETH)!! tx_hash: 0x0161e7cb528408427fce8eda171a251632d0b28cb89bf8dfd9616189964ae08b except Exception as err: - print(f'Transaction error!! tx_hash: {tx_hash}; error: {err}') + print(f'Transaction error!! tx_hash: {tx_hash_bytes.hex()}; error: {err}') else: print(f'Transaction error!!') @@ -301,10 +301,10 @@ async def swap_usdt_to_eth( ) amount_out_min = TokenAmount( - amount=float(token_amount.Ether) + amount=(float(token_amount.Ether) * from_token_price_dollar / to_token_price_dollar - * (100 - slippage) / 100, + * (100 - slippage) / 100), decimals=await to_token_contract.functions.decimals().call() ) @@ -352,7 +352,7 @@ async def swap_usdt_to_eth( f'tx_hash: {tx_hash}') # Transaction success (0.0004 ETH -> 1.13988 USDT)!! tx_hash: 0x16ed6ce885e1f65a4a068b5e9253a5ebe2251ae93ed878ab583830515c627fb0 except Exception as err: - print(f'Transaction error!! tx_hash: {tx_hash}; error: {err}') + print(f'Transaction error!! tx_hash: {tx_hash_bytes.hex()}; error: {err}') else: print(f'Transaction error!!') @@ -406,10 +406,10 @@ async def swap_usdt_to_usdc_e( ) amount_out_min = TokenAmount( - amount=float(token_amount.Ether) + amount=(float(token_amount.Ether) * from_token_price_dollar / to_token_price_dollar - * (100 - slippage) / 100, + * (100 - slippage) / 100), decimals=await to_token_contract.functions.decimals().call() ) @@ -462,6 +462,6 @@ async def swap_usdt_to_usdc_e( f'tx_hash: {tx_hash}') # Transaction success (2.027439 USDT -> 1.946341 USDC.e)!! tx_hash: 0xbd678a795c66238f067a0df7f49c759d7e3bc422a60c8fd7baadd1532566c98c except Exception as err: - print(f'Transaction error!! tx_hash: {tx_hash}; error: {err}') + print(f'Transaction error!! tx_hash: {tx_hash_bytes.hex()}; error: {err}') else: print(f'Transaction error!!') \ No newline at end of file diff --git a/lesson_03_unverified_contracts_part_1_dz/tasks/spacefi_hard.py b/lesson_03_unverified_contracts_part_1_dz/tasks/spacefi_hard.py index 2af6f15..80c4bac 100644 --- a/lesson_03_unverified_contracts_part_1_dz/tasks/spacefi_hard.py +++ b/lesson_03_unverified_contracts_part_1_dz/tasks/spacefi_hard.py @@ -86,9 +86,6 @@ async def swap( abi=ABIs.SpaceFiABI ) - from_token_price_dollar = await self.client.get_token_price(token_symbol=from_token_symbol) - to_token_price_dollar = await self.client.get_token_price(token_symbol=to_token_symbol) - if from_token_symbol != 'ETH': from_token_contract = self.client.w3.eth.contract( address=AsyncWeb3.to_checksum_address(ZKSYNC_TOKENS_DICT[from_token_symbol]), @@ -111,15 +108,20 @@ async def swap( amount=amount, decimals=from_token_decimals ) + + from_token_price_dollar = await self.client.get_token_price(token_symbol=from_token_symbol) + to_token_price_dollar = await self.client.get_token_price(token_symbol=to_token_symbol) + amount_out_min = TokenAmount( - amount=float(amount_in.Ether) + amount=(float(amount_in.Ether) * from_token_price_dollar / to_token_price_dollar - * (1 - slippage / 100), + * (100 - slippage) / 100), decimals=to_token_decimals ) - - checksum_path = [ # list comprehension + + # list comprehension + checksum_path = [ AsyncWeb3.to_checksum_address(path_part) for path_part in SPACEFI_PATHS[(from_token_symbol, to_token_symbol)] ] @@ -185,6 +187,6 @@ async def swap( # Transaction success (0.0008 ETH -> 0.000029105322888639857 WBTC)!! tx_hash: 0x669310c1ec16ed385e8d0778cc96c05e2bc3d8b2e6d3490f4363b370bc6d2446 # Transaction success (0.00003 WBTC -> 0.0007439477656330048 ETH)!! tx_hash: 0x5879c726265b08d2c62424401ca4b03b89e4eef249f0d40983f05b3a24a872af except Exception as err: - print(f'Transaction error!! tx_hash: {tx_hash}; error: {err}') + print(f'Transaction error!! tx_hash: {tx_hash_bytes.hex()}; error: {err}') else: print(f'Transaction error!!') \ No newline at end of file From 715606239391fcaf9023360ed2ce696d57ac19a2 Mon Sep 17 00:00:00 2001 From: maked0n1an Date: Wed, 13 Nov 2024 00:32:43 +0200 Subject: [PATCH 07/18] KoiFinance: added swap_eth_to_usdc. --- .../tasks/koi_finance.py | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 lesson_03_unverified_contracts_part_2_dz/tasks/koi_finance.py diff --git a/lesson_03_unverified_contracts_part_2_dz/tasks/koi_finance.py b/lesson_03_unverified_contracts_part_2_dz/tasks/koi_finance.py new file mode 100644 index 0000000..c369f5e --- /dev/null +++ b/lesson_03_unverified_contracts_part_2_dz/tasks/koi_finance.py @@ -0,0 +1,136 @@ +import asyncio +import random +import time + +from web3 import AsyncWeb3 +from uniswap_universal_router_decoder import RouterCodec, FunctionRecipient + +from client import Client +from data.models import ABIs, Permit2ABI, TokenAmount, Tokens +from utils import TxUtils + + +class KoiFinance: + def __init__(self, client: Client): + self.client = client + + async def get_raw_tx_params(self, wei_value: float = 0) -> dict: + return { + "chainId": await self.client.w3.eth.chain_id, + "from": self.client.account.address, + "value": wei_value, + "gasPrice": await self.client.w3.eth.gas_price, + "nonce": await self.client.w3.eth.get_transaction_count(self.client.account.address), + } + + async def swap_eth_to_usdc( + self, + token_amount: TokenAmount, + slippage: float = 1 + ): + from_token_address = Tokens.WETH + from_token_contract = self.client.w3.eth.contract( + address=from_token_address, + abi=ABIs.TokenABI + ) + to_token_address = Tokens.USDC + to_token_contract = self.client.w3.eth.contract( + address=to_token_address, + abi=ABIs.TokenABI + ) + from_token_symbol, to_token_symbol, to_token_decimals = await asyncio.gather( + from_token_contract.functions.symbol().call(), + to_token_contract.functions.symbol().call(), + to_token_contract.functions.decimals().call() + ) + + # адрес свапалки + router_address = AsyncWeb3.to_checksum_address('0x3388530FbaF0C916fA7C0390413DFB178Cb33CBb') + router_contract = self.client.w3.eth.contract( + address=router_address, + abi=ABIs.KoiFinance + ) + + from_token_price_dollar, to_token_price_dollar = await asyncio.gather( + self.client.get_token_price(token_symbol=from_token_symbol), + self.client.get_token_price(token_symbol=to_token_symbol) + ) + + swap_ratios = [0.2, 0.4] + selected_small_ratio = random.choice(swap_ratios) + # selected_small_ratio = random.uniform(0.2, 0.4) + selected_big_ratio = 1 - selected_small_ratio + + amount_out_min = TokenAmount( + amount=( + float(token_amount.Ether) + * from_token_price_dollar + / to_token_price_dollar + * (100 - slippage) / 100 + ), + decimals=to_token_decimals + ) + small_amount_in_wei = int(token_amount.Wei * selected_small_ratio) + big_amount_in_wei = int(token_amount.Wei * selected_big_ratio) + + small_amount_out_wei = int(amount_out_min.Wei * selected_small_ratio) + big_amount_out_wei = int(amount_out_min.Wei * selected_big_ratio) + + inputs = [ + ('0x' + + TxUtils.to_cut_hex_prefix_and_zfill(2) + + TxUtils.to_cut_hex_prefix_and_zfill(hex(token_amount.Wei))), + + ('0x' + + TxUtils.to_cut_hex_prefix_and_zfill(1) + + TxUtils.to_cut_hex_prefix_and_zfill(hex(small_amount_in_wei)) + + TxUtils.to_cut_hex_prefix_and_zfill(hex(small_amount_out_wei)) + + TxUtils.to_cut_hex_prefix_and_zfill("a0") + + TxUtils.to_cut_hex_prefix_and_zfill("") + + TxUtils.to_cut_hex_prefix_and_zfill(2) + + TxUtils.to_cut_hex_prefix_and_zfill(Tokens.WETH) + + TxUtils.to_cut_hex_prefix_and_zfill(Tokens.USDC_E) + + TxUtils.to_cut_hex_prefix_and_zfill(1) + + TxUtils.to_cut_hex_prefix_and_zfill(Tokens.USDC_E) + + TxUtils.to_cut_hex_prefix_and_zfill(Tokens.USDC) + + TxUtils.to_cut_hex_prefix_and_zfill("")), + + ('0x' + + TxUtils.to_cut_hex_prefix_and_zfill(1) + + TxUtils.to_cut_hex_prefix_and_zfill(hex(big_amount_in_wei)) + + TxUtils.to_cut_hex_prefix_and_zfill(hex(big_amount_out_wei)) + + TxUtils.to_cut_hex_prefix_and_zfill("a0") + + TxUtils.to_cut_hex_prefix_and_zfill("") + + TxUtils.to_cut_hex_prefix_and_zfill(2) + + TxUtils.to_cut_hex_prefix_and_zfill(Tokens.WETH) + + TxUtils.to_cut_hex_prefix_and_zfill(Tokens.USDC_E) + + TxUtils.to_cut_hex_prefix_and_zfill(1) + + TxUtils.to_cut_hex_prefix_and_zfill(Tokens.USDC_E) + + TxUtils.to_cut_hex_prefix_and_zfill(Tokens.USDC) + + TxUtils.to_cut_hex_prefix_and_zfill("")) + ] + + tx_hash_bytes = await self.client.send_transaction( + to=router_address, + data=router_contract.encodeABI( + 'execute', + args=( + '0x0b0808', + inputs, + int(time.time() + 1200) + )), + value=token_amount.Wei, + max_priority_fee_per_gas=0, + ) + + if tx_hash_bytes: + try: + tx_hash = await self.client.verif_tx(tx_hash=tx_hash_bytes) + print(f'Transaction success ({token_amount.Ether} ETH -> {amount_out_min.Ether} {to_token_symbol})!! ' + f'tx_hash: {tx_hash}') + # Transaction success (0.001 ETH -> 2.28988 USDC)!! tx_hash: 0x5e97aaaa972dc2aca2bdb8b6241fe6dd5bb9eaeb238d0dcd941c31c46198b51e + except Exception as err: + print( + f'Transaction error!! tx_hash: {tx_hash_bytes.hex()}; error: {err}') + else: + print(f'Transaction error!!') From 86ff20d4c40ce6f6fd6eb789e1e1dc5e9daa2073 Mon Sep 17 00:00:00 2001 From: Alex <137455739+maked0n1an@users.noreply.github.com> Date: Wed, 13 Nov 2024 01:33:52 +0300 Subject: [PATCH 08/18] Merge (#6) --- lesson_03_unverified_contracts_part_1/config.py | 4 ++-- lesson_03_unverified_contracts_part_2/config.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lesson_03_unverified_contracts_part_1/config.py b/lesson_03_unverified_contracts_part_1/config.py index c4c851c..8c7ccfe 100644 --- a/lesson_03_unverified_contracts_part_1/config.py +++ b/lesson_03_unverified_contracts_part_1/config.py @@ -1,2 +1,2 @@ -PRIVATE_KEY = '...' -PROXY = '...' +PRIVATE_KEY = '' +PROXY = '' diff --git a/lesson_03_unverified_contracts_part_2/config.py b/lesson_03_unverified_contracts_part_2/config.py index c4c851c..8c7ccfe 100644 --- a/lesson_03_unverified_contracts_part_2/config.py +++ b/lesson_03_unverified_contracts_part_2/config.py @@ -1,2 +1,2 @@ -PRIVATE_KEY = '...' -PROXY = '...' +PRIVATE_KEY = '' +PROXY = '' From c6781be4dce30e0c0b7d5152a5ef651b99d4039f Mon Sep 17 00:00:00 2001 From: maked0n1an Date: Wed, 13 Nov 2024 12:38:02 +0200 Subject: [PATCH 09/18] Added new function parse_params. --- .../config.py | 4 +- .../data/abis/token.json | 772 ++++++++++++++++++ .../data/models.py | 2 +- .../main.py | 23 + .../tasks/koi_finance.py | 4 +- .../utils.py | 43 +- 6 files changed, 839 insertions(+), 9 deletions(-) create mode 100644 lesson_03_unverified_contracts_part_2_dz/data/abis/token.json create mode 100644 lesson_03_unverified_contracts_part_2_dz/main.py diff --git a/lesson_03_unverified_contracts_part_2_dz/config.py b/lesson_03_unverified_contracts_part_2_dz/config.py index c4c851c..8c7ccfe 100644 --- a/lesson_03_unverified_contracts_part_2_dz/config.py +++ b/lesson_03_unverified_contracts_part_2_dz/config.py @@ -1,2 +1,2 @@ -PRIVATE_KEY = '...' -PROXY = '...' +PRIVATE_KEY = '' +PROXY = '' diff --git a/lesson_03_unverified_contracts_part_2_dz/data/abis/token.json b/lesson_03_unverified_contracts_part_2_dz/data/abis/token.json new file mode 100644 index 0000000..3869640 --- /dev/null +++ b/lesson_03_unverified_contracts_part_2_dz/data/abis/token.json @@ -0,0 +1,772 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_user", + "type": "address" + } + ], + "name": "BlockPlaced", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_user", + "type": "address" + } + ], + "name": "BlockReleased", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_blockedUser", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_balance", + "type": "uint256" + } + ], + "name": "DestroyedBlockedFunds", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_destination", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "Mint", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_contract", + "type": "address" + } + ], + "name": "NewPrivilegedContract", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "Redeem", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_contract", + "type": "address" + } + ], + "name": "RemovedPrivilegedContract", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_trustedDeFiContract", + "type": "address" + } + ], + "name": "addPrivilegedContract", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_user", + "type": "address" + } + ], + "name": "addToBlockedList", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_owner", + "type": "address" + }, + { + "internalType": "address", + "name": "_spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "bridgeBurn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "bridgeMint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_blockedUser", + "type": "address" + } + ], + "name": "destroyBlockedFunds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_name", + "type": "string" + }, + { + "internalType": "string", + "name": "_symbol", + "type": "string" + }, + { + "internalType": "uint8", + "name": "_decimals", + "type": "uint8" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_name", + "type": "string" + }, + { + "internalType": "string", + "name": "_symbol", + "type": "string" + }, + { + "internalType": "uint8", + "name": "_decimals", + "type": "uint8" + }, + { + "internalType": "address", + "name": "_l2Gateway", + "type": "address" + }, + { + "internalType": "address", + "name": "_l1Counterpart", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isBlocked", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isTrusted", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "l1Address", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "l2Gateway", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_destination", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "_recipients", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "_values", + "type": "uint256[]" + } + ], + "name": "multiTransfer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "redeem", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_user", + "type": "address" + } + ], + "name": "removeFromBlockedList", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_trustedDeFiContract", + "type": "address" + } + ], + "name": "removePrivilegedContract", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_sender", + "type": "address" + }, + { + "internalType": "address", + "name": "_recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/lesson_03_unverified_contracts_part_2_dz/data/models.py b/lesson_03_unverified_contracts_part_2_dz/data/models.py index 7620ff8..afdeed2 100644 --- a/lesson_03_unverified_contracts_part_2_dz/data/models.py +++ b/lesson_03_unverified_contracts_part_2_dz/data/models.py @@ -327,4 +327,4 @@ class ABIs: "stateMutability": "payable", "type": "function" }, - ] + ] \ No newline at end of file diff --git a/lesson_03_unverified_contracts_part_2_dz/main.py b/lesson_03_unverified_contracts_part_2_dz/main.py new file mode 100644 index 0000000..5c8e91a --- /dev/null +++ b/lesson_03_unverified_contracts_part_2_dz/main.py @@ -0,0 +1,23 @@ +import asyncio +from client import Client +import config +from data.models import TokenAmount +from tasks.koi_finance import KoiFinance + + +async def main(): + client = Client( + private_key=config.PRIVATE_KEY, + rpc='https://mainnet.era.zksync.io', + proxy=config.PROXY + ) + koi_finance = KoiFinance(client) + await koi_finance.swap_eth_to_usdc( + token_amount=TokenAmount(0.002), + slippage=1 + ) + + +if __name__ == "__main__": + asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) + asyncio.run(main()) diff --git a/lesson_03_unverified_contracts_part_2_dz/tasks/koi_finance.py b/lesson_03_unverified_contracts_part_2_dz/tasks/koi_finance.py index c369f5e..9038615 100644 --- a/lesson_03_unverified_contracts_part_2_dz/tasks/koi_finance.py +++ b/lesson_03_unverified_contracts_part_2_dz/tasks/koi_finance.py @@ -3,10 +3,9 @@ import time from web3 import AsyncWeb3 -from uniswap_universal_router_decoder import RouterCodec, FunctionRecipient from client import Client -from data.models import ABIs, Permit2ABI, TokenAmount, Tokens +from data.models import ABIs, TokenAmount, Tokens from utils import TxUtils @@ -134,3 +133,4 @@ async def swap_eth_to_usdc( f'Transaction error!! tx_hash: {tx_hash_bytes.hex()}; error: {err}') else: print(f'Transaction error!!') + \ No newline at end of file diff --git a/lesson_03_unverified_contracts_part_2_dz/utils.py b/lesson_03_unverified_contracts_part_2_dz/utils.py index c038013..d7c0500 100644 --- a/lesson_03_unverified_contracts_part_2_dz/utils.py +++ b/lesson_03_unverified_contracts_part_2_dz/utils.py @@ -10,7 +10,8 @@ def get_json(path: str | list[str] | tuple[str]): class TxUtils: @staticmethod - def to_cut_hex_prefix_and_zfill(hex_data: str, length: int = 64): + def to_cut_hex_prefix_and_zfill(data: int | str, length: int = 64): + str_hex_data = str(data) """ Convert the hex string to lowercase, remove the '0x' prefix, and fill it with zeros to the specified length. @@ -21,7 +22,41 @@ def to_cut_hex_prefix_and_zfill(hex_data: str, length: int = 64): Returns: str: The modified string with '0x' prefix removed and zero-filled to the specified length. """ - if hex_data.startswith('0x'): - hex_data = hex_data[2:] + if str_hex_data.startswith('0x'): + str_hex_data = str_hex_data[2:] - return hex_data.zfill(length) + return str_hex_data.zfill(length) + + @staticmethod + def parse_params( + params: str, + has_function_signature: bool = True + ) -> None: + """ + Parse a string of parameters, optionally printing function signature and memory addresses. + + Args: + params (str): The string of parameters to parse. + has_function_signature (bool, optional): Whether to print the function signature (default is True). + + Returns: + None + """ + if has_function_signature: + function_signature = params[:10] + print('Function signature:', function_signature) + params = params[10:] + else: + params = params[2:] + + count = 0 + while params: + memory_address = hex(count * 32)[2:].zfill(3) + print(f'{memory_address}: {params[:64]}') + count += 1 + params = params[64:] + print() + + @staticmethod + def print_normalize_number(number: int): + print(f'{number:,}'.replace(',', '_')) From bab7e9ac734bdbc484b02636671a0bf6be5ca669 Mon Sep 17 00:00:00 2001 From: maked0n1an Date: Thu, 14 Nov 2024 02:05:18 +0200 Subject: [PATCH 10/18] Models: added new models. --- .../data/models.py | 89 +++++-------------- .../maveric.py | 2 +- 2 files changed, 21 insertions(+), 70 deletions(-) diff --git a/lesson_03_unverified_contracts_part_2_dz/data/models.py b/lesson_03_unverified_contracts_part_2_dz/data/models.py index afdeed2..8b65401 100644 --- a/lesson_03_unverified_contracts_part_2_dz/data/models.py +++ b/lesson_03_unverified_contracts_part_2_dz/data/models.py @@ -2,6 +2,8 @@ from web3 import AsyncWeb3 +from utils import get_json + class TokenAmount: Wei: int @@ -29,11 +31,16 @@ class Tokens: USDC_E = AsyncWeb3.to_checksum_address('0x3355df6D4c9C3035724Fd0e3914dE96A5a83aaf4') USDT = AsyncWeb3.to_checksum_address('0x493257fd37edb34451f62edf8d2a0c418852ba4c') STAR = AsyncWeb3.to_checksum_address('0x838A66F841DD5148475a8918db0732c239499a03') + WBTC = AsyncWeb3.to_checksum_address('0xbbeb516fb02a01611cbbe0453fe3c580d7281011') + MAV = AsyncWeb3.to_checksum_address('0x787c09494ec8bcb24dcaf8659e7d5d69979ee508') ETH = AsyncWeb3.to_checksum_address(f'0x{"".zfill(40)}') USDC_E_ZK_LP = AsyncWeb3.to_checksum_address('0x40b768de8b2e4ed83d982804cb2fcc53d2529be9') ZK_WETH_LP = AsyncWeb3.to_checksum_address('0x1a32a715b4ebef211bbf4baa414f563b25cc50c9') ZK = AsyncWeb3.to_checksum_address('0x5a7d6b2f92c77fad6ccabd7ee0624e64907eaf3e') + USDC_E_USDT_LP = AsyncWeb3.to_checksum_address('0xa65349507212f9d1df0b001e221ceb78ff23b155') + USDT_WBTC_LP = AsyncWeb3.to_checksum_address('0xc029c9569c51d24af555106951078b5b4e11894a') + WETH_WBTC_LP = AsyncWeb3.to_checksum_address('0xb3479139e07568ba954c8a14d5a8b3466e35533d') ZERO = AsyncWeb3.to_checksum_address('0x0000000000000000000000000000000000000000') @@ -202,7 +209,7 @@ class ABIs: } ] - MavericABI = [ + MaverickABI = [ { "constant": False, "inputs": [ @@ -248,83 +255,27 @@ class ABIs: }, { "inputs": [ - { - "name": "data", - "type": "bytes[]" - } + {"name": "amountOutMin", "type": "uint256"}, + {"name": "address", "type": "address"} ], - "name": "multicall", + "name": "unwrapWETH9", "outputs": [], "stateMutability": "payable", "type": "function" - } - ] - - SyncSwapABI = [ + }, { "inputs": [ { - "components": [ - { - "components": [ - { - "internalType": "address", - "name": "pool", - "type": "address" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - }, - { - "internalType": "address", - "name": "callback", - "type": "address" - }, - { - "internalType": "bytes", - "name": "callbackData", - "type": "bytes" - }, - { - "name": "flag", - "type": "bool" - } - ], - "internalType": "struct IRouter.SwapStep[]", - "name": "steps", - "type": "tuple[]" - }, - { - "internalType": "address", - "name": "tokenIn", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amountIn", - "type": "uint256" - } - ], - "internalType": "struct IRouter.SwapPath[]", - "name": "paths", - "type": "tuple[]" - }, - { - "internalType": "uint256", - "name": "amountOutMin", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" + "name": "data", + "type": "bytes[]" } ], - "name": "swap", + "name": "multicall", "outputs": [], "stateMutability": "payable", "type": "function" - }, - ] \ No newline at end of file + } + ] + +SyncSwapFullAbi = get_json('data/abis/syncswap_abi.json') +PermitTokenABI = get_json('data/abis/permit_token_abi.json') \ No newline at end of file diff --git a/lesson_03_unverified_contracts_part_2_dz/maveric.py b/lesson_03_unverified_contracts_part_2_dz/maveric.py index a8e8993..9510ac5 100644 --- a/lesson_03_unverified_contracts_part_2_dz/maveric.py +++ b/lesson_03_unverified_contracts_part_2_dz/maveric.py @@ -49,7 +49,7 @@ async def main(from_token_amount: TokenAmount, slippage: float = 5): router_address = AsyncWeb3.to_checksum_address('0x39E098A153Ad69834a9Dac32f0FCa92066aD03f4') router_contract = client.w3.eth.contract( address=router_address, - abi=ABIs.MavericABI + abi=ABIs.MaverickABI ) from_token_price_dollar = await client.get_token_price(token_symbol=from_token_symbol) From bc7e52618919ffc046d96943e5aabdc73dfa1b8f Mon Sep 17 00:00:00 2001 From: maked0n1an Date: Thu, 14 Nov 2024 02:06:12 +0200 Subject: [PATCH 11/18] Maverick: added 2 methods for HW. --- .../tasks/maverick.py | 292 ++++++++++++++++++ 1 file changed, 292 insertions(+) create mode 100644 lesson_03_unverified_contracts_part_2_dz/tasks/maverick.py diff --git a/lesson_03_unverified_contracts_part_2_dz/tasks/maverick.py b/lesson_03_unverified_contracts_part_2_dz/tasks/maverick.py new file mode 100644 index 0000000..b60a046 --- /dev/null +++ b/lesson_03_unverified_contracts_part_2_dz/tasks/maverick.py @@ -0,0 +1,292 @@ +import asyncio +import random +import time + +from web3 import AsyncWeb3 +from web3.contract import AsyncContract + +from client import Client +from data.models import ABIs, TokenAmount, Tokens + + +class Maverick: + def __init__(self, client: Client): + self.client = client + + async def _approve_token_if_needed( + self, + token_contract: AsyncContract, + router_address: str, + token_amount: TokenAmount, + ): + allowance, token_symbol = await asyncio.gather( + token_contract.functions.allowance( + self.client.account.address, + router_address + ).call(), + token_contract.functions.symbol().call() + ) + + if allowance >= token_amount.Wei: + return + + approve_tx_hash_bytes = await self.client.send_transaction( + to=token_contract.address, + data=token_contract.encodeABI( + 'approve', + args=(router_address, token_amount.Wei) + ), + max_priority_fee_per_gas=0 + ) + if approve_tx_hash_bytes: + try: + await self.client.verif_tx(tx_hash=approve_tx_hash_bytes) + waiting_time = round(random.choice([1, 5]), 2) + print( + f'Approved {token_amount.Ether} {token_symbol} to swap on Maverick, ' + f'sleeping {waiting_time} secs...' + ) + await asyncio.sleep(waiting_time) + except Exception as err: + print( + f'Approve transaction error!! tx_hash: ' + f'{approve_tx_hash_bytes.hex()}; error: {err}' + ) + else: + print('Failed') + + async def swap_usdc_to_eth( + self, + token_amount: TokenAmount, + slippage: float = 1, + is_all_balance: bool = False + ): + """ + Function signature: 0xc04b8d59 - exactInput((bytes,address,uint256,uint256,uint256)) + 000: 0000000000000000000000000000000000000000000000000000000000000020 - ccылка на 20 байт + 020: 00000000000000000000000000000000000000000000000000000000000000a0 - размер структуры (160 байт), + 160 / 32 = 5 - cлед 5 строк - это структура (tuple) + 040: 0000000000000000000000000000000000000000000000000000000000000000 - 0x00000...0000 address + 060: 0000000000000000000000000000000000000000000000000000000067349f69 - deadline (int(time.time() + 3 * 60)) + 080: 000000000000000000000000000000000000000000000000000000000065f852 - amountIn (6_682_706 USDC) + 0a0: 00000000000000000000000000000000000000000000000000076bb11d5370a1 - amountOutMin (2_088_733_282_365_601 ETH) + 0c0: 0000000000000000000000000000000000000000000000000000000000000064 - размер массива байтов (100 байт), 100 / 5 = 20 bytes = 40 hexs + 0e0: 1d17cbcf0d6d143135ae902365d2e5e2a16538d4ef1cada686c12f7a0008cd5b - 1d17cbcf0d6d143135ae902365d2e5e2a16538d4 USDC + 100: 78eefaad35ad6f013355df6d4c9c3035724fd0e3914de96a5a83aaf4688ea0d0 - ef1cada686c12f7a0008cd5b78eefaad35ad6f01 pool USDC/USDC.e + 120: 7acadd7d74ec7c729f1d0ca0dd4bb6655aea5775959fbc2557cc8789bc1bf90a - 3355df6d4c9c3035724fd0e3914de96a5a83aaf4 USDC.e + 140: 239d9a9100000000000000000000000000000000000000000000000000000000 - 688ea0d07acadd7d74ec7c729f1d0ca0dd4bb665 USDC.e/WETH + 5aea5775959fbc2557cc8789bc1bf90a239d9a91 WETH + + Function signature: 0x49404b7c - unwrapWETH9(uint256,address) + 000: 00000000000000000000000000000000000000000000000000076bb11d5370a1 - amountOutMin (2_088_733_282_365_601 ETH) + 020: 0000000000000000000000002f5844b8b5c03bbc48408bfda1340f5181643f53 - our address + """ + path = [ + Tokens.USDC, + 'ef1cada686c12f7a0008cd5b78eefaad35ad6f01', # pool USDC/USDC.e + Tokens.USDC_E, + '688ea0d07acadd7d74ec7c729f1d0ca0dd4bb665', # pool USDC.e/WETH + Tokens.WETH, + ] + + from_token_contract = self.client.w3.eth.contract( + address=AsyncWeb3.to_checksum_address(path[0]), + abi=ABIs.TokenABI + ) + to_token_contract = self.client.w3.eth.contract( + address=AsyncWeb3.to_checksum_address(path[-1]), + abi=ABIs.TokenABI + ) + router_contract = self.client.w3.eth.contract( + address=AsyncWeb3.to_checksum_address('0x39E098A153Ad69834a9Dac32f0FCa92066aD03f4'), + abi=ABIs.MaverickABI + ) + + from_token_symbol, to_token_decimals = await asyncio.gather( + from_token_contract.functions.symbol().call(), + to_token_contract.functions.decimals().call(), + ) + + if is_all_balance: + from_token_balance, from_token_decimals = await asyncio.gather( + from_token_contract.functions.balanceOf(self.client.account.address).call(), + from_token_contract.functions.decimals().call() + ) + token_amount = TokenAmount( + amount=from_token_balance, + decimals=from_token_decimals, + wei=True + ) + + from_token_price_dollar = await self.client.get_token_price(token_symbol=from_token_symbol) + to_token_price_dollar = await self.client.get_token_price('ETH') + + amount_out_min = TokenAmount( + amount=(float(token_amount.Ether) + * from_token_price_dollar + / to_token_price_dollar + * (100 - slippage) / 100), + decimals=to_token_decimals + ) + + await self._approve_token_if_needed( + token_contract=from_token_contract, + router_address=router_contract.address, + token_amount=token_amount + ) + + b_path = b'' + for address in path: + address = AsyncWeb3.to_checksum_address(address) + b_path += AsyncWeb3.to_bytes(hexstr=address) + + first_data = router_contract.encodeABI( + 'exactInput', + args=[( + b_path, + Tokens.ZERO, + int(time.time()) + 3 * 60, + token_amount.Wei, + amount_out_min.Wei, + )] + ) + second_data = router_contract.encodeABI( + 'unwrapWETH9', + args=(amount_out_min.Wei, self.client.account.address) + ) + tx_hash_bytes = await self.client.send_transaction( + to=router_contract.address, + data=router_contract.encodeABI( + 'multicall', + args=[[first_data, second_data]] + ), + value=token_amount.Wei, + max_priority_fee_per_gas=0, + ) + + # Why not using args=[first_data, second_data] ? + # Because of the function signature: multicall(bytes[]) + # If we have function signature: function_name(arg_1, arg_2, ...), then we use args=[arg_1, arg_2, ...] + # but when function signature is function_name(bytes[], ...), then we use args=[[arg_1, arg_2], ...] + + if tx_hash_bytes: + try: + tx_hash = await self.client.verif_tx(tx_hash=tx_hash_bytes) + print(f'Transaction success ({token_amount.Ether} {from_token_symbol} -> {amount_out_min.Ether} ETH)!! ' + f'tx_hash: {tx_hash}') + except Exception as err: + print(f'Transaction error!! tx_hash: {tx_hash_bytes.hex()}; error: {err}') + else: + print(f'Transaction error!!') + + async def swap_usdc_to_mav( + self, + token_amount: TokenAmount | None = None, + slippage: float = 1, + is_all_balance: bool = False + ): + """ + Function signature: 0xc04b8d59 - exactInput((bytes,address,uint256,uint256,uint256)) + 000: 0000000000000000000000000000000000000000000000000000000000000020 - ссылка на 20 байт + 020: 00000000000000000000000000000000000000000000000000000000000000a0 - размер структуры (160 байт), + 160 / 32 = 5 - cлед 5 строк - это структура (tuple) + 040: 0000000000000000000000002f5844b8b5c03bbc48408bfda1340f5181643f53 - address + 060: 000000000000000000000000000000000000000000000000000000006734c77a - deadline (int(time.time() + 10 * 60)) + 080: 00000000000000000000000000000000000000000000000000000000004b8e30 - amountIn (4_951_600 USDC) + 0a0: 000000000000000000000000000000000000000000000001875e8c616665fa1f - amountOutMin (28_201_132_266_598_300_191 MAV) + 0c0: 0000000000000000000000000000000000000000000000000000000000000064 - размер массива байтов (100 байт), + 100 / 5 = 20 bytes = 40 hexs + 0e0: 1d17cbcf0d6d143135ae902365d2e5e2a16538d4ef1cada686c12f7a0008cd5b - 1d17cbcf0d6d143135ae902365d2e5e2a16538d4 USDC + 100: 78eefaad35ad6f013355df6d4c9c3035724fd0e3914de96a5a83aaf474e398c7 - ef1cada686c12f7a0008cd5b78eefaad35ad6f01 pool USDC/USDC.e + 120: 9eb7a653b432f6313edf776c8d930142787c09494ec8bcb24dcaf8659e7d5d69 - 3355df6d4c9c3035724fd0e3914de96a5a83aaf4 USDC.e + 140: 979ee50800000000000000000000000000000000000000000000000000000000 - 74e398c79eb7a653b432f6313edf776c8d930142 USDC.e/MAV + 787c09494ec8bcb24dcaf8659e7d5d69979ee508 MAV + """ + path = [ + Tokens.USDC, + 'ef1cada686c12f7a0008cd5b78eefaad35ad6f01', # pool USDC/USDC.e + Tokens.USDC_E, + '74e398c79eb7a653b432f6313edf776c8d930142', # pool USDC.e/MAV + Tokens.STAR, + ] + + from_token_contract = self.client.w3.eth.contract( + address=AsyncWeb3.to_checksum_address(path[0]), + abi=ABIs.TokenABI + ) + to_token_contract = self.client.w3.eth.contract( + address=AsyncWeb3.to_checksum_address(path[-1]), + abi=ABIs.TokenABI + ) + router_contract = self.client.w3.eth.contract( + address=AsyncWeb3.to_checksum_address('0x39E098A153Ad69834a9Dac32f0FCa92066aD03f4'), + abi=ABIs.MaverickABI + ) + + from_token_symbol, to_token_decimals = await asyncio.gather( + from_token_contract.functions.symbol().call(), + to_token_contract.functions.decimals().call(), + ) + + if is_all_balance: + from_token_balance, from_token_decimals = await asyncio.gather( + from_token_contract.functions.balanceOf(self.client.account.address).call(), + from_token_contract.functions.decimals().call() + ) + token_amount = TokenAmount( + amount=from_token_balance, + decimals=from_token_decimals, + wei=True + ) + + from_token_price_dollar = await self.client.get_token_price(from_token_symbol) + to_token_price_dollar = await self.client.get_token_price('MAV') + + amount_out_min = TokenAmount( + amount=(float(token_amount.Ether) + * from_token_price_dollar + / to_token_price_dollar + * (100 - slippage) / 100), + decimals=to_token_decimals + ) + + await self._approve_token_if_needed( + token_contract=from_token_contract, + router_address=router_contract.address, + token_amount=token_amount + ) + + b_path = b'' + for address in path: + address = AsyncWeb3.to_checksum_address(address) + b_path += AsyncWeb3.to_bytes(hexstr=address) + + first_data = router_contract.encodeABI( + 'exactInput', + args=[( + b_path, + self.client.account.address, + int(time.time()) + 10 * 60, + token_amount.Wei, + amount_out_min.Wei, + )] + ) + tx_hash_bytes = await self.client.send_transaction( + to=router_contract.address, + data=router_contract.encodeABI( + 'multicall', + args=[[first_data]] + ), + value=token_amount.Wei, + max_priority_fee_per_gas=0, + ) + + if tx_hash_bytes: + try: + tx_hash = await self.client.verif_tx(tx_hash=tx_hash_bytes) + print(f'Transaction success ({token_amount.Ether} {from_token_symbol} -> {amount_out_min.Ether} MAV)!! ' + f'tx_hash: {tx_hash}') + except Exception as err: + print(f'Transaction error!! tx_hash: {tx_hash_bytes.hex()}; error: {err}') + else: + print(f'Transaction error!!') From 73cc90971eb8b39d7d60f2cdb55227c9e0e887a6 Mon Sep 17 00:00:00 2001 From: maked0n1an Date: Thu, 14 Nov 2024 02:07:20 +0200 Subject: [PATCH 12/18] Some fixes. --- lesson_03_unverified_contracts_part_2_dz/syncswap.py | 4 ++-- lesson_03_unverified_contracts_part_2_dz/tasks/koi_finance.py | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/lesson_03_unverified_contracts_part_2_dz/syncswap.py b/lesson_03_unverified_contracts_part_2_dz/syncswap.py index 034c15f..39fb411 100644 --- a/lesson_03_unverified_contracts_part_2_dz/syncswap.py +++ b/lesson_03_unverified_contracts_part_2_dz/syncswap.py @@ -5,7 +5,7 @@ import config from client import Client -from data.models import ABIs, TokenAmount, Tokens, TxArgs +from data.models import ABIs, SyncSwapFullAbi, TokenAmount, Tokens, TxArgs async def main(from_token_amount: TokenAmount, slippage: float = 5): @@ -33,7 +33,7 @@ async def main(from_token_amount: TokenAmount, slippage: float = 5): router_address = AsyncWeb3.to_checksum_address('0x9B5def958d0f3b6955cBEa4D5B7809b2fb26b059') router_contract = client.w3.eth.contract( address=router_address, - abi=ABIs.SyncSwapABI + abi=SyncSwapFullAbi ) from_token_price_dollar = await client.get_token_price(token_symbol=from_token_symbol) diff --git a/lesson_03_unverified_contracts_part_2_dz/tasks/koi_finance.py b/lesson_03_unverified_contracts_part_2_dz/tasks/koi_finance.py index 9038615..8159e15 100644 --- a/lesson_03_unverified_contracts_part_2_dz/tasks/koi_finance.py +++ b/lesson_03_unverified_contracts_part_2_dz/tasks/koi_finance.py @@ -127,10 +127,8 @@ async def swap_eth_to_usdc( tx_hash = await self.client.verif_tx(tx_hash=tx_hash_bytes) print(f'Transaction success ({token_amount.Ether} ETH -> {amount_out_min.Ether} {to_token_symbol})!! ' f'tx_hash: {tx_hash}') - # Transaction success (0.001 ETH -> 2.28988 USDC)!! tx_hash: 0x5e97aaaa972dc2aca2bdb8b6241fe6dd5bb9eaeb238d0dcd941c31c46198b51e except Exception as err: - print( - f'Transaction error!! tx_hash: {tx_hash_bytes.hex()}; error: {err}') + print(f'Transaction error!! tx_hash: {tx_hash_bytes.hex()}; error: {err}') else: print(f'Transaction error!!') \ No newline at end of file From fbae38f97c5cdd848287a211cc86a1d22ae5f9ad Mon Sep 17 00:00:00 2001 From: maked0n1an Date: Thu, 14 Nov 2024 12:12:15 +0200 Subject: [PATCH 13/18] SyncSwap: swap_usdc_to_eth implemented! --- .../tasks/sync_swap.py | 251 ++++++++++++++++++ 1 file changed, 251 insertions(+) create mode 100644 lesson_03_unverified_contracts_part_2_dz/tasks/sync_swap.py diff --git a/lesson_03_unverified_contracts_part_2_dz/tasks/sync_swap.py b/lesson_03_unverified_contracts_part_2_dz/tasks/sync_swap.py new file mode 100644 index 0000000..b1ea936 --- /dev/null +++ b/lesson_03_unverified_contracts_part_2_dz/tasks/sync_swap.py @@ -0,0 +1,251 @@ +import asyncio +import random +import time + +from eth_abi import abi +from web3 import AsyncWeb3 +from web3.contract import AsyncContract + +from client import Client +from data.models import ( + TokenAmount, + Tokens, + ABIs, + SyncSwapFullAbi, +) + + +class SyncSwap: + def __init__(self, client: Client): + self.client = client + + async def _get_raw_tx_params(self, wei_value: float = 0) -> dict: + return { + "chainId": await self.client.w3.eth.chain_id, + "from": self.client.account.address, + "value": wei_value, + "gasPrice": await self.client.w3.eth.gas_price, + "nonce": await self.client.w3.eth.get_transaction_count(self.client.account.address), + } + + async def _approve_token_if_needed( + self, + token_contract: AsyncContract, + router_address: str, + token_amount: TokenAmount, + is_unlimited: bool = False + ): + allowance, token_symbol = await asyncio.gather( + token_contract.functions.allowance( + self.client.account.address, + router_address + ).call(), + token_contract.functions.symbol().call() + ) + + if token_amount.Wei <= allowance: + return + + token_amount_wei = ( + 2 ** 256 - 1 + if is_unlimited + else token_amount.Wei + ) + token_amount_msg = ( + f'unlimited {token_symbol}' + if is_unlimited + else f'{token_amount.Ether} {token_symbol}' + ) + + approve_tx_hash_bytes = await self.client.send_transaction( + to=token_contract.address, + data=token_contract.encodeABI( + 'approve', + args=(router_address, token_amount_wei) + ), + max_priority_fee_per_gas=0 + ) + if approve_tx_hash_bytes: + try: + await self.client.verif_tx(tx_hash=approve_tx_hash_bytes) + waiting_time = round(random.choice([1, 5]), 2) + print( + f'Approved {token_amount_msg} to swap on SyncSwap, ' + f'sleeping {waiting_time} secs...' + ) + await asyncio.sleep(waiting_time) + except Exception as err: + print( + f'Approve transaction error!! tx_hash: ' + f'{approve_tx_hash_bytes.hex()}; error: {err}' + ) + else: + print('Failed approve transaction') + + async def swap_usdc_e_to_eth( + self, + token_amount: TokenAmount | None = None, + slippage: float = 0.5, + is_all_balance: bool = False, + ) -> str: + """ + Function signature: 0x7b2151e5 + 000: 0000000000000000000000000000000000000000000000000000000000000120 + 020: 00000000000000000000000000000000000000000000000000037ffdfb3a5842 - 985_153_748_490_306 (amountOutMin) + 040: 000000000000000000000000000000000000000000000000000000006734f8a1 - deadline(int(time.time() + 3 hours)) + 060: 0000000000000000000000003355df6d4c9c3035724fd0e3914de96a5a83aaf4 - USDC.e + 080: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff - amountPermit + 0a0: 000000000000000000000000000000000000000000000000000000006734f88b - deadline + 0c0: 000000000000000000000000000000000000000000000000000000000000001c - 28 (?) + 0e0: 487b3565872c99a82016d110ea112a85af0bf1c8ea871b9c2f2baec8665b4c99 - permit + 100: 62cd3ec837687ed58d39926a6513dbc7125717bfd519e9025b661043b70ef9a3 + 120: 0000000000000000000000000000000000000000000000000000000000000001 + 140: 0000000000000000000000000000000000000000000000000000000000000020 + 160: 0000000000000000000000000000000000000000000000000000000000000060 + 180: 0000000000000000000000003355df6d4c9c3035724fd0e3914de96a5a83aaf4 - USDC.e + 1a0: 0000000000000000000000000000000000000000000000000000000000323473 - amountIn (3_290_227) + 1c0: 0000000000000000000000000000000000000000000000000000000000000003 + 1e0: 0000000000000000000000000000000000000000000000000000000000000060 + 200: 00000000000000000000000000000000000000000000000000000000000001a0 + 220: 00000000000000000000000000000000000000000000000000000000000002e0 + 240: 000000000000000000000000a65349507212f9d1df0b001e221ceb78ff23b155 - USDC.e/USDT LP + 260: 00000000000000000000000000000000000000000000000000000000000000a0 + 280: 0000000000000000000000000000000000000000000000000000000000000000 + 2a0: 0000000000000000000000000000000000000000000000000000000000000120 + 2c0: 0000000000000000000000000000000000000000000000000000000000000000 + 2e0: 0000000000000000000000000000000000000000000000000000000000000060 + 300: 0000000000000000000000003355df6d4c9c3035724fd0e3914de96a5a83aaf4 - USDC.e + 320: 000000000000000000000000c029c9569c51d24af555106951078b5b4e11894a - USDT/WBTC LP + 340: 0000000000000000000000000000000000000000000000000000000000000000 + 360: 0000000000000000000000000000000000000000000000000000000000000000 + 380: 000000000000000000000000c029c9569c51d24af555106951078b5b4e11894a - USDT/WBTC LP + 3a0: 00000000000000000000000000000000000000000000000000000000000000a0 + 3c0: 0000000000000000000000000000000000000000000000000000000000000000 + 3e0: 0000000000000000000000000000000000000000000000000000000000000120 + 400: 0000000000000000000000000000000000000000000000000000000000000001 + 420: 0000000000000000000000000000000000000000000000000000000000000060 + 440: 000000000000000000000000493257fd37edb34451f62edf8d2a0c418852ba4c - USDT + 460: 000000000000000000000000b3479139e07568ba954c8a14d5a8b3466e35533d - WETH/WBTC LP + 480: 0000000000000000000000000000000000000000000000000000000000000000 + 4a0: 0000000000000000000000000000000000000000000000000000000000000000 + 4c0: 000000000000000000000000b3479139e07568ba954c8a14d5a8b3466e35533d - WETH/WBTC LP + 4e0: 00000000000000000000000000000000000000000000000000000000000000a0 + 500: 0000000000000000000000000000000000000000000000000000000000000000 + 520: 0000000000000000000000000000000000000000000000000000000000000120 + 540: 0000000000000000000000000000000000000000000000000000000000000001 + 560: 0000000000000000000000000000000000000000000000000000000000000060 + 580: 000000000000000000000000bbeb516fb02a01611cbbe0453fe3c580d7281011 - WBTC + 5a0: 0000000000000000000000002f5844b8b5c03bbc48408bfda1340f5181643f53 - own address + 5c0: 0000000000000000000000000000000000000000000000000000000000000001 + 5e0: 0000000000000000000000000000000000000000000000000000000000000000 + """ + from_token_contract = self.client.w3.eth.contract( + address=Tokens.USDC_E, + abi=ABIs.TokenABI + ) + to_token_contract = self.client.w3.eth.contract( + address=Tokens.WETH, + abi=ABIs.TokenABI + ) + from_token_symbol, to_token_symbol, to_token_decimals = await asyncio.gather( + from_token_contract.functions.symbol().call(), + to_token_contract.functions.symbol().call(), + to_token_contract.functions.decimals().call() + ) + + router_contract = self.client.w3.eth.contract( + address=AsyncWeb3.to_checksum_address( + '0x9B5def958d0f3b6955cBEa4D5B7809b2fb26b059'), + abi=SyncSwapFullAbi + ) + from_token_price_dollar, to_token_price_dollar = await asyncio.gather( + self.client.get_token_price(token_symbol=from_token_symbol), + self.client.get_token_price(token_symbol=to_token_symbol) + ) + + if is_all_balance: + from_token_balance, from_token_decimals = await asyncio.gather( + from_token_contract.functions.balanceOf( + self.client.account.address).call(), + from_token_contract.functions.decimals().call() + ) + token_amount = TokenAmount( + amount=from_token_balance, + decimals=from_token_decimals, + wei=True + ) + + deadline = int(time.time()) + 11850 + amount_out_min = TokenAmount( + amount=(float(token_amount.Ether) + * from_token_price_dollar + / to_token_price_dollar + * (100 - slippage) / 100 + ), + decimals=to_token_decimals + ) + + await self._approve_token_if_needed( + token_contract=from_token_contract, + router_address=router_contract.address, + token_amount=token_amount, + is_unlimited=True + ) + + steps = [ + [ + Tokens.USDC_E_ZK_LP, + self.client.w3.to_hex( + abi.encode( + ['address', 'address', 'uint8'], + [from_token_contract.address, Tokens.ZK_WETH_C_LP, 2] + ) + ), + Tokens.ZERO, + '0x', + False + ], + [ + Tokens.ZK_WETH_C_LP, + self.client.w3.to_hex( + abi.encode( + ['address', 'address', 'uint8'], + [Tokens.ZK, self.client.account.address, 1] + ) + ), + Tokens.ZERO, + '0x', + False + ] + ] + paths = [ + steps, + from_token_contract.address, + token_amount.Wei + ] + tx_params = await router_contract.functions.swap( + [paths], + amount_out_min.Wei, + deadline + ).build_transaction(await self._get_raw_tx_params()) + + signed_tx = self.client.w3.eth.account.sign_transaction( + tx_params, self.client.private_key + ) + tx_hash_bytes = await self.client.w3.eth.send_raw_transaction( + signed_tx.rawTransaction + ) + + if tx_hash_bytes: + try: + tx_hash = await self.client.verif_tx(tx_hash=tx_hash_bytes) + print( + f'Transaction success ({token_amount.Ether} ' + f'{from_token_symbol} -> {amount_out_min.Ether} ' + f'{to_token_symbol})!! tx_hash: {tx_hash}' + ) + except Exception as err: + print( + f'Transaction error!! tx_hash: {tx_hash_bytes.hex()}; error: {err}') + else: + print(f'Transaction error!!') From 05d71a68776e21acc717140a047245c6e140e241 Mon Sep 17 00:00:00 2001 From: maked0n1an Date: Thu, 14 Nov 2024 13:01:03 +0200 Subject: [PATCH 14/18] SyncSwap: tried to decode full. --- .../tasks/sync_swap.py | 84 ++++++++----------- 1 file changed, 35 insertions(+), 49 deletions(-) diff --git a/lesson_03_unverified_contracts_part_2_dz/tasks/sync_swap.py b/lesson_03_unverified_contracts_part_2_dz/tasks/sync_swap.py index b1ea936..5667581 100644 --- a/lesson_03_unverified_contracts_part_2_dz/tasks/sync_swap.py +++ b/lesson_03_unverified_contracts_part_2_dz/tasks/sync_swap.py @@ -89,55 +89,41 @@ async def swap_usdc_e_to_eth( is_all_balance: bool = False, ) -> str: """ - Function signature: 0x7b2151e5 - 000: 0000000000000000000000000000000000000000000000000000000000000120 - 020: 00000000000000000000000000000000000000000000000000037ffdfb3a5842 - 985_153_748_490_306 (amountOutMin) - 040: 000000000000000000000000000000000000000000000000000000006734f8a1 - deadline(int(time.time() + 3 hours)) - 060: 0000000000000000000000003355df6d4c9c3035724fd0e3914de96a5a83aaf4 - USDC.e - 080: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff - amountPermit - 0a0: 000000000000000000000000000000000000000000000000000000006734f88b - deadline - 0c0: 000000000000000000000000000000000000000000000000000000000000001c - 28 (?) - 0e0: 487b3565872c99a82016d110ea112a85af0bf1c8ea871b9c2f2baec8665b4c99 - permit - 100: 62cd3ec837687ed58d39926a6513dbc7125717bfd519e9025b661043b70ef9a3 - 120: 0000000000000000000000000000000000000000000000000000000000000001 - 140: 0000000000000000000000000000000000000000000000000000000000000020 - 160: 0000000000000000000000000000000000000000000000000000000000000060 - 180: 0000000000000000000000003355df6d4c9c3035724fd0e3914de96a5a83aaf4 - USDC.e - 1a0: 0000000000000000000000000000000000000000000000000000000000323473 - amountIn (3_290_227) - 1c0: 0000000000000000000000000000000000000000000000000000000000000003 - 1e0: 0000000000000000000000000000000000000000000000000000000000000060 - 200: 00000000000000000000000000000000000000000000000000000000000001a0 - 220: 00000000000000000000000000000000000000000000000000000000000002e0 - 240: 000000000000000000000000a65349507212f9d1df0b001e221ceb78ff23b155 - USDC.e/USDT LP - 260: 00000000000000000000000000000000000000000000000000000000000000a0 - 280: 0000000000000000000000000000000000000000000000000000000000000000 - 2a0: 0000000000000000000000000000000000000000000000000000000000000120 - 2c0: 0000000000000000000000000000000000000000000000000000000000000000 - 2e0: 0000000000000000000000000000000000000000000000000000000000000060 - 300: 0000000000000000000000003355df6d4c9c3035724fd0e3914de96a5a83aaf4 - USDC.e - 320: 000000000000000000000000c029c9569c51d24af555106951078b5b4e11894a - USDT/WBTC LP - 340: 0000000000000000000000000000000000000000000000000000000000000000 - 360: 0000000000000000000000000000000000000000000000000000000000000000 - 380: 000000000000000000000000c029c9569c51d24af555106951078b5b4e11894a - USDT/WBTC LP - 3a0: 00000000000000000000000000000000000000000000000000000000000000a0 - 3c0: 0000000000000000000000000000000000000000000000000000000000000000 - 3e0: 0000000000000000000000000000000000000000000000000000000000000120 - 400: 0000000000000000000000000000000000000000000000000000000000000001 - 420: 0000000000000000000000000000000000000000000000000000000000000060 - 440: 000000000000000000000000493257fd37edb34451f62edf8d2a0c418852ba4c - USDT - 460: 000000000000000000000000b3479139e07568ba954c8a14d5a8b3466e35533d - WETH/WBTC LP - 480: 0000000000000000000000000000000000000000000000000000000000000000 - 4a0: 0000000000000000000000000000000000000000000000000000000000000000 - 4c0: 000000000000000000000000b3479139e07568ba954c8a14d5a8b3466e35533d - WETH/WBTC LP - 4e0: 00000000000000000000000000000000000000000000000000000000000000a0 - 500: 0000000000000000000000000000000000000000000000000000000000000000 - 520: 0000000000000000000000000000000000000000000000000000000000000120 - 540: 0000000000000000000000000000000000000000000000000000000000000001 - 560: 0000000000000000000000000000000000000000000000000000000000000060 - 580: 000000000000000000000000bbeb516fb02a01611cbbe0453fe3c580d7281011 - WBTC - 5a0: 0000000000000000000000002f5844b8b5c03bbc48408bfda1340f5181643f53 - own address - 5c0: 0000000000000000000000000000000000000000000000000000000000000001 - 5e0: 0000000000000000000000000000000000000000000000000000000000000000 + Function signature: 0xd7570e45 - function selector/method ID + 000: 0000000000000000000000000000000000000000000000000000000000000060 - ссылка на следующую строку (000 + 60 = 060) + 020: 0000000000000000000000000000000000000000000000000001bf8b38d396b5 - 492_079_651_460_789 (amountOutMin) + 040: 000000000000000000000000000000000000000000000000000000006735fa7a - deadline + 060: 0000000000000000000000000000000000000000000000000000000000000001 - начало структуры + 080: 0000000000000000000000000000000000000000000000000000000000000020 - ссылка на следующую строку (080 + 20 = 0a0) + 0a0: 0000000000000000000000000000000000000000000000000000000000000060 - следующие 60(16) = 96(10) байтов = след. 3 строки + 0c0: 0000000000000000000000003355df6d4c9c3035724fd0e3914de96a5a83aaf4 - USDC.e - адрес входящего токена - 1 строка + 0e0: 0000000000000000000000000000000000000000000000000000000000186a4f - 1_600_079 (amountIn) - 2 строка + 100: 0000000000000000000000000000000000000000000000000000000000000002 - начало второй структуры - 3 строка + 120: 0000000000000000000000000000000000000000000000000000000000000040 - ссылка на вторую строку после этой + (120(16) + 40(16) = 160(16)) + 140: 0000000000000000000000000000000000000000000000000000000000000180 - 180(16) / 20(16) = 6 след строк + 160: 00000000000000000000000040b768de8b2e4ed83d982804cb2fcc53d2529be9 - USDC_E_ZK_LP + 180: 00000000000000000000000000000000000000000000000000000000000000a0 - 384(10) + 160(10) = 544(10) = 220(16) + ccылка на 220 строку + 1a0: 0000000000000000000000000000000000000000000000000000000000000000 - 0x + 1c0: 0000000000000000000000000000000000000000000000000000000000000120 - 1c0(16) + 120(16) = 448 + 288 = 736(10) = 2E0(16) + 1e0: 0000000000000000000000000000000000000000000000000000000000000000 + 200: 0000000000000000000000000000000000000000000000000000000000000060 - следующие 60(16) = 96(10) байтов = след. 3 строки + 220: 0000000000000000000000003355df6d4c9c3035724fd0e3914de96a5a83aaf4 - USDC.e - адрес входящего токена + 240: 00000000000000000000000090899441d5c9801d57773a3d5b8b880520cf2fe1 - ZK_WETH_C_LP + 260: 0000000000000000000000000000000000000000000000000000000000000002 - const + 280: 0000000000000000000000000000000000000000000000000000000000000000 - zero address or 0x + 2a0: 00000000000000000000000090899441d5c9801d57773a3d5b8b880520cf2fe1 - ZK_WETH_C_LP + 2c0: 00000000000000000000000000000000000000000000000000000000000000a0 - 704(10) + 160(10) = 864(10) = 360(16) + ccылка на 360 строку + 2e0: 0000000000000000000000000000000000000000000000000000000000000000 - zero address + 300: 0000000000000000000000000000000000000000000000000000000000000120 - 300(16) + 120(16) = 768 + 288 = 1056 = 420(16) + 320: 0000000000000000000000000000000000000000000000000000000000000000 + 340: 0000000000000000000000000000000000000000000000000000000000000060 - следующие 60(16) = 96(10) байтов = след. 3 строки + 360: 0000000000000000000000005a7d6b2f92c77fad6ccabd7ee0624e64907eaf3e - ZK + 380: 0000000000000000000000002f5844b8b5c03bbc48408bfda1340f5181643f53 - my_address + 3a0: 0000000000000000000000000000000000000000000000000000000000000001 - const + 3c0: 0000000000000000000000000000000000000000000000000000000000000000 - zero_address """ from_token_contract = self.client.w3.eth.contract( address=Tokens.USDC_E, From c03d86213d5c2e233cd93494211f81c711acb10b Mon Sep 17 00:00:00 2001 From: maked0n1an Date: Thu, 14 Nov 2024 13:01:20 +0200 Subject: [PATCH 15/18] Models: updated. --- lesson_03_unverified_contracts_part_2_dz/data/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lesson_03_unverified_contracts_part_2_dz/data/models.py b/lesson_03_unverified_contracts_part_2_dz/data/models.py index 8b65401..41a1a75 100644 --- a/lesson_03_unverified_contracts_part_2_dz/data/models.py +++ b/lesson_03_unverified_contracts_part_2_dz/data/models.py @@ -33,11 +33,12 @@ class Tokens: STAR = AsyncWeb3.to_checksum_address('0x838A66F841DD5148475a8918db0732c239499a03') WBTC = AsyncWeb3.to_checksum_address('0xbbeb516fb02a01611cbbe0453fe3c580d7281011') MAV = AsyncWeb3.to_checksum_address('0x787c09494ec8bcb24dcaf8659e7d5d69979ee508') + ZK = AsyncWeb3.to_checksum_address('0x5a7d6b2f92c77fad6ccabd7ee0624e64907eaf3e') ETH = AsyncWeb3.to_checksum_address(f'0x{"".zfill(40)}') USDC_E_ZK_LP = AsyncWeb3.to_checksum_address('0x40b768de8b2e4ed83d982804cb2fcc53d2529be9') ZK_WETH_LP = AsyncWeb3.to_checksum_address('0x1a32a715b4ebef211bbf4baa414f563b25cc50c9') - ZK = AsyncWeb3.to_checksum_address('0x5a7d6b2f92c77fad6ccabd7ee0624e64907eaf3e') + ZK_WETH_C_LP = AsyncWeb3.to_checksum_address('0x90899441D5c9801d57773a3d5b8B880520CF2fe1') USDC_E_USDT_LP = AsyncWeb3.to_checksum_address('0xa65349507212f9d1df0b001e221ceb78ff23b155') USDT_WBTC_LP = AsyncWeb3.to_checksum_address('0xc029c9569c51d24af555106951078b5b4e11894a') WETH_WBTC_LP = AsyncWeb3.to_checksum_address('0xb3479139e07568ba954c8a14d5a8b3466e35533d') @@ -277,5 +278,4 @@ class ABIs: } ] -SyncSwapFullAbi = get_json('data/abis/syncswap_abi.json') -PermitTokenABI = get_json('data/abis/permit_token_abi.json') \ No newline at end of file +SyncSwapFullAbi = get_json('data/abis/syncswap_router_abi.json') \ No newline at end of file From ad945d476c26ad551a50d4ec140134cb3d6067a9 Mon Sep 17 00:00:00 2001 From: maked0n1an Date: Thu, 14 Nov 2024 13:02:12 +0200 Subject: [PATCH 16/18] Info: updated. --- .../info.txt | 42 ++++++++++++------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/lesson_03_unverified_contracts_part_2_dz/info.txt b/lesson_03_unverified_contracts_part_2_dz/info.txt index 15846df..5685600 100644 --- a/lesson_03_unverified_contracts_part_2_dz/info.txt +++ b/lesson_03_unverified_contracts_part_2_dz/info.txt @@ -3,24 +3,34 @@ В этом уроке мы разберёмся как работать с неверифицированными контрактами, поработаем с площадкой Koi Finance, SyncSwap и Maverick. Наши задания: -- задание №1: сделать скрипт на следующие cвапы в сети zkSync на площадке Maverick (https://app.mav.xyz/?chain=324): -swap_eth_to_busd +- задание №1**: сделать скрипт на следующие cвапы в сети zkSync на площадке KoiFinance (https://dapp.koi.finance/swap): +swap_eth_to_usdc (отличается от той, что в видеоуроке) +Пример: +(0.001 ETH -> 3.113 USDC): https://era.zksync.network/tx/0x293462efd1b0ca29fc731e84d868eb0589506b1b4c332b374b5bb9ca5e2a0c81 + +- задание №2**: сделать скрипт на cвапы в сети zkSync на площадке Maverick (https://app.mav.xyz/?chain=324): swap_usdc_to_eth -swap_busd_to_eth +swap_usdc_to_mav +Функции должны иметь флаг is_all_balance, который будет использовать весь баланс на счету для обмена. +Примеры: +(6.68 USDC -> 0.00212 ETH): https://explorer.zksync.io/tx/0x802636e2f48fecc239882252b5437fe1e62cd1c0044b3ef3c30e172a91e99f81 +(4.5 USDC -> 28.48 MAV): https://explorer.zksync.io/tx/0x5dc17f0cfd6dda8a1522963e2f9ea0537060ba7a03535d40771eea12559586e1 +- задание №3**: сделать скрипт на cвапы в сети zkSync на площадке SyncSwap (https://syncswap.xyz/): +swap_usdc_to_eth +Функции должны иметь флаг is_all_balance, который будет использовать весь баланс на счету для обмена. +Примеры: +(1.600079 USDC.e -> 0.000492 ETH): https://explorer.zksync.io/tx/0x37287fa8108c897938d89f4a40755fb8840d3c502ed13627c007d564f9edf624 -- задание №2: сделать скрипт на cвапы в сети zkSync на площадке KoiFinance (https://dapp.koi.finance/swap): -swap_usdt_to_eth -swap_usdt_to_usdc_e -Функции должны иметь флаг is_all_balance, который будет использовать весь баланс на счету для обмена. Реализацию data -делать через самописный ABI функции swap. -Примеры: -(2.83 USDT -> 0.00092 ETH): https://explorer.zksync.io/tx/0x226e9a7c741618f3e867b8f156fae43d0c4dd4bf773fc00c17e24c7e61d4696b -(1.734763 USDT -> 1.665372 USDC.e): https://explorer.zksync.io/tx/0x179df85ee97094190b17433b68c0a87f382a39188a44255917ed0afed9a386b2 +Внимание! +Все транзакции тестировать на малых суммах, потому что при неправильно составленном пути может быть потеряна часть токенов. +Как, например, в этом случае: https://era.zksync.network/tx/0x412d7c487570013a8f5e2b4f2a8ffb939ba049b5ef5e8dfde412899d7353ad35 +(я просто отправил 3 бакса на контракт :)) -- задание №3*: реализовать скрипт для универсальных свапов from_token -> to_token -Подсказка к заданию №2: сигнатура функции -async def swap_usdt_to_eth(self, token_amount: TokenAmount | None = None, slippage: float = 0.5, is_all_balance: bool = False) -> str. -Подсказка к заданию №3: сигнатура функции -async def swap(self, from_token_symbol, to_token_symbol, amount, slippage) -> str. \ No newline at end of file +Подсказка к заданиям: +сделать функцию для парсинга данных из tx, чтобы автоматически за вас разбивал строку на 64 символа и выводил красиво в консоль. +Желательно сбоку добавить hex-нумерацию по типу: +000: 000000000000000000000000...00000000000020 +020: 000000000000000000000000...00000000000002 +сигнатура функций как обычно: async def swap_eth_to_usdc(self, token_amount, slippage, is_all_balance(?)) \ No newline at end of file From d6fb49c78f13ab3d61263932fbd9a0a9afca2d82 Mon Sep 17 00:00:00 2001 From: maked0n1an Date: Thu, 14 Nov 2024 20:04:24 +0200 Subject: [PATCH 17/18] Some fixes. --- .../tasks/koi_finance.py | 9 --------- .../tasks/sync_swap.py | 2 +- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/lesson_03_unverified_contracts_part_2_dz/tasks/koi_finance.py b/lesson_03_unverified_contracts_part_2_dz/tasks/koi_finance.py index 8159e15..b872f09 100644 --- a/lesson_03_unverified_contracts_part_2_dz/tasks/koi_finance.py +++ b/lesson_03_unverified_contracts_part_2_dz/tasks/koi_finance.py @@ -13,15 +13,6 @@ class KoiFinance: def __init__(self, client: Client): self.client = client - async def get_raw_tx_params(self, wei_value: float = 0) -> dict: - return { - "chainId": await self.client.w3.eth.chain_id, - "from": self.client.account.address, - "value": wei_value, - "gasPrice": await self.client.w3.eth.gas_price, - "nonce": await self.client.w3.eth.get_transaction_count(self.client.account.address), - } - async def swap_eth_to_usdc( self, token_amount: TokenAmount, diff --git a/lesson_03_unverified_contracts_part_2_dz/tasks/sync_swap.py b/lesson_03_unverified_contracts_part_2_dz/tasks/sync_swap.py index 5667581..474332a 100644 --- a/lesson_03_unverified_contracts_part_2_dz/tasks/sync_swap.py +++ b/lesson_03_unverified_contracts_part_2_dz/tasks/sync_swap.py @@ -87,7 +87,7 @@ async def swap_usdc_e_to_eth( token_amount: TokenAmount | None = None, slippage: float = 0.5, is_all_balance: bool = False, - ) -> str: + ): """ Function signature: 0xd7570e45 - function selector/method ID 000: 0000000000000000000000000000000000000000000000000000000000000060 - ссылка на следующую строку (000 + 60 = 060) From bb3d0919e7b6971cdd063133f2e1f0625c18acd2 Mon Sep 17 00:00:00 2001 From: maked0n1an Date: Thu, 14 Nov 2024 20:11:13 +0200 Subject: [PATCH 18/18] main: updated. --- lesson_03_unverified_contracts_part_2_dz/main.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lesson_03_unverified_contracts_part_2_dz/main.py b/lesson_03_unverified_contracts_part_2_dz/main.py index 5c8e91a..11e0aeb 100644 --- a/lesson_03_unverified_contracts_part_2_dz/main.py +++ b/lesson_03_unverified_contracts_part_2_dz/main.py @@ -3,6 +3,8 @@ import config from data.models import TokenAmount from tasks.koi_finance import KoiFinance +from tasks.maverick import Maverick +from tasks.sync_swap import SyncSwap async def main(): @@ -13,9 +15,19 @@ async def main(): ) koi_finance = KoiFinance(client) await koi_finance.swap_eth_to_usdc( - token_amount=TokenAmount(0.002), + token_amount=TokenAmount(0.001), slippage=1 ) + maverick = Maverick(client) + await maverick.swap_usdc_to_mav( + slippage=1, + is_all_balance=True + ) + sync_swap = SyncSwap(client) + await sync_swap.swap_usdc_e_to_eth( + slippage=1, + is_all_balance=True + ) if __name__ == "__main__":