From 04744d479948d366815bd645053464594175d2f2 Mon Sep 17 00:00:00 2001 From: abel Date: Fri, 8 Sep 2023 16:30:06 -0300 Subject: [PATCH 1/3] (feat) Added gRPC keepalive options to all gRPC channels --- examples/SendToInjective.py | 5 ++- ..._WithdrawValidatorCommissionAndRewards.py} | 0 pyinjective/async_client.py | 40 ++++++++++++++++--- 3 files changed, 37 insertions(+), 8 deletions(-) rename examples/chain_client/{48_WithdrawValidatorCommission_Rewards => 48_WithdrawValidatorCommissionAndRewards.py} (100%) diff --git a/examples/SendToInjective.py b/examples/SendToInjective.py index 5d0d1072..3defdf5c 100644 --- a/examples/SendToInjective.py +++ b/examples/SendToInjective.py @@ -27,8 +27,9 @@ async def main() -> None: data = '{"@type": "/injective.exchange.v1beta1.MsgDeposit","sender": "inj14au322k9munkmx5wrchz9q30juf5wjgz2cfqku","subaccountId": "0xaf79152ac5df276d9a8e1e2e22822f9713474902000000000000000000000000","amount": {"denom": "inj","amount": "1000000000000000000"}}' - import_peggo = pkg_resources.read_text(pyinjective, 'Peggo_ABI.json') - peggo_abi = json.loads(import_peggo) + with open("../pyinjective/Peggo_ABI.json") as pego_file: + peggo_data = pego_file.read() + peggo_abi = json.loads(peggo_data) peggo_composer.sendToInjective(ethereum_endpoint=ethereum_endpoint, private_key=private_key, token_contract=token_contract, receiver=receiver, amount=amount, maxFeePerGas=maxFeePerGas_Gwei, maxPriorityFeePerGas=maxPriorityFeePerGas_Gwei, data=data, peggo_abi=peggo_abi) diff --git a/examples/chain_client/48_WithdrawValidatorCommission_Rewards b/examples/chain_client/48_WithdrawValidatorCommissionAndRewards.py similarity index 100% rename from examples/chain_client/48_WithdrawValidatorCommission_Rewards rename to examples/chain_client/48_WithdrawValidatorCommissionAndRewards.py diff --git a/pyinjective/async_client.py b/pyinjective/async_client.py index 3374f6c1..4c11f13d 100644 --- a/pyinjective/async_client.py +++ b/pyinjective/async_client.py @@ -70,6 +70,13 @@ DEFAULT_SESSION_RENEWAL_OFFSET = 120 # seconds DEFAULT_BLOCK_TIME = 2 # seconds +GRPC_CHANNEL_OPTIONS = [ + ("grpc.keepalive_time_ms", 45000), + ("grpc.keepalive_timeout_ms", 5000), + ("grpc.http2.max_pings_without_data", 5), + ("grpc.keepalive_permit_without_calls", 1), +] + class AsyncClient: def __init__( @@ -88,9 +95,16 @@ def __init__( # chain stubs self.chain_channel = ( - grpc.aio.secure_channel(network.grpc_endpoint, credentials) + grpc.aio.secure_channel( + target=network.grpc_endpoint, + credentials=credentials, + options=GRPC_CHANNEL_OPTIONS, + ) if (network.use_secure_connection and credentials is not None) - else grpc.aio.insecure_channel(network.grpc_endpoint) + else grpc.aio.insecure_channel( + target=network.grpc_endpoint, + options=GRPC_CHANNEL_OPTIONS, + ) ) self.stubCosmosTendermint = tendermint_query_grpc.ServiceStub( @@ -106,9 +120,16 @@ def __init__( # exchange stubs self.exchange_channel = ( - grpc.aio.secure_channel(network.grpc_exchange_endpoint, credentials) + grpc.aio.secure_channel( + target=network.grpc_exchange_endpoint, + credentials=credentials, + options=GRPC_CHANNEL_OPTIONS, + ) if (network.use_secure_connection and credentials is not None) - else grpc.aio.insecure_channel(network.grpc_exchange_endpoint) + else grpc.aio.insecure_channel( + target=network.grpc_exchange_endpoint, + options=GRPC_CHANNEL_OPTIONS, + ) ) self.stubMeta = exchange_meta_rpc_grpc.InjectiveMetaRPCStub( self.exchange_channel @@ -137,9 +158,16 @@ def __init__( # explorer stubs self.explorer_channel = ( - grpc.aio.secure_channel(network.grpc_explorer_endpoint, credentials) + grpc.aio.secure_channel( + target=network.grpc_explorer_endpoint, + credentials=credentials, + options=GRPC_CHANNEL_OPTIONS, + ) if (network.use_secure_connection and credentials is not None) - else grpc.aio.insecure_channel(network.grpc_explorer_endpoint) + else grpc.aio.insecure_channel( + target=network.grpc_explorer_endpoint, + options=GRPC_CHANNEL_OPTIONS, + ) ) self.stubExplorer = explorer_rpc_grpc.InjectiveExplorerRPCStub( self.explorer_channel From 203500c67a0e4065b6c5f9a32d365fe1266839ad Mon Sep 17 00:00:00 2001 From: abel Date: Fri, 8 Sep 2023 16:40:59 -0300 Subject: [PATCH 2/3] (fix) Updated CHANGELOG and version number --- README.md | 3 +++ setup.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f46ba42c..70da3529 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,9 @@ make tests ``` ### Changelogs +**0.8.4** +* Added keepalive options to gRPC channels. + **0.8.3** * Fix dependency issue in setup.py. diff --git a/setup.py b/setup.py index fc00456f..c7dc1bcf 100755 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ EMAIL = "achilleas@injectivelabs.com" AUTHOR = "Injective Labs" REQUIRES_PYTHON = ">=3.9" -VERSION = "0.8.3" +VERSION = "0.8.4" REQUIRED = [ "aiohttp", From 9370531ac278510f60effcfd9c083bfa5240c388 Mon Sep 17 00:00:00 2001 From: abel Date: Mon, 11 Sep 2023 23:17:06 -0300 Subject: [PATCH 3/3] (feat) Added from chain price and value translation to all market classes. Fixed an error SendToInjective example script --- README.md | 2 +- examples/SendToInjective.py | 6 --- pyinjective/async_client.py | 40 +++------------ pyinjective/core/market.py | 27 ++++++++++- pyinjective/sendtocosmos.py | 2 +- tests/core/test_market.py | 97 +++++++++++++++++++++++++++++++++++++ 6 files changed, 131 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 70da3529..4707446e 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ make tests ### Changelogs **0.8.4** -* Added keepalive options to gRPC channels. +* Added methods to SpotMarket, DerivativeMarket and BianaryOptionMarket to translate chain prices and quantities to human-readable format. **0.8.3** * Fix dependency issue in setup.py. diff --git a/examples/SendToInjective.py b/examples/SendToInjective.py index 3defdf5c..31614bef 100644 --- a/examples/SendToInjective.py +++ b/examples/SendToInjective.py @@ -1,15 +1,9 @@ import json -import requests - import asyncio -import logging from pyinjective.core.network import Network from pyinjective.sendtocosmos import Peggo -import importlib.resources as pkg_resources -import pyinjective - async def main() -> None: # select network: testnet, mainnet network = Network.testnet() diff --git a/pyinjective/async_client.py b/pyinjective/async_client.py index 4c11f13d..3374f6c1 100644 --- a/pyinjective/async_client.py +++ b/pyinjective/async_client.py @@ -70,13 +70,6 @@ DEFAULT_SESSION_RENEWAL_OFFSET = 120 # seconds DEFAULT_BLOCK_TIME = 2 # seconds -GRPC_CHANNEL_OPTIONS = [ - ("grpc.keepalive_time_ms", 45000), - ("grpc.keepalive_timeout_ms", 5000), - ("grpc.http2.max_pings_without_data", 5), - ("grpc.keepalive_permit_without_calls", 1), -] - class AsyncClient: def __init__( @@ -95,16 +88,9 @@ def __init__( # chain stubs self.chain_channel = ( - grpc.aio.secure_channel( - target=network.grpc_endpoint, - credentials=credentials, - options=GRPC_CHANNEL_OPTIONS, - ) + grpc.aio.secure_channel(network.grpc_endpoint, credentials) if (network.use_secure_connection and credentials is not None) - else grpc.aio.insecure_channel( - target=network.grpc_endpoint, - options=GRPC_CHANNEL_OPTIONS, - ) + else grpc.aio.insecure_channel(network.grpc_endpoint) ) self.stubCosmosTendermint = tendermint_query_grpc.ServiceStub( @@ -120,16 +106,9 @@ def __init__( # exchange stubs self.exchange_channel = ( - grpc.aio.secure_channel( - target=network.grpc_exchange_endpoint, - credentials=credentials, - options=GRPC_CHANNEL_OPTIONS, - ) + grpc.aio.secure_channel(network.grpc_exchange_endpoint, credentials) if (network.use_secure_connection and credentials is not None) - else grpc.aio.insecure_channel( - target=network.grpc_exchange_endpoint, - options=GRPC_CHANNEL_OPTIONS, - ) + else grpc.aio.insecure_channel(network.grpc_exchange_endpoint) ) self.stubMeta = exchange_meta_rpc_grpc.InjectiveMetaRPCStub( self.exchange_channel @@ -158,16 +137,9 @@ def __init__( # explorer stubs self.explorer_channel = ( - grpc.aio.secure_channel( - target=network.grpc_explorer_endpoint, - credentials=credentials, - options=GRPC_CHANNEL_OPTIONS, - ) + grpc.aio.secure_channel(network.grpc_explorer_endpoint, credentials) if (network.use_secure_connection and credentials is not None) - else grpc.aio.insecure_channel( - target=network.grpc_explorer_endpoint, - options=GRPC_CHANNEL_OPTIONS, - ) + else grpc.aio.insecure_channel(network.grpc_explorer_endpoint) ) self.stubExplorer = explorer_rpc_grpc.InjectiveExplorerRPCStub( self.explorer_channel diff --git a/pyinjective/core/market.py b/pyinjective/core/market.py index 9a42b88c..2dd59fae 100644 --- a/pyinjective/core/market.py +++ b/pyinjective/core/market.py @@ -34,6 +34,13 @@ def price_to_chain_format(self, human_readable_value: Decimal) -> Decimal: return extended_chain_formatted_value + def quantity_from_chain_format(self, chain_value: Decimal) -> Decimal: + return chain_value / Decimal(f"1e{self.base_token.decimals}") + + def price_from_chain_format(self, chain_value: Decimal) -> Decimal: + decimals = self.base_token.decimals - self.quote_token.decimals + return chain_value * Decimal(f"1e{decimals}") + @dataclass(eq=True, frozen=True) class DerivativeMarket: id: str @@ -92,6 +99,15 @@ def calculate_margin_in_chain_format( return extended_chain_formatted_margin + def quantity_from_chain_format(self, chain_value: Decimal) -> Decimal: + return chain_value + + def price_from_chain_format(self, chain_value: Decimal) -> Decimal: + return chain_value * Decimal(f"1e-{self.quote_token.decimals}") + + def margin_from_chain_format(self, chain_value: Decimal) -> Decimal: + return chain_value * Decimal(f"1e-{self.quote_token.decimals}") + @dataclass(eq=True, frozen=True) class BinaryOptionMarket: id: str @@ -148,4 +164,13 @@ def calculate_margin_in_chain_format( quantized_margin = (margin // min_quantity_tick_size) * min_quantity_tick_size extended_chain_formatted_margin = quantized_margin * Decimal(f"1e{ADDITIONAL_CHAIN_FORMAT_DECIMALS}") - return extended_chain_formatted_margin \ No newline at end of file + return extended_chain_formatted_margin + + def quantity_from_chain_format(self, chain_value: Decimal, special_denom: Optional[Denom] = None) -> Decimal: + # Binary option markets do not have a base market to provide the number of decimals + decimals = 0 if special_denom is None else special_denom.base + return chain_value * Decimal(f"1e-{decimals}") + + def price_from_chain_format(self, chain_value: Decimal, special_denom: Optional[Denom] = None) -> Decimal: + decimals = self.quote_token.decimals if special_denom is None else special_denom.quote + return chain_value * Decimal(f"1e-{decimals}") diff --git a/pyinjective/sendtocosmos.py b/pyinjective/sendtocosmos.py index e19b3a4c..772ac50a 100644 --- a/pyinjective/sendtocosmos.py +++ b/pyinjective/sendtocosmos.py @@ -6,7 +6,7 @@ class Peggo: def __init__(self, network: str): self.network = network - def sendToInjective(self, ethereum_endpoint: str, private_key: str, token_contract: str, receiver: str, amount: int, + def sendToInjective(self, ethereum_endpoint: str, private_key: str, token_contract: str, receiver: str, amount: float, maxFeePerGas: int, maxPriorityFeePerGas: int, peggo_abi: str, data: str, decimals=18): if self.network == 'testnet': peggy_proxy_address = "0xd2C6753F6B1783EF0a3857275e16e79D91b539a3" diff --git a/tests/core/test_market.py b/tests/core/test_market.py index 1038c51b..f3340268 100644 --- a/tests/core/test_market.py +++ b/tests/core/test_market.py @@ -39,6 +39,24 @@ def test_convert_price_to_chain_format(self, inj_usdt_spot_market: SpotMarket): assert (quantized_chain_format_value == chain_value) + def test_convert_quantity_from_chain_format(self, inj_usdt_spot_market: SpotMarket): + expected_quantity = Decimal("123.456") + + chain_format_quantity = expected_quantity * Decimal(f"1e{inj_usdt_spot_market.base_token.decimals}") + human_readable_quantity = inj_usdt_spot_market.quantity_from_chain_format(chain_value=chain_format_quantity) + + assert (expected_quantity == human_readable_quantity) + + def test_convert_price_from_chain_format(self, inj_usdt_spot_market: SpotMarket): + expected_price = Decimal("123.456") + + price_decimals = inj_usdt_spot_market.quote_token.decimals - inj_usdt_spot_market.base_token.decimals + chain_format_price = expected_price * Decimal(f"1e{price_decimals}") + human_readable_price = inj_usdt_spot_market.price_from_chain_format(chain_value=chain_format_price) + + assert (expected_price == human_readable_price) + + class TestDerivativeMarket: @@ -76,6 +94,32 @@ def test_convert_margin_to_chain_format(self, btc_usdt_perp_market: DerivativeMa assert (quantized_chain_format_value == chain_value) + def test_convert_quantity_from_chain_format(self, btc_usdt_perp_market: DerivativeMarket): + expected_quantity = Decimal("123.456") + + chain_format_quantity = expected_quantity + human_readable_quantity = btc_usdt_perp_market.quantity_from_chain_format(chain_value=chain_format_quantity) + + assert (expected_quantity == human_readable_quantity) + + def test_convert_price_from_chain_format(self, btc_usdt_perp_market: DerivativeMarket): + expected_price = Decimal("123.456") + + price_decimals = btc_usdt_perp_market.quote_token.decimals + chain_format_price = expected_price * Decimal(f"1e{price_decimals}") + human_readable_price = btc_usdt_perp_market.price_from_chain_format(chain_value=chain_format_price) + + assert (expected_price == human_readable_price) + + def test_convert_margin_from_chain_format(self, btc_usdt_perp_market: DerivativeMarket): + expected_margin = Decimal("123.456") + + price_decimals = btc_usdt_perp_market.quote_token.decimals + chain_format_margin = expected_margin * Decimal(f"1e{price_decimals}") + human_readable_margin = btc_usdt_perp_market.margin_from_chain_format(chain_value=chain_format_margin) + + assert (expected_margin == human_readable_margin) + class TestBinaryOptionMarket: def test_convert_quantity_to_chain_format_with_fixed_denom(self, first_match_bet_market: BinaryOptionMarket): @@ -211,3 +255,56 @@ def test_calculate_margin_for_sell_without_fixed_denom(self, first_match_bet_mar quantized_chain_format_margin = quantized_margin * Decimal(f"1e18") assert (quantized_chain_format_margin == chain_value) + + def test_convert_quantity_from_chain_format_with_fixed_denom(self, first_match_bet_market: BinaryOptionMarket): + original_quantity = Decimal("123.456789") + fixed_denom = Denom( + description="Fixed denom", + base=2, + quote=4, + min_quantity_tick_size=100, + min_price_tick_size=10000, + ) + + chain_formatted_quantity = original_quantity * Decimal(f"1e{fixed_denom.base}") + + human_readable_quantity = first_match_bet_market.quantity_from_chain_format( + chain_value=chain_formatted_quantity, special_denom=fixed_denom + ) + + assert (original_quantity == human_readable_quantity) + + def test_convert_quantity_from_chain_format_without_fixed_denom(self, first_match_bet_market: BinaryOptionMarket): + original_quantity = Decimal("123.456789") + + chain_formatted_quantity = original_quantity + + human_readable_quantity = first_match_bet_market.quantity_from_chain_format(chain_value=chain_formatted_quantity) + + assert (original_quantity == human_readable_quantity) + + def test_convert_price_from_chain_format_with_fixed_denom(self, first_match_bet_market: BinaryOptionMarket): + original_price = Decimal("123.456789") + fixed_denom = Denom( + description="Fixed denom", + base=2, + quote=4, + min_quantity_tick_size=100, + min_price_tick_size=10000, + ) + + chain_formatted_price = original_price * Decimal(f"1e{fixed_denom.quote}") + + human_readable_price = first_match_bet_market.price_from_chain_format( + chain_value=chain_formatted_price, special_denom=fixed_denom + ) + + assert (original_price == human_readable_price) + + def test_convert_price_from_chain_format_without_fixed_denom(self, first_match_bet_market: BinaryOptionMarket): + original_price = Decimal("123.456789") + chain_formatted_price = original_price * Decimal(f"1e{first_match_bet_market.quote_token.decimals}") + + human_readable_price = first_match_bet_market.price_from_chain_format(chain_value=chain_formatted_price) + + assert (original_price == human_readable_price) \ No newline at end of file