diff --git a/pyproject.toml b/pyproject.toml index ee3f1de..016d706 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "firefly_exchange_client" -version = "0.2.2" +version = "0.3.0" description = "Library to interact with firefly exchange protocol including its off-chain api-gateway and on-chain contracts" readme = "README.md" requires-python = ">=3.8" diff --git a/src/firefly_exchange_client/api_service.py b/src/firefly_exchange_client/api_service.py index 25c7c44..8a99ba8 100644 --- a/src/firefly_exchange_client/api_service.py +++ b/src/firefly_exchange_client/api_service.py @@ -2,22 +2,22 @@ from .interfaces import * class APIService(): - def __init__(self, url): + def __init__(self, url:str) -> None: self.server_url = url self.auth_token = None self.client = aiohttp.ClientSession() - async def close_session(self): + async def close_session(self) -> None: if self.client is not None: return await self.client.close() - async def get(self, service_url, query={}, auth_required=False): + async def get(self, service_url:str, query:dict={}, auth_required:bool=False) -> dict: """ Makes a GET request and returns the results Inputs: - - service_url(str): the url to make the request to. - - query(dict): the get query. - - auth_required(bool): indicates whether authorization is required for the call or not. + - service_url: the url to make the request to. + - query: the get query. + - auth_required: indicates whether authorization is required for the call or not. """ url = self._create_url(service_url) @@ -35,13 +35,13 @@ async def get(self, service_url, query={}, auth_required=False): except: raise Exception("Error while getting {}: {}".format(url, response)) - async def post(self, service_url, data, auth_required=False): + async def post(self, service_url:str, data:dict, auth_required:bool=False) -> dict: """ Makes a POST request and returns the results Inputs: - - service_url(str): the url to make the request to. - - data(dict): the data to post with POST request. - - auth_required(bool): indicates whether authorization is required for the call or not. + - service_url: the url to make the request to. + - data: the data to post with POST request. + - auth_required: indicates whether authorization is required for the call or not. """ url = self._create_url(service_url) response = None @@ -59,13 +59,13 @@ async def post(self, service_url, data, auth_required=False): except: raise Exception("Error while posting to {}: {}".format(url, response)) - async def delete(self,service_url, data, auth_required=False): + async def delete(self,service_url:str, data:dict, auth_required:bool=False) -> dict: """ Makes a DELETE request and returns the results Inputs: - - service_url(str): the url to make the request to. - - data(dict): the data to post with POST request. - - auth_required(bool): indicates whether authorization is required for the call or not. + - service_url: the url to make the request to. + - data: the data to post with POST request. + - auth_required: indicates whether authorization is required for the call or not. """ url = self._create_url(service_url) @@ -86,8 +86,10 @@ async def delete(self,service_url, data, auth_required=False): ''' Private methods ''' - def _create_url(self, path): + def _create_url(self, path:str) -> str: """ Appends namespace to server url + Inputs: + - path: the route name/path """ return "{}{}".format(self.server_url, path) diff --git a/src/firefly_exchange_client/client.py b/src/firefly_exchange_client/client.py index 53d9539..0794d1e 100644 --- a/src/firefly_exchange_client/client.py +++ b/src/firefly_exchange_client/client.py @@ -8,12 +8,14 @@ from .sockets import Sockets from .enumerations import * from .websocket_client import WebsocketClient +from typing import Union +from decimal import Decimal from eth_account import Account from eth_utils import to_wei, from_wei class FireflyClient: - def __init__(self, are_terms_accepted, network, private_key): + def __init__(self, are_terms_accepted:bool, network:Network, private_key:str) -> None: self.are_terms_accepted = are_terms_accepted self.network = network self.w3 = self._connect_w3(self.network["url"]) @@ -26,7 +28,14 @@ def __init__(self, are_terms_accepted, network, private_key): self.onboarding_signer = OnboardingSigner() - async def init(self, user_onboarding=True): + async def init(self, user_onboarding:bool=True) -> None: + """ + Initializes the client and on boards user onto exchange + + Inputs: + - user_onboarding: boolean indicating if a user wants to onboard on to exchange or not + """ + self.contracts.contract_addresses = await self.get_contract_addresses() if "error" in self.contracts.contract_addresses: @@ -47,7 +56,7 @@ async def init(self, user_onboarding=True): self.webSocketClient.set_token(self.apis.auth_token) - async def onboard_user(self, token:str=None): + async def onboard_user(self, token:str=None) -> str : """ On boards the user address and returns user authentication token. Inputs: @@ -72,13 +81,13 @@ async def onboard_user(self, token:str=None): return user_auth_token - async def authorize_signed_hash(self, signed_hash:str): + async def authorize_signed_hash(self, signed_hash:str) -> dict: """ Registers user as an authorized user on server and returns authorization token. Inputs: - signed_hash: signed onboarding hash Returns: - - dict: response from user authorization API Firefly + - dict: response from user authorization API Bluefin """ return await self.apis.post( SERVICE_URLS["USER"]["AUTHORIZE"], @@ -88,9 +97,10 @@ async def authorize_signed_hash(self, signed_hash:str): "isTermAccepted": self.are_terms_accepted, }) - def add_market(self, symbol: MARKET_SYMBOLS, trader_contract=None): + def add_market(self, symbol: MARKET_SYMBOLS, trader_contract=None) -> bool: """ Adds Order signer for market to instance's order_signers dict. + Inputs: - symbol(MARKET_SYMBOLS): Market symbol of order signer. - orders_contract(str): Contract address of the orders contract. @@ -117,30 +127,31 @@ def add_market(self, symbol: MARKET_SYMBOLS, trader_contract=None): ) return True - def add_contract(self,name,address,market=None): + def add_contract(self, name:str, address: str, market=None) -> None: """ Adds contracts to the instance's contracts dictionary. The contract name should match the contract's abi name in ./abi directory or a new abi should be added with the desired name. Inputs: - - name(str): The contract name. - - address(str): The contract address. - - market(str): The market (ETH/BTC) this contract belongs to (required for market specific contracts). + - name: The contract name. + - address: The contract address. + - market: The market (ETH/BTC) this contract belongs to (required for market specific contracts). """ abi = self.contracts.get_contract_abi(name) if market: contract=self.w3.eth.contract(address=Web3.toChecksumAddress(address), abi=abi) - self.contracts.set_contracts(market=market,name=name,contract=contract) + self.contracts.set_contracts(contract, name, market) else: contract=self.w3.eth.contract(address=Web3.toChecksumAddress(address), abi=abi) - self.contracts.set_contracts(name=name,contract=contract) + self.contracts.set_contracts(contract, name) return - def create_order_to_sign(self, params:OrderSignatureRequest): + def create_order_to_sign(self, params:OrderSignatureRequest) -> Order: """ Creates order signature request for an order. + Inputs: - - params (OrderSignatureRequest): parameters to create order with - + - params: parameters to create order with + Returns: - Order: order raw info """ @@ -164,15 +175,16 @@ def create_order_to_sign(self, params:OrderSignatureRequest): salt = default_value(params, "salt", random_number(1000000)), ) - def create_signed_order(self, params:OrderSignatureRequest): + def create_signed_order(self, params:OrderSignatureRequest) -> OrderSignatureResponse: """ Create an order from provided params and signs it using the private key of the account - Inputs: - - params (OrderSignatureRequest): parameters to create order with - - Returns: - - OrderSignatureResponse: order raw info and generated signature + + Inputs: + - params: parameters to create order with + + Returns: + - OrderSignatureResponse: order raw info and generated signature """ # from params create order to sign @@ -200,34 +212,33 @@ def create_signed_order(self, params:OrderSignatureRequest): maker=order["maker"] ) - def create_signed_cancel_order(self,params:OrderSignatureRequest, parentAddress:str=""): + def create_signed_cancel_order(self, params:OrderSignatureRequest, parentAddress:str="") -> bool: """ Creates a cancel order request from provided params and signs it using the private key of the account Inputs: - - params (OrderSignatureRequest): parameters to create cancel order with - - parentAddress (str): Only provided by a sub account + - params: parameters to create cancel order with + - parentAddress (optional): Only provided by a sub account Returns: - OrderSignatureResponse: generated cancel signature """ - try: - signer:OrderSigner = self._get_order_signer(params["symbol"]) - order_to_sign = self.create_order_to_sign(params) - hash = signer.get_order_hash(order_to_sign) - return self.create_signed_cancel_orders(params["symbol"], hash, parentAddress) - except Exception as e: - return "" + signer:OrderSigner = self._get_order_signer(params["symbol"]) + order_to_sign = self.create_order_to_sign(params) + hash = signer.get_order_hash(order_to_sign) + return self.create_signed_cancel_orders(params["symbol"], hash, parentAddress) - def create_signed_cancel_orders(self, symbol:MARKET_SYMBOLS, order_hash:list, parentAddress:str=""): + + def create_signed_cancel_orders(self, symbol:MARKET_SYMBOLS, order_hash:List[str], parentAddress:str="") -> OrderCancellationRequest: """ Creates a cancel order from provided params and sign it using the private key of the account Inputs: - - params (list): a list of order hashes - - parentAddress (str): only provided by a sub account + - symbol: the market for which cancel orders are to be signed for + - order_hash: list of order hashes to be cancelled + - parentAddress (optional): only provided by a sub account Returns: OrderCancellationRequest: containing symbol, hashes and signature """ @@ -244,11 +255,11 @@ def create_signed_cancel_orders(self, symbol:MARKET_SYMBOLS, order_hash:list, pa parentAddress=parentAddress ) - async def post_cancel_order(self,params:OrderCancellationRequest): + async def post_cancel_order(self,params:OrderCancellationRequest) -> dict: """ POST cancel order request to Firefly Inputs: - - params(dict): a dictionary with OrderCancellationRequest required params + - params: a dictionary with OrderCancellationRequest required params Returns: - dict: response from orders delete API Firefly """ @@ -264,15 +275,17 @@ async def post_cancel_order(self,params:OrderCancellationRequest): auth_required=True ) - async def cancel_all_orders(self, symbol:MARKET_SYMBOLS, status: List[ORDER_STATUS], parentAddress:str=""): + async def cancel_all_orders(self, symbol:MARKET_SYMBOLS, status: List[ORDER_STATUS], parentAddress:str="") -> dict: """ GETs all orders of specified status for the specified symbol, and creates a cancellation request for all orders and POSTs the cancel order request to Firefly + Inputs: - symbol (MARKET_SYMBOLS): Market for which orders are to be cancelled - status (List[ORDER_STATUS]): status of orders that need to be cancelled - parentAddress (str): address of parent account, only provided by sub account + Returns: - dict: response from orders delete API Firefly """ @@ -289,10 +302,8 @@ async def cancel_all_orders(self, symbol:MARKET_SYMBOLS, status: List[ORDER_STAT if len(hashes) > 0: req = self.create_signed_cancel_orders(symbol, hashes, parentAddress) return await self.post_cancel_order(req) - - return False - async def post_signed_order(self, params:PlaceOrderRequest): + async def post_signed_order(self, params:PlaceOrderRequest) -> dict: """ Creates an order from provided params and signs it using the private key of the account @@ -327,15 +338,15 @@ async def post_signed_order(self, params:PlaceOrderRequest): ) ## Contract calls - async def deposit_margin_to_bank(self, amount): + async def deposit_margin_to_bank(self, amount:Union[int, str, float]) -> bool: """ Deposits given amount of USDC from user's account to margin bank Inputs: - amount (number): quantity of usdc to be deposited to bank in base decimals (1,2 etc) + amount: quantity of usdc to be deposited to bank in base decimals (1,2 etc) Returns: - Boolean: true if amount is successfully deposited, false otherwise + Boolean: true if amount is successfully deposited """ usdc_contract = self.contracts.get_contract(name="USDC"); @@ -366,15 +377,15 @@ async def deposit_margin_to_bank(self, amount): return True; - async def withdraw_margin_from_bank(self, amount): + async def withdraw_margin_from_bank(self, amount:Union[int, str, float]) -> bool: """ Withdraws given amount of usdc from margin bank if possible Inputs: - amount (number): quantity of usdc to be withdrawn from bank in base decimals (1,2 etc) + amount: quantity of usdc to be withdrawn from bank in base decimals (1,2 etc) Returns: - Boolean: true if amount is successfully withdrawn, false otherwise + Boolean: true if amount is successfully withdrawn """ mb_contract = self.contracts.get_contract(name="MarginBank"); @@ -392,15 +403,15 @@ async def withdraw_margin_from_bank(self, amount): return True; - async def adjust_leverage(self, symbol, leverage, parentAddress:str=""): + async def adjust_leverage(self, symbol: MARKET_SYMBOLS, leverage: Union[int, str], parentAddress:str="") -> Union[bool, dict]: """ Adjusts user leverage to the provided one for their current position on-chain and off-chain. If a user has no position for the provided symbol, leverage only recorded off-chain Inputs: - symbol (MARKET_SYMBOLS): market for which to adjust user leverage - leverage (number): new leverage to be set. Must be in base decimals (1,2 etc.) - parentAddress (str): optional, if provided, the leverage of parent is + symbol: market for which to adjust user leverage + leverage: new leverage to be set. Must be in base decimals (1,2 etc.) + parentAddress (optional): if provided, the leverage of parent is being adjusted (for sub accounts only) Returns: Boolean: true if the leverage is successfully adjusted @@ -435,15 +446,15 @@ async def adjust_leverage(self, symbol, leverage, parentAddress:str=""): return True - async def adjust_margin(self, symbol, operation, amount, parentAddress:str=""): + async def adjust_margin(self, symbol: MARKET_SYMBOLS, operation: ADJUST_MARGIN, amount: Union[int, float, str], parentAddress:str="") -> bool: """ Adjusts user's on-chain position by adding or removing the specified amount of margin. Performs on-chain contract call, the user must have gas tokens Inputs: - symbol (MARKET_SYMBOLS): market for which to adjust user leverage - operation (ADJUST_MARGIN): ADD/REMOVE adding or removing margin to position - amount (number): amount of margin to be adjusted - parentAddress (str): optional, if provided, the margin of parent is + symbol: market for which to adjust user leverage + operation: ADD/REMOVE adding or removing margin to position + amount: amount of margin to be adjusted + parentAddress (optional): if provided, the margin of parent is being adjusted (for sub accounts only) Returns: Boolean: true if the margin is adjusted @@ -470,13 +481,14 @@ async def adjust_margin(self, symbol, operation, amount, parentAddress:str=""): return True - async def update_sub_account(self, symbol, sub_account_address, status): + async def update_sub_account(self, symbol: MARKET_SYMBOLS, sub_account_address:str, status: bool) -> bool: """ Used to whitelist and account as a sub account or revoke sub account status from an account. + Inputs: - symbol (MARKET_SYMBOLS): market on which sub account status is to be updated - sub_account_address (str): address of the sub account - status (bool): new status of the sub account + symbol : market on which sub account status is to be updated + sub_account_address: address of the sub account + status: new status of the sub account Returns: Boolean: true if the sub account status is update @@ -494,7 +506,7 @@ async def update_sub_account(self, symbol, sub_account_address, status): return True - async def get_native_chain_token_balance(self): + async def get_native_chain_token_balance(self) -> Union[int, Decimal]: """ Returns user's native chain token (ETH/BOBA) balance """ @@ -503,7 +515,7 @@ async def get_native_chain_token_balance(self): except Exception as e: raise(Exception("Failed to get balance, Exception: {}".format(e))) - async def get_usdc_balance(self): + async def get_usdc_balance(self) -> Union[int, Decimal]: """ Returns user's USDC token balance on Firefly. """ @@ -514,7 +526,7 @@ async def get_usdc_balance(self): except Exception as e: raise(Exception("Failed to get balance, Exception: {}".format(e))) - async def get_margin_bank_balance(self): + async def get_margin_bank_balance(self) -> Union[int, Decimal]: """ Returns user's Margin Bank balance. """ @@ -526,11 +538,11 @@ async def get_margin_bank_balance(self): ## Market endpoints - async def get_orderbook(self, params:GetOrderbookRequest): + async def get_orderbook(self, params:GetOrderbookRequest) -> dict: """ Returns a dictionary containing the orderbook snapshot. Inputs: - - params(GetOrderbookRequest): the order symbol and limit(orderbook depth) + - params: the order symbol and limit(orderbook depth) Returns: - dict: Orderbook snapshot """ @@ -541,7 +553,7 @@ async def get_orderbook(self, params:GetOrderbookRequest): params ) - async def get_exchange_status(self): + async def get_exchange_status(self) -> dict: """ Returns a dictionary containing the exchange status. Returns: @@ -549,7 +561,7 @@ async def get_exchange_status(self): """ return await self.apis.get(SERVICE_URLS["MARKET"]["STATUS"], {}) - async def get_market_symbols(self): + async def get_market_symbols(self) -> dict: """ Returns a list of active market symbols. Returns: @@ -560,11 +572,11 @@ async def get_market_symbols(self): {} ) - async def get_funding_rate(self,symbol:MARKET_SYMBOLS): + async def get_funding_rate(self, symbol:MARKET_SYMBOLS) -> dict: """ Returns a dictionary containing the current funding rate on market. Inputs: - - symbol(MARKET_SYMBOLS): symbol of market + - symbol: symbol of market Returns: - dict: Funding rate into """ @@ -573,12 +585,14 @@ async def get_funding_rate(self,symbol:MARKET_SYMBOLS): {"symbol": symbol.value} ) - async def get_funding_history(self,params:GetFundingHistoryRequest): + async def get_funding_history(self, params:GetFundingHistoryRequest) -> GetFundingHistoryResponse: """ Returns a list of the user's funding payments, a boolean indicating if there is/are more page(s), and the next page number + Inputs: - - params(GetFundingHistoryRequest): params required to fetch funding history + - params: params required to fetch funding history + Returns: - GetFundingHistoryResponse: - isMoreDataAvailable: boolean indicating if there is/are more page(s) @@ -594,7 +608,7 @@ async def get_funding_history(self,params:GetFundingHistoryRequest): auth_required=True ) - async def get_market_meta_info(self,symbol:MARKET_SYMBOLS=None): + async def get_market_meta_info(self, symbol:MARKET_SYMBOLS=None) -> dict: """ Returns a dictionary containing market meta info. Inputs: @@ -609,11 +623,13 @@ async def get_market_meta_info(self,symbol:MARKET_SYMBOLS=None): query ) - async def get_market_data(self,symbol:MARKET_SYMBOLS=None): + async def get_market_data(self, symbol:MARKET_SYMBOLS=None) -> dict: """ Returns a dictionary containing market's current data about best ask/bid, 24 hour volume, market price etc.. + Inputs: - symbol(MARKET_SYMBOLS): the market symbol + Returns: - dict: meta info """ @@ -624,12 +640,14 @@ async def get_market_data(self,symbol:MARKET_SYMBOLS=None): query ) - async def get_exchange_info(self,symbol:MARKET_SYMBOLS=None): + async def get_exchange_info(self, symbol:MARKET_SYMBOLS=None) -> dict: """ Returns a dictionary containing exchange info for market(s). The min/max trade size, max allowed oi open min/max trade price, step size, tick size etc... + Inputs: - - symbol(MARKET_SYMBOLS): the market symbol + - symbol: the market symbol + Returns: - dict: exchange info """ @@ -639,11 +657,11 @@ async def get_exchange_info(self,symbol:MARKET_SYMBOLS=None): query ) - async def get_market_candle_stick_data(self,params:GetCandleStickRequest): + async def get_market_candle_stick_data(self,params:GetCandleStickRequest) -> dict: """ Returns a list containing the candle stick data. Inputs: - - params(GetCandleStickRequest): params required to fetch candle stick data + - params: params required to fetch candle stick data Returns: - list: the candle stick data """ @@ -654,11 +672,13 @@ async def get_market_candle_stick_data(self,params:GetCandleStickRequest): params ) - async def get_market_recent_trades(self,params:GetMarketRecentTradesRequest): + async def get_market_recent_trades(self,params:GetMarketRecentTradesRequest) -> dict: """ Returns a list containing the recent trades data. + Inputs: - - params(GetCandleStickRequest): params required to fetch candle stick data + - params: params required to fetch candle stick data + Returns: - ist: the recent trades """ @@ -669,11 +689,13 @@ async def get_market_recent_trades(self,params:GetMarketRecentTradesRequest): params ) - async def get_contract_addresses(self, symbol:MARKET_SYMBOLS=None): + async def get_contract_addresses(self, symbol:MARKET_SYMBOLS=None) -> dict: """ Returns all contract addresses for the provided market. + Inputs: - symbol(MARKET_SYMBOLS): the market symbol + Returns: - dict: all the contract addresses """ @@ -692,17 +714,19 @@ def get_account(self): """ return self.account - def get_public_address(self): + def get_public_address(self) -> str: """ Returns the user account public address """ return self.account.address - async def get_orders(self,params:GetOrderRequest): + async def get_orders(self, params:GetOrderRequest): """ Returns a list of orders. + Inputs: - - params(GetOrderRequest): params required to query orders (e.g. symbol,statuses) + - params: params required to query orders (e.g. symbol,statuses) + Returns: - list: a list of orders """ @@ -719,6 +743,7 @@ async def get_transaction_history(self,params:GetTransactionHistoryRequest): Returns a list of transaction. Inputs: - params(GetTransactionHistoryRequest): params to query transactions (e.g. symbol) + Returns: - list: a list of transactions """ @@ -764,6 +789,9 @@ async def get_user_account_data(self, parentAddress:str = ""): Returns user account data. Inputs: - parentAddress: an optional field, used by sub accounts to fetch parent account state + Returns: + - dict of user account information + """ return await self.apis.get( service_url = SERVICE_URLS["USER"]["ACCOUNT"], @@ -771,13 +799,13 @@ async def get_user_account_data(self, parentAddress:str = ""): auth_required = True ) - async def get_user_leverage(self, symbol:MARKET_SYMBOLS, parentAddress:str=""): + async def get_user_leverage(self, symbol:MARKET_SYMBOLS, parentAddress:str="") -> int: """ Returns user market default leverage. Inputs: - - symbol(MARKET_SYMBOLS): market symbol to get user market default leverage for. + - symbol: market symbol to get user market default leverage for. Returns: - - str: user default leverage + - str: user leverage on exchange """ account_data_by_market = (await self.get_user_account_data(parentAddress))["accountDataByMarket"] @@ -791,7 +819,7 @@ async def get_user_leverage(self, symbol:MARKET_SYMBOLS, parentAddress:str=""): ## Internal methods - def _get_order_signer(self,symbol:MARKET_SYMBOLS=None): + def _get_order_signer(self, symbol:MARKET_SYMBOLS=None): """ Returns the order signer for the specified symbol, else returns a dictionary of symbol -> order signer Inputs: diff --git a/src/firefly_exchange_client/constants.py b/src/firefly_exchange_client/constants.py index a768eb5..644e38d 100644 --- a/src/firefly_exchange_client/constants.py +++ b/src/firefly_exchange_client/constants.py @@ -1,4 +1,4 @@ -Networks = { +Networks:dict = { "DEV": { "url": "https://l2-dev.firefly.exchange/", "chainId": 78602, @@ -36,10 +36,8 @@ EIP712_DOMAIN_NAME = "IsolatedTrader" - EIP712_DOMAIN_STRING = "EIP712Domain(string name,string version,uint128 chainId,address verifyingContract)" - EIP712_ORDER_STRUCT_STRING = \ "Order(" + \ "bytes8 flags," + \ @@ -62,10 +60,6 @@ "SECONDS_IN_A_MONTH": 2592000 } -ADDRESSES = { - "ZERO": "0x0000000000000000000000000000000000000000", -} - SERVICE_URLS = { "MARKET": { "ORDER_BOOK": "/orderbook", diff --git a/src/firefly_exchange_client/contract_abis.py b/src/firefly_exchange_client/contract_abis.py index ed0d791..31048f9 100644 --- a/src/firefly_exchange_client/contract_abis.py +++ b/src/firefly_exchange_client/contract_abis.py @@ -1,4 +1,6 @@ -## ABIs of contracts to be used by client +''' + ABIs of Bluefin protocol contracts and USDC Token used by the client +''' MarginBank = { "_format": "hh-sol-artifact-1", diff --git a/src/firefly_exchange_client/contracts.py b/src/firefly_exchange_client/contracts.py index be072ec..5f1784b 100644 --- a/src/firefly_exchange_client/contracts.py +++ b/src/firefly_exchange_client/contracts.py @@ -1,11 +1,11 @@ from .contract_abis import MarginBank, Perpetual, USDC class Contracts: - def __init__(self): + def __init__(self) -> None: self.contracts = {} self.contract_addresses = {} - def set_contract_addresses(self,contract_address,market=None,name=None): + def set_contract_addresses(self,contract_address:dict, market:str=None, name:str=None) -> None: if market and name: if name: self.contract_addresses[market][name] = contract_address @@ -17,7 +17,7 @@ def set_contract_addresses(self,contract_address,market=None,name=None): self.contract_addresses = contract_address return - def set_contracts(self,contract,name,market=None): + def set_contracts(self, contract, name:str, market:str=None) -> None: if market: if market not in self.contracts: self.contracts[market] = {} @@ -34,11 +34,11 @@ def set_contracts(self,contract,name,market=None): ## GETTERS - def get_contract_abi(self,name): + def get_contract_abi(self, name:str): """ Returns contract abi. Inputs: - - name(str): The contract name. + - name: The contract name. """ if name == "MarginBank": @@ -50,7 +50,7 @@ def get_contract_abi(self,name): else: raise Exception("Unknown contract name: {}".format(name)) - def get_contract(self,name,market=""): + def get_contract(self, name:str, market:str=None): """ Returns the contract object. Inputs: @@ -67,12 +67,12 @@ def get_contract(self,name,market=""): except Exception as e: raise(Exception("Failed to get contract, Exception: {}".format(e))) - def get_contract_address(self,name=None,market=None): + def get_contract_address(self, name:str=None, market:str=None) -> str: """ Returns the contract address. If neither of the inputs provided, will return a dict with all contract addresses. Inputs: - - name(str): The contract name. - - market(str): The market the contract belongs to (if only market provided will return all address of market as dict). + - name: The contract name. + - market: The market the contract belongs to (if only market provided will return all address of market as dict). """ try: if market and name: diff --git a/src/firefly_exchange_client/interfaces.py b/src/firefly_exchange_client/interfaces.py index ee60846..cfbbd91 100644 --- a/src/firefly_exchange_client/interfaces.py +++ b/src/firefly_exchange_client/interfaces.py @@ -165,4 +165,12 @@ class FundingHistoryResponse(TypedDict): class GetFundingHistoryResponse(TypedDict): isMoreDataAvailable: bool # boolean indicating if there is more data available nextCursor: int # next page number - data: List[FundingHistoryResponse] \ No newline at end of file + data: List[FundingHistoryResponse] + + +class Network(TypedDict): + url: str + chainId: int + apiGateway: str + socketURL: str + onboardingUrl: str diff --git a/src/firefly_exchange_client/onboarding_signer.py b/src/firefly_exchange_client/onboarding_signer.py index 0cb8da7..697767d 100644 --- a/src/firefly_exchange_client/onboarding_signer.py +++ b/src/firefly_exchange_client/onboarding_signer.py @@ -3,10 +3,10 @@ from .signer import Signer class OnboardingSigner(Signer): - def __init__(self): + def __init__(self) ->None: super().__init__() - def create_signature(self, msg, private_key): + def create_signature(self, msg:str , private_key:str) -> str: """ Signs the message. Inputs: diff --git a/src/firefly_exchange_client/order_signer.py b/src/firefly_exchange_client/order_signer.py index ce0ba50..58df35e 100644 --- a/src/firefly_exchange_client/order_signer.py +++ b/src/firefly_exchange_client/order_signer.py @@ -1,4 +1,5 @@ from web3 import Web3 +from typing import List from .utilities import bn_to_bytes8, hash_string, address_to_bytes32 from .constants import * from .signer import Signer @@ -6,14 +7,22 @@ class OrderSigner(Signer): - def __init__(self, network_id, orders_contract_address, domain="IsolatedTrader", version="1.0"): + def __init__(self, network_id:int, orders_contract_address:dict, domain:str="IsolatedTrader", version:str="1.0") -> None: super().__init__() self.network_id = network_id self.contract_address = orders_contract_address; self.domain = domain self.version = version - def get_order_flags(self, order): + def get_order_flags(self, order:Order) -> str: + """ + Creates value of order flags based on the booleans. + Inputs: + - order: the order to be signed + Returns: + - str: encoded flags of the order + """ + flag = 0 if order["reduceOnly"]: @@ -30,7 +39,7 @@ def get_order_flags(self, order): str(flag).encode('utf-8') ]).decode().ljust(66, '0') - def get_domain_hash(self): + def get_domain_hash(self) -> str: """ Returns domain hash """ @@ -86,14 +95,14 @@ def get_order_hash(self, order:Order): return self.get_eip712_hash(self.get_domain_hash(), struct_hash) if struct_hash else ""; - def sign_order(self, order:Order, private_key): + def sign_order(self, order:Order, private_key:str) -> str: """ Used to create an order signature. The method will use the provided key in params(if any) to sign the order. Args: - order (Order): an order containing order fields (look at Order interface) - private_key (str): private key of the account to be used for signing + order: an order containing order fields (look at Order interface) + private_key: private key of the account to be used for signing Returns: str: generated signature @@ -101,14 +110,14 @@ def sign_order(self, order:Order, private_key): order_hash = self.get_order_hash(order) return self.sign_hash(order_hash, private_key, "01") - def sign_cancellation_hash(self,order_hash:list): + def sign_cancellation_hash(self, order_hash:List[str]) -> str: """ Used to create a cancel order signature. The method will use the provided key in params(if any) to sign the cancel order. Args: - order_hash(list): a list containing all orders to be cancelled - private_key (str): private key of the account to be used for signing + order_hash: a list containing all orders to be cancelled + private_key: private key of the account to be used for signing Returns: str: generated signature """ diff --git a/src/firefly_exchange_client/signer.py b/src/firefly_exchange_client/signer.py index 89f6e24..6da0c02 100644 --- a/src/firefly_exchange_client/signer.py +++ b/src/firefly_exchange_client/signer.py @@ -2,11 +2,11 @@ import eth_account class Signer: - def __init__(self): + def __init__(self) -> None: pass - def get_eip712_hash(self, domain_hash, struct_hash): + def get_eip712_hash(self, domain_hash:str, struct_hash:str) -> str: """ Returns the EIP712 hash. Inputs: @@ -27,9 +27,13 @@ def get_eip712_hash(self, domain_hash, struct_hash): ).hex() - def sign_hash(self, hash, private_key, append=''): + def sign_hash(self, hash:str, private_key:str, append:str='') -> str: """ - Signs the hash and returns the signature. + Signs the hash and returns the signature. + Inputs: + - hash: stringified hash to be signed + - private_key: the private key of signer + - append (optional): string 0/1/2... etc to be appended to signature """ result = eth_account.account.Account.sign_message( eth_account.messages.encode_defunct(hexstr=hash), diff --git a/src/firefly_exchange_client/socket_manager.py b/src/firefly_exchange_client/socket_manager.py index 9a13fe7..3675554 100644 --- a/src/firefly_exchange_client/socket_manager.py +++ b/src/firefly_exchange_client/socket_manager.py @@ -19,7 +19,7 @@ def __init__( on_ping=None, on_pong=None, logger=None, - ): + ) -> None: threading.Thread.__init__(self) if not logger: logger = logging.getLogger(__name__) @@ -32,7 +32,7 @@ def __init__( self.on_pong = on_pong self.on_error = on_error - def create_ws_connection(self): + def create_ws_connection(self) -> None: self.logger.debug( "Creating connection with WebSocket Server: %s", self.stream_url ) @@ -42,17 +42,17 @@ def create_ws_connection(self): ) self._callback(self.on_open) - def run(self): + def run(self) -> None: self.read_data() - def send_message(self, message): + def send_message(self, message) -> None: self.logger.debug("Sending message to WebSocket Server: %s", message) self.ws.send(message) - def ping(self): + def ping(self) -> None: self.ws.ping() - def read_data(self): + def read_data(self) -> None: data = "" while True: try: @@ -86,14 +86,14 @@ def read_data(self): data = data.decode("utf-8") self._callback(self.on_message, data) - def close(self): + def close(self) -> None: if not self.ws.connected: self.logger.warn("Websocket already closed") else: self.ws.send_close() return - def _callback(self, callback, *args): + def _callback(self, callback, *args) -> None: if callback: try: callback(self, *args) diff --git a/src/firefly_exchange_client/sockets.py b/src/firefly_exchange_client/sockets.py index 8546394..25749ae 100644 --- a/src/firefly_exchange_client/sockets.py +++ b/src/firefly_exchange_client/sockets.py @@ -5,13 +5,13 @@ class Sockets: callbacks={} - def __init__(self, url, timeout=10, token=None) -> None: + def __init__(self, url:str, timeout:int=10, token:str=None) -> None: self.url = url self.timeout = timeout self.token = token return - def _establish_connection(self): + def _establish_connection(self) -> bool: """ Connects to the desired url """ @@ -21,7 +21,7 @@ def _establish_connection(self): except: return False - def set_token(self, token): + def set_token(self, token) -> None: """ Sets default user token Inputs: @@ -29,7 +29,7 @@ def set_token(self, token): """ self.token = token - async def open(self): + async def open(self) -> None: """ opens socket instance connection """ @@ -40,7 +40,7 @@ async def open(self): return - async def close(self): + async def close(self) -> None: """ closes the socket instance connection """ @@ -48,7 +48,7 @@ async def close(self): return @sio.on("*") - def listener(event,data): + def listener(event,data) -> None: """ Listens to all events emitted by the server """ @@ -63,14 +63,14 @@ def listener(event,data): pass return - async def listen(self,event,callback): + async def listen(self, event:str, callback) -> None: """ Assigns callbacks to desired events """ Sockets.callbacks[event] = callback return - async def subscribe_global_updates_by_symbol(self,symbol: MARKET_SYMBOLS): + async def subscribe_global_updates_by_symbol(self, symbol: MARKET_SYMBOLS) -> bool: """ Allows user to subscribe to global updates for the desired symbol. Inputs: @@ -91,7 +91,7 @@ async def subscribe_global_updates_by_symbol(self,symbol: MARKET_SYMBOLS): print("Error: ", e) return False - async def unsubscribe_global_updates_by_symbol(self,symbol: MARKET_SYMBOLS): + async def unsubscribe_global_updates_by_symbol(self,symbol: MARKET_SYMBOLS) -> bool: """ Allows user to unsubscribe to global updates for the desired symbol. Inputs: @@ -111,13 +111,13 @@ async def unsubscribe_global_updates_by_symbol(self,symbol: MARKET_SYMBOLS): except: return False - async def subscribe_user_update_by_token(self, parent_account: str=None, user_token: str=None): + async def subscribe_user_update_by_token(self, parent_account:str=None, user_token: str=None) -> bool: """ Allows user to subscribe to their account updates. Inputs: - parent_account(str): address of parent account. Only whitelisted sub-account can listen to its parent account position updates - - token(str): auth token generated when onboarding on firefly + - user_token(str): auth token generated when onboarding on firefly """ try: if not self.connection_established: @@ -127,7 +127,7 @@ async def subscribe_user_update_by_token(self, parent_account: str=None, user_to { "e": SOCKET_EVENTS.USER_UPDATES_ROOM.value, 'pa': parent_account, - "t": self.token if user_token == None else user_token, + "t": user_token or self.token, }, ]) return True @@ -135,12 +135,12 @@ async def subscribe_user_update_by_token(self, parent_account: str=None, user_to print(e); return False - async def unsubscribe_user_update_by_token(self, parent_account: str=None, user_token:str=None): + async def unsubscribe_user_update_by_token(self, parent_account:str=None, user_token:str=None) -> bool: """ Allows user to unsubscribe to their account updates. Inputs: - - parent_account(str): address of parent account. Only for sub-accounts - - token: auth token generated when onboarding on firefly + - parent_account: address of parent account. Only for sub-accounts + - user_token: auth token generated when onboarding on firefly """ try: if not self.connection_established: @@ -150,7 +150,7 @@ async def unsubscribe_user_update_by_token(self, parent_account: str=None, user_ { "e": SOCKET_EVENTS.USER_UPDATES_ROOM.value, 'pa': parent_account, - "t": self.token if user_token == None else user_token, + "t": user_token or self.token, }, ]) return True diff --git a/src/firefly_exchange_client/utilities.py b/src/firefly_exchange_client/utilities.py index b77d3de..677be63 100644 --- a/src/firefly_exchange_client/utilities.py +++ b/src/firefly_exchange_client/utilities.py @@ -3,48 +3,48 @@ from web3 import Web3 import time -def strip_hex_prefix(input): +def strip_hex_prefix(input:str) -> str: if input[0:2] == '0x': return input[2:] else: return input -def address_to_bytes32(addr): +def address_to_bytes32(addr) -> str: return '0x000000000000000000000000' + strip_hex_prefix(addr) -def hash_string(value: str): +def hash_string(value: str) -> str: return Web3.soliditySha3(["string"], [value] ).hex() -def bn_to_bytes8(value:int): +def bn_to_bytes8(value:int) -> str: return str("0x"+"0"*16+hex(value)[2:]).encode('utf-8') -def default_value(dict, key, default_value): +def default_value(dict:dict, key:str, default_value): if key in dict: return dict[key] else: return default_value -def default_enum_value(dict, key, default_value): +def default_enum_value(dict:dict, key:str, default_value): if key in dict: return dict[key].value else: return default_value.value -def current_unix_timestamp(): +def current_unix_timestamp() -> int: return int(datetime.now().timestamp()) -def random_number(max_range): +def random_number(max_range:int) -> int: return current_unix_timestamp() + randint(0, max_range) + randint(0, max_range) -def extract_query(value:dict): - query="" +def extract_query(value:dict) -> str: + query:str="" for i,j in value.items(): query+="&{}={}".format(i,j) return query[1:] -def extract_enums(params:dict,enums:list): +def extract_enums(params:dict, enums:list) -> dict: for i in enums: if i in params.keys(): if type(params[i]) == list: @@ -61,7 +61,7 @@ def config_logging(logging, logging_level, log_file: str = None): logging_level (int/str): For logging to include all messages with log levels >= logging_level. Ex: 10 or "DEBUG" logging level should be based on https://docs.python.org/3/library/logging.html#logging-levels Keyword Args: - log_file (str, optional): The filename to pass the logging to a file, instead of using console. Default filemode: "a" + log_file (optional): The filename to pass the logging to a file, instead of using console. Default filemode: "a" """ logging.Formatter.converter = time.gmtime # date time in GMT/UTC diff --git a/src/firefly_exchange_client/websocket_client.py b/src/firefly_exchange_client/websocket_client.py index 56e753f..ffb748e 100644 --- a/src/firefly_exchange_client/websocket_client.py +++ b/src/firefly_exchange_client/websocket_client.py @@ -1,17 +1,18 @@ import json -import logging +from logging import Logger, getLogger from .socket_manager import SocketManager from .enumerations import MARKET_SYMBOLS, SOCKET_EVENTS class WebsocketClient: def __init__( self, - stream_url, - token=None, - logger=None, - ): + stream_url:str, + token:str=None, + logger:Logger=None, + ) -> None: if not logger: - logger = logging.getLogger(__name__) + logger = getLogger(__name__) + self.logger = logger self.token = token self.stream_url = stream_url @@ -24,8 +25,8 @@ def initialize_socket( on_error=None, on_ping=None, on_pong=None, - logger=None, - ): + logger:Logger=None, + ) -> None: self.socket_manager = SocketManager( self.stream_url, on_message=self.listener, @@ -43,7 +44,7 @@ def initialize_socket( self.socket_manager.start() - def set_token(self, token): + def set_token(self, token:str) -> None: """ Sets default user token Inputs: @@ -51,17 +52,17 @@ def set_token(self, token): """ self.token = token - def listen(self,event,callback): + def listen(self, event:str, callback) -> None: """ Assigns callbacks to desired events """ self.callbacks[event] = callback return - def send(self, message: dict): + def send(self, message: dict) -> None: self.socket_manager.send_message(json.dumps(message)) - def subscribe_global_updates_by_symbol(self,symbol: MARKET_SYMBOLS): + def subscribe_global_updates_by_symbol(self,symbol: MARKET_SYMBOLS) -> bool: """ Allows user to subscribe to global updates for the desired symbol. Inputs: @@ -81,7 +82,7 @@ def subscribe_global_updates_by_symbol(self,symbol: MARKET_SYMBOLS): except Exception: return False - def unsubscribe_global_updates_by_symbol(self,symbol: MARKET_SYMBOLS): + def unsubscribe_global_updates_by_symbol(self,symbol: MARKET_SYMBOLS) -> bool: """ Allows user to unsubscribe to global updates for the desired symbol. Inputs: @@ -101,11 +102,11 @@ def unsubscribe_global_updates_by_symbol(self,symbol: MARKET_SYMBOLS): except: return False - def subscribe_user_update_by_token(self, user_token: str=None): + def subscribe_user_update_by_token(self, user_token:str=None) -> bool: """ Allows user to subscribe to their account updates. Inputs: - - token(str): auth token generated when onboarding on firefly + - user_token: auth token generated when onboarding on firefly """ try: if not self.socket_manager.ws.connected: @@ -121,11 +122,11 @@ def subscribe_user_update_by_token(self, user_token: str=None): except: return False - def unsubscribe_user_update_by_token(self, user_token:str=None): + def unsubscribe_user_update_by_token(self, user_token:str=None) -> bool: """ Allows user to unsubscribe to their account updates. Inputs: - - token: auth token generated when onboarding on firefly + - user_token: auth token generated when onboarding on firefly """ try: if not self.socket_manager.ws.connected: @@ -141,15 +142,14 @@ def unsubscribe_user_update_by_token(self, user_token:str=None): except: return False - def ping(self): + def ping(self) -> None: self.logger.debug("Sending ping to WebSocket Server") self.socket_manager.ping() - def stop(self, id=None): + def stop(self, id=None) -> None: self.socket_manager.close() - # self.socket_manager.join() - def listener(self,_, message): + def listener(self,_, message) -> None: """ Listens to all events emitted by the server """