import logging from typing import Any, Callable, Dict, Optional, Union from uuid import UUID from eth_abi import decode_single, encode_single from eth_utils import function_signature_to_4byte_selector from moonstreamdb.blockchain import AvailableBlockchainType from web3 import Web3 from web3._utils.abi import normalize_event_input_types from web3.contract import ContractFunction from web3.middleware import geth_poa_middleware from web3.providers.rpc import HTTPProvider from .settings import ( MOONSTREAM_AMOY_WEB3_PROVIDER_URI, MOONSTREAM_ARBITRUM_NOVA_WEB3_PROVIDER_URI, MOONSTREAM_ARBITRUM_SEPOLIA_WEB3_PROVIDER_URI, MOONSTREAM_AVALANCHE_FUJI_WEB3_PROVIDER_URI, MOONSTREAM_AVALANCHE_WEB3_PROVIDER_URI, MOONSTREAM_BLAST_SEPOLIA_WEB3_PROVIDER_URI, MOONSTREAM_BLAST_WEB3_PROVIDER_URI, MOONSTREAM_ETHEREUM_WEB3_PROVIDER_URI, MOONSTREAM_MUMBAI_WEB3_PROVIDER_URI, MOONSTREAM_POLYGON_WEB3_PROVIDER_URI, MOONSTREAM_WYRM_WEB3_PROVIDER_URI, MOONSTREAM_XAI_SEPOLIA_WEB3_PROVIDER_URI, MOONSTREAM_XAI_WEB3_PROVIDER_URI, MOONSTREAM_XDAI_WEB3_PROVIDER_URI, MOONSTREAM_ZKSYNC_ERA_SEPOLIA_WEB3_PROVIDER_URI, MOONSTREAM_ZKSYNC_ERA_TESTNET_WEB3_PROVIDER_URI, MOONSTREAM_ZKSYNC_ERA_WEB3_PROVIDER_URI, NB_ACCESS_ID_HEADER, multicall_contract_abi, multicall_contracts, ) logger = logging.getLogger(__name__) moonstream_web3_provider = Web3( Web3.HTTPProvider(MOONSTREAM_ETHEREUM_WEB3_PROVIDER_URI) ) def yield_web3_provider() -> Web3: return moonstream_web3_provider WEB3_CLIENT_REQUEST_TIMEOUT_SECONDS = 10 def connect( blockchain_type: AvailableBlockchainType, web3_uri: Optional[str] = None, access_id: Optional[UUID] = None, user_token: Optional[UUID] = None, ) -> Web3: request_kwargs: Dict[str, Any] = {} if blockchain_type != AvailableBlockchainType.WYRM: request_kwargs = { "headers": { "Content-Type": "application/json", } } if access_id is not None: request_kwargs["headers"][NB_ACCESS_ID_HEADER] = str(access_id) elif user_token is not None: request_kwargs["headers"]["Authorization"] = f"Bearer {user_token}" if web3_uri is None: if blockchain_type == AvailableBlockchainType.ETHEREUM: web3_uri = MOONSTREAM_ETHEREUM_WEB3_PROVIDER_URI elif blockchain_type == AvailableBlockchainType.POLYGON: web3_uri = MOONSTREAM_POLYGON_WEB3_PROVIDER_URI elif blockchain_type == AvailableBlockchainType.MUMBAI: web3_uri = MOONSTREAM_MUMBAI_WEB3_PROVIDER_URI elif blockchain_type == AvailableBlockchainType.AMOY: web3_uri = MOONSTREAM_AMOY_WEB3_PROVIDER_URI elif blockchain_type == AvailableBlockchainType.XDAI: web3_uri = MOONSTREAM_XDAI_WEB3_PROVIDER_URI elif blockchain_type == AvailableBlockchainType.WYRM: web3_uri = MOONSTREAM_WYRM_WEB3_PROVIDER_URI elif blockchain_type == AvailableBlockchainType.ZKSYNC_ERA_TESTNET: web3_uri = MOONSTREAM_ZKSYNC_ERA_TESTNET_WEB3_PROVIDER_URI elif blockchain_type == AvailableBlockchainType.ZKSYNC_ERA: web3_uri = MOONSTREAM_ZKSYNC_ERA_WEB3_PROVIDER_URI elif blockchain_type == AvailableBlockchainType.ZKSYNC_ERA_SEPOLIA: web3_uri = MOONSTREAM_ZKSYNC_ERA_SEPOLIA_WEB3_PROVIDER_URI elif blockchain_type == AvailableBlockchainType.ARBITRUM_NOVA: web3_uri = MOONSTREAM_ARBITRUM_NOVA_WEB3_PROVIDER_URI elif blockchain_type == AvailableBlockchainType.ARBITRUM_SEPOLIA: web3_uri = MOONSTREAM_ARBITRUM_SEPOLIA_WEB3_PROVIDER_URI elif blockchain_type == AvailableBlockchainType.XAI: web3_uri = MOONSTREAM_XAI_WEB3_PROVIDER_URI elif blockchain_type == AvailableBlockchainType.XAI_SEPOLIA: web3_uri = MOONSTREAM_XAI_SEPOLIA_WEB3_PROVIDER_URI elif blockchain_type == AvailableBlockchainType.AVALANCHE: web3_uri = MOONSTREAM_AVALANCHE_WEB3_PROVIDER_URI elif blockchain_type == AvailableBlockchainType.AVALANCHE_FUJI: web3_uri = MOONSTREAM_AVALANCHE_FUJI_WEB3_PROVIDER_URI elif blockchain_type == AvailableBlockchainType.BLAST: web3_uri = MOONSTREAM_BLAST_WEB3_PROVIDER_URI elif blockchain_type == AvailableBlockchainType.BLAST_SEPOLIA: web3_uri = MOONSTREAM_BLAST_SEPOLIA_WEB3_PROVIDER_URI else: raise Exception("Wrong blockchain type provided for web3 URI") if web3_uri.startswith("http://") or web3_uri.startswith("https://"): request_kwargs["timeout"] = WEB3_CLIENT_REQUEST_TIMEOUT_SECONDS web3_client = Web3(HTTPProvider(web3_uri, request_kwargs=request_kwargs)) # type: ignore else: web3_client = Web3(Web3.IPCProvider(web3_uri)) if blockchain_type != AvailableBlockchainType.ETHEREUM: web3_client.middleware_onion.inject(geth_poa_middleware, layer=0) return web3_client def multicall( web3_client: Web3, blockchain_type: AvailableBlockchainType, calls: list, method: str = "tryAggregate", block_identifier: Union[str, int, bytes, None] = "latest", ) -> list: """ Calls multicall contract with given calls and returns list of results """ multicall_contract = web3_client.eth.contract( address=Web3.toChecksumAddress(multicall_contracts[blockchain_type]), abi=multicall_contract_abi, ) return multicall_contract.get_function_by_name(method)(False, calls).call( block_identifier=block_identifier ) def cast_to_python_type(evm_type: str) -> Callable: if evm_type.startswith(("uint", "int")): return int elif evm_type.startswith("bytes"): return bytes elif evm_type == "string": return str elif evm_type == "address": return Web3.toChecksumAddress elif evm_type == "bool": return bool else: raise ValueError(f"Cannot convert to python type {evm_type}") class FunctionInput: def __init__(self, name: str, value: Any, solidity_type: str): self.name = name self.value = value self.solidity_type = solidity_type class FunctionSignature: def __init__(self, function: ContractFunction): self.name = function.abi["name"] self.inputs = [ {"name": arg["name"], "type": arg["type"]} for arg in normalize_event_input_types(function.abi.get("inputs", [])) ] self.input_types_signature = "({})".format( ",".join([inp["type"] for inp in self.inputs]) ) self.output_types_signature = "({})".format( ",".join( [ arg["type"] for arg in normalize_event_input_types( function.abi.get("outputs", []) ) ] ) ) self.signature = "{}{}".format(self.name, self.input_types_signature) self.fourbyte = function_signature_to_4byte_selector(self.signature) def encode_data(self, args=None) -> bytes: return ( self.fourbyte + encode_single(self.input_types_signature, args) if args else self.fourbyte ) def decode_data(self, output): return decode_single(self.output_types_signature, output)